diff --git a/.gitignore b/.gitignore index c4365f72ab..1450388ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ ipch/* *.vcxproj.user *.VC.opendb *.VC.db +/.vscode/ # cmake stuff CMakeCache.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b4b4b7cd8a..a7b709f734 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ if(SUPPORTS_X11) option(USE_X11 "Support X11 window system" ON) endif() if(SUPPORTS_WAYLAND) - option(USE_WAYLAND "Support Wayland window system" OFF) + option(USE_WAYLAND "Support Wayland window system" ON) endif() if((LINUX OR FREEBSD) OR ANDROID) option(USE_EGL "Support EGL OpenGL context creation" ON) @@ -53,6 +53,7 @@ if((LINUX OR FREEBSD) AND NOT ANDROID) option(USE_DRMKMS "Support DRM/KMS OpenGL contexts" OFF) option(USE_FBDEV "Support FBDev OpenGL contexts" OFF) option(USE_EVDEV "Support EVDev controller interface" OFF) + option(USE_DBUS "Enable DBus support for screensaver inhibiting" ON) endif() # Force EGL when using Wayland diff --git a/README.md b/README.md index 7e75d4aadf..3ed22abc37 100644 --- a/README.md +++ b/README.md @@ -104,13 +104,9 @@ You will need a device with armv7 (32-bit ARM), AArch64 (64-bit ARM), or x86_64 Google Play is the preferred distribution mechanism and will result in smaller download sizes: https://play.google.com/store/apps/details?id=com.github.stenzek.duckstation -**No support is provided for the Android app**, it is free and your expectations should be in line with that. Please **do not** email me about issues about it, they will be ignored. This repository should also not be used to raise issues about the app, as it does not contain the app code, only the desktop versions. +**No support is provided for the Android app**, it is free and your expectations should be in line with that. Please **do not** email me about issues about it, they will be ignored. -If you must use an APK, download links are: - -Download link: https://www.duckstation.org/android/duckstation-android.apk - -Changelog link: https://www.duckstation.org/android/changelog.txt +If you must use an APK, download links are listed in https://www.duckstation.org/android/ To use: 1. Install and run the app for the first time. @@ -215,22 +211,6 @@ Hotkeys: - **Tab:** Temporarily disable speed limiter - **Space:** Pause/resume emulation -## Screenshots -

- Monkey Hero - Ridge Racer Type 4 - Tomb Raider 2 - Quake 2 - Croc - Croc 2 - Final Fantasy 7 - Mega Man 8 - Final Fantasy 8 in Fullscreen UI - Spyro in Fullscreen UI - Threads of Fate in Fullscreen UI - Game Grid -

- ## Disclaimers Icon by icons8: https://icons8.com/icon/74847/platforms.undefined.short-title diff --git a/data/resources/database/gamedb.json b/data/resources/database/gamedb.json index 52f68eca23..638c1bff03 100644 --- a/data/resources/database/gamedb.json +++ b/data/resources/database/gamedb.json @@ -46737,7 +46737,8 @@ "serial": "SLPS-02728", "name": "Breath of Fire IV (USA) (Beta)", "codes": [ - "SLPS-02728" + "SLPS-02728", + "SLPM-87159" ], "languages": [ "Japanese" diff --git a/data/resources/database/gamesettings.ini b/data/resources/database/gamesettings.ini index ae7f3bdb78..258306e703 100644 --- a/data/resources/database/gamesettings.ini +++ b/data/resources/database/gamesettings.ini @@ -253,8 +253,8 @@ ForceRecompilerICache = true ForceRecompilerICache = true -# SCUS-94200 (Battle Arena Toshinden (USA)) -[SCUS-94200] +# SCUS-94003 (Battle Arena Toshinden (USA)) +[SCUS-94003] ForceRecompilerICache = true @@ -283,11 +283,6 @@ ForceRecompilerICache = true ForceRecompilerICache = true -# SLPS-02701 (Next Tetris, The (Japan)) -[SLPS-02701] -ForceRecompilerICache = true - - # SLUS-00862 (Next Tetris, The (USA)) [SLUS-00862] ForceRecompilerICache = true @@ -303,11 +298,6 @@ ForceRecompilerICache = true ForceRecompilerICache = true -# SLPM-87159 (Breath of Fire IV - Utsurowazaru Mono (Japan)) -[SLPM-87159] -ForceRecompilerICache = true - - # SLPS-02728 (Breath of Fire IV - Utsurowazaru Mono (Japan)) [SLPS-02728] ForceRecompilerICache = true @@ -533,11 +523,6 @@ ForceInterpreter = true ForceInterlacing = true -# SLPS 02120 (Shiritsu Justice Gakuen: Nekketsu Seishun Nikki 2 (Japan)) -[SLPS-02120] -ForceInterlacing = true - - # Hexen (SLUS-00348) [SLUS-00348] DisableUpscaling = true @@ -653,21 +638,11 @@ ForcePGXPCPUMode = true ForcePGXPCPUMode = true -# SLPS-91205 (Grandia (Japan) (Disc 1)) -[SLPS-91205] -ForcePGXPCPUMode = true - - # SLPS-02125 (Grandia (Japan) (Disc 2)) [SLPS-02125] ForcePGXPCPUMode = true -# SLPS-91206 (Grandia (Japan) (Disc 2)) -[SLPS-91206] -ForcePGXPCPUMode = true - - # SCUS-94457 (Grandia (USA) (Disc 1)) [SCUS-94457] ForcePGXPCPUMode = true @@ -976,12 +951,6 @@ DMAMaxSliceTicks = 200 GPUMaxRunAhead = 1 -# SLUS-0381 (Star Wars - Rebel Assault II - The Hidden Empire (USA) (Disc 1)) -[SLUS-0381] -DMAMaxSliceTicks = 200 -GPUMaxRunAhead = 1 - - # SLUS-00386 (Star Wars - Rebel Assault II - The Hidden Empire (USA) (Disc 2)) [SLUS-00386] DMAMaxSliceTicks = 200 @@ -1058,11 +1027,6 @@ DMAMaxSliceTicks = 100 DMAMaxSliceTicks = 100 -# SLPS-91064 (Armored Core (Japan) (Rev 1)) -[SLPS-91064] -DMAMaxSliceTicks = 100 - - # SCUS-94182 (Armored Core (USA)) [SCUS-94182] DMAMaxSliceTicks = 100 @@ -1105,27 +1069,6 @@ DMAMaxSliceTicks = 100 DMAHaltTicks = 150 -# SLPS-02364 (Chrono Cross (Japan) (Disc 1)) -[SLPS-02364] -ForceRecompilerICache = true -DMAMaxSliceTicks = 100 -DMAHaltTicks = 150 - - -# SLPS-02777 (Chrono Cross (Japan) (Disc 1)) -[SLPS-02777] -ForceRecompilerICache = true -DMAMaxSliceTicks = 100 -DMAHaltTicks = 150 - - -# SLPS-91464 (Chrono Cross (Japan) (Disc 1)) -[SLPS-91464] -ForceRecompilerICache = true -DMAMaxSliceTicks = 100 -DMAHaltTicks = 150 - - # SLPM-87396 (Chrono Cross (Japan) (Disc 2)) [SLPM-87396] ForceRecompilerICache = true @@ -1133,27 +1076,6 @@ DMAMaxSliceTicks = 100 DMAHaltTicks = 150 -# SLPS-02365 (Chrono Cross (Japan) (Disc 2)) -[SLPS-02365] -ForceRecompilerICache = true -DMAMaxSliceTicks = 100 -DMAHaltTicks = 150 - - -# SLPS-02778 (Chrono Cross (Japan) (Disc 2)) -[SLPS-02778] -ForceRecompilerICache = true -DMAMaxSliceTicks = 100 -DMAHaltTicks = 150 - - -# SLPS-91465 (Chrono Cross (Japan) (Disc 2)) -[SLPS-91465] -ForceRecompilerICache = true -DMAMaxSliceTicks = 100 -DMAHaltTicks = 150 - - # SLUS-01041 (Chrono Cross (USA) (Disc 1)) [SLUS-01041] ForceRecompilerICache = true @@ -1290,11 +1212,6 @@ DisplayActiveEndOffset = 68 ForceRecompilerLUTFastmem = True -# SLPM-86943 (Tony Hawk's Pro Skater (Japan)) -[SLPM-86943] -ForceRecompilerLUTFastmem = True - - # SLUS-00860 (Tony Hawk's Pro Skater (USA)) [SLUS-00860] ForceRecompilerLUTFastmem = True @@ -1390,11 +1307,6 @@ ForceRecompilerLUTFastmem = True ForceRecompilerLUTFastmem = True -# SLPS-01009 (Lagnacure (Japan, Asia)) -[SLPS-01009] -ForceRecompilerLUTFastmem = True - - # SCUS-94243 (Einhaender (USA)) [SCUS-94243] ForceRecompilerLUTFastmem = true diff --git a/dep/rcheevos/include/rc_api_editor.h b/dep/rcheevos/include/rc_api_editor.h index 2bda503258..46880d5828 100644 --- a/dep/rcheevos/include/rc_api_editor.h +++ b/dep/rcheevos/include/rc_api_editor.h @@ -44,6 +44,7 @@ rc_api_fetch_code_notes_response_t; int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params); int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response); +int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response); /* --- Update Code Note --- */ @@ -76,6 +77,7 @@ rc_api_update_code_note_response_t; int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params); int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response); +int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response); /* --- Update Achievement --- */ @@ -121,6 +123,7 @@ rc_api_update_achievement_response_t; int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params); int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response); +int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response); /* --- Update Leaderboard --- */ @@ -170,6 +173,7 @@ rc_api_update_leaderboard_response_t; int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params); int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response); +int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response); /* --- Fetch Badge Range --- */ @@ -199,6 +203,7 @@ rc_api_fetch_badge_range_response_t; int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params); int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response); +int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response); /* --- Add Game Hash --- */ @@ -238,6 +243,7 @@ rc_api_add_game_hash_response_t; int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params); int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response); +int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response); #ifdef __cplusplus diff --git a/dep/rcheevos/include/rc_api_info.h b/dep/rcheevos/include/rc_api_info.h index 7979cc391f..7d3a31607c 100644 --- a/dep/rcheevos/include/rc_api_info.h +++ b/dep/rcheevos/include/rc_api_info.h @@ -64,6 +64,7 @@ rc_api_fetch_achievement_info_response_t; int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params); int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response); +int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response); /* --- Fetch Leaderboard Info --- */ @@ -135,6 +136,7 @@ rc_api_fetch_leaderboard_info_response_t; int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params); int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response); +int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response); /* --- Fetch Games List --- */ @@ -173,6 +175,7 @@ rc_api_fetch_games_list_response_t; int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params); int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response); +int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response); #ifdef __cplusplus diff --git a/dep/rcheevos/include/rc_api_request.h b/dep/rcheevos/include/rc_api_request.h index 8ba482a8fe..81c855fd3c 100644 --- a/dep/rcheevos/include/rc_api_request.h +++ b/dep/rcheevos/include/rc_api_request.h @@ -3,6 +3,8 @@ #include "rc_error.h" +#include + #ifdef __cplusplus extern "C" { #endif @@ -10,15 +12,25 @@ extern "C" { /** * A block of memory for variable length data (like strings and arrays). */ -typedef struct rc_api_buffer_t { +typedef struct rc_api_buffer_chunk_t { /* The current location where data is being written */ char* write; /* The first byte past the end of data where writing cannot occur */ char* end; + /* The first byte of the data */ + char* start; /* The next block in the allocated memory chain */ - struct rc_api_buffer_t* next; - /* The buffer containing the data. The actual size may be larger than 256 bytes for buffers allocated in - * the next chain. The 256 byte size specified is for the initial allocation within the container object. */ + struct rc_api_buffer_chunk_t* next; +} +rc_api_buffer_chunk_t; + +/** + * A preallocated block of memory for variable length data (like strings and arrays). + */ +typedef struct rc_api_buffer_t { + /* The chunk data (will point at the local data member) */ + struct rc_api_buffer_chunk_t chunk; + /* Small chunk of memory pre-allocated for the chunk */ char data[256]; } rc_api_buffer_t; @@ -31,6 +43,8 @@ typedef struct rc_api_request_t { const char* url; /* Additional query args that should be sent via a POST command. If null, GET may be used */ const char* post_data; + /* The HTTP Content-Type of the POST data. */ + const char* content_type; /* Storage for the url and post_data */ rc_api_buffer_t buffer; @@ -41,7 +55,7 @@ rc_api_request_t; * Common attributes for all server responses. */ typedef struct rc_api_response_t { - /* Server-provided success indicator (non-zero on failure) */ + /* Server-provided success indicator (non-zero on success, zero on failure) */ int succeeded; /* Server-provided message associated to the failure */ const char* error_message; @@ -56,6 +70,15 @@ void rc_api_destroy_request(rc_api_request_t* request); void rc_api_set_host(const char* hostname); void rc_api_set_image_host(const char* hostname); +typedef struct rc_api_server_response_t { + /* Pointer to the data returned from the server */ + const char* body; + /* Length of data returned from the server (Content-Length) */ + size_t body_length; + /* HTTP status code returned from the server */ + int http_status_code; +} rc_api_server_response_t; + #ifdef __cplusplus } #endif diff --git a/dep/rcheevos/include/rc_api_runtime.h b/dep/rcheevos/include/rc_api_runtime.h index 68f56fd512..4534ff182f 100644 --- a/dep/rcheevos/include/rc_api_runtime.h +++ b/dep/rcheevos/include/rc_api_runtime.h @@ -59,6 +59,7 @@ rc_api_resolve_hash_response_t; int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params); int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response); +int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response); /* --- Fetch Game Data --- */ @@ -155,6 +156,7 @@ rc_api_fetch_game_data_response_t; int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params); int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response); +int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response); /* --- Ping --- */ @@ -185,6 +187,7 @@ rc_api_ping_response_t; int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params); int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response); +int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_ping_response(rc_api_ping_response_t* response); /* --- Award Achievement --- */ @@ -214,6 +217,8 @@ typedef struct rc_api_award_achievement_response_t { unsigned awarded_achievement_id; /* The updated player score */ unsigned new_player_score; + /* The updated player softcore score */ + unsigned new_player_score_softcore; /* The number of achievements the user has not yet unlocked for this game * (in hardcore/non-hardcore per hardcore flag in request) */ unsigned achievements_remaining; @@ -225,6 +230,7 @@ rc_api_award_achievement_response_t; int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params); int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response); +int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response); /* --- Submit Leaderboard Entry --- */ @@ -282,6 +288,7 @@ rc_api_submit_lboard_entry_response_t; int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params); int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response); +int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response); #ifdef __cplusplus diff --git a/dep/rcheevos/include/rc_api_user.h b/dep/rcheevos/include/rc_api_user.h index 758842557a..8f96044c82 100644 --- a/dep/rcheevos/include/rc_api_user.h +++ b/dep/rcheevos/include/rc_api_user.h @@ -33,6 +33,8 @@ typedef struct rc_api_login_response_t { const char* api_token; /* The current score of the player */ unsigned score; + /* The current softcore score of the player */ + unsigned score_softcore; /* The number of unread messages waiting for the player on the web site */ unsigned num_unread_messages; /* The preferred name to display for the player */ @@ -45,6 +47,7 @@ rc_api_login_response_t; int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params); int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response); +int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_login_response(rc_api_login_response_t* response); /* --- Start Session --- */ @@ -73,6 +76,7 @@ rc_api_start_session_response_t; int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params); int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response); +int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response); /* --- Fetch User Unlocks --- */ @@ -108,6 +112,7 @@ rc_api_fetch_user_unlocks_response_t; int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params); int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response); +int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response); void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response); #ifdef __cplusplus diff --git a/dep/rcheevos/include/rc_client.h b/dep/rcheevos/include/rc_client.h new file mode 100644 index 0000000000..40e9a977a6 --- /dev/null +++ b/dep/rcheevos/include/rc_client.h @@ -0,0 +1,584 @@ +#ifndef RC_CLIENT_H +#define RC_CLIENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "rc_api_request.h" +#include "rc_error.h" + +#include +#include +#include + +/* implementation abstracted in rc_client_internal.h */ +typedef struct rc_client_t rc_client_t; +typedef struct rc_client_async_handle_t rc_client_async_handle_t; + +/*****************************************************************************\ +| Callbacks | +\*****************************************************************************/ + +/** + * Callback used to read num_bytes bytes from memory starting at address into buffer. + * Returns the number of bytes read. A return value of 0 indicates the address was invalid. + */ +typedef uint32_t (*rc_client_read_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client); + +/** + * Internal method passed to rc_client_server_call_t to process the server response. + */ +typedef void (*rc_client_server_callback_t)(const rc_api_server_response_t* server_response, void* callback_data); + +/** + * Callback used to issue a request to the server. + */ +typedef void (*rc_client_server_call_t)(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client); + +/** + * Generic callback for asynchronous eventing. + */ +typedef void (*rc_client_callback_t)(int result, const char* error_message, rc_client_t* client, void* userdata); + +/** + * Callback for logging or displaying a message. + */ +typedef void (*rc_client_message_callback_t)(const char* message, const rc_client_t* client); + +/** + * Marks an async process as aborted. The associated callback will not be called. + */ +void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle); + +/*****************************************************************************\ +| Runtime | +\*****************************************************************************/ + +/** + * Creates a new rc_client_t object. + */ +rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function); + +/** + * Releases resources associated to a rc_client_t object. + * Pointer will no longer be valid after making this call. + */ +void rc_client_destroy(rc_client_t* client); + +/** + * Sets whether hardcore is enabled (on by default). + * Can be called with a game loaded. + * Enabling hardcore with a game loaded will raise an RC_CLIENT_EVENT_RESET + * event. Processing will be disabled until rc_client_reset is called. + */ +void rc_client_set_hardcore_enabled(rc_client_t* client, int enabled); + +/** + * Gets whether hardcore is enabled (on by default). + */ +int rc_client_get_hardcore_enabled(const rc_client_t* client); + +/** + * Sets whether encore mode is enabled (off by default). + * Evaluated when loading a game. Has no effect while a game is loaded. + */ +void rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled); + +/** + * Gets whether encore mode is enabled (off by default). + */ +int rc_client_get_encore_mode_enabled(const rc_client_t* client); + +/** + * Sets whether unofficial achievements should be loaded. + * Evaluated when loading a game. Has no effect while a game is loaded. + */ +void rc_client_set_unofficial_enabled(rc_client_t* client, int enabled); + +/** + * Gets whether unofficial achievements should be loaded. + */ +int rc_client_get_unofficial_enabled(const rc_client_t* client); + +/** + * Sets whether spectator mode is enabled (off by default). + * If enabled, events for achievement unlocks and leaderboard submissions will be + * raised, but server calls to actually perform the unlock/submit will not occur. + * Can be modified while a game is loaded. Evaluated at unlock/submit time. + * Cannot be modified if disabled before a game is loaded. + */ +void rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled); + +/** + * Gets whether spectator mode is enabled (off by default). + */ +int rc_client_get_spectator_mode_enabled(const rc_client_t* client); + +/** + * Attaches client-specific data to the runtime. + */ +void rc_client_set_userdata(rc_client_t* client, void* userdata); + +/** + * Gets the client-specific data attached to the runtime. + */ +void* rc_client_get_userdata(const rc_client_t* client); + +/** + * Sets the name of the server to use. + */ +void rc_client_set_host(const rc_client_t* client, const char* hostname); + +/*****************************************************************************\ +| Logging | +\*****************************************************************************/ + +/** + * Sets the logging level and provides a callback to be called to do the logging. + */ +void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback); +enum +{ + RC_CLIENT_LOG_LEVEL_NONE = 0, + RC_CLIENT_LOG_LEVEL_ERROR = 1, + RC_CLIENT_LOG_LEVEL_WARN = 2, + RC_CLIENT_LOG_LEVEL_INFO = 3, + RC_CLIENT_LOG_LEVEL_VERBOSE = 4 +}; + +/*****************************************************************************\ +| User | +\*****************************************************************************/ + +/** + * Attempt to login a user. + */ +rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* client, + const char* username, const char* password, + rc_client_callback_t callback, void* callback_userdata); + +/** + * Attempt to login a user. + */ +rc_client_async_handle_t* rc_client_begin_login_with_token(rc_client_t* client, + const char* username, const char* token, + rc_client_callback_t callback, void* callback_userdata); + +/** + * Logout the user. + */ +void rc_client_logout(rc_client_t* client); + +typedef struct rc_client_user_t { + const char* display_name; + const char* username; + const char* token; + uint32_t score; + uint32_t score_softcore; + uint32_t num_unread_messages; +} rc_client_user_t; + +/** + * Gets information about the logged in user. Will return NULL if the user is not logged in. + */ +const rc_client_user_t* rc_client_get_user_info(const rc_client_t* client); + +/** + * Gets the URL for the user's profile picture. + * Returns RC_OK on success. + */ +int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size); + +typedef struct rc_client_user_game_summary_t +{ + uint32_t num_core_achievements; + uint32_t num_unofficial_achievements; + uint32_t num_unlocked_achievements; + uint32_t num_unsupported_achievements; + + uint32_t points_core; + uint32_t points_unlocked; +} rc_client_user_game_summary_t; + +/** + * Gets a breakdown of the number of achievements in the game, and how many the user has unlocked. + * Used for the "You have unlocked X of Y achievements" message shown when the game starts. + */ +void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary); + +/*****************************************************************************\ +| Game | +\*****************************************************************************/ + +/** + * Start loading an unidentified game. + */ +rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* client, + uint32_t console_id, const char* file_path, + const uint8_t* data, size_t data_size, + rc_client_callback_t callback, void* callback_userdata); + +/** + * Start loading a game. + */ +rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const char* hash, + rc_client_callback_t callback, void* callback_userdata); + +/** + * Unloads the current game. + */ +void rc_client_unload_game(rc_client_t* client); + +typedef struct rc_client_game_t { + uint32_t id; + uint32_t console_id; + const char* title; + const char* hash; + const char* badge_name; +} rc_client_game_t; + +/** + * Get information about the current game. Returns NULL if no game is loaded. + */ +const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client); + +/** + * Gets the URL for the game image. + * Returns RC_OK on success. + */ +int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size); + +/** + * Changes the active disc in a multi-disc game. + */ +rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* file_path, + const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata); + +/*****************************************************************************\ +| Subsets | +\*****************************************************************************/ + +typedef struct rc_client_subset_t { + uint32_t id; + const char* title; + char badge_name[16]; + + uint32_t num_achievements; + uint32_t num_leaderboards; +} rc_client_subset_t; + +const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id); + +/*****************************************************************************\ +| Achievements | +\*****************************************************************************/ + +enum { + RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE = 0, /* unprocessed */ + RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE = 1, /* eligible to trigger */ + RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED = 2, /* earned by user */ + RC_CLIENT_ACHIEVEMENT_STATE_DISABLED = 3 /* not supported by this version of the runtime */ +}; + +enum { + RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE = 0, + RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE = (1 << 0), + RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL = (1 << 1), + RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE | RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL +}; + +enum { + RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN = 0, + RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED = 1, + RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED = 2, + RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED = 3, + RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL = 4, + RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5, + RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6, + RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7 +}; + +enum { + RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE = 0, + RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE = (1 << 0), + RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE = (1 << 1), + RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH = RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE | RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE +}; + +typedef struct rc_client_achievement_t { + const char* title; + const char* description; + char badge_name[8]; + char measured_progress[24]; + float measured_percent; + uint32_t id; + uint32_t points; + time_t unlock_time; + uint8_t state; + uint8_t category; + uint8_t bucket; + uint8_t unlocked; +} rc_client_achievement_t; + +/** + * Get information about an achievement. Returns NULL if not found. + */ +const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* client, uint32_t id); + +/** + * Gets the URL for the achievement image. + * Returns RC_OK on success. + */ +int rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size); + +typedef struct rc_client_achievement_bucket_t { + rc_client_achievement_t** achievements; + uint32_t num_achievements; + + const char* label; + uint32_t subset_id; + uint8_t bucket_type; +} rc_client_achievement_bucket_t; + +typedef struct rc_client_achievement_list_t { + rc_client_achievement_bucket_t* buckets; + uint32_t num_buckets; +} rc_client_achievement_list_t; + +enum { + RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE = 0, + RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS = 1 +}; + +/** + * Creates a list of achievements matching the specified category and grouping. + * Returns an allocated list that must be free'd by calling rc_client_destroy_achievement_list. + */ +rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* client, int category, int grouping); + +/** + * Destroys a list allocated by rc_client_get_achievement_list. + */ +void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list); + +/*****************************************************************************\ +| Leaderboards | +\*****************************************************************************/ + +enum { + RC_CLIENT_LEADERBOARD_STATE_INACTIVE = 0, + RC_CLIENT_LEADERBOARD_STATE_ACTIVE = 1, + RC_CLIENT_LEADERBOARD_STATE_TRACKING = 2, + RC_CLIENT_LEADERBOARD_STATE_DISABLED = 3 +}; + +typedef struct rc_client_leaderboard_t { + const char* title; + const char* description; + const char* tracker_value; + uint32_t id; + uint8_t state; + uint8_t lower_is_better; +} rc_client_leaderboard_t; + +/** + * Get information about a leaderboard. Returns NULL if not found. + */ +const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id); + +typedef struct rc_client_leaderboard_tracker_t { + char display[24]; + uint32_t id; +} rc_client_leaderboard_tracker_t; + +typedef struct rc_client_leaderboard_bucket_t { + rc_client_leaderboard_t** leaderboards; + uint32_t num_leaderboards; + + const char* label; + uint32_t subset_id; + uint8_t bucket_type; +} rc_client_leaderboard_bucket_t; + +typedef struct rc_client_leaderboard_list_t { + rc_client_leaderboard_bucket_t* buckets; + uint32_t num_buckets; +} rc_client_leaderboard_list_t; + +enum { + RC_CLIENT_LEADERBOARD_BUCKET_UNKNOWN = 0, + RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE = 1, + RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE = 2, + RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED = 3, + RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4 +}; + +enum { + RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE = 0, + RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING = 1 +}; + +/** + * Creates a list of leaderboards matching the specified grouping. + * Returns an allocated list that must be free'd by calling rc_client_destroy_leaderboard_list. + */ +rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* client, int grouping); + +/** + * Destroys a list allocated by rc_client_get_leaderboard_list. + */ +void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list); + +typedef struct rc_client_leaderboard_entry_t { + const char* user; + char display[24]; + time_t submitted; + uint32_t rank; + uint32_t index; +} rc_client_leaderboard_entry_t; + +typedef struct rc_client_leaderboard_entry_list_t { + rc_client_leaderboard_entry_t* entries; + uint32_t num_entries; + int32_t user_index; +} rc_client_leaderboard_entry_list_t; + +typedef void (*rc_client_fetch_leaderboard_entries_callback_t)(int result, const char* error_message, + rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* callback_userdata); + +/** + * Fetches a list of leaderboard entries from the server. + * Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list. + */ +rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id, + uint32_t first_entry, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); + +/** + * Fetches a list of leaderboard entries from the server containing the logged-in user. + * Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list. + */ +rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries_around_user(rc_client_t* client, uint32_t leaderboard_id, + uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata); + +/** + * Gets the URL for the profile picture of the user associated to a leaderboard entry. + * Returns RC_OK on success. + */ +int rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_entry_t* entry, char buffer[], size_t buffer_size); + +/** + * Destroys a list allocated by rc_client_begin_fetch_leaderboard_entries or rc_client_begin_fetch_leaderboard_entries_around_user. + */ +void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list); + +/*****************************************************************************\ +| Rich Presence | +\*****************************************************************************/ + +/** + * Gets the current rich presence message. + * Returns the number of characters written to buffer. + */ +size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size); + +/*****************************************************************************\ +| Processing | +\*****************************************************************************/ + +enum { + RC_CLIENT_EVENT_TYPE_NONE = 0, + RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED = 1, /* [achievement] was earned by the player */ + RC_CLIENT_EVENT_LEADERBOARD_STARTED = 2, /* [leaderboard] attempt has started */ + RC_CLIENT_EVENT_LEADERBOARD_FAILED = 3, /* [leaderboard] attempt failed */ + RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED = 4, /* [leaderboard] attempt submitted */ + RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW = 5, /* [achievement] challenge indicator should be shown */ + RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE = 6, /* [achievement] challenge indicator should be hidden */ + RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW = 7, /* progress indicator should be shown for [achievement] */ + RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE = 8, /* progress indicator should be hidden */ + RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE = 9, /* progress indicator should be updated to reflect new badge/progress for [achievement] */ + RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW = 10, /* [leaderboard_tracker] should be shown */ + RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE = 11, /* [leaderboard_tracker] should be hidden */ + RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE = 12, /* [leaderboard_tracker] updated */ + RC_CLIENT_EVENT_RESET = 13, /* emulated system should be reset (as the result of enabling hardcore) */ + RC_CLIENT_EVENT_GAME_COMPLETED = 14, /* all achievements for the game have been earned */ + RC_CLIENT_EVENT_SERVER_ERROR = 15 /* an API response returned a [server_error] and will not be retried */ +}; + +typedef struct rc_client_server_error_t +{ + const char* error_message; + const char* api; +} rc_client_server_error_t; + +typedef struct rc_client_event_t +{ + uint32_t type; + + rc_client_achievement_t* achievement; + rc_client_leaderboard_t* leaderboard; + rc_client_leaderboard_tracker_t* leaderboard_tracker; + rc_client_server_error_t* server_error; + +} rc_client_event_t; + +/** + * Callback used to notify the client when certain events occur. + */ +typedef void (*rc_client_event_handler_t)(const rc_client_event_t* event, rc_client_t* client); + +/** + * Provides a callback for event handling. + */ +void rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler); + +/** + * Provides a callback for reading memory. + */ +void rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler); + +/** + * Determines if there are any active achievements/leaderboards/rich presence that need processing. + */ +int rc_client_is_processing_required(rc_client_t* client); + +/** + * Processes achievements for the current frame. + */ +void rc_client_do_frame(rc_client_t* client); + +/** + * Processes the periodic queue. + * Called internally by rc_client_do_frame. + * Should be explicitly called if rc_client_do_frame is not being called because emulation is paused. + */ +void rc_client_idle(rc_client_t* client); + +/** + * Informs the runtime that the emulator has been reset. Will reset all achievements and leaderboards + * to their initial state (includes hiding indicators/trackers). + */ +void rc_client_reset(rc_client_t* client); + +/** + * Gets the number of bytes needed to serialized the runtime state. + */ +size_t rc_client_progress_size(rc_client_t* client); + +/** + * Serializes the runtime state into a buffer. + * Returns RC_OK on success, or an error indicator. + */ +int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer); + +/** + * Deserializes the runtime state from a buffer. + * Returns RC_OK on success, or an error indicator. + */ +int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized); + +#ifdef __cplusplus +} +#endif + +#endif /* RC_RUNTIME_H */ diff --git a/dep/rcheevos/include/rc_consoles.h b/dep/rcheevos/include/rc_consoles.h index 21c369c0a1..0dd9c1ee1e 100644 --- a/dep/rcheevos/include/rc_consoles.h +++ b/dep/rcheevos/include/rc_consoles.h @@ -10,6 +10,7 @@ extern "C" { \*****************************************************************************/ enum { + RC_CONSOLE_UNKNOWN = 0, RC_CONSOLE_MEGA_DRIVE = 1, RC_CONSOLE_NINTENDO_64 = 2, RC_CONSOLE_SUPER_NINTENDO = 3, @@ -85,6 +86,11 @@ enum { RC_CONSOLE_ARCADIA_2001 = 73, RC_CONSOLE_INTERTON_VC_4000 = 74, RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER = 75, + RC_CONSOLE_PC_ENGINE_CD = 76, + RC_CONSOLE_ATARI_JAGUAR_CD = 77, + RC_CONSOLE_NINTENDO_DSI = 78, + RC_CONSOLE_TI83 = 79, + RC_CONSOLE_UZEBOX = 80, RC_CONSOLE_HUBS = 100, RC_CONSOLE_EVENTS = 101 diff --git a/dep/rcheevos/include/rc_error.h b/dep/rcheevos/include/rc_error.h index 91a98c11cd..b80bc0bab6 100644 --- a/dep/rcheevos/include/rc_error.h +++ b/dep/rcheevos/include/rc_error.h @@ -36,7 +36,13 @@ enum { RC_INVALID_MEASURED_TARGET = -23, RC_INVALID_COMPARISON = -24, RC_INVALID_STATE = -25, - RC_INVALID_JSON = -26 + RC_INVALID_JSON = -26, + RC_API_FAILURE = -27, + RC_LOGIN_REQUIRED = -28, + RC_NO_GAME_LOADED = -29, + RC_HARDCORE_DISABLED = -30, + RC_ABORTED = -31, + RC_NO_RESPONSE = -32 }; const char* rc_error_str(int ret); diff --git a/dep/rcheevos/include/rc_hash.h b/dep/rcheevos/include/rc_hash.h index b5fd59babb..ba9ea1c027 100644 --- a/dep/rcheevos/include/rc_hash.h +++ b/dep/rcheevos/include/rc_hash.h @@ -27,20 +27,20 @@ extern "C" { /* data for rc_hash_iterate */ - struct rc_hash_iterator + typedef struct rc_hash_iterator { - uint8_t* buffer; + const uint8_t* buffer; size_t buffer_size; uint8_t consoles[12]; int index; const char* path; - }; + } rc_hash_iterator_t; /* initializes a rc_hash_iterator * - path must be provided * - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file) */ - void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, uint8_t* buffer, size_t buffer_size); + void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size); /* releases resources associated to a rc_hash_iterator */ @@ -92,11 +92,12 @@ extern "C" { /* ===================================================== */ - #define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) - #define RC_HASH_CDTRACK_LAST ((uint32_t)-2) - #define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) + #define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */ + #define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */ + #define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) /* the largest data/audio track */ + #define RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION ((uint32_t)-4) /* the first data/audio track of the second session */ - /* opens a track from the specified file. track 0 indicates the largest data track should be opened. + /* opens a track from the specified file. see the RC_HASH_CDTRACK_ defines for special tracks. * returns a handle to be passed to the other functions, or NULL if the track could not be opened. */ typedef void* (*rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track); diff --git a/dep/rcheevos/include/rc_runtime.h b/dep/rcheevos/include/rc_runtime.h index 6ad5f0266a..d7a25c707d 100644 --- a/dep/rcheevos/include/rc_runtime.h +++ b/dep/rcheevos/include/rc_runtime.h @@ -88,9 +88,12 @@ typedef struct rc_runtime_t { rc_value_t* variables; rc_value_t** next_variable; + + char owns_self; } rc_runtime_t; +rc_runtime_t* rc_runtime_alloc(void); void rc_runtime_init(rc_runtime_t* runtime); void rc_runtime_destroy(rc_runtime_t* runtime); @@ -121,7 +124,8 @@ enum { RC_RUNTIME_EVENT_LBOARD_TRIGGERED, RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED, RC_RUNTIME_EVENT_LBOARD_DISABLED, - RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED + RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED, + RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED }; typedef struct rc_runtime_event_t { diff --git a/dep/rcheevos/include/rc_runtime_types.h b/dep/rcheevos/include/rc_runtime_types.h index c446ab7309..dc3ab60c92 100644 --- a/dep/rcheevos/include/rc_runtime_types.h +++ b/dep/rcheevos/include/rc_runtime_types.h @@ -56,6 +56,7 @@ enum { RC_MEMSIZE_32_BITS_BE, RC_MEMSIZE_FLOAT, RC_MEMSIZE_MBF32, + RC_MEMSIZE_MBF32_LE, RC_MEMSIZE_VARIABLE }; @@ -169,7 +170,8 @@ enum { RC_OPERATOR_NONE, RC_OPERATOR_MULT, RC_OPERATOR_DIV, - RC_OPERATOR_AND + RC_OPERATOR_AND, + RC_OPERATOR_XOR }; typedef struct rc_condition_t rc_condition_t; @@ -198,6 +200,9 @@ struct rc_condition_t { /* Whether or not the condition evaluated true on the last check */ char is_true; + + /* Unique identifier of optimized comparator to use */ + char optimized_comparator; }; /*****************************************************************************\ diff --git a/dep/rcheevos/src/rapi/rc_api_common.c b/dep/rcheevos/src/rapi/rc_api_common.c index c703af7525..7fdc7beb18 100644 --- a/dep/rcheevos/src/rapi/rc_api_common.c +++ b/dep/rcheevos/src/rapi/rc_api_common.c @@ -10,7 +10,9 @@ #include #define RETROACHIEVEMENTS_HOST "https://retroachievements.org" -#define RETROACHIEVEMENTS_IMAGE_HOST "http://i.retroachievements.org" +#define RETROACHIEVEMENTS_IMAGE_HOST "https://media.retroachievements.org" +#define RETROACHIEVEMENTS_HOST_NONSSL "http://retroachievements.org" +#define RETROACHIEVEMENTS_IMAGE_HOST_NONSSL "http://media.retroachievements.org" static char* g_host = NULL; static char* g_imagehost = NULL; @@ -18,157 +20,174 @@ static char* g_imagehost = NULL; /* --- rc_json --- */ -static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen); -static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field); +static int rc_json_parse_object(rc_json_iterator_t* iterator, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen); +static int rc_json_parse_array(rc_json_iterator_t* iterator, rc_json_field_t* field); -static int rc_json_parse_field(const char** json_ptr, rc_json_field_t* field) { +static int rc_json_match_char(rc_json_iterator_t* iterator, char c) +{ + if (iterator->json < iterator->end && *iterator->json == c) { + ++iterator->json; + return 1; + } + + return 0; +} + +static void rc_json_skip_whitespace(rc_json_iterator_t* iterator) +{ + while (iterator->json < iterator->end && isspace((unsigned char)*iterator->json)) + ++iterator->json; +} + +static int rc_json_find_closing_quote(rc_json_iterator_t* iterator) +{ + while (iterator->json < iterator->end) { + if (*iterator->json == '"') + return 1; + + if (*iterator->json == '\\') { + ++iterator->json; + if (iterator->json == iterator->end) + return 0; + } + + if (*iterator->json == '\0') + return 0; + + ++iterator->json; + } + + return 0; +} + +static int rc_json_parse_field(rc_json_iterator_t* iterator, rc_json_field_t* field) { int result; - field->value_start = *json_ptr; + if (iterator->json >= iterator->end) + return RC_INVALID_JSON; + + field->value_start = iterator->json; - switch (**json_ptr) + switch (*iterator->json) { case '"': /* quoted string */ - ++(*json_ptr); - while (**json_ptr != '"') { - if (**json_ptr == '\\') - ++(*json_ptr); - - if (**json_ptr == '\0') - return RC_INVALID_JSON; - - ++(*json_ptr); - } - ++(*json_ptr); + ++iterator->json; + if (!rc_json_find_closing_quote(iterator)) + return RC_INVALID_JSON; + ++iterator->json; break; case '-': case '+': /* signed number */ - ++(*json_ptr); + ++iterator->json; /* fallthrough to number */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* number */ - do { - ++(*json_ptr); - } while (**json_ptr >= '0' && **json_ptr <= '9'); - if (**json_ptr == '.') { - do { - ++(*json_ptr); - } while (**json_ptr >= '0' && **json_ptr <= '9'); + while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9') + ++iterator->json; + + if (rc_json_match_char(iterator, '.')) { + while (iterator->json < iterator->end && *iterator->json >= '0' && *iterator->json <= '9') + ++iterator->json; } break; case '[': /* array */ - result = rc_json_parse_array(json_ptr, field); + result = rc_json_parse_array(iterator, field); if (result != RC_OK) - return result; + return result; break; case '{': /* object */ - result = rc_json_parse_object(json_ptr, NULL, 0, &field->array_size); + result = rc_json_parse_object(iterator, NULL, 0, &field->array_size); if (result != RC_OK) return result; break; default: /* non-quoted text [true,false,null] */ - if (!isalpha((unsigned char)**json_ptr)) + if (!isalpha((unsigned char)*iterator->json)) return RC_INVALID_JSON; - do { - ++(*json_ptr); - } while (isalnum((unsigned char)**json_ptr)); + while (iterator->json < iterator->end && isalnum((unsigned char)*iterator->json)) + ++iterator->json; break; } - field->value_end = *json_ptr; + field->value_end = iterator->json; return RC_OK; } -static int rc_json_parse_array(const char** json_ptr, rc_json_field_t* field) { +static int rc_json_parse_array(rc_json_iterator_t* iterator, rc_json_field_t* field) { rc_json_field_t unused_field; - const char* json = *json_ptr; int result; - if (*json != '[') + if (!rc_json_match_char(iterator, '[')) return RC_INVALID_JSON; - ++json; field->array_size = 0; - if (*json != ']') { - do - { - while (isspace((unsigned char)*json)) - ++json; - result = rc_json_parse_field(&json, &unused_field); - if (result != RC_OK) - return result; + if (rc_json_match_char(iterator, ']')) /* empty array */ + return RC_OK; - ++field->array_size; + do + { + rc_json_skip_whitespace(iterator); - while (isspace((unsigned char)*json)) - ++json; + result = rc_json_parse_field(iterator, &unused_field); + if (result != RC_OK) + return result; - if (*json != ',') - break; + ++field->array_size; - ++json; - } while (1); + rc_json_skip_whitespace(iterator); + } while (rc_json_match_char(iterator, ',')); - if (*json != ']') - return RC_INVALID_JSON; - } + if (!rc_json_match_char(iterator, ']')) + return RC_INVALID_JSON; - *json_ptr = ++json; return RC_OK; } -static int rc_json_get_next_field(rc_json_object_field_iterator_t* iterator) { - const char* json = iterator->json; +static int rc_json_get_next_field(rc_json_iterator_t* iterator, rc_json_field_t* field) { + rc_json_skip_whitespace(iterator); - while (isspace((unsigned char)*json)) - ++json; - - if (*json != '"') + if (!rc_json_match_char(iterator, '"')) return RC_INVALID_JSON; - iterator->field.name = ++json; - while (*json != '"') { - if (!*json) + field->name = iterator->json; + while (iterator->json < iterator->end && *iterator->json != '"') { + if (!*iterator->json) return RC_INVALID_JSON; - ++json; + ++iterator->json; } - iterator->name_len = json - iterator->field.name; - ++json; - - while (isspace((unsigned char)*json)) - ++json; - if (*json != ':') + if (iterator->json == iterator->end) return RC_INVALID_JSON; - ++json; + field->name_len = iterator->json - field->name; + ++iterator->json; + + rc_json_skip_whitespace(iterator); + + if (!rc_json_match_char(iterator, ':')) + return RC_INVALID_JSON; - while (isspace((unsigned char)*json)) - ++json; + rc_json_skip_whitespace(iterator); - if (rc_json_parse_field(&json, &iterator->field) < 0) + if (rc_json_parse_field(iterator, field) < 0) return RC_INVALID_JSON; - while (isspace((unsigned char)*json)) - ++json; + rc_json_skip_whitespace(iterator); - iterator->json = json; return RC_OK; } -static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen) { - rc_json_object_field_iterator_t iterator; - const char* json = *json_ptr; +static int rc_json_parse_object(rc_json_iterator_t* iterator, rc_json_field_t* fields, size_t field_count, unsigned* fields_seen) { size_t i; unsigned num_fields = 0; + rc_json_field_t field; int result; if (fields_seen) @@ -177,60 +196,105 @@ static int rc_json_parse_object(const char** json_ptr, rc_json_field_t* fields, for (i = 0; i < field_count; ++i) fields[i].value_start = fields[i].value_end = NULL; - if (*json != '{') + if (!rc_json_match_char(iterator, '{')) return RC_INVALID_JSON; - ++json; - if (*json == '}') { - *json_ptr = ++json; + if (rc_json_match_char(iterator, '}')) /* empty object */ return RC_OK; - } - - memset(&iterator, 0, sizeof(iterator)); - iterator.json = json; do { - result = rc_json_get_next_field(&iterator); + result = rc_json_get_next_field(iterator, &field); if (result != RC_OK) return result; for (i = 0; i < field_count; ++i) { - if (!fields[i].value_start && strncmp(fields[i].name, iterator.field.name, iterator.name_len) == 0 && - fields[i].name[iterator.name_len] == '\0') { - fields[i].value_start = iterator.field.value_start; - fields[i].value_end = iterator.field.value_end; - fields[i].array_size = iterator.field.array_size; + if (!fields[i].value_start && fields[i].name_len == field.name_len && + memcmp(fields[i].name, field.name, field.name_len) == 0) { + fields[i].value_start = field.value_start; + fields[i].value_end = field.value_end; + fields[i].array_size = field.array_size; break; } } ++num_fields; - if (*iterator.json != ',') - break; - ++iterator.json; - } while (1); + } while (rc_json_match_char(iterator, ',')); - if (*iterator.json != '}') + if (!rc_json_match_char(iterator, '}')) return RC_INVALID_JSON; if (fields_seen) *fields_seen = num_fields; - *json_ptr = ++iterator.json; return RC_OK; } -int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator) { - if (*iterator->json != ',' && *iterator->json != '{') +int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field) { + if (!rc_json_match_char(iterator, ',') && !rc_json_match_char(iterator, '{')) return 0; - ++iterator->json; - return (rc_json_get_next_field(iterator) == RC_OK); + return (rc_json_get_next_field(iterator, field) == RC_OK); } -int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_json_field_t* fields, size_t field_count) { +int rc_json_get_object_string_length(const char* json) { + const char* json_start = json; + + rc_json_iterator_t iterator; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = json; + iterator.end = json + (1024 * 1024 * 1024); /* arbitrary 1GB limit on JSON response */ + + rc_json_parse_object(&iterator, NULL, 0, NULL); + + return (int)(iterator.json - json_start); +} + +static int rc_json_extract_html_error(rc_api_response_t* response, const rc_api_server_response_t* server_response) { + const char* json = server_response->body; + const char* end = json; + + const char* title_start = strstr(json, ""); + if (title_start) { + title_start += 7; + if (isdigit((int)*title_start)) { + const char* title_end = strstr(title_start + 7, ""); + if (title_end) { + char* dst = rc_buf_reserve(&response->buffer, (title_end - title_start) + 1); + response->error_message = dst; + memcpy(dst, title_start, title_end - title_start); + dst += (title_end - title_start); + *dst++ = '\0'; + rc_buf_consume(&response->buffer, response->error_message, dst); + response->succeeded = 0; + return RC_INVALID_JSON; + } + } + } + + while (*end && *end != '\n' && end - json < 200) + ++end; + + if (end > json && end[-1] == '\r') + --end; + + if (end > json) { + char* dst = rc_buf_reserve(&response->buffer, (end - json) + 1); + response->error_message = dst; + memcpy(dst, json, end - json); + dst += (end - json); + *dst++ = '\0'; + rc_buf_consume(&response->buffer, response->error_message, dst); + } + + response->succeeded = 0; + return RC_INVALID_JSON; +} + +int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_server_response_t* server_response, rc_json_field_t* fields, size_t field_count) { + int result; + #ifndef NDEBUG if (field_count < 2) return RC_INVALID_STATE; @@ -240,37 +304,28 @@ int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_jso return RC_INVALID_STATE; #endif - if (*json == '{') { - int result = rc_json_parse_object(&json, fields, field_count, NULL); - - rc_json_get_optional_string(&response->error_message, response, &fields[1], "Error", NULL); - rc_json_get_optional_bool(&response->succeeded, &fields[0], "Success", 1); + response->error_message = NULL; - return result; + if (!server_response || !server_response->body || !*server_response->body) { + response->succeeded = 0; + return RC_NO_RESPONSE; } - response->error_message = NULL; + if (*server_response->body != '{') { + result = rc_json_extract_html_error(response, server_response); + } + else { + rc_json_iterator_t iterator; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = server_response->body; + iterator.end = server_response->body + server_response->body_length; + result = rc_json_parse_object(&iterator, fields, field_count, NULL); - if (*json) { - const char* end = json; - while (*end && *end != '\n' && end - json < 200) - ++end; - - if (end > json && end[-1] == '\r') - --end; - - if (end > json) { - char* dst = rc_buf_reserve(&response->buffer, (end - json) + 1); - response->error_message = dst; - memcpy(dst, json, end - json); - dst += (end - json); - *dst++ = '\0'; - rc_buf_consume(&response->buffer, response->error_message, dst); - } + rc_json_get_optional_string(&response->error_message, response, &fields[1], "Error", NULL); + rc_json_get_optional_bool(&response->succeeded, &fields[0], "Success", 1); } - response->succeeded = 0; - return RC_INVALID_JSON; + return result; } static int rc_json_missing_field(rc_api_response_t* response, const rc_json_field_t* field) { @@ -293,42 +348,48 @@ static int rc_json_missing_field(rc_api_response_t* response, const rc_json_fiel } int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name) { - const char* json = field->value_start; + rc_json_iterator_t iterator; + #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif - if (!json) + if (!field->value_start) return rc_json_missing_field(response, field); - return (rc_json_parse_object(&json, fields, field_count, &field->array_size) == RC_OK); + memset(&iterator, 0, sizeof(iterator)); + iterator.json = field->value_start; + iterator.end = field->value_end; + return (rc_json_parse_object(&iterator, fields, field_count, &field->array_size) == RC_OK); } -static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_field_t* iterator) { - if (!iterator->array_size) - return 0; +static int rc_json_get_array_entry_value(rc_json_field_t* field, rc_json_iterator_t* iterator) { + rc_json_skip_whitespace(iterator); - while (isspace((unsigned char)*iterator->value_start)) - ++iterator->value_start; + if (iterator->json >= iterator->end) + return 0; - rc_json_parse_field(&iterator->value_start, field); + if (rc_json_parse_field(iterator, field) != RC_OK) + return 0; - while (isspace((unsigned char)*iterator->value_start)) - ++iterator->value_start; + rc_json_skip_whitespace(iterator); - ++iterator->value_start; /* skip , or ] */ + if (!rc_json_match_char(iterator, ',')) + rc_json_match_char(iterator, ']'); - --iterator->array_size; return 1; } int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { - rc_json_field_t iterator; + rc_json_iterator_t iterator; + rc_json_field_t array; rc_json_field_t value; unsigned* entry; - if (!rc_json_get_required_array(num_entries, &iterator, response, field, field_name)) + if (!rc_json_get_required_array(num_entries, &array, response, field, field_name)) return RC_MISSING_VALUE; if (*num_entries) { @@ -338,6 +399,10 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r 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_unum(entry, &value, field_name)) @@ -353,10 +418,12 @@ int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, r return RC_OK; } -int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { +int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name) { #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (!field->value_start || *field->value_start != '[') { @@ -364,28 +431,27 @@ int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator, return rc_json_missing_field(response, field); } - memcpy(iterator, field, sizeof(*iterator)); - ++iterator->value_start; /* skip [ */ + memcpy(array_field, field, sizeof(*array_field)); + ++array_field->value_start; /* skip [ */ *num_entries = field->array_size; return 1; } -int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_field_t* iterator) { - if (!iterator->array_size) - return 0; +int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator) { + rc_json_skip_whitespace(iterator); - while (isspace((unsigned char)*iterator->value_start)) - ++iterator->value_start; + if (iterator->json >= iterator->end) + return 0; - rc_json_parse_object(&iterator->value_start, fields, field_count, NULL); + if (rc_json_parse_object(iterator, fields, field_count, NULL) != RC_OK) + return 0; - while (isspace((unsigned char)*iterator->value_start)) - ++iterator->value_start; + rc_json_skip_whitespace(iterator); - ++iterator->value_start; /* skip , or ] */ + if (!rc_json_match_char(iterator, ',')) + rc_json_match_char(iterator, ']'); - --iterator->array_size; return 1; } @@ -451,6 +517,8 @@ int rc_json_get_string(const char** out, rc_api_buffer_t* buffer, const rc_json_ #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (!src) { @@ -562,6 +630,8 @@ int rc_json_get_num(int* out, const rc_json_field_t* field, const char* field_na #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (!src) { @@ -613,6 +683,8 @@ int rc_json_get_unum(unsigned* out, const rc_json_field_t* field, const char* fi #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (!src) { @@ -654,11 +726,13 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (*field->value_start == '\"') { memset(&tm, 0, sizeof(tm)); - if (sscanf(field->value_start + 1, "%d-%d-%d %d:%d:%d", + if (sscanf_s(field->value_start + 1, "%d-%d-%d %d:%d:%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) { tm.tm_mon--; /* 0-based */ tm.tm_year -= 1900; /* 1900 based */ @@ -669,9 +743,11 @@ int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* * timezone conversion twice and manually removing the difference */ { time_t local_timet = mktime(&tm); - struct tm* gmt_tm = gmtime(&local_timet); - time_t skewed_timet = mktime(gmt_tm); /* applies local time adjustment second time */ - time_t tz_offset = skewed_timet - local_timet; + time_t skewed_timet, tz_offset; + struct tm gmt_tm; + gmtime_s(&gmt_tm, &local_timet); + skewed_timet = mktime(&gmt_tm); /* applies local time adjustment second time */ + tz_offset = skewed_timet - local_timet; *out = local_timet - tz_offset; } @@ -696,6 +772,8 @@ int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_n #ifndef NDEBUG if (strcmp(field->name, field_name) != 0) return 0; +#else + (void)field_name; #endif if (src) { @@ -731,31 +809,32 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js /* --- rc_buf --- */ void rc_buf_init(rc_api_buffer_t* buffer) { - buffer->write = &buffer->data[0]; - buffer->end = &buffer->data[sizeof(buffer->data)]; - buffer->next = NULL; + buffer->chunk.write = buffer->chunk.start = &buffer->data[0]; + buffer->chunk.end = &buffer->data[sizeof(buffer->data)]; + buffer->chunk.next = NULL; } void rc_buf_destroy(rc_api_buffer_t* buffer) { + rc_api_buffer_chunk_t *chunk; #ifdef DEBUG_BUFFERS int count = 0; int wasted = 0; int total = 0; #endif - /* first buffer is not allocated */ - buffer = buffer->next; + /* first chunk is not allocated. skip it. */ + chunk = buffer->chunk.next; /* deallocate any additional buffers */ - while (buffer) { - rc_api_buffer_t* next = buffer->next; + while (chunk) { + rc_api_buffer_chunk_t* next = chunk->next; #ifdef DEBUG_BUFFERS - total += (int)(buffer->end - buffer->data); - wasted += (int)(buffer->end - buffer->write); + total += (int)(chunk->end - chunk->data); + wasted += (int)(chunk->end - chunk->write); ++count; #endif - free(buffer); - buffer = next; + free(chunk); + chunk = next; } #ifdef DEBUG_BUFFERS @@ -765,47 +844,50 @@ void rc_buf_destroy(rc_api_buffer_t* buffer) { } char* rc_buf_reserve(rc_api_buffer_t* buffer, size_t amount) { + rc_api_buffer_chunk_t* chunk = &buffer->chunk; size_t remaining; - while (buffer) { - remaining = buffer->end - buffer->write; + while (chunk) { + remaining = chunk->end - chunk->write; if (remaining >= amount) - return buffer->write; + return chunk->write; - if (!buffer->next) { - /* allocate a chunk of memory that is a multiple of 256-bytes. casting it to an rc_api_buffer_t will - * effectively unbound the data field, so use write and end pointers to track how data is being used. + if (!chunk->next) { + /* allocate a chunk of memory that is a multiple of 256-bytes. the first 32 bytes will be associated + * to the chunk header, and the remaining will be used for data. */ - const size_t buffer_prefix_size = sizeof(rc_api_buffer_t) - sizeof(buffer->data); - const size_t alloc_size = (amount + buffer_prefix_size + 0xFF) & ~0xFF; - buffer->next = (rc_api_buffer_t*)malloc(alloc_size); - if (!buffer->next) + const size_t chunk_header_size = sizeof(rc_api_buffer_chunk_t); + const size_t alloc_size = (chunk_header_size + amount + 0xFF) & ~0xFF; + chunk->next = (rc_api_buffer_chunk_t*)malloc(alloc_size); + if (!chunk->next) break; - buffer->next->write = buffer->next->data; - buffer->next->end = buffer->next->write + (alloc_size - buffer_prefix_size); - buffer->next->next = NULL; + chunk->next->start = (char*)chunk->next + chunk_header_size; + chunk->next->write = chunk->next->start; + chunk->next->end = (char*)chunk->next + alloc_size; + chunk->next->next = NULL; } - buffer = buffer->next; + chunk = chunk->next; } return NULL; } void rc_buf_consume(rc_api_buffer_t* buffer, const char* start, char* end) { + rc_api_buffer_chunk_t* chunk = &buffer->chunk; do { - if (buffer->write == start) { - size_t offset = (end - buffer->data); + if (chunk->write == start) { + size_t offset = (end - chunk->start); offset = (offset + 7) & ~7; - buffer->write = &buffer->data[offset]; + chunk->write = &chunk->start[offset]; - if (buffer->write > buffer->end) - buffer->write = buffer->end; + if (chunk->write > chunk->end) + chunk->write = chunk->end; break; } - buffer = buffer->next; - } while (buffer); + chunk = chunk->next; + } while (chunk); } void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount) { @@ -814,6 +896,18 @@ void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount) { return (void*)ptr; } +char* rc_buf_strncpy(rc_api_buffer_t* buffer, const char* src, size_t len) { + char* dst = rc_buf_reserve(buffer, len + 1); + memcpy(dst, src, len); + dst[len] = '\0'; + rc_buf_consume(buffer, dst, dst + len + 2); + return dst; +} + +char* rc_buf_strcpy(rc_api_buffer_t* buffer, const char* src) { + return rc_buf_strncpy(buffer, src, strlen(src)); +} + void rc_api_destroy_request(rc_api_request_t* request) { rc_buf_destroy(&request->buffer); } @@ -828,13 +922,13 @@ void rc_api_format_md5(char checksum[33], const unsigned char digest[16]) { /* --- rc_url_builder --- */ void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer, size_t estimated_size) { - rc_api_buffer_t* used_buffer; + rc_api_buffer_chunk_t* used_buffer; memset(builder, 0, sizeof(*builder)); builder->buffer = buffer; builder->write = builder->start = rc_buf_reserve(buffer, estimated_size); - used_buffer = buffer; + used_buffer = &buffer->chunk; while (used_buffer && used_buffer->write != builder->write) used_buffer = used_buffer->next; @@ -857,7 +951,7 @@ static int rc_url_builder_reserve(rc_api_url_builder_t* builder, size_t amount) if (remaining < amount) { const size_t used = builder->write - builder->start; const size_t current_size = builder->end - builder->start; - const size_t buffer_prefix_size = sizeof(rc_api_buffer_t) - sizeof(builder->buffer->data); + const size_t buffer_prefix_size = sizeof(rc_api_buffer_chunk_t); char* new_start; size_t new_size = (current_size < 256) ? 256 : current_size * 2; do { @@ -1054,6 +1148,16 @@ static void rc_api_update_host(char** host, const char* hostname) { void rc_api_set_host(const char* hostname) { rc_api_update_host(&g_host, hostname); + + if (!hostname) { + /* also clear out the image hostname */ + rc_api_set_image_host(NULL); + } + else if (strcmp(hostname, RETROACHIEVEMENTS_HOST_NONSSL) == 0) { + /* if just pointing at the non-HTTPS host, explicitly use the default image host + * so it doesn't try to use the web host directly */ + rc_api_set_image_host(RETROACHIEVEMENTS_IMAGE_HOST_NONSSL); + } } void rc_api_set_image_host(const char* hostname) { diff --git a/dep/rcheevos/src/rapi/rc_api_common.h b/dep/rcheevos/src/rapi/rc_api_common.h index 9e2dc53b08..99b17c6d61 100644 --- a/dep/rcheevos/src/rapi/rc_api_common.h +++ b/dep/rcheevos/src/rapi/rc_api_common.h @@ -10,10 +10,13 @@ extern "C" { #endif +#define RC_CONTENT_TYPE_URLENCODED "application/x-www-form-urlencoded" + typedef struct rc_api_url_builder_t { char* write; char* start; char* end; + /* pointer to a preallocated rc_api_buffer_t */ rc_api_buffer_t* buffer; int result; } @@ -23,22 +26,24 @@ void rc_url_builder_init(rc_api_url_builder_t* builder, rc_api_buffer_t* buffer, void rc_url_builder_append(rc_api_url_builder_t* builder, const char* data, size_t len); const char* rc_url_builder_finalize(rc_api_url_builder_t* builder); +#define RC_JSON_NEW_FIELD(n) {NULL,NULL,n,sizeof(n)-1,0} + typedef struct rc_json_field_t { - const char* name; const char* value_start; const char* value_end; + const char* name; + size_t name_len; unsigned array_size; } rc_json_field_t; -typedef struct rc_json_object_field_iterator_t { - rc_json_field_t field; +typedef struct rc_json_iterator_t { const char* json; - size_t name_len; + const char* end; } -rc_json_object_field_iterator_t; +rc_json_iterator_t; -int rc_json_parse_response(rc_api_response_t* response, const char* json, rc_json_field_t* fields, size_t field_count); +int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_server_response_t* server_response, rc_json_field_t* fields, size_t field_count); int rc_json_get_string(const char** out, rc_api_buffer_t* buffer, const rc_json_field_t* field, const char* field_name); int rc_json_get_num(int* out, const rc_json_field_t* field, const char* field_name); int rc_json_get_unum(unsigned* out, const rc_json_field_t* field, const char* field_name); @@ -55,15 +60,18 @@ int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_js int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name); int rc_json_get_required_unum_array(unsigned** entries, unsigned* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); -int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* iterator, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); -int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_field_t* iterator); -int rc_json_get_next_object_field(rc_json_object_field_iterator_t* iterator); +int rc_json_get_required_array(unsigned* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name); +int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator); +int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field); +int rc_json_get_object_string_length(const char* json); void rc_buf_init(rc_api_buffer_t* buffer); void rc_buf_destroy(rc_api_buffer_t* buffer); char* rc_buf_reserve(rc_api_buffer_t* buffer, size_t amount); void rc_buf_consume(rc_api_buffer_t* buffer, const char* start, char* end); void* rc_buf_alloc(rc_api_buffer_t* buffer, size_t amount); +char* rc_buf_strcpy(rc_api_buffer_t* buffer, const char* src); +char* rc_buf_strncpy(rc_api_buffer_t* buffer, const char* src, size_t len); void rc_url_builder_append_encoded_str(rc_api_url_builder_t* builder, const char* str); void rc_url_builder_append_num_param(rc_api_url_builder_t* builder, const char* param, int value); diff --git a/dep/rcheevos/src/rapi/rc_api_editor.c b/dep/rcheevos/src/rapi/rc_api_editor.c index 3d2bd7401e..ea4cbe19ba 100644 --- a/dep/rcheevos/src/rapi/rc_api_editor.c +++ b/dep/rcheevos/src/rapi/rc_api_editor.c @@ -22,12 +22,24 @@ int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; return builder.result; } int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response) { - rc_json_field_t iterator; + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_fetch_code_notes_server_response(response, &response_obj); +} + +int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response) { + rc_json_field_t array_field; + rc_json_iterator_t iterator; rc_api_code_note_t* note; const char* address_str; const char* last_author = ""; @@ -36,25 +48,25 @@ int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"CodeNotes"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("CodeNotes") }; rc_json_field_t note_fields[] = { - {"Address"}, - {"User"}, - {"Note"} + RC_JSON_NEW_FIELD("Address"), + RC_JSON_NEW_FIELD("User"), + RC_JSON_NEW_FIELD("Note") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; - if (!rc_json_get_required_array(&response->num_notes, &iterator, &response->response, &fields[2], "CodeNotes")) + if (!rc_json_get_required_array(&response->num_notes, &array_field, &response->response, &fields[2], "CodeNotes")) return RC_MISSING_VALUE; if (response->num_notes) { @@ -62,15 +74,21 @@ int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* if (!response->notes) return RC_OUT_OF_MEMORY; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; + note = response->notes; while (rc_json_get_array_entry_object(note_fields, sizeof(note_fields) / sizeof(note_fields[0]), &iterator)) { /* an empty note represents a record that was deleted on the server */ /* a note set to '' also represents a deleted note (remnant of a bug) */ /* NOTE: len will include the quotes */ - len = note_fields[2].value_end - note_fields[2].value_start; - if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) { - --response->num_notes; - continue; + if (note_fields[2].value_start) { + len = note_fields[2].value_end - note_fields[2].value_start; + if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) { + --response->num_notes; + continue; + } } if (!rc_json_get_required_string(&address_str, &response->response, ¬e_fields[0], "Address")) @@ -123,27 +141,38 @@ int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api rc_url_builder_append_str_param(&builder, "n", api_params->note); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; return builder.result; } int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_update_code_note_server_response(response, &response_obj); +} + +int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error") /* unused fields - {"GameID"}, - {"Address"}, - {"Note"} + RC_JSON_NEW_FIELD("GameID"), + RC_JSON_NEW_FIELD("Address"), + RC_JSON_NEW_FIELD("Note") */ }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; @@ -206,23 +235,34 @@ int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_a rc_url_builder_append_str_param(&builder, "h", buffer); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; return builder.result; } int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_update_achievement_server_response(response, &response_obj); +} + +int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"AchievementID"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("AchievementID") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; @@ -297,23 +337,34 @@ int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_a rc_url_builder_append_str_param(&builder, "h", buffer); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; return builder.result; } int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_update_leaderboard_server_response(response, &response_obj); +} + +int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"LeaderboardID"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("LeaderboardID") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; @@ -338,24 +389,37 @@ int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_ap rc_url_builder_append_str_param(&builder, "r", "badgeiter"); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; + + (void)api_params; return builder.result; } int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_fetch_badge_range_server_response(response, &response_obj); +} + +int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"FirstBadge"}, - {"NextBadge"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("FirstBadge"), + RC_JSON_NEW_FIELD("NextBadge") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; @@ -399,33 +463,44 @@ int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_ad rc_url_builder_append_str_param(&builder, "d", api_params->hash_description); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; return builder.result; } int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_add_game_hash_server_response(response, &response_obj); +} + +int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"Response"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Response") }; rc_json_field_t response_fields[] = { - {"GameID"} + RC_JSON_NEW_FIELD("GameID") /* unused fields - {"MD5"}, - {"ConsoleID"}, - {"GameTitle"}, - {"Success"} + RC_JSON_NEW_FIELD("MD5"), + RC_JSON_NEW_FIELD("ConsoleID"), + RC_JSON_NEW_FIELD("GameTitle"), + RC_JSON_NEW_FIELD("Success") */ }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; diff --git a/dep/rcheevos/src/rapi/rc_api_info.c b/dep/rcheevos/src/rapi/rc_api_info.c index 7dc758a1c2..0ee4de79c5 100644 --- a/dep/rcheevos/src/rapi/rc_api_info.c +++ b/dep/rcheevos/src/rapi/rc_api_info.c @@ -27,45 +27,57 @@ int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_url_builder_append_unum_param(&builder, "c", api_params->count); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; } return builder.result; } int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_fetch_achievement_info_server_response(response, &response_obj); +} + +int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response) { rc_api_achievement_awarded_entry_t* entry; - rc_json_field_t iterator; + rc_json_field_t array_field; + rc_json_iterator_t iterator; unsigned timet; int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"AchievementID"}, - {"Response"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("AchievementID"), + RC_JSON_NEW_FIELD("Response") /* unused fields - {"Offset"}, - {"Count"}, - {"FriendsOnly"}, + RC_JSON_NEW_FIELD("Offset"), + RC_JSON_NEW_FIELD("Count"), + RC_JSON_NEW_FIELD("FriendsOnly") * unused fields */ }; rc_json_field_t response_fields[] = { - {"NumEarned"}, - {"TotalPlayers"}, - {"GameID"}, - {"RecentWinner"} /* array */ + RC_JSON_NEW_FIELD("NumEarned"), + RC_JSON_NEW_FIELD("TotalPlayers"), + RC_JSON_NEW_FIELD("GameID"), + RC_JSON_NEW_FIELD("RecentWinner") /* array */ }; rc_json_field_t entry_fields[] = { - {"User"}, - {"DateAwarded"} + RC_JSON_NEW_FIELD("User"), + RC_JSON_NEW_FIELD("DateAwarded") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK) return result; @@ -81,7 +93,7 @@ int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[2], "GameID")) return RC_MISSING_VALUE; - if (!rc_json_get_required_array(&response->num_recently_awarded, &iterator, &response->response, &response_fields[3], "RecentWinner")) + if (!rc_json_get_required_array(&response->num_recently_awarded, &array_field, &response->response, &response_fields[3], "RecentWinner")) return RC_MISSING_VALUE; if (response->num_recently_awarded) { @@ -89,6 +101,10 @@ int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info if (!response->recently_awarded) return RC_OUT_OF_MEMORY; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; + entry = response->recently_awarded; while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User")) @@ -130,57 +146,69 @@ int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_url_builder_append_unum_param(&builder, "c", api_params->count); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; return builder.result; } int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_fetch_leaderboard_info_server_response(response, &response_obj); +} + +int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response) { rc_api_lboard_info_entry_t* entry; - rc_json_field_t iterator; + rc_json_field_t array_field; + rc_json_iterator_t iterator; unsigned timet; int result; size_t len; char format[16]; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"LeaderboardData"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("LeaderboardData") }; rc_json_field_t leaderboarddata_fields[] = { - {"LBID"}, - {"LBFormat"}, - {"LowerIsBetter"}, - {"LBTitle"}, - {"LBDesc"}, - {"LBMem"}, - {"GameID"}, - {"LBAuthor"}, - {"LBCreated"}, - {"LBUpdated"}, - {"Entries"} /* array */ + RC_JSON_NEW_FIELD("LBID"), + RC_JSON_NEW_FIELD("LBFormat"), + RC_JSON_NEW_FIELD("LowerIsBetter"), + RC_JSON_NEW_FIELD("LBTitle"), + RC_JSON_NEW_FIELD("LBDesc"), + RC_JSON_NEW_FIELD("LBMem"), + RC_JSON_NEW_FIELD("GameID"), + RC_JSON_NEW_FIELD("LBAuthor"), + RC_JSON_NEW_FIELD("LBCreated"), + RC_JSON_NEW_FIELD("LBUpdated"), + RC_JSON_NEW_FIELD("Entries") /* array */ /* unused fields - {"GameTitle"}, - {"ConsoleID"}, - {"ConsoleName"}, - {"ForumTopicID"}, - {"GameIcon"}, + RC_JSON_NEW_FIELD("GameTitle"), + RC_JSON_NEW_FIELD("ConsoleID"), + RC_JSON_NEW_FIELD("ConsoleName"), + RC_JSON_NEW_FIELD("ForumTopicID"), + RC_JSON_NEW_FIELD("GameIcon") * unused fields */ }; rc_json_field_t entry_fields[] = { - {"User"}, - {"Rank"}, - {"Index"}, - {"Score"}, - {"DateSubmitted"} + RC_JSON_NEW_FIELD("User"), + RC_JSON_NEW_FIELD("Rank"), + RC_JSON_NEW_FIELD("Index"), + RC_JSON_NEW_FIELD("Score"), + RC_JSON_NEW_FIELD("DateSubmitted") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK) return result; @@ -218,7 +246,7 @@ int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info response->format = RC_FORMAT_VALUE; } - if (!rc_json_get_required_array(&response->num_entries, &iterator, &response->response, &leaderboarddata_fields[10], "Entries")) + if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &leaderboarddata_fields[10], "Entries")) return RC_MISSING_VALUE; if (response->num_entries) { @@ -226,6 +254,10 @@ int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info if (!response->entries) return RC_OUT_OF_MEMORY; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; + entry = response->entries; while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User")) @@ -270,26 +302,38 @@ int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api rc_url_builder_append_unum_param(&builder, "c", api_params->console_id); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; return builder.result; } int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_fetch_games_list_server_response(response, &response_obj); +} + +int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response) { rc_api_game_list_entry_t* entry; - rc_json_object_field_iterator_t iterator; + rc_json_iterator_t iterator; + rc_json_field_t field; int result; char* end; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"Response"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Response") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK) return result; @@ -308,13 +352,14 @@ int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* memset(&iterator, 0, sizeof(iterator)); iterator.json = fields[2].value_start; + iterator.end = fields[2].value_end; entry = response->entries; - while (rc_json_get_next_object_field(&iterator)) { - entry->id = strtol(iterator.field.name, &end, 10); + while (rc_json_get_next_object_field(&iterator, &field)) { + entry->id = strtol(field.name, &end, 10); - iterator.field.name = ""; - if (!rc_json_get_string(&entry->name, &response->response.buffer, &iterator.field, "")) + field.name = ""; + if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, "")) return RC_MISSING_VALUE; ++entry; diff --git a/dep/rcheevos/src/rapi/rc_api_runtime.c b/dep/rcheevos/src/rapi/rc_api_runtime.c index e42db3cf16..7bac4ec53d 100644 --- a/dep/rcheevos/src/rapi/rc_api_runtime.c +++ b/dep/rcheevos/src/rapi/rc_api_runtime.c @@ -24,22 +24,33 @@ int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_res rc_url_builder_append_str_param(&builder, "r", "gameid"); rc_url_builder_append_str_param(&builder, "m", api_params->game_hash); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; return builder.result; } int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_resolve_hash_server_response(response, &response_obj); +} + +int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"GameID"}, + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("GameID") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK) return result; @@ -65,17 +76,30 @@ int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_ if (rc_api_url_build_dorequest(&builder, "patch", api_params->username, api_params->api_token)) { rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; } return builder.result; } int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_fetch_game_data_server_response(response, &response_obj); +} + +int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) { rc_api_achievement_definition_t* achievement; rc_api_leaderboard_definition_t* leaderboard; - rc_json_field_t iterator; + rc_json_field_t array_field; + rc_json_iterator_t iterator; const char* str; const char* last_author = ""; + const char* last_author_field = ""; size_t last_author_len = 0; size_t len; unsigned timet; @@ -83,52 +107,52 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r char format[16]; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"PatchData"} /* nested object */ + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("PatchData") /* nested object */ }; rc_json_field_t patchdata_fields[] = { - {"ID"}, - {"Title"}, - {"ConsoleID"}, - {"ImageIcon"}, - {"RichPresencePatch"}, - {"Achievements"}, /* array */ - {"Leaderboards"} /* array */ + RC_JSON_NEW_FIELD("ID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("ConsoleID"), + RC_JSON_NEW_FIELD("ImageIcon"), + RC_JSON_NEW_FIELD("RichPresencePatch"), + RC_JSON_NEW_FIELD("Achievements"), /* array */ + RC_JSON_NEW_FIELD("Leaderboards") /* array */ /* unused fields - {"ForumTopicID"}, - {"Flags"}, + RC_JSON_NEW_FIELD("ForumTopicID"), + RC_JSON_NEW_FIELD("Flags") * unused fields */ }; rc_json_field_t achievement_fields[] = { - {"ID"}, - {"Title"}, - {"Description"}, - {"Flags"}, - {"Points"}, - {"MemAddr"}, - {"Author"}, - {"BadgeName"}, - {"Created"}, - {"Modified"} + RC_JSON_NEW_FIELD("ID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("Description"), + RC_JSON_NEW_FIELD("Flags"), + RC_JSON_NEW_FIELD("Points"), + RC_JSON_NEW_FIELD("MemAddr"), + RC_JSON_NEW_FIELD("Author"), + RC_JSON_NEW_FIELD("BadgeName"), + RC_JSON_NEW_FIELD("Created"), + RC_JSON_NEW_FIELD("Modified") }; rc_json_field_t leaderboard_fields[] = { - {"ID"}, - {"Title"}, - {"Description"}, - {"Mem"}, - {"Format"}, - {"LowerIsBetter"}, - {"Hidden"} + RC_JSON_NEW_FIELD("ID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("Description"), + RC_JSON_NEW_FIELD("Mem"), + RC_JSON_NEW_FIELD("Format"), + RC_JSON_NEW_FIELD("LowerIsBetter"), + RC_JSON_NEW_FIELD("Hidden") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; @@ -174,7 +198,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r if (!response->rich_presence_script) response->rich_presence_script = ""; - if (!rc_json_get_required_array(&response->num_achievements, &iterator, &response->response, &patchdata_fields[5], "Achievements")) + if (!rc_json_get_required_array(&response->num_achievements, &array_field, &response->response, &patchdata_fields[5], "Achievements")) return RC_MISSING_VALUE; if (response->num_achievements) { @@ -182,6 +206,10 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r if (!response->achievements) return RC_OUT_OF_MEMORY; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; + achievement = response->achievements; while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) { if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID")) @@ -199,8 +227,8 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName")) return RC_MISSING_VALUE; - len = achievement_fields[7].value_end - achievement_fields[7].value_start; - if (len == last_author_len && memcmp(achievement_fields[7].value_start, last_author, len) == 0) { + len = achievement_fields[6].value_end - achievement_fields[6].value_start; + if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) { achievement->author = last_author; } else { @@ -208,6 +236,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r return RC_MISSING_VALUE; last_author = achievement->author; + last_author_field = achievement_fields[6].value_start; last_author_len = len; } @@ -222,7 +251,7 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r } } - if (!rc_json_get_required_array(&response->num_leaderboards, &iterator, &response->response, &patchdata_fields[6], "Leaderboards")) + if (!rc_json_get_required_array(&response->num_leaderboards, &array_field, &response->response, &patchdata_fields[6], "Leaderboards")) return RC_MISSING_VALUE; if (response->num_leaderboards) { @@ -230,6 +259,10 @@ int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* r if (!response->leaderboards) return RC_OUT_OF_MEMORY; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; + leaderboard = response->leaderboards; while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) { if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID")) @@ -284,21 +317,32 @@ int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_reques rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; } return builder.result; } int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_ping_server_response(response, &response_obj); +} + +int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response) { rc_json_field_t fields[] = { - {"Success"}, - {"Error"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - return rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + return rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); } void rc_api_destroy_ping_response(rc_api_ping_response_t* response) { @@ -337,25 +381,37 @@ int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_ap rc_url_builder_append_str_param(&builder, "v", buffer); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; } return builder.result; } int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_award_achievement_server_response(response, &response_obj); +} + +int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"Score"}, - {"AchievementID"}, - {"AchievementsRemaining"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Score"), + RC_JSON_NEW_FIELD("SoftcoreScore"), + RC_JSON_NEW_FIELD("AchievementID"), + RC_JSON_NEW_FIELD("AchievementsRemaining") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK) return result; @@ -373,8 +429,9 @@ int rc_api_process_award_achievement_response(rc_api_award_achievement_response_ } rc_json_get_optional_unum(&response->new_player_score, &fields[2], "Score", 0); - rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[3], "AchievementID", 0); - rc_json_get_optional_unum(&response->achievements_remaining, &fields[4], "AchievementsRemaining", (unsigned)-1); + rc_json_get_optional_unum(&response->new_player_score_softcore, &fields[3], "SoftcoreScore", 0); + rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[4], "AchievementID", 0); + rc_json_get_optional_unum(&response->achievements_remaining, &fields[5], "AchievementsRemaining", (unsigned)-1); return RC_OK; } @@ -416,67 +473,79 @@ int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_ rc_url_builder_append_str_param(&builder, "v", buffer); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; } return builder.result; } int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_submit_lboard_entry_server_response(response, &response_obj); +} + +int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response) { rc_api_lboard_entry_t* entry; - rc_json_field_t iterator; + rc_json_field_t array_field; + rc_json_iterator_t iterator; const char* str; int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"Response"} /* nested object */ + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("Response") /* nested object */ }; rc_json_field_t response_fields[] = { - {"Score"}, - {"BestScore"}, - {"RankInfo"}, /* nested object */ - {"TopEntries"} /* array */ + RC_JSON_NEW_FIELD("Score"), + RC_JSON_NEW_FIELD("BestScore"), + RC_JSON_NEW_FIELD("RankInfo"), /* nested object */ + RC_JSON_NEW_FIELD("TopEntries") /* array */ /* unused fields - {"LBData"}, / * array * / - {"ScoreFormatted"}, - {"TopEntriesFriends"}, / * array * / + RC_JSON_NEW_FIELD("LBData"), / * array * / + RC_JSON_NEW_FIELD("ScoreFormatted"), + RC_JSON_NEW_FIELD("TopEntriesFriends") / * array * / * unused fields */ }; /* unused fields rc_json_field_t lbdata_fields[] = { - {"Format"}, - {"LeaderboardID"}, - {"GameID"}, - {"Title"}, - {"LowerIsBetter"} + RC_JSON_NEW_FIELD("Format"), + RC_JSON_NEW_FIELD("LeaderboardID"), + RC_JSON_NEW_FIELD("GameID"), + RC_JSON_NEW_FIELD("Title"), + RC_JSON_NEW_FIELD("LowerIsBetter") }; * unused fields */ rc_json_field_t entry_fields[] = { - {"User"}, - {"Rank"}, - {"Score"} + RC_JSON_NEW_FIELD("User"), + RC_JSON_NEW_FIELD("Rank"), + RC_JSON_NEW_FIELD("Score") /* unused fields - {"DateSubmitted"}, + RC_JSON_NEW_FIELD("DateSubmitted") * unused fields */ }; rc_json_field_t rank_info_fields[] = { - {"Rank"}, - {"NumEntries"} + RC_JSON_NEW_FIELD("Rank"), + RC_JSON_NEW_FIELD("NumEntries") /* unused fields - {"LowerIsBetter"}, - {"UserRank"}, + RC_JSON_NEW_FIELD("LowerIsBetter"), + RC_JSON_NEW_FIELD("UserRank") * unused fields */ }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; @@ -495,7 +564,7 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo return RC_MISSING_VALUE; response->num_entries = (unsigned)atoi(str); - if (!rc_json_get_required_array(&response->num_top_entries, &iterator, &response->response, &response_fields[3], "TopEntries")) + if (!rc_json_get_required_array(&response->num_top_entries, &array_field, &response->response, &response_fields[3], "TopEntries")) return RC_MISSING_VALUE; if (response->num_top_entries) { @@ -503,6 +572,10 @@ int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_respo if (!response->top_entries) return RC_OUT_OF_MEMORY; + memset(&iterator, 0, sizeof(iterator)); + iterator.json = array_field.value_start; + iterator.end = array_field.value_end; + entry = response->top_entries; while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) { if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User")) diff --git a/dep/rcheevos/src/rapi/rc_api_user.c b/dep/rcheevos/src/rapi/rc_api_user.c index e5181ba950..4a13b8df22 100644 --- a/dep/rcheevos/src/rapi/rc_api_user.c +++ b/dep/rcheevos/src/rapi/rc_api_user.c @@ -1,6 +1,8 @@ #include "rc_api_user.h" #include "rc_api_common.h" +#include "../rcheevos/rc_version.h" + #include /* --- Login --- */ @@ -25,26 +27,38 @@ int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_requ return RC_INVALID_STATE; request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; return builder.result; } int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_login_server_response(response, &response_obj); +} + +int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"User"}, - {"Token"}, - {"Score"}, - {"Messages"}, - {"DisplayName"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("User"), + RC_JSON_NEW_FIELD("Token"), + RC_JSON_NEW_FIELD("Score"), + RC_JSON_NEW_FIELD("SoftcoreScore"), + RC_JSON_NEW_FIELD("Messages"), + RC_JSON_NEW_FIELD("DisplayName") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; @@ -54,9 +68,10 @@ int rc_api_process_login_response(rc_api_login_response_t* response, const char* return RC_MISSING_VALUE; rc_json_get_optional_unum(&response->score, &fields[4], "Score", 0); - rc_json_get_optional_unum(&response->num_unread_messages, &fields[5], "Messages", 0); + rc_json_get_optional_unum(&response->score_softcore, &fields[5], "SoftcoreScore", 0); + rc_json_get_optional_unum(&response->num_unread_messages, &fields[6], "Messages", 0); - rc_json_get_optional_string(&response->display_name, &response->response, &fields[6], "DisplayName", response->username); + rc_json_get_optional_string(&response->display_name, &response->response, &fields[7], "DisplayName", response->username); return RC_OK; } @@ -86,22 +101,34 @@ int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_st */ rc_url_builder_append_unum_param(&builder, "a", 3); rc_url_builder_append_unum_param(&builder, "m", api_params->game_id); + rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; } return builder.result; } int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_start_session_server_response(response, &response_obj); +} + +int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response) { rc_json_field_t fields[] = { - {"Success"}, - {"Error"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error") }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - return rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + return rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); } void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response) { @@ -120,27 +147,38 @@ int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_a rc_url_builder_append_unum_param(&builder, "g", api_params->game_id); rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0); request->post_data = rc_url_builder_finalize(&builder); + request->content_type = RC_CONTENT_TYPE_URLENCODED; } return builder.result; } int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response) { + rc_api_server_response_t response_obj; + + memset(&response_obj, 0, sizeof(response_obj)); + response_obj.body = server_response; + response_obj.body_length = rc_json_get_object_string_length(server_response); + + return rc_api_process_fetch_user_unlocks_server_response(response, &response_obj); +} + +int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response) { int result; rc_json_field_t fields[] = { - {"Success"}, - {"Error"}, - {"UserUnlocks"} + RC_JSON_NEW_FIELD("Success"), + RC_JSON_NEW_FIELD("Error"), + RC_JSON_NEW_FIELD("UserUnlocks") /* unused fields - { "GameID" }, - { "HardcoreMode" } + RC_JSON_NEW_FIELD("GameID"), + RC_JSON_NEW_FIELD("HardcoreMode") * unused fields */ }; memset(response, 0, sizeof(*response)); rc_buf_init(&response->response.buffer); - result = rc_json_parse_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); + result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0])); if (result != RC_OK || !response->response.succeeded) return result; diff --git a/dep/rcheevos/src/rcheevos/alloc.c b/dep/rcheevos/src/rcheevos/alloc.c index c361eecee6..ac2a07bc08 100644 --- a/dep/rcheevos/src/rcheevos/alloc.c +++ b/dep/rcheevos/src/rcheevos/alloc.c @@ -159,6 +159,17 @@ void rc_destroy_parse_state(rc_parse_state_t* parse) } } +unsigned rc_djb2(const char* input) +{ + unsigned result = 5381; + char c; + + while ((c = *input++) != '\0') + result = ((result << 5) + result) + c; /* result = result * 33 + c */ + + return result; +} + const char* rc_error_str(int ret) { switch (ret) { @@ -189,7 +200,12 @@ const char* rc_error_str(int ret) case RC_INVALID_COMPARISON: return "Invalid comparison"; case RC_INVALID_STATE: return "Invalid state"; case RC_INVALID_JSON: return "Invalid JSON"; - + case RC_API_FAILURE: return "API call failed"; + case RC_LOGIN_REQUIRED: return "Login required"; + case RC_NO_GAME_LOADED: return "No game loaded"; + case RC_HARDCORE_DISABLED: return "Hardcore disabled"; + case RC_ABORTED: return "Aborted"; + case RC_NO_RESPONSE: return "No response"; default: return "Unknown error"; } } diff --git a/dep/rcheevos/src/rcheevos/compat.c b/dep/rcheevos/src/rcheevos/compat.c index 877c64ddb2..0ba7601ad2 100644 --- a/dep/rcheevos/src/rcheevos/compat.c +++ b/dep/rcheevos/src/rcheevos/compat.c @@ -3,6 +3,8 @@ #include #include +#ifdef RC_C89_HELPERS + int rc_strncasecmp(const char* left, const char* right, size_t length) { while (length) @@ -44,20 +46,94 @@ char* rc_strdup(const char* str) { const size_t length = strlen(str); char* buffer = (char*)malloc(length + 1); - memcpy(buffer, str, length + 1); + if (buffer) + memcpy(buffer, str, length + 1); return buffer; } int rc_snprintf(char* buffer, size_t size, const char* format, ...) { - int result; - va_list args; + int result; + va_list args; + + va_start(args, format); + +#ifdef __STDC_WANT_SECURE_LIB__ + result = vsprintf_s(buffer, size, format, args); +#else + /* assume buffer is large enough and ignore size */ + (void)size; + result = vsprintf(buffer, format, args); +#endif + + va_end(args); + + return result; +} + +#endif - va_start(args, format); - /* assume buffer is large enough and ignore size */ - (void)size; - result = vsprintf(buffer, format, args); - va_end(args); +#ifndef __STDC_WANT_SECURE_LIB__ - return result; +struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer) +{ + struct tm* tm = gmtime(timer); + memcpy(buf, tm, sizeof(*tm)); + return buf; } + +#endif + +#ifndef RC_NO_THREADS +#ifdef _WIN32 + +/* https://gist.github.com/roxlu/1c1af99f92bafff9d8d9 */ + +#define WIN32_LEAN_AND_MEAN +#include + +void rc_mutex_init(rc_mutex_t* mutex) +{ + /* default security, not owned by calling thread, unnamed */ + mutex->handle = CreateMutex(NULL, FALSE, NULL); +} + +void rc_mutex_destroy(rc_mutex_t* mutex) +{ + CloseHandle(mutex->handle); +} + +void rc_mutex_lock(rc_mutex_t* mutex) +{ + WaitForSingleObject(mutex->handle, 0xFFFFFFFF); +} + +void rc_mutex_unlock(rc_mutex_t* mutex) +{ + ReleaseMutex(mutex->handle); +} + +#else + +void rc_mutex_init(rc_mutex_t* mutex) +{ + pthread_mutex_init(mutex, NULL); +} + +void rc_mutex_destroy(rc_mutex_t* mutex) +{ + pthread_mutex_destroy(mutex); +} + +void rc_mutex_lock(rc_mutex_t* mutex) +{ + pthread_mutex_lock(mutex); +} + +void rc_mutex_unlock(rc_mutex_t* mutex) +{ + pthread_mutex_unlock(mutex); +} + +#endif +#endif /* RC_NO_THREADS */ diff --git a/dep/rcheevos/src/rcheevos/condition.c b/dep/rcheevos/src/rcheevos/condition.c index 03aa0484ea..55a8084e71 100644 --- a/dep/rcheevos/src/rcheevos/condition.c +++ b/dep/rcheevos/src/rcheevos/condition.c @@ -1,6 +1,91 @@ #include "rc_internal.h" #include +#include + +static int rc_test_condition_compare(unsigned value1, unsigned value2, char oper) { + switch (oper) { + case RC_OPERATOR_EQ: return value1 == value2; + case RC_OPERATOR_NE: return value1 != value2; + case RC_OPERATOR_LT: return value1 < value2; + case RC_OPERATOR_LE: return value1 <= value2; + case RC_OPERATOR_GT: return value1 > value2; + case RC_OPERATOR_GE: return value1 >= value2; + default: return 1; + } +} + +static char rc_condition_determine_comparator(const rc_condition_t* self) { + switch (self->oper) { + case RC_OPERATOR_EQ: + case RC_OPERATOR_NE: + case RC_OPERATOR_LT: + case RC_OPERATOR_LE: + case RC_OPERATOR_GT: + case RC_OPERATOR_GE: + break; + + default: + /* not a comparison. should not be getting compared. but if it is, legacy behavior was to return 1 */ + return RC_PROCESSING_COMPARE_ALWAYS_TRUE; + } + + if ((self->operand1.type == RC_OPERAND_ADDRESS || self->operand1.type == RC_OPERAND_DELTA) && + !self->operand1.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand1)) { + /* left side is an integer memory reference */ + int needs_translate = (self->operand1.size != self->operand1.value.memref->value.size); + + if (self->operand2.type == RC_OPERAND_CONST) { + /* right side is a constant */ + if (self->operand1.type == RC_OPERAND_ADDRESS) + return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_CONST; + + return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_CONST; + } + else if ((self->operand2.type == RC_OPERAND_ADDRESS || self->operand2.type == RC_OPERAND_DELTA) && + !self->operand2.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand2)) { + /* right side is an integer memory reference */ + const int is_same_memref = (self->operand1.value.memref == self->operand2.value.memref); + needs_translate |= (self->operand2.size != self->operand2.value.memref->value.size); + + if (self->operand1.type == RC_OPERAND_ADDRESS) { + if (self->operand2.type == RC_OPERAND_ADDRESS) { + if (is_same_memref && !needs_translate) { + /* comparing a memref to itself, will evaluate to a constant */ + return rc_test_condition_compare(0, 0, self->oper) ? RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE; + } + + return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF; + } + + assert(self->operand2.type == RC_OPERAND_DELTA); + + if (is_same_memref) { + /* delta comparison is optimized to compare with itself (for detecting change) */ + return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_DELTA; + } + } + else { + assert(self->operand1.type == RC_OPERAND_DELTA); + + if (self->operand2.type == RC_OPERAND_ADDRESS) { + if (is_same_memref) { + /* delta comparison is optimized to compare with itself (for detecting change) */ + return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_MEMREF; + } + } + } + } + } + + if (self->operand1.type == RC_OPERAND_CONST && self->operand2.type == RC_OPERAND_CONST) { + /* comparing constants will always generate a constant result */ + return rc_test_condition_compare(self->operand1.value.num, self->operand2.value.num, self->oper) ? + RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE; + } + + return RC_PROCESSING_COMPARE_DEFAULT; +} static int rc_parse_operator(const char** memaddr) { const char* oper = *memaddr; @@ -50,6 +135,10 @@ static int rc_parse_operator(const char** memaddr) { ++(*memaddr); return RC_OPERATOR_AND; + case '^': + ++(*memaddr); + return RC_OPERATOR_XOR; + case '\0':/* end of string */ case '_': /* next condition */ case 'S': /* next condset */ @@ -71,6 +160,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse self->current_hits = 0; self->is_true = 0; self->pause = 0; + self->optimized_comparator = RC_PROCESSING_COMPARE_DEFAULT; if (*aux != 0 && aux[1] == ':') { switch (*aux) { @@ -135,6 +225,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse case RC_OPERATOR_MULT: case RC_OPERATOR_DIV: case RC_OPERATOR_AND: + case RC_OPERATOR_XOR: /* modifying operators are only valid on modifying statements */ if (can_modify) break; @@ -209,6 +300,9 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse self->required_hits = 0; } + if (parse->buffer != 0) + self->optimized_comparator = rc_condition_determine_comparator(self); + *memaddr = aux; return self; } @@ -228,12 +322,203 @@ int rc_condition_is_combining(const rc_condition_t* self) { } } +static int rc_test_condition_compare_memref_to_const(rc_condition_t* self) { + const unsigned value1 = self->operand1.value.memref->value.value; + const unsigned value2 = self->operand2.value.num; + assert(self->operand1.size == self->operand1.value.memref->value.size); + return rc_test_condition_compare(value1, value2, self->oper); +} + +static int rc_test_condition_compare_delta_to_const(rc_condition_t* self) { + const rc_memref_value_t* memref1 = &self->operand1.value.memref->value; + const unsigned value1 = (memref1->changed) ? memref1->prior : memref1->value; + const unsigned value2 = self->operand2.value.num; + assert(self->operand1.size == self->operand1.value.memref->value.size); + return rc_test_condition_compare(value1, value2, self->oper); +} + +static int rc_test_condition_compare_memref_to_memref(rc_condition_t* self) { + const unsigned value1 = self->operand1.value.memref->value.value; + const unsigned value2 = self->operand2.value.memref->value.value; + assert(self->operand1.size == self->operand1.value.memref->value.size); + assert(self->operand2.size == self->operand2.value.memref->value.size); + return rc_test_condition_compare(value1, value2, self->oper); +} + +static int rc_test_condition_compare_memref_to_delta(rc_condition_t* self) { + const rc_memref_value_t* memref = &self->operand1.value.memref->value; + assert(self->operand1.value.memref == self->operand2.value.memref); + assert(self->operand1.size == self->operand1.value.memref->value.size); + assert(self->operand2.size == self->operand2.value.memref->value.size); + + if (memref->changed) + return rc_test_condition_compare(memref->value, memref->prior, self->oper); + + switch (self->oper) { + case RC_OPERATOR_EQ: + case RC_OPERATOR_GE: + case RC_OPERATOR_LE: + return 1; + + default: + return 0; + } +} + +static int rc_test_condition_compare_delta_to_memref(rc_condition_t* self) { + const rc_memref_value_t* memref = &self->operand1.value.memref->value; + assert(self->operand1.value.memref == self->operand2.value.memref); + assert(self->operand1.size == self->operand1.value.memref->value.size); + assert(self->operand2.size == self->operand2.value.memref->value.size); + + if (memref->changed) + return rc_test_condition_compare(memref->prior, memref->value, self->oper); + + switch (self->oper) { + case RC_OPERATOR_EQ: + case RC_OPERATOR_GE: + case RC_OPERATOR_LE: + return 1; + + default: + return 0; + } +} + +static int rc_test_condition_compare_memref_to_const_transformed(rc_condition_t* self) { + rc_typed_value_t value1; + const unsigned value2 = self->operand2.value.num; + + value1.type = RC_VALUE_TYPE_UNSIGNED; + value1.value.u32 = self->operand1.value.memref->value.value; + rc_transform_memref_value(&value1, self->operand1.size); + + return rc_test_condition_compare(value1.value.u32, value2, self->oper); +} + +static int rc_test_condition_compare_delta_to_const_transformed(rc_condition_t* self) { + rc_typed_value_t value1; + const rc_memref_value_t* memref1 = &self->operand1.value.memref->value; + const unsigned value2 = self->operand2.value.num; + + value1.type = RC_VALUE_TYPE_UNSIGNED; + value1.value.u32 = (memref1->changed) ? memref1->prior : memref1->value; + rc_transform_memref_value(&value1, self->operand1.size); + + return rc_test_condition_compare(value1.value.u32, value2, self->oper); +} + +static int rc_test_condition_compare_memref_to_memref_transformed(rc_condition_t* self) { + rc_typed_value_t value1, value2; + + value1.type = RC_VALUE_TYPE_UNSIGNED; + value1.value.u32 = self->operand1.value.memref->value.value; + rc_transform_memref_value(&value1, self->operand1.size); + + value2.type = RC_VALUE_TYPE_UNSIGNED; + value2.value.u32 = self->operand2.value.memref->value.value; + rc_transform_memref_value(&value2, self->operand2.size); + + return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper); +} + +static int rc_test_condition_compare_memref_to_delta_transformed(rc_condition_t* self) { + const rc_memref_value_t* memref = &self->operand1.value.memref->value; + assert(self->operand1.value.memref == self->operand2.value.memref); + + if (memref->changed) { + rc_typed_value_t value1, value2; + + value1.type = RC_VALUE_TYPE_UNSIGNED; + value1.value.u32 = memref->value; + rc_transform_memref_value(&value1, self->operand1.size); + + value2.type = RC_VALUE_TYPE_UNSIGNED; + value2.value.u32 = memref->prior; + rc_transform_memref_value(&value2, self->operand2.size); + + return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper); + } + + switch (self->oper) { + case RC_OPERATOR_EQ: + case RC_OPERATOR_GE: + case RC_OPERATOR_LE: + return 1; + + default: + return 0; + } +} + +static int rc_test_condition_compare_delta_to_memref_transformed(rc_condition_t* self) { + const rc_memref_value_t* memref = &self->operand1.value.memref->value; + assert(self->operand1.value.memref == self->operand2.value.memref); + + if (memref->changed) { + rc_typed_value_t value1, value2; + + value1.type = RC_VALUE_TYPE_UNSIGNED; + value1.value.u32 = memref->prior; + rc_transform_memref_value(&value1, self->operand1.size); + + value2.type = RC_VALUE_TYPE_UNSIGNED; + value2.value.u32 = memref->value; + rc_transform_memref_value(&value2, self->operand2.size); + + return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper); + } + + switch (self->oper) { + case RC_OPERATOR_EQ: + case RC_OPERATOR_GE: + case RC_OPERATOR_LE: + return 1; + + default: + return 0; + } +} + int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) { rc_typed_value_t value1, value2; - rc_evaluate_operand(&value1, &self->operand1, eval_state); - if (eval_state->add_value.type != RC_VALUE_TYPE_NONE) + if (eval_state->add_value.type != RC_VALUE_TYPE_NONE) { + /* if there's an accumulator, we can't use the optimized comparators */ + rc_evaluate_operand(&value1, &self->operand1, eval_state); rc_typed_value_add(&value1, &eval_state->add_value); + } else { + /* use an optimized comparator whenever possible */ + switch (self->optimized_comparator) { + case RC_PROCESSING_COMPARE_MEMREF_TO_CONST: + return rc_test_condition_compare_memref_to_const(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA: + return rc_test_condition_compare_memref_to_delta(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF: + return rc_test_condition_compare_memref_to_memref(self); + case RC_PROCESSING_COMPARE_DELTA_TO_CONST: + return rc_test_condition_compare_delta_to_const(self); + case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF: + return rc_test_condition_compare_delta_to_memref(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED: + return rc_test_condition_compare_memref_to_const_transformed(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED: + return rc_test_condition_compare_memref_to_delta_transformed(self); + case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED: + return rc_test_condition_compare_memref_to_memref_transformed(self); + case RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED: + return rc_test_condition_compare_delta_to_const_transformed(self); + case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED: + return rc_test_condition_compare_delta_to_memref_transformed(self); + case RC_PROCESSING_COMPARE_ALWAYS_TRUE: + return 1; + case RC_PROCESSING_COMPARE_ALWAYS_FALSE: + return 0; + default: + rc_evaluate_operand(&value1, &self->operand1, eval_state); + break; + } + } rc_evaluate_operand(&value2, &self->operand2, eval_state); @@ -260,5 +545,11 @@ void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED); value->value.u32 &= amount.value.u32; break; + + case RC_OPERATOR_XOR: + rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED); + rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED); + value->value.u32 ^= amount.value.u32; + break; } } diff --git a/dep/rcheevos/src/rcheevos/condset.c b/dep/rcheevos/src/rcheevos/condset.c index c7c7e6dd6b..e77a235815 100644 --- a/dep/rcheevos/src/rcheevos/condset.c +++ b/dep/rcheevos/src/rcheevos/condset.c @@ -84,6 +84,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in switch ((*next)->oper) { case RC_OPERATOR_AND: + case RC_OPERATOR_XOR: case RC_OPERATOR_DIV: case RC_OPERATOR_MULT: case RC_OPERATOR_NONE: @@ -209,8 +210,7 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc case RC_CONDITION_SUB_SOURCE: rc_evaluate_condition_value(&value, condition, eval_state); - rc_typed_value_convert(&value, RC_VALUE_TYPE_SIGNED); - value.value.i32 = -value.value.i32; + rc_typed_value_negate(&value); rc_typed_value_add(&eval_state->add_value, &value); eval_state->add_address = 0; continue; diff --git a/dep/rcheevos/src/rcheevos/consoleinfo.c b/dep/rcheevos/src/rcheevos/consoleinfo.c index 9f2665d7e3..0ef0089e8f 100644 --- a/dep/rcheevos/src/rcheevos/consoleinfo.c +++ b/dep/rcheevos/src/rcheevos/consoleinfo.c @@ -39,6 +39,9 @@ const char* rc_console_name(int console_id) case RC_CONSOLE_ATARI_JAGUAR: return "Atari Jaguar"; + case RC_CONSOLE_ATARI_JAGUAR_CD: + return "Atari Jaguar CD"; + case RC_CONSOLE_ATARI_LYNX: return "Atari Lynx"; @@ -132,6 +135,9 @@ const char* rc_console_name(int console_id) case RC_CONSOLE_NINTENDO_DS: return "Nintendo DS"; + case RC_CONSOLE_NINTENDO_DSI: + return "Nintendo DSi"; + case RC_CONSOLE_NINTENDO_3DS: return "Nintendo 3DS"; @@ -156,6 +162,9 @@ const char* rc_console_name(int console_id) case RC_CONSOLE_PC_ENGINE: return "PC Engine"; + case RC_CONSOLE_PC_ENGINE_CD: + return "PC Engine CD"; + case RC_CONSOLE_PLAYSTATION: return "PlayStation"; @@ -198,9 +207,15 @@ const char* rc_console_name(int console_id) case RC_CONSOLE_THOMSONTO8: return "Thomson TO8"; + case RC_CONSOLE_TI83: + return "TI-83"; + case RC_CONSOLE_TIC80: return "TIC-80"; + case RC_CONSOLE_UZEBOX: + return "Uzebox"; + case RC_CONSOLE_VECTREX: return "Vectrex"; @@ -435,6 +450,13 @@ static const rc_memory_region_t _rc_memory_regions_gameboy_advance[] = { }; static const rc_memory_regions_t rc_memory_regions_gameboy_advance = { _rc_memory_regions_gameboy_advance, 2 }; +/* ===== GameCube ===== */ +/* https://wiibrew.org/wiki/Memory_map */ +static const rc_memory_region_t _rc_memory_regions_gamecube[] = { + { 0x00000000U, 0x017FFFFF, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_gamecube = { _rc_memory_regions_gamecube, 1 }; + /* ===== Game Gear ===== */ /* http://www.smspower.org/Development/MemoryMap */ static const rc_memory_region_t _rc_memory_regions_game_gear[] = { @@ -518,6 +540,15 @@ static const rc_memory_region_t _rc_memory_regions_megadrive[] = { }; static const rc_memory_regions_t rc_memory_regions_megadrive = { _rc_memory_regions_megadrive, 2 }; +/* ===== MegaDrive 32X (Genesis 32X) ===== */ +/* http://devster.monkeeh.com/sega/32xguide1.txt */ +static const rc_memory_region_t _rc_memory_regions_megadrive_32x[] = { + { 0x000000U, 0x00FFFFU, 0x00FF0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, /* Main MegaDrive RAM */ + { 0x010000U, 0x04FFFFU, 0x06000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "32X RAM"}, /* Additional 32X RAM */ + { 0x050000U, 0x05FFFFU, 0x00000000U, RC_MEMORY_TYPE_SAVE_RAM, "Cartridge RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_megadrive_32x = { _rc_memory_regions_megadrive_32x, 3 }; + /* ===== MSX ===== */ /* https://www.msx.org/wiki/The_Memory */ /* MSX only has 64KB of addressable RAM, of which 32KB is reserved for the system/BIOS. @@ -540,6 +571,35 @@ static const rc_memory_region_t _rc_memory_regions_neo_geo_pocket[] = { }; static const rc_memory_regions_t rc_memory_regions_neo_geo_pocket = { _rc_memory_regions_neo_geo_pocket, 1 }; +/* ===== Neo Geo CD ===== */ +/* https://wiki.neogeodev.org/index.php?title=68k_memory_map */ +/* NeoCD exposes $000000-$1FFFFF as System RAM, but it seems like only the WORKRAM section is used. + * This is consistent with http://www.hardmvs.fr/manuals/NeoGeoProgrammersGuide.pdf (page25), which says: + * + * Furthermore, the NEO-GEO provides addresses 100000H-10FFFFH as a work area, out of which the + * addresses 10F300H-10FFFFH are reserved exclusively for use by the system program. Therefore, + * every game is to use addresses 100000H-10F2FFH. + * + * Also note that PRG files (game ROM) can be loaded anywhere else in the $000000-$1FFFFF range. + * AoF3 illustrates this pretty clearly: https://wiki.neogeodev.org/index.php?title=IPL_file + * + * PROG_CD.PRG,0,0 + * PROG_CDX.PRG,0,058000 + * CNV_NM.PRG,0,0C0000 + * FIX_DATA.PRG,0,0FD000 + * OBJACTLK.PRG,0,130000 + * SSEL_CNV.PRG,0,15A000 + * SSEL_BAK.PRG,0,16F000 + * HITMSG.PRG,0,170000 + * SSEL_SPR.PRG,0,19D000 + */ +static const rc_memory_region_t _rc_memory_regions_neo_geo_cd[] = { + { 0x000000U, 0x00F2FFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + /* NOTE: some BIOS settings are exposed through the reserved RAM: https://wiki.neogeodev.org/index.php?title=68k_ASM_defines */ + { 0x00F300U, 0x00FFFFU, 0x0010F300U, RC_MEMORY_TYPE_SYSTEM_RAM, "Reserved RAM" }, +}; +static const rc_memory_regions_t rc_memory_regions_neo_geo_cd = { _rc_memory_regions_neo_geo_cd, 2 }; + /* ===== Nintendo Entertainment System ===== */ /* https://wiki.nesdev.com/w/index.php/CPU_memory_map */ static const rc_memory_region_t _rc_memory_regions_nes[] = { @@ -587,6 +647,13 @@ static const rc_memory_region_t _rc_memory_regions_nintendo_ds[] = { }; static const rc_memory_regions_t rc_memory_regions_nintendo_ds = { _rc_memory_regions_nintendo_ds, 1 }; +/* ===== Nintendo DSi ===== */ +/* https://problemkaputt.de/gbatek.htm#dsiiomap */ +static const rc_memory_region_t _rc_memory_regions_nintendo_dsi[] = { + { 0x000000U, 0xFFFFFFU, 0x02000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_nintendo_dsi = { _rc_memory_regions_nintendo_dsi, 1 }; + /* ===== Oric ===== */ static const rc_memory_region_t _rc_memory_regions_oric[] = { /* actual size depends on machine type - up to 64KB */ @@ -605,11 +672,18 @@ static const rc_memory_regions_t rc_memory_regions_pc8800 = { _rc_memory_regions /* http://www.archaicpixels.com/Memory_Map */ static const rc_memory_region_t _rc_memory_regions_pc_engine[] = { { 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, +}; +static const rc_memory_regions_t rc_memory_regions_pc_engine = { _rc_memory_regions_pc_engine, 1 }; + +/* ===== PC Engine CD===== */ +/* http://www.archaicpixels.com/Memory_Map */ +static const rc_memory_region_t _rc_memory_regions_pc_engine_cd[] = { + { 0x000000U, 0x001FFFU, 0x1F0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, { 0x002000U, 0x011FFFU, 0x100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "CD RAM" }, { 0x012000U, 0x041FFFU, 0x0D0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Super System Card RAM" }, { 0x042000U, 0x0427FFU, 0x1EE000U, RC_MEMORY_TYPE_SAVE_RAM, "CD Battery-backed RAM" } }; -static const rc_memory_regions_t rc_memory_regions_pc_engine = { _rc_memory_regions_pc_engine, 4 }; +static const rc_memory_regions_t rc_memory_regions_pc_engine_cd = { _rc_memory_regions_pc_engine_cd, 4 }; /* ===== PC-FX ===== */ /* http://daifukkat.su/pcfx/data/memmap.html */ @@ -711,6 +785,13 @@ static const rc_memory_region_t _rc_memory_regions_thomson_to8[] = { }; static const rc_memory_regions_t rc_memory_regions_thomson_to8 = { _rc_memory_regions_thomson_to8, 1 }; +/* ===== TI-83 ===== */ +/* https://tutorials.eeems.ca/ASMin28Days/lesson/day03.html#mem */ +static const rc_memory_region_t _rc_memory_regions_ti83[] = { + { 0x000000U, 0x007FFFU, 0x008000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, +}; +static const rc_memory_regions_t rc_memory_regions_ti83 = { _rc_memory_regions_ti83, 1 }; + /* ===== TIC-80 ===== */ /* https://github.com/nesbox/TIC-80/wiki/RAM */ static const rc_memory_region_t _rc_memory_regions_tic80[] = { @@ -727,6 +808,13 @@ static const rc_memory_region_t _rc_memory_regions_tic80[] = { }; static const rc_memory_regions_t rc_memory_regions_tic80 = { _rc_memory_regions_tic80, 10 }; +/* ===== Uzebox ===== */ +/* https://uzebox.org/index.php */ +static const rc_memory_region_t _rc_memory_regions_uzebox[] = { + { 0x000000U, 0x000FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_uzebox = { _rc_memory_regions_uzebox, 1 }; + /* ===== Vectrex ===== */ /* https://roadsidethoughts.com/vectrex/vectrex-memory-map.htm */ static const rc_memory_region_t _rc_memory_regions_vectrex[] = { @@ -761,6 +849,14 @@ static const rc_memory_region_t _rc_memory_regions_wasm4[] = { }; static const rc_memory_regions_t rc_memory_regions_wasm4 = { _rc_memory_regions_wasm4, 1 }; +/* ===== Wii ===== */ +/* https://wiibrew.org/wiki/Memory_map */ +static const rc_memory_region_t _rc_memory_regions_wii[] = { + { 0x00000000U, 0x017FFFFF, 0x80000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }, + { 0x01800000U, 0x057FFFFF, 0x90000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" } +}; +static const rc_memory_regions_t rc_memory_regions_wii = { _rc_memory_regions_wii, 2 }; + /* ===== WonderSwan ===== */ /* http://daifukkat.su/docs/wsman/#ovr_memmap */ static const rc_memory_region_t _rc_memory_regions_wonderswan[] = { @@ -810,6 +906,7 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) return &rc_memory_regions_atari7800; case RC_CONSOLE_ATARI_JAGUAR: + case RC_CONSOLE_ATARI_JAGUAR_CD: return &rc_memory_regions_atari_jaguar; case RC_CONSOLE_ATARI_LYNX: @@ -840,6 +937,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_GAMEBOY_ADVANCE: return &rc_memory_regions_gameboy_advance; + case RC_CONSOLE_GAMECUBE: + return &rc_memory_regions_gamecube; + case RC_CONSOLE_GAME_GEAR: return &rc_memory_regions_game_gear; @@ -856,17 +956,20 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) return &rc_memory_regions_master_system; case RC_CONSOLE_MEGA_DRIVE: - case RC_CONSOLE_SEGA_32X: - /* NOTE: 32x adds an extra 512KB of memory (256KB RAM + 256KB VRAM) to the - * Genesis, but we currently don't support it. */ return &rc_memory_regions_megadrive; + case RC_CONSOLE_SEGA_32X: + return &rc_memory_regions_megadrive_32x; + case RC_CONSOLE_MSX: return &rc_memory_regions_msx; case RC_CONSOLE_NEOGEO_POCKET: return &rc_memory_regions_neo_geo_pocket; + case RC_CONSOLE_NEO_GEO_CD: + return &rc_memory_regions_neo_geo_cd; + case RC_CONSOLE_NINTENDO: return &rc_memory_regions_nes; @@ -876,6 +979,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_NINTENDO_DS: return &rc_memory_regions_nintendo_ds; + case RC_CONSOLE_NINTENDO_DSI: + return &rc_memory_regions_nintendo_dsi; + case RC_CONSOLE_ORIC: return &rc_memory_regions_oric; @@ -885,6 +991,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_PC_ENGINE: return &rc_memory_regions_pc_engine; + case RC_CONSOLE_PC_ENGINE_CD: + return &rc_memory_regions_pc_engine_cd; + case RC_CONSOLE_PCFX: return &rc_memory_regions_pcfx; @@ -921,9 +1030,15 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_THOMSONTO8: return &rc_memory_regions_thomson_to8; + case RC_CONSOLE_TI83: + return &rc_memory_regions_ti83; + case RC_CONSOLE_TIC80: return &rc_memory_regions_tic80; + case RC_CONSOLE_UZEBOX: + return &rc_memory_regions_uzebox; + case RC_CONSOLE_VECTREX: return &rc_memory_regions_vectrex; @@ -933,6 +1048,9 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id) case RC_CONSOLE_WASM4: return &rc_memory_regions_wasm4; + case RC_CONSOLE_WII: + return &rc_memory_regions_wii; + case RC_CONSOLE_WONDERSWAN: return &rc_memory_regions_wonderswan; diff --git a/dep/rcheevos/src/rcheevos/memref.c b/dep/rcheevos/src/rcheevos/memref.c index f4ea6ac28d..fff2a6bca5 100644 --- a/dep/rcheevos/src/rcheevos/memref.c +++ b/dep/rcheevos/src/rcheevos/memref.c @@ -95,6 +95,7 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) { switch (*aux++) { case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break; case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break; + case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break; default: return RC_INVALID_FP_OPERAND; @@ -201,6 +202,21 @@ static void rc_transform_memref_mbf32(rc_typed_value_t* value) { value->type = RC_VALUE_TYPE_FLOAT; } +static void rc_transform_memref_mbf32_le(rc_typed_value_t* value) { + /* decodes a Microsoft Binary Format float */ + /* Locomotive BASIC (CPC) uses MBF40, but in little endian format */ + const unsigned mantissa = value->value.u32 & 0x007FFFFF; + const int exponent = (int)(value->value.u32 >> 24) - 129; + const int sign = (value->value.u32 & 0x00800000); + + if (mantissa == 0 && exponent == -129) + value->value.f32 = (sign) ? -0.0f : 0.0f; + else + value->value.f32 = rc_build_float(mantissa, exponent, sign); + + value->type = RC_VALUE_TYPE_FLOAT; +} + static const unsigned char rc_bits_set[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 }; void rc_transform_memref_value(rc_typed_value_t* value, char size) { @@ -293,6 +309,10 @@ void rc_transform_memref_value(rc_typed_value_t* value, char size) { rc_transform_memref_mbf32(value); break; + case RC_MEMSIZE_MBF32_LE: + rc_transform_memref_mbf32_le(value); + break; + default: break; } @@ -319,6 +339,7 @@ static const unsigned rc_memref_masks[] = { 0xffffffff, /* RC_MEMSIZE_32_BITS_BE */ 0xffffffff, /* RC_MEMSIZE_FLOAT */ 0xffffffff, /* RC_MEMSIZE_MBF32 */ + 0xffffffff, /* RC_MEMSIZE_MBF32_LE */ 0xffffffff /* RC_MEMSIZE_VARIABLE */ }; @@ -354,6 +375,7 @@ static const char rc_memref_shared_sizes[] = { RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS_BE */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT */ RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */ + RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */ RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */ }; @@ -365,7 +387,7 @@ char rc_memref_shared_size(char size) { return rc_memref_shared_sizes[index]; } -static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) { +unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) { if (!peek) return 0; @@ -422,7 +444,7 @@ void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs) *memrefs = 0; } -static unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) { +static unsigned rc_get_memref_value_value(const rc_memref_value_t* memref, int operand_type) { switch (operand_type) { /* most common case explicitly first, even though it could be handled by default case. diff --git a/dep/rcheevos/src/rcheevos/operand.c b/dep/rcheevos/src/rcheevos/operand.c index 1501607ff0..f8abbbd7ba 100644 --- a/dep/rcheevos/src/rcheevos/operand.c +++ b/dep/rcheevos/src/rcheevos/operand.c @@ -59,6 +59,8 @@ static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_par self->value.luafunc = luaL_ref(parse->L, LUA_REGISTRYINDEX); } +#else + (void)parse; #endif /* RC_DISABLE_LUA */ self->type = RC_OPERAND_LUA; @@ -300,6 +302,7 @@ int rc_operand_is_float_memref(const rc_operand_t* self) { switch (self->size) { case RC_MEMSIZE_FLOAT: case RC_MEMSIZE_MBF32: + case RC_MEMSIZE_MBF32_LE: return 1; default: @@ -319,6 +322,13 @@ int rc_operand_is_memref(const rc_operand_t* self) { } } +int rc_operand_is_float(const rc_operand_t* self) { + if (self->type == RC_OPERAND_FP) + return 1; + + return rc_operand_is_float_memref(self); +} + unsigned rc_transform_operand_value(unsigned value, const rc_operand_t* self) { switch (self->type) { diff --git a/dep/rcheevos/src/rcheevos/rc_client.c b/dep/rcheevos/src/rcheevos/rc_client.c new file mode 100644 index 0000000000..76b7bf127e --- /dev/null +++ b/dep/rcheevos/src/rcheevos/rc_client.c @@ -0,0 +1,4669 @@ +#include "rc_client_internal.h" + +#include "rc_api_info.h" +#include "rc_api_runtime.h" +#include "rc_api_user.h" +#include "rc_consoles.h" +#include "rc_internal.h" +#include "rc_hash.h" + +#include "../rapi/rc_api_common.h" + +#include + +#define RC_CLIENT_UNKNOWN_GAME_ID (uint32_t)-1 +#define RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS (10 * 60) /* ten minutes */ + +/* clock_t can wrap. For most cases, we won't have to worry about it because it won't + * overflow until after over a month of runtime. But some cases can overflow in as short as + * 36 minutes. Use substraction as a secondary check to ensure an overflow hasn't occurred. */ +#define RC_CLIENT_CLOCK_IS_BEFORE(clk, cmp_clk) (clk < cmp_clk && (cmp_clk - clk) > 0) + +struct rc_client_async_handle_t { + uint8_t aborted; +}; + +typedef struct rc_client_generic_callback_data_t { + rc_client_t* client; + rc_client_callback_t callback; + void* callback_userdata; + rc_client_async_handle_t async_handle; +} rc_client_generic_callback_data_t; + +typedef struct rc_client_pending_media_t +{ + const char* file_path; + uint8_t* data; + size_t data_size; + rc_client_callback_t callback; + void* callback_userdata; +} rc_client_pending_media_t; + +typedef struct rc_client_load_state_t +{ + rc_client_t* client; + rc_client_callback_t callback; + void* callback_userdata; + + rc_client_game_info_t* game; + rc_client_subset_info_t* subset; + rc_client_game_hash_t* hash; + + rc_hash_iterator_t hash_iterator; + rc_client_pending_media_t* pending_media; + + uint32_t* hardcore_unlocks; + uint32_t* softcore_unlocks; + uint32_t num_hardcore_unlocks; + uint32_t num_softcore_unlocks; + + rc_client_async_handle_t async_handle; + + uint8_t progress; + uint8_t outstanding_requests; + uint8_t hash_console_id; +} rc_client_load_state_t; + +static void rc_client_begin_fetch_game_data(rc_client_load_state_t* callback_data); +static void rc_client_hide_progress_tracker(rc_client_t* client, rc_client_game_info_t* game); +static void rc_client_load_error(rc_client_load_state_t* load_state, int result, const char* error_message); +static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* load_state, const char* hash, const char* file_path); +static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now); +static void rc_client_raise_leaderboard_events(rc_client_t* client, rc_client_subset_info_t* subset); +static void rc_client_raise_pending_events(rc_client_t* client, rc_client_game_info_t* game); +static void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard); +static void rc_client_reschedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* callback, clock_t when); + +/* ===== Construction/Destruction ===== */ + +static void rc_client_dummy_event_handler(const rc_client_event_t* event, rc_client_t* client) +{ +} + +rc_client_t* rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function) +{ + rc_client_t* client = (rc_client_t*)calloc(1, sizeof(rc_client_t)); + if (!client) + return NULL; + + client->state.hardcore = 1; + + client->callbacks.read_memory = read_memory_function; + client->callbacks.server_call = server_call_function; + client->callbacks.event_handler = rc_client_dummy_event_handler; + rc_client_set_legacy_peek(client, RC_CLIENT_LEGACY_PEEK_AUTO); + + rc_mutex_init(&client->state.mutex); + + rc_buf_init(&client->state.buffer); + + return client; +} + +void rc_client_destroy(rc_client_t* client) +{ + if (!client) + return; + + rc_client_unload_game(client); + + rc_buf_destroy(&client->state.buffer); + + rc_mutex_destroy(&client->state.mutex); + + free(client); +} + +/* ===== Logging ===== */ + +static rc_client_t* g_hash_client = NULL; + +static void rc_client_log_hash_message(const char* message) { + rc_client_log_message(g_hash_client, message); +} + +void rc_client_log_message(const rc_client_t* client, const char* message) +{ + if (client->callbacks.log_call) + client->callbacks.log_call(message, client); +} + +static void rc_client_log_message_va(const rc_client_t* client, const char* format, va_list args) +{ + if (client->callbacks.log_call) { + char buffer[2048]; + +#ifdef __STDC_WANT_SECURE_LIB__ + 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 */ + vsprintf(buffer, format, args); +#endif + + client->callbacks.log_call(buffer, client); + } +} + +#ifdef RC_NO_VARIADIC_MACROS + +void RC_CLIENT_LOG_ERR_FORMATTED(const rc_client_t* client, const char* format, ...) +{ + if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR) { + va_list args; + va_start(args, format); + rc_client_log_message_va(client, format, args); + va_end(args); + } +} + +void RC_CLIENT_LOG_WARN_FORMATTED(const rc_client_t* client, const char* format, ...) +{ + if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_WARN) { + va_list args; + va_start(args, format); + rc_client_log_message_va(client, format, args); + va_end(args); + } +} + +void RC_CLIENT_LOG_INFO_FORMATTED(const rc_client_t* client, const char* format, ...) +{ + if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) { + va_list args; + va_start(args, format); + rc_client_log_message_va(client, format, args); + va_end(args); + } +} + +void RC_CLIENT_LOG_VERBOSE_FORMATTED(const rc_client_t* client, const char* format, ...) +{ + if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_VERBOSE) { + va_list args; + va_start(args, format); + rc_client_log_message_va(client, format, args); + va_end(args); + } +} + +#else + +void rc_client_log_message_formatted(const rc_client_t* client, const char* format, ...) +{ + va_list args; + va_start(args, format); + rc_client_log_message_va(client, format, args); + va_end(args); +} + +#endif /* RC_NO_VARIADIC_MACROS */ + +void rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback) +{ + client->callbacks.log_call = callback; + client->state.log_level = callback ? level : RC_CLIENT_LOG_LEVEL_NONE; +} + +/* ===== Common ===== */ + +static int rc_client_async_handle_aborted(rc_client_t* client, rc_client_async_handle_t* async_handle) +{ + int aborted; + + rc_mutex_lock(&client->state.mutex); + aborted = async_handle->aborted; + rc_mutex_unlock(&client->state.mutex); + + return aborted; +} + +void rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle) +{ + if (async_handle && client) { + rc_mutex_lock(&client->state.mutex); + async_handle->aborted = 1; + rc_mutex_unlock(&client->state.mutex); + } +} + +static const char* rc_client_server_error_message(int* result, int http_status_code, const rc_api_response_t* response) +{ + if (!response->succeeded) { + if (*result == RC_OK) { + *result = RC_API_FAILURE; + if (!response->error_message) + return "Unexpected API failure with no error message"; + } + + if (response->error_message) + return response->error_message; + } + + if (*result != RC_OK) + return rc_error_str(*result); + + return NULL; +} + +static void rc_client_raise_server_error_event(rc_client_t* client, const char* api, const char* error_message) +{ + rc_client_server_error_t server_error; + rc_client_event_t client_event; + + server_error.api = api; + server_error.error_message = error_message; + + memset(&client_event, 0, sizeof(client_event)); + client_event.type = RC_CLIENT_EVENT_SERVER_ERROR; + client_event.server_error = &server_error; + + client->callbacks.event_handler(&client_event, client); +} + +static int rc_client_should_retry(const rc_api_server_response_t* server_response) +{ + switch (server_response->http_status_code) { + case 502: /* 502 Bad Gateway */ + /* nginx connection pool full. retry */ + return 1; + + case 503: /* 503 Service Temporarily Unavailable */ + /* site is in maintenance mode. retry */ + return 1; + + case 429: /* 429 Too Many Requests */ + /* too many unlocks occurred at the same time */ + return 1; + + default: + return 0; + } +} + +static int rc_client_get_image_url(char buffer[], size_t buffer_size, int image_type, const char* image_name) +{ + rc_api_fetch_image_request_t image_request; + rc_api_request_t request; + int result; + + if (!buffer) + return RC_INVALID_STATE; + + memset(&image_request, 0, sizeof(image_request)); + image_request.image_type = image_type; + image_request.image_name = image_name; + result = rc_api_init_fetch_image_request(&request, &image_request); + if (result == RC_OK) + snprintf(buffer, buffer_size, "%s", request.url); + + rc_api_destroy_request(&request); + return result; +} + +/* ===== User ===== */ + +static void rc_client_login_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_generic_callback_data_t* login_callback_data = (rc_client_generic_callback_data_t*)callback_data; + rc_client_t* client = login_callback_data->client; + rc_api_login_response_t login_response; + rc_client_load_state_t* load_state; + const char* error_message; + int result; + + if (rc_client_async_handle_aborted(client, &login_callback_data->async_handle)) { + RC_CLIENT_LOG_VERBOSE(client, "Login aborted"); + free(login_callback_data); + return; + } + + if (client->state.user == RC_CLIENT_USER_STATE_NONE) { + /* logout was called */ + if (login_callback_data->callback) + login_callback_data->callback(RC_ABORTED, "Login aborted", client, login_callback_data->callback_userdata); + + free(login_callback_data); + return; + } + + result = rc_api_process_login_server_response(&login_response, server_response); + error_message = rc_client_server_error_message(&result, server_response->http_status_code, &login_response.response); + if (error_message) { + rc_mutex_lock(&client->state.mutex); + client->state.user = RC_CLIENT_USER_STATE_NONE; + load_state = client->state.load; + rc_mutex_unlock(&client->state.mutex); + + RC_CLIENT_LOG_ERR_FORMATTED(client, "Login failed: %s", error_message); + if (login_callback_data->callback) + login_callback_data->callback(result, error_message, client, login_callback_data->callback_userdata); + + if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN) + rc_client_begin_fetch_game_data(load_state); + } + else { + client->user.username = rc_buf_strcpy(&client->state.buffer, login_response.username); + + if (strcmp(login_response.username, login_response.display_name) == 0) + client->user.display_name = client->user.username; + else + client->user.display_name = rc_buf_strcpy(&client->state.buffer, login_response.display_name); + + client->user.token = rc_buf_strcpy(&client->state.buffer, login_response.api_token); + client->user.score = login_response.score; + client->user.score_softcore = login_response.score_softcore; + client->user.num_unread_messages = login_response.num_unread_messages; + + rc_mutex_lock(&client->state.mutex); + client->state.user = RC_CLIENT_USER_STATE_LOGGED_IN; + load_state = client->state.load; + rc_mutex_unlock(&client->state.mutex); + + RC_CLIENT_LOG_INFO_FORMATTED(client, "%s logged in successfully", login_response.display_name); + + if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN) + rc_client_begin_fetch_game_data(load_state); + + if (login_callback_data->callback) + login_callback_data->callback(RC_OK, NULL, client, login_callback_data->callback_userdata); + } + + rc_api_destroy_login_response(&login_response); + free(login_callback_data); +} + +static rc_client_async_handle_t* rc_client_begin_login(rc_client_t* client, + const rc_api_login_request_t* login_request, rc_client_callback_t callback, void* callback_userdata) +{ + rc_client_generic_callback_data_t* callback_data; + rc_api_request_t request; + int result = rc_api_init_login_request(&request, login_request); + const char* error_message = rc_error_str(result); + + if (result == RC_OK) { + rc_mutex_lock(&client->state.mutex); + + if (client->state.user == RC_CLIENT_USER_STATE_LOGIN_REQUESTED) { + error_message = "Login already in progress"; + result = RC_INVALID_STATE; + } + client->state.user = RC_CLIENT_USER_STATE_LOGIN_REQUESTED; + + rc_mutex_unlock(&client->state.mutex); + } + + if (result != RC_OK) { + callback(result, error_message, client, callback_userdata); + return NULL; + } + + callback_data = (rc_client_generic_callback_data_t*)calloc(1, sizeof(*callback_data)); + if (!callback_data) { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return NULL; + } + + callback_data->client = client; + callback_data->callback = callback; + callback_data->callback_userdata = callback_userdata; + + client->callbacks.server_call(&request, rc_client_login_callback, callback_data, client); + rc_api_destroy_request(&request); + + return &callback_data->async_handle; +} + +rc_client_async_handle_t* rc_client_begin_login_with_password(rc_client_t* client, + const char* username, const char* password, rc_client_callback_t callback, void* callback_userdata) +{ + rc_api_login_request_t login_request; + + if (!client) { + callback(RC_INVALID_STATE, "client is required", client, callback_userdata); + return NULL; + } + + if (!username || !username[0]) { + callback(RC_INVALID_STATE, "username is required", client, callback_userdata); + return NULL; + } + + if (!password || !password[0]) { + callback(RC_INVALID_STATE, "password is required", client, callback_userdata); + return NULL; + } + + memset(&login_request, 0, sizeof(login_request)); + login_request.username = username; + login_request.password = password; + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Attempting to log in %s (with password)", username); + return rc_client_begin_login(client, &login_request, callback, callback_userdata); +} + +rc_client_async_handle_t* rc_client_begin_login_with_token(rc_client_t* client, + const char* username, const char* token, rc_client_callback_t callback, void* callback_userdata) +{ + rc_api_login_request_t login_request; + + if (!client) { + callback(RC_INVALID_STATE, "client is required", client, callback_userdata); + return NULL; + } + + if (!username || !username[0]) { + callback(RC_INVALID_STATE, "username is required", client, callback_userdata); + return NULL; + } + + if (!token || !token[0]) { + callback(RC_INVALID_STATE, "token is required", client, callback_userdata); + return NULL; + } + + memset(&login_request, 0, sizeof(login_request)); + login_request.username = username; + login_request.api_token = token; + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Attempting to log in %s (with token)", username); + return rc_client_begin_login(client, &login_request, callback, callback_userdata); +} + +void rc_client_logout(rc_client_t* client) +{ + rc_client_load_state_t* load_state; + + if (!client) + return; + + switch (client->state.user) { + case RC_CLIENT_USER_STATE_LOGGED_IN: + RC_CLIENT_LOG_INFO_FORMATTED(client, "Logging %s out", client->user.display_name); + break; + + case RC_CLIENT_USER_STATE_LOGIN_REQUESTED: + RC_CLIENT_LOG_INFO(client, "Aborting login"); + break; + } + + rc_mutex_lock(&client->state.mutex); + + client->state.user = RC_CLIENT_USER_STATE_NONE; + memset(&client->user, 0, sizeof(client->user)); + + load_state = client->state.load; + + rc_mutex_unlock(&client->state.mutex); + + rc_client_unload_game(client); + + if (load_state && load_state->progress == RC_CLIENT_LOAD_STATE_AWAIT_LOGIN) + rc_client_load_error(load_state, RC_ABORTED, "Login aborted"); +} + +const rc_client_user_t* rc_client_get_user_info(const rc_client_t* client) +{ + return (client && client->state.user == RC_CLIENT_USER_STATE_LOGGED_IN) ? &client->user : NULL; +} + +int rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size) +{ + if (!user) + return RC_INVALID_STATE; + + return rc_client_get_image_url(buffer, buffer_size, RC_IMAGE_TYPE_USER, user->display_name); +} + +static void rc_client_subset_get_user_game_summary(const rc_client_subset_info_t* subset, + rc_client_user_game_summary_t* summary, const uint8_t unlock_bit) +{ + rc_client_achievement_info_t* achievement = subset->achievements; + rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements; + for (; achievement < stop; ++achievement) { + switch (achievement->public_.category) { + case RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE: + ++summary->num_core_achievements; + summary->points_core += achievement->public_.points; + + if (achievement->public_.unlocked & unlock_bit) { + ++summary->num_unlocked_achievements; + summary->points_unlocked += achievement->public_.points; + } + else if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED) { + ++summary->num_unsupported_achievements; + } + + break; + + case RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL: + ++summary->num_unofficial_achievements; + break; + + default: + continue; + } + } +} + +void rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary) +{ + const uint8_t unlock_bit = (client->state.hardcore) ? + RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE; + + if (!summary) + return; + + memset(summary, 0, sizeof(*summary)); + if (!client || !client->game) + return; + + rc_mutex_lock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */ + + rc_client_subset_get_user_game_summary(client->game->subsets, summary, unlock_bit); + + rc_mutex_unlock((rc_mutex_t*)&client->state.mutex); /* remove const cast for mutex access */ +} + +/* ===== Game ===== */ + +static void rc_client_free_game(rc_client_game_info_t* game) +{ + rc_runtime_destroy(&game->runtime); + + rc_buf_destroy(&game->buffer); + + free(game); +} + +static void rc_client_free_load_state(rc_client_load_state_t* load_state) +{ + if (load_state->game) + rc_client_free_game(load_state->game); + + if (load_state->hardcore_unlocks) + free(load_state->hardcore_unlocks); + if (load_state->softcore_unlocks) + free(load_state->softcore_unlocks); + + free(load_state); +} + +static void rc_client_begin_load_state(rc_client_load_state_t* load_state, uint8_t state, uint8_t num_requests) +{ + rc_mutex_lock(&load_state->client->state.mutex); + + load_state->progress = state; + load_state->outstanding_requests += num_requests; + + rc_mutex_unlock(&load_state->client->state.mutex); +} + +static int rc_client_end_load_state(rc_client_load_state_t* load_state) +{ + int remaining_requests = 0; + int aborted = 0; + + rc_mutex_lock(&load_state->client->state.mutex); + + if (load_state->outstanding_requests > 0) + --load_state->outstanding_requests; + remaining_requests = load_state->outstanding_requests; + + if (load_state->client->state.load != load_state) + aborted = 1; + + rc_mutex_unlock(&load_state->client->state.mutex); + + if (aborted) { + /* we can't actually free the load_state itself if there are any outstanding requests + * or their callbacks will try to use the free'd memory. As they call end_load_state, + * the outstanding_requests count will reach zero and the memory will be free'd then. */ + if (remaining_requests == 0) { + /* if one of the callbacks called rc_client_load_error, progress will be set to + * RC_CLIENT_LOAD_STATE_UNKNOWN. There's no need to call the callback with RC_ABORTED + * in that case, as it will have already been called with something more appropriate. */ + if (load_state->progress != RC_CLIENT_LOAD_STATE_UNKNOWN_GAME && load_state->callback) + load_state->callback(RC_ABORTED, "The requested game is no longer active", load_state->client, load_state->callback_userdata); + + rc_client_free_load_state(load_state); + } + + return -1; + } + + return remaining_requests; +} + +static void rc_client_load_error(rc_client_load_state_t* load_state, int result, const char* error_message) +{ + int remaining_requests = 0; + + rc_mutex_lock(&load_state->client->state.mutex); + + load_state->progress = RC_CLIENT_LOAD_STATE_UNKNOWN_GAME; + if (load_state->client->state.load == load_state) + load_state->client->state.load = NULL; + + remaining_requests = load_state->outstanding_requests; + + rc_mutex_unlock(&load_state->client->state.mutex); + + if (load_state->callback) + load_state->callback(result, error_message, load_state->client, load_state->callback_userdata); + + /* we can't actually free the load_state itself if there are any outstanding requests + * or their callbacks will try to use the free'd memory. as they call end_load_state, + * the outstanding_requests count will reach zero and the memory will be free'd then. */ + if (remaining_requests == 0) + rc_client_free_load_state(load_state); +} + +static void rc_client_load_aborted(rc_client_load_state_t* load_state) +{ + /* prevent callback from being called when manually aborted */ + load_state->callback = NULL; + + /* mark the game as no longer being loaded */ + rc_client_load_error(load_state, RC_ABORTED, NULL); + + /* decrement the async counter and potentially free the load_state object */ + rc_client_end_load_state(load_state); +} + +static void rc_client_invalidate_memref_achievements(rc_client_game_info_t* game, rc_client_t* client, rc_memref_t* memref) +{ + rc_client_subset_info_t* subset = game->subsets; + for (; subset; subset = subset->next) { + rc_client_achievement_info_t* achievement = subset->achievements; + rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements; + for (; achievement < stop; ++achievement) { + if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_DISABLED) + continue; + + if (rc_trigger_contains_memref(achievement->trigger, memref)) { + achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED; + achievement->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED; + RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabled achievement %u. Invalid address %06X", achievement->public_.id, memref->address); + } + } + } +} + +static void rc_client_invalidate_memref_leaderboards(rc_client_game_info_t* game, rc_client_t* client, rc_memref_t* memref) +{ + rc_client_subset_info_t* subset = game->subsets; + for (; subset; subset = subset->next) { + rc_client_leaderboard_info_t* leaderboard = subset->leaderboards; + rc_client_leaderboard_info_t* stop = leaderboard + subset->public_.num_leaderboards; + for (; leaderboard < stop; ++leaderboard) { + if (leaderboard->public_.state == RC_CLIENT_LEADERBOARD_STATE_DISABLED) + continue; + + if (rc_trigger_contains_memref(&leaderboard->lboard->start, memref)) + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED; + else if (rc_trigger_contains_memref(&leaderboard->lboard->cancel, memref)) + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED; + else if (rc_trigger_contains_memref(&leaderboard->lboard->submit, memref)) + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED; + else if (rc_value_contains_memref(&leaderboard->lboard->value, memref)) + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED; + else + continue; + + RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabled leaderboard %u. Invalid address %06X", leaderboard->public_.id, memref->address); + } + } +} + +static void rc_client_validate_addresses(rc_client_game_info_t* game, rc_client_t* client) +{ + const rc_memory_regions_t* regions = rc_console_memory_regions(game->public_.console_id); + const uint32_t max_address = (regions && regions->num_regions > 0) ? + regions->region[regions->num_regions - 1].end_address : 0xFFFFFFFF; + uint8_t buffer[8]; + uint32_t total_count = 0; + uint32_t invalid_count = 0; + + rc_memref_t** last_memref = &game->runtime.memrefs; + rc_memref_t* memref = game->runtime.memrefs; + for (; memref; memref = memref->next) { + if (!memref->value.is_indirect) { + total_count++; + + if (memref->address > max_address || + client->callbacks.read_memory(memref->address, buffer, 1, client) == 0) { + /* invalid address, remove from chain so we don't have to evaluate it in the future. + * it's still there, so anything referencing it will always fetch 0. */ + *last_memref = memref->next; + + rc_client_invalidate_memref_achievements(game, client, memref); + rc_client_invalidate_memref_leaderboards(game, client, memref); + + invalid_count++; + continue; + } + } + + last_memref = &memref->next; + } + + game->max_valid_address = max_address; + RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "%u/%u memory addresses valid", total_count - invalid_count, total_count); +} + +static void rc_client_update_legacy_runtime_achievements(rc_client_game_info_t* game, uint32_t active_count) +{ + if (active_count > 0) { + rc_client_achievement_info_t* achievement; + rc_client_achievement_info_t* stop; + rc_runtime_trigger_t* trigger; + + rc_client_subset_info_t* subset = game->subsets; + for (; subset; subset = subset->next) { + if (!subset->active) + continue; + + achievement = subset->achievements; + stop = achievement + subset->public_.num_achievements; + + if (active_count <= game->runtime.trigger_capacity) { + if (active_count != 0) + memset(game->runtime.triggers, 0, active_count * sizeof(rc_runtime_trigger_t)); + } + else { + if (game->runtime.triggers) + free(game->runtime.triggers); + + game->runtime.trigger_capacity = active_count; + game->runtime.triggers = (rc_runtime_trigger_t*)calloc(1, active_count * sizeof(rc_runtime_trigger_t)); + if (!game->runtime.triggers) { + /* Unexpected, no callback available, just fail */ + break; + } + } + + trigger = game->runtime.triggers; + achievement = subset->achievements; + for (; achievement < stop; ++achievement) { + if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE) { + trigger->id = achievement->public_.id; + memcpy(trigger->md5, achievement->md5, 16); + trigger->trigger = achievement->trigger; + ++trigger; + } + } + } + } + + game->runtime.trigger_count = active_count; +} + +static uint32_t rc_client_subset_count_active_achievements(const rc_client_subset_info_t* subset) +{ + rc_client_achievement_info_t* achievement = subset->achievements; + rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements; + uint32_t active_count = 0; + + for (; achievement < stop; ++achievement) { + if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE) + ++active_count; + } + + return active_count; +} + +static void rc_client_update_active_achievements(rc_client_game_info_t* game) +{ + uint32_t active_count = 0; + rc_client_subset_info_t* subset = game->subsets; + for (; subset; subset = subset->next) { + if (subset->active) + active_count += rc_client_subset_count_active_achievements(subset); + } + + rc_client_update_legacy_runtime_achievements(game, active_count); +} + +static uint32_t rc_client_subset_toggle_hardcore_achievements(rc_client_subset_info_t* subset, rc_client_t* client, uint8_t active_bit) +{ + rc_client_achievement_info_t* achievement = subset->achievements; + rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements; + uint32_t active_count = 0; + + for (; achievement < stop; ++achievement) { + if ((achievement->public_.unlocked & active_bit) == 0) { + switch (achievement->public_.state) { + case RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED: + case RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE: + rc_reset_trigger(achievement->trigger); + achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE; + ++active_count; + break; + + case RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE: + ++active_count; + break; + } + } + else if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE || + achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE) { + + /* if it's active despite being unlocked, and we're in encore mode, leave it active */ + if (client->state.encore_mode) { + ++active_count; + continue; + } + + achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED; + achievement->public_.unlock_time = (active_bit == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) ? + achievement->unlock_time_hardcore : achievement->unlock_time_softcore; + + if (achievement->trigger && achievement->trigger->state == RC_TRIGGER_STATE_PRIMED) { + rc_client_event_t client_event; + memset(&client_event, 0, sizeof(client_event)); + client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE; + client_event.achievement = &achievement->public_; + client->callbacks.event_handler(&client_event, client); + } + } + } + + return active_count; +} + +static void rc_client_toggle_hardcore_achievements(rc_client_game_info_t* game, rc_client_t* client, uint8_t active_bit) +{ + uint32_t active_count = 0; + rc_client_subset_info_t* subset = game->subsets; + for (; subset; subset = subset->next) { + if (subset->active) + active_count += rc_client_subset_toggle_hardcore_achievements(subset, client, active_bit); + } + + rc_client_update_legacy_runtime_achievements(game, active_count); +} + +static void rc_client_activate_achievements(rc_client_game_info_t* game, rc_client_t* client) +{ + const uint8_t active_bit = (client->state.encore_mode) ? + RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE : (client->state.hardcore) ? + RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE; + + rc_client_toggle_hardcore_achievements(game, client, active_bit); +} + +static void rc_client_activate_leaderboards(rc_client_game_info_t* game, rc_client_t* client) +{ + rc_client_leaderboard_info_t* leaderboard; + rc_client_leaderboard_info_t* stop; + + unsigned active_count = 0; + rc_client_subset_info_t* subset = game->subsets; + for (; subset; subset = subset->next) { + if (!subset->active) + continue; + + leaderboard = subset->leaderboards; + stop = leaderboard + subset->public_.num_leaderboards; + + for (; leaderboard < stop; ++leaderboard) { + switch (leaderboard->public_.state) { + case RC_CLIENT_LEADERBOARD_STATE_DISABLED: + continue; + + case RC_CLIENT_LEADERBOARD_STATE_INACTIVE: + if (client->state.hardcore) { + rc_reset_lboard(leaderboard->lboard); + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE; + ++active_count; + } + break; + + default: + if (client->state.hardcore) + ++active_count; + else + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_INACTIVE; + break; + } + } + } + + if (active_count > 0) { + rc_runtime_lboard_t* lboard; + + if (active_count <= game->runtime.lboard_capacity) { + if (active_count != 0) + memset(game->runtime.lboards, 0, active_count * sizeof(rc_runtime_lboard_t)); + } + else { + if (game->runtime.lboards) + free(game->runtime.lboards); + + game->runtime.lboard_capacity = active_count; + game->runtime.lboards = (rc_runtime_lboard_t*)calloc(1, active_count * sizeof(rc_runtime_lboard_t)); + } + + lboard = game->runtime.lboards; + + subset = game->subsets; + for (; subset; subset = subset->next) { + if (!subset->active) + continue; + + leaderboard = subset->leaderboards; + stop = leaderboard + subset->public_.num_leaderboards; + for (; leaderboard < stop; ++leaderboard) { + if (leaderboard->public_.state == RC_CLIENT_LEADERBOARD_STATE_ACTIVE || + leaderboard->public_.state == RC_CLIENT_LEADERBOARD_STATE_TRACKING) { + lboard->id = leaderboard->public_.id; + memcpy(lboard->md5, leaderboard->md5, 16); + lboard->lboard = leaderboard->lboard; + ++lboard; + } + } + } + } + + game->runtime.lboard_count = active_count; +} + +static void rc_client_deactivate_leaderboards(rc_client_game_info_t* game, rc_client_t* client) +{ + rc_client_leaderboard_info_t* leaderboard; + rc_client_leaderboard_info_t* stop; + + rc_client_subset_info_t* subset = game->subsets; + for (; subset; subset = subset->next) { + if (!subset->active) + continue; + + leaderboard = subset->leaderboards; + stop = leaderboard + subset->public_.num_leaderboards; + + for (; leaderboard < stop; ++leaderboard) { + switch (leaderboard->public_.state) { + case RC_CLIENT_LEADERBOARD_STATE_DISABLED: + case RC_CLIENT_LEADERBOARD_STATE_INACTIVE: + continue; + + case RC_CLIENT_LEADERBOARD_STATE_TRACKING: + rc_client_release_leaderboard_tracker(client->game, leaderboard); + /* fallthrough to default */ + default: + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_INACTIVE; + break; + } + } + } + + game->runtime.lboard_count = 0; +} + +static void rc_client_apply_unlocks(rc_client_subset_info_t* subset, uint32_t* unlocks, uint32_t num_unlocks, uint8_t mode) +{ + rc_client_achievement_info_t* start = subset->achievements; + rc_client_achievement_info_t* stop = start + subset->public_.num_achievements; + rc_client_achievement_info_t* scan; + unsigned i; + + for (i = 0; i < num_unlocks; ++i) { + uint32_t id = unlocks[i]; + for (scan = start; scan < stop; ++scan) { + if (scan->public_.id == id) { + scan->public_.unlocked |= mode; + + if (scan == start) + ++start; + else if (scan + 1 == stop) + --stop; + break; + } + } + } +} + +static void rc_client_activate_game(rc_client_load_state_t* load_state) +{ + rc_client_t* client = load_state->client; + + rc_mutex_lock(&client->state.mutex); + load_state->progress = (client->state.load == load_state) ? + RC_CLIENT_LOAD_STATE_DONE : RC_CLIENT_LOAD_STATE_UNKNOWN_GAME; + client->state.load = NULL; + rc_mutex_unlock(&client->state.mutex); + + if (load_state->progress != RC_CLIENT_LOAD_STATE_DONE) { + /* previous load state was aborted */ + if (load_state->callback) + load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata); + } + else if ((!load_state->softcore_unlocks || !load_state->hardcore_unlocks) && + client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) { + /* unlocks not available - assume malloc failed */ + if (load_state->callback) + load_state->callback(RC_INVALID_STATE, "Unlock arrays were not allocated", client, load_state->callback_userdata); + } + else { + if (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) { + rc_client_apply_unlocks(load_state->subset, load_state->softcore_unlocks, + load_state->num_softcore_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE); + rc_client_apply_unlocks(load_state->subset, load_state->hardcore_unlocks, + load_state->num_hardcore_unlocks, RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH); + } + + rc_mutex_lock(&client->state.mutex); + if (client->state.load == NULL) + client->game = load_state->game; + rc_mutex_unlock(&client->state.mutex); + + if (client->game != load_state->game) { + /* previous load state was aborted */ + if (load_state->callback) + load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata); + } + else { + /* if a change media request is pending, kick it off */ + rc_client_pending_media_t* pending_media; + + rc_mutex_lock(&load_state->client->state.mutex); + pending_media = load_state->pending_media; + load_state->pending_media = NULL; + rc_mutex_unlock(&load_state->client->state.mutex); + + if (pending_media) { + rc_client_begin_change_media(client, pending_media->file_path, + pending_media->data, pending_media->data_size, pending_media->callback, pending_media->callback_userdata); + if (pending_media->data) + free(pending_media->data); + free((void*)pending_media->file_path); + free(pending_media); + } + + /* client->game must be set before calling this function so it can query the console_id */ + rc_client_validate_addresses(load_state->game, client); + + rc_client_activate_achievements(load_state->game, client); + rc_client_activate_leaderboards(load_state->game, client); + + if (load_state->hash->hash[0] != '[') { + if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_LOCKED) { + /* schedule the periodic ping */ + rc_client_scheduled_callback_data_t* callback_data = rc_buf_alloc(&load_state->game->buffer, sizeof(rc_client_scheduled_callback_data_t)); + memset(callback_data, 0, sizeof(*callback_data)); + callback_data->callback = rc_client_ping; + callback_data->related_id = load_state->game->public_.id; + callback_data->when = clock() + 30 * CLOCKS_PER_SEC; + rc_client_schedule_callback(client, callback_data); + } + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Game %u loaded, hardcode %s%s", load_state->game->public_.id, + client->state.hardcore ? "enabled" : "disabled", + (client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) ? ", spectating" : ""); + } + else { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Subset %u loaded", load_state->subset->public_.id); + } + + if (load_state->callback) + load_state->callback(RC_OK, NULL, client, load_state->callback_userdata); + + /* detach the game object so it doesn't get freed by free_load_state */ + load_state->game = NULL; + } + } + + rc_client_free_load_state(load_state); +} + +static void rc_client_start_session_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data; + rc_api_start_session_response_t start_session_response; + int outstanding_requests; + const char* error_message; + int result; + + if (rc_client_async_handle_aborted(load_state->client, &load_state->async_handle)) { + rc_client_t* client = load_state->client; + rc_client_load_aborted(load_state); + RC_CLIENT_LOG_VERBOSE(client, "Load aborted while starting session"); + return; + } + + result = rc_api_process_start_session_server_response(&start_session_response, server_response); + error_message = rc_client_server_error_message(&result, server_response->http_status_code, &start_session_response.response); + outstanding_requests = rc_client_end_load_state(load_state); + + if (error_message) { + rc_client_load_error(callback_data, result, error_message); + } + else if (outstanding_requests < 0) { + /* previous load state was aborted, load_state was free'd */ + } + else { + if (outstanding_requests == 0) + rc_client_activate_game(load_state); + } + + rc_api_destroy_start_session_response(&start_session_response); +} + +static void rc_client_unlocks_callback(const rc_api_server_response_t* server_response, void* callback_data, int mode) +{ + rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data; + rc_api_fetch_user_unlocks_response_t fetch_user_unlocks_response; + int outstanding_requests; + const char* error_message; + int result; + + if (rc_client_async_handle_aborted(load_state->client, &load_state->async_handle)) { + rc_client_t* client = load_state->client; + rc_client_load_aborted(load_state); + RC_CLIENT_LOG_VERBOSE(client, "Load aborted while fetching unlocks"); + return; + } + + result = rc_api_process_fetch_user_unlocks_server_response(&fetch_user_unlocks_response, server_response); + error_message = rc_client_server_error_message(&result, server_response->http_status_code, &fetch_user_unlocks_response.response); + outstanding_requests = rc_client_end_load_state(load_state); + + if (error_message) { + rc_client_load_error(callback_data, result, error_message); + } + else if (outstanding_requests < 0) { + /* previous load state was aborted, load_state was free'd */ + } + else { + if (mode == RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE) { + const size_t array_size = fetch_user_unlocks_response.num_achievement_ids * sizeof(uint32_t); + load_state->num_hardcore_unlocks = fetch_user_unlocks_response.num_achievement_ids; + load_state->hardcore_unlocks = (uint32_t*)malloc(array_size); + if (load_state->hardcore_unlocks) + memcpy(load_state->hardcore_unlocks, fetch_user_unlocks_response.achievement_ids, array_size); + } + else { + const size_t array_size = fetch_user_unlocks_response.num_achievement_ids * sizeof(uint32_t); + load_state->num_softcore_unlocks = fetch_user_unlocks_response.num_achievement_ids; + load_state->softcore_unlocks = (uint32_t*)malloc(array_size); + if (load_state->softcore_unlocks) + memcpy(load_state->softcore_unlocks, fetch_user_unlocks_response.achievement_ids, array_size); + } + + if (outstanding_requests == 0) + rc_client_activate_game(load_state); + } + + rc_api_destroy_fetch_user_unlocks_response(&fetch_user_unlocks_response); +} + +static void rc_client_hardcore_unlocks_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_unlocks_callback(server_response, callback_data, RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE); +} + +static void rc_client_softcore_unlocks_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_unlocks_callback(server_response, callback_data, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE); +} + +static void rc_client_begin_start_session(rc_client_load_state_t* load_state) +{ + rc_api_start_session_request_t start_session_params; + rc_api_fetch_user_unlocks_request_t unlock_params; + rc_client_t* client = load_state->client; + rc_api_request_t start_session_request; + rc_api_request_t hardcore_unlock_request; + rc_api_request_t softcore_unlock_request; + int result; + + memset(&start_session_params, 0, sizeof(start_session_params)); + start_session_params.username = client->user.username; + start_session_params.api_token = client->user.token; + start_session_params.game_id = load_state->hash->game_id; + + result = rc_api_init_start_session_request(&start_session_request, &start_session_params); + if (result != RC_OK) { + rc_client_load_error(load_state, result, rc_error_str(result)); + } + else { + memset(&unlock_params, 0, sizeof(unlock_params)); + unlock_params.username = client->user.username; + unlock_params.api_token = client->user.token; + unlock_params.game_id = load_state->hash->game_id; + unlock_params.hardcore = 1; + + result = rc_api_init_fetch_user_unlocks_request(&hardcore_unlock_request, &unlock_params); + if (result != RC_OK) { + rc_client_load_error(load_state, result, rc_error_str(result)); + } + else { + unlock_params.hardcore = 0; + + result = rc_api_init_fetch_user_unlocks_request(&softcore_unlock_request, &unlock_params); + if (result != RC_OK) { + rc_client_load_error(load_state, result, rc_error_str(result)); + } + else { + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 3); + + /* TODO: create single server request to do all three of these */ + RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Starting session for game %u", start_session_params.game_id); + client->callbacks.server_call(&start_session_request, rc_client_start_session_callback, load_state, client); + client->callbacks.server_call(&hardcore_unlock_request, rc_client_hardcore_unlocks_callback, load_state, client); + client->callbacks.server_call(&softcore_unlock_request, rc_client_softcore_unlocks_callback, load_state, client); + + rc_api_destroy_request(&softcore_unlock_request); + } + + rc_api_destroy_request(&hardcore_unlock_request); + } + + rc_api_destroy_request(&start_session_request); + } +} + +static void rc_client_copy_achievements(rc_client_load_state_t* load_state, + rc_client_subset_info_t* subset, + const rc_api_achievement_definition_t* achievement_definitions, uint32_t num_achievements) +{ + const rc_api_achievement_definition_t* read; + const rc_api_achievement_definition_t* stop; + rc_client_achievement_info_t* achievements; + rc_client_achievement_info_t* achievement; + rc_client_achievement_info_t* scan; + rc_api_buffer_t* buffer; + rc_parse_state_t parse; + const char* memaddr; + size_t size; + int trigger_size; + + subset->achievements = NULL; + subset->public_.num_achievements = num_achievements; + + if (num_achievements == 0) + return; + + stop = achievement_definitions + num_achievements; + + /* if not testing unofficial, filter them out */ + if (!load_state->client->state.unofficial_enabled) { + for (read = achievement_definitions; read < stop; ++read) { + if (read->category != RC_ACHIEVEMENT_CATEGORY_CORE) + --num_achievements; + } + + subset->public_.num_achievements = num_achievements; + + if (num_achievements == 0) + return; + } + + /* preallocate space for achievements */ + size = 24 /* assume average title length of 24 */ + + 48 /* assume average description length of 48 */ + + sizeof(rc_trigger_t) + sizeof(rc_condset_t) * 2 /* trigger container */ + + sizeof(rc_condition_t) * 8 /* assume average trigger length of 8 conditions */ + + sizeof(rc_client_achievement_info_t); + rc_buf_reserve(&load_state->game->buffer, size * num_achievements); + + /* allocate the achievement array */ + size = sizeof(rc_client_achievement_info_t) * num_achievements; + buffer = &load_state->game->buffer; + achievement = achievements = rc_buf_alloc(buffer, size); + memset(achievements, 0, size); + + /* copy the achievement data */ + for (read = achievement_definitions; read < stop; ++read) { + if (read->category != RC_ACHIEVEMENT_CATEGORY_CORE && !load_state->client->state.unofficial_enabled) + continue; + + achievement->public_.title = rc_buf_strcpy(buffer, read->title); + achievement->public_.description = rc_buf_strcpy(buffer, read->description); + snprintf(achievement->public_.badge_name, sizeof(achievement->public_.badge_name), "%s", read->badge_name); + achievement->public_.id = read->id; + achievement->public_.points = read->points; + achievement->public_.category = (read->category != RC_ACHIEVEMENT_CATEGORY_CORE) ? + RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL : RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE; + + memaddr = read->definition; + rc_runtime_checksum(memaddr, achievement->md5); + + trigger_size = rc_trigger_size(memaddr); + if (trigger_size < 0) { + RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Parse error %d processing achievement %u", trigger_size, read->id); + achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED; + achievement->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED; + } + else { + /* populate the item, using the communal memrefs pool */ + rc_init_parse_state(&parse, rc_buf_reserve(buffer, trigger_size), NULL, 0); + parse.first_memref = &load_state->game->runtime.memrefs; + parse.variables = &load_state->game->runtime.variables; + achievement->trigger = RC_ALLOC(rc_trigger_t, &parse); + rc_parse_trigger_internal(achievement->trigger, &memaddr, &parse); + + if (parse.offset < 0) { + RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Parse error %d processing achievement %u", parse.offset, read->id); + achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_DISABLED; + achievement->public_.bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED; + } + else { + rc_buf_consume(buffer, parse.buffer, (char*)parse.buffer + parse.offset); + achievement->trigger->memrefs = NULL; /* memrefs managed by runtime */ + } + + rc_destroy_parse_state(&parse); + } + + achievement->created_time = read->created; + achievement->updated_time = read->updated; + + scan = achievement; + while (scan > achievements) { + --scan; + if (strcmp(scan->author, read->author) == 0) { + achievement->author = scan->author; + break; + } + } + if (!achievement->author) + achievement->author = rc_buf_strcpy(buffer, read->author); + + ++achievement; + } + + subset->achievements = achievements; +} + +static void rc_client_copy_leaderboards(rc_client_load_state_t* load_state, + rc_client_subset_info_t* subset, + const rc_api_leaderboard_definition_t* leaderboard_definitions, uint32_t num_leaderboards) +{ + const rc_api_leaderboard_definition_t* read; + const rc_api_leaderboard_definition_t* stop; + rc_client_leaderboard_info_t* leaderboards; + rc_client_leaderboard_info_t* leaderboard; + rc_api_buffer_t* buffer; + rc_parse_state_t parse; + const char* memaddr; + const char* ptr; + size_t size; + int lboard_size; + + subset->leaderboards = NULL; + subset->public_.num_leaderboards = num_leaderboards; + + if (num_leaderboards == 0) + return; + + /* preallocate space for achievements */ + size = 24 /* assume average title length of 24 */ + + 48 /* assume average description length of 48 */ + + sizeof(rc_lboard_t) /* lboard container */ + + (sizeof(rc_trigger_t) + sizeof(rc_condset_t) * 2) * 3 /* start/submit/cancel */ + + (sizeof(rc_value_t) + sizeof(rc_condset_t)) /* value */ + + sizeof(rc_condition_t) * 4 * 4 /* assume average of 4 conditions in each start/submit/cancel/value */ + + sizeof(rc_client_leaderboard_info_t); + rc_buf_reserve(&load_state->game->buffer, size * num_leaderboards); + + /* allocate the achievement array */ + size = sizeof(rc_client_leaderboard_info_t) * num_leaderboards; + buffer = &load_state->game->buffer; + leaderboard = leaderboards = rc_buf_alloc(buffer, size); + memset(leaderboards, 0, size); + + /* copy the achievement data */ + read = leaderboard_definitions; + stop = read + num_leaderboards; + do { + leaderboard->public_.title = rc_buf_strcpy(buffer, read->title); + leaderboard->public_.description = rc_buf_strcpy(buffer, read->description); + leaderboard->public_.id = read->id; + leaderboard->public_.lower_is_better = read->lower_is_better; + leaderboard->format = (uint8_t)read->format; + leaderboard->hidden = (uint8_t)read->hidden; + + memaddr = read->definition; + rc_runtime_checksum(memaddr, leaderboard->md5); + + ptr = strstr(memaddr, "VAL:"); + if (ptr != NULL) { + /* calculate the DJB2 hash of the VAL portion of the string*/ + uint32_t hash = 5381; + ptr += 4; /* skip 'VAL:' */ + while (*ptr && (ptr[0] != ':' || ptr[1] != ':')) + hash = (hash << 5) + hash + *ptr++; + leaderboard->value_djb2 = hash; + } + + lboard_size = rc_lboard_size(memaddr); + if (lboard_size < 0) { + RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Parse error %d processing leaderboard %u", lboard_size, read->id); + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED; + } + else { + /* populate the item, using the communal memrefs pool */ + rc_init_parse_state(&parse, rc_buf_reserve(buffer, lboard_size), NULL, 0); + parse.first_memref = &load_state->game->runtime.memrefs; + parse.variables = &load_state->game->runtime.variables; + leaderboard->lboard = RC_ALLOC(rc_lboard_t, &parse); + rc_parse_lboard_internal(leaderboard->lboard, memaddr, &parse); + + if (parse.offset < 0) { + RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Parse error %d processing leaderboard %u", parse.offset, read->id); + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_DISABLED; + } + else { + rc_buf_consume(buffer, parse.buffer, (char*)parse.buffer + parse.offset); + leaderboard->lboard->memrefs = NULL; /* memrefs managed by runtime */ + } + + rc_destroy_parse_state(&parse); + } + + ++leaderboard; + ++read; + } while (read < stop); + + subset->leaderboards = leaderboards; +} + +static const char* rc_client_subset_extract_title(rc_client_game_info_t* game, const char* title) +{ + const char* subset_prefix = strstr(title, "[Subset - "); + if (subset_prefix) { + const char* start = subset_prefix + 10; + const char* stop = strstr(start, "]"); + const size_t len = stop - start; + char* result = (char*)rc_buf_alloc(&game->buffer, len + 1); + + memcpy(result, start, len); + result[len] = '\0'; + return result; + } + + return NULL; +} + +static void rc_client_fetch_game_data_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data; + rc_api_fetch_game_data_response_t fetch_game_data_response; + int outstanding_requests; + const char* error_message; + int result; + + if (rc_client_async_handle_aborted(load_state->client, &load_state->async_handle)) { + rc_client_t* client = load_state->client; + rc_client_load_aborted(load_state); + RC_CLIENT_LOG_VERBOSE(client, "Load aborted while fetching game data"); + return; + } + + result = rc_api_process_fetch_game_data_server_response(&fetch_game_data_response, server_response); + error_message = rc_client_server_error_message(&result, server_response->http_status_code, &fetch_game_data_response.response); + + outstanding_requests = rc_client_end_load_state(load_state); + + if (error_message) { + rc_client_load_error(load_state, result, error_message); + } + else if (outstanding_requests < 0) { + /* previous load state was aborted, load_state was free'd */ + } + else { + rc_client_subset_info_t* subset; + + subset = (rc_client_subset_info_t*)rc_buf_alloc(&load_state->game->buffer, sizeof(rc_client_subset_info_t)); + memset(subset, 0, sizeof(*subset)); + subset->public_.id = fetch_game_data_response.id; + subset->active = 1; + snprintf(subset->public_.badge_name, sizeof(subset->public_.badge_name), "%s", fetch_game_data_response.image_name); + load_state->subset = subset; + + if (load_state->game->public_.console_id != RC_CONSOLE_UNKNOWN && + fetch_game_data_response.console_id != load_state->game->public_.console_id) { + RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Data for game %u is for console %u, expecting console %u", + fetch_game_data_response.id, fetch_game_data_response.console_id, load_state->game->public_.console_id); + } + + /* kick off the start session request while we process the game data */ + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_STARTING_SESSION, 1); + if (load_state->client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) { + /* we can't unlock achievements without a session, lock spectator mode for the game */ + load_state->client->state.spectator_mode = RC_CLIENT_SPECTATOR_MODE_LOCKED; + } + else { + rc_client_begin_start_session(load_state); + } + + /* process the game data */ + rc_client_copy_achievements(load_state, subset, + fetch_game_data_response.achievements, fetch_game_data_response.num_achievements); + rc_client_copy_leaderboards(load_state, subset, + fetch_game_data_response.leaderboards, fetch_game_data_response.num_leaderboards); + + if (!load_state->game->subsets) { + /* core set */ + rc_mutex_lock(&load_state->client->state.mutex); + load_state->game->public_.title = rc_buf_strcpy(&load_state->game->buffer, fetch_game_data_response.title); + load_state->game->subsets = subset; + load_state->game->public_.badge_name = subset->public_.badge_name; + load_state->game->public_.console_id = fetch_game_data_response.console_id; + rc_mutex_unlock(&load_state->client->state.mutex); + + subset->public_.title = load_state->game->public_.title; + + if (fetch_game_data_response.rich_presence_script && fetch_game_data_response.rich_presence_script[0]) { + result = rc_runtime_activate_richpresence(&load_state->game->runtime, fetch_game_data_response.rich_presence_script, NULL, 0); + if (result != RC_OK) { + RC_CLIENT_LOG_WARN_FORMATTED(load_state->client, "Parse error %d processing rich presence", result); + } + } + } + else { + rc_client_subset_info_t* scan; + + /* subset - extract subset title */ + subset->public_.title = rc_client_subset_extract_title(load_state->game, fetch_game_data_response.title); + if (!subset->public_.title) { + const char* core_subset_title = rc_client_subset_extract_title(load_state->game, load_state->game->public_.title); + if (core_subset_title) { + rc_client_subset_info_t* scan = load_state->game->subsets; + for (; scan; scan = scan->next) { + if (scan->public_.title == load_state->game->public_.title) { + scan->public_.title = core_subset_title; + break; + } + } + } + + subset->public_.title = rc_buf_strcpy(&load_state->game->buffer, fetch_game_data_response.title); + } + + /* append to subset list */ + scan = load_state->game->subsets; + while (scan->next) + scan = scan->next; + scan->next = subset; + } + + outstanding_requests = rc_client_end_load_state(load_state); + if (outstanding_requests < 0) { + /* previous load state was aborted, load_state was free'd */ + } + else { + if (outstanding_requests == 0) + rc_client_activate_game(load_state); + } + } + + rc_api_destroy_fetch_game_data_response(&fetch_game_data_response); +} + +static void rc_client_begin_fetch_game_data(rc_client_load_state_t* load_state) +{ + rc_api_fetch_game_data_request_t fetch_game_data_request; + rc_client_t* client = load_state->client; + rc_api_request_t request; + int result; + + if (load_state->hash->game_id == 0) { + char hash[33]; + + if (rc_hash_iterate(hash, &load_state->hash_iterator)) { + /* found another hash to try */ + load_state->hash_console_id = load_state->hash_iterator.consoles[load_state->hash_iterator.index - 1]; + rc_client_load_game(load_state, hash, NULL); + return; + } + + if (load_state->game->media_hash && + load_state->game->media_hash->game_hash && + load_state->game->media_hash->game_hash->next) { + /* multiple hashes were tried, create a CSV */ + struct rc_client_game_hash_t* game_hash = load_state->game->media_hash->game_hash; + int count = 1; + char* ptr; + size_t size; + + size = strlen(game_hash->hash) + 1; + while (game_hash->next) { + game_hash = game_hash->next; + size += strlen(game_hash->hash) + 1; + count++; + } + + ptr = (char*)rc_buf_alloc(&load_state->game->buffer, size); + ptr += size - 1; + *ptr = '\0'; + game_hash = load_state->game->media_hash->game_hash; + do { + const size_t hash_len = strlen(game_hash->hash); + ptr -= hash_len; + memcpy(ptr, game_hash->hash, hash_len); + + game_hash = game_hash->next; + if (!game_hash) + break; + + ptr--; + *ptr = ','; + } while (1); + + load_state->game->public_.hash = ptr; + load_state->game->public_.console_id = RC_CONSOLE_UNKNOWN; + } else { + /* only a single hash was tried, capture it */ + load_state->game->public_.console_id = load_state->hash_console_id; + load_state->game->public_.hash = load_state->hash->hash; + } + + load_state->game->public_.title = "Unknown Game"; + load_state->game->public_.badge_name = ""; + client->game = load_state->game; + load_state->game = NULL; + + rc_client_load_error(load_state, RC_NO_GAME_LOADED, "Unknown game"); + return; + } + + if (load_state->hash->hash[0] != '[') { + load_state->game->public_.id = load_state->hash->game_id; + load_state->game->public_.hash = load_state->hash->hash; + } + + /* done with the hashing code, release the global pointer */ + g_hash_client = NULL; + + rc_mutex_lock(&client->state.mutex); + result = client->state.user; + if (result == RC_CLIENT_USER_STATE_LOGIN_REQUESTED) + load_state->progress = RC_CLIENT_LOAD_STATE_AWAIT_LOGIN; + rc_mutex_unlock(&client->state.mutex); + + switch (result) { + case RC_CLIENT_USER_STATE_LOGGED_IN: + break; + + case RC_CLIENT_USER_STATE_LOGIN_REQUESTED: + /* do nothing, this function will be called again after login completes */ + return; + + default: + rc_client_load_error(load_state, RC_LOGIN_REQUIRED, rc_error_str(RC_LOGIN_REQUIRED)); + return; + } + + memset(&fetch_game_data_request, 0, sizeof(fetch_game_data_request)); + fetch_game_data_request.username = client->user.username; + fetch_game_data_request.api_token = client->user.token; + fetch_game_data_request.game_id = load_state->hash->game_id; + + result = rc_api_init_fetch_game_data_request(&request, &fetch_game_data_request); + if (result != RC_OK) { + rc_client_load_error(load_state, result, rc_error_str(result)); + return; + } + + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA, 1); + + RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Fetching data for game %u", fetch_game_data_request.game_id); + client->callbacks.server_call(&request, rc_client_fetch_game_data_callback, load_state, client); + rc_api_destroy_request(&request); +} + +static void rc_client_identify_game_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data; + rc_client_t* client = load_state->client; + rc_api_resolve_hash_response_t resolve_hash_response; + int outstanding_requests; + const char* error_message; + int result; + + if (rc_client_async_handle_aborted(client, &load_state->async_handle)) { + rc_client_load_aborted(load_state); + RC_CLIENT_LOG_VERBOSE(client, "Load aborted during game identification"); + return; + } + + result = rc_api_process_resolve_hash_server_response(&resolve_hash_response, server_response); + error_message = rc_client_server_error_message(&result, server_response->http_status_code, &resolve_hash_response.response); + + if (error_message) { + rc_client_end_load_state(load_state); + rc_client_load_error(load_state, result, error_message); + } + else { + /* hash exists outside the load state - always update it */ + load_state->hash->game_id = resolve_hash_response.game_id; + RC_CLIENT_LOG_INFO_FORMATTED(client, "Identified game: %u (%s)", load_state->hash->game_id, load_state->hash->hash); + + /* have to call end_load_state after updating hash in case the load_state gets free'd */ + outstanding_requests = rc_client_end_load_state(load_state); + if (outstanding_requests < 0) { + /* previous load state was aborted, load_state was free'd */ + } + else { + rc_client_begin_fetch_game_data(load_state); + } + } + + rc_api_destroy_resolve_hash_response(&resolve_hash_response); +} + +rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char* hash) +{ + rc_client_game_hash_t* game_hash; + + rc_mutex_lock(&client->state.mutex); + game_hash = client->hashes; + while (game_hash) { + if (strcasecmp(game_hash->hash, hash) == 0) + break; + + game_hash = game_hash->next; + } + + if (!game_hash) { + game_hash = rc_buf_alloc(&client->state.buffer, sizeof(rc_client_game_hash_t)); + memset(game_hash, 0, sizeof(*game_hash)); + snprintf(game_hash->hash, sizeof(game_hash->hash), "%s", hash); + game_hash->game_id = RC_CLIENT_UNKNOWN_GAME_ID; + game_hash->next = client->hashes; + client->hashes = game_hash; + } + rc_mutex_unlock(&client->state.mutex); + + return game_hash; +} + +static rc_client_async_handle_t* rc_client_load_game(rc_client_load_state_t* load_state, + const char* hash, const char* file_path) +{ + rc_client_t* client = load_state->client; + rc_client_game_hash_t* old_hash; + + if (client->state.load == NULL) { + rc_client_unload_game(client); + client->state.load = load_state; + + if (load_state->game == NULL) { + load_state->game = (rc_client_game_info_t*)calloc(1, sizeof(*load_state->game)); + if (!load_state->game) { + if (load_state->callback) + load_state->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, load_state->callback_userdata); + + rc_client_free_load_state(load_state); + return NULL; + } + + rc_buf_init(&load_state->game->buffer); + rc_runtime_init(&load_state->game->runtime); + } + } + else if (client->state.load != load_state) { + /* previous load was aborted */ + if (load_state->callback) + load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata); + + rc_client_free_load_state(load_state); + return NULL; + } + + old_hash = load_state->hash; + load_state->hash = rc_client_find_game_hash(client, hash); + + if (file_path) { + rc_client_media_hash_t* media_hash = + (rc_client_media_hash_t*)rc_buf_alloc(&load_state->game->buffer, sizeof(*media_hash)); + media_hash->game_hash = load_state->hash; + media_hash->path_djb2 = rc_djb2(file_path); + media_hash->next = load_state->game->media_hash; + load_state->game->media_hash = media_hash; + } + else if (load_state->game->media_hash && load_state->game->media_hash->game_hash == old_hash) { + load_state->game->media_hash->game_hash = load_state->hash; + } + + if (load_state->hash->game_id == RC_CLIENT_UNKNOWN_GAME_ID) { + rc_api_resolve_hash_request_t resolve_hash_request; + rc_api_request_t request; + int result; + + memset(&resolve_hash_request, 0, sizeof(resolve_hash_request)); + resolve_hash_request.game_hash = hash; + + result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request); + if (result != RC_OK) { + rc_client_load_error(load_state, result, rc_error_str(result)); + return NULL; + } + + rc_client_begin_load_state(load_state, RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME, 1); + + client->callbacks.server_call(&request, rc_client_identify_game_callback, load_state, client); + + rc_api_destroy_request(&request); + } + else { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Identified game: %u (%s)", load_state->hash->game_id, load_state->hash->hash); + + rc_client_begin_fetch_game_data(load_state); + } + + return &load_state->async_handle; +} + +rc_hash_iterator_t* rc_client_get_load_state_hash_iterator(rc_client_t* client) +{ + if (client && client->state.load) + return &client->state.load->hash_iterator; + + return NULL; +} + +rc_client_async_handle_t* rc_client_begin_load_game(rc_client_t* client, const char* hash, rc_client_callback_t callback, void* callback_userdata) +{ + rc_client_load_state_t* load_state; + + if (!client) { + callback(RC_INVALID_STATE, "client is required", client, callback_userdata); + return NULL; + } + + if (!hash || !hash[0]) { + callback(RC_INVALID_STATE, "hash is required", client, callback_userdata); + return NULL; + } + + load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state)); + if (!load_state) { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return NULL; + } + + load_state->client = client; + load_state->callback = callback; + load_state->callback_userdata = callback_userdata; + + return rc_client_load_game(load_state, hash, NULL); +} + +rc_client_async_handle_t* rc_client_begin_identify_and_load_game(rc_client_t* client, + uint32_t console_id, const char* file_path, + const uint8_t* data, size_t data_size, + rc_client_callback_t callback, void* callback_userdata) +{ + rc_client_load_state_t* load_state; + char hash[33]; + + if (!client) { + callback(RC_INVALID_STATE, "client is required", client, callback_userdata); + return NULL; + } + + if (data) { + if (file_path) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Identifying game: %zu bytes at %p (%s)", data_size, data, file_path); + } + else { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Identifying game: %zu bytes at %p", data_size, data); + } + } + else if (file_path) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Identifying game: %s", file_path); + } + else { + callback(RC_INVALID_STATE, "either data or file_path is required", client, callback_userdata); + return NULL; + } + + if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) { + g_hash_client = client; + rc_hash_init_error_message_callback(rc_client_log_hash_message); + rc_hash_init_verbose_message_callback(rc_client_log_hash_message); + } + + if (!file_path) + file_path = "?"; + + load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state)); + if (!load_state) { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return NULL; + } + load_state->client = client; + load_state->callback = callback; + load_state->callback_userdata = callback_userdata; + + if (console_id == RC_CONSOLE_UNKNOWN) { + rc_hash_initialize_iterator(&load_state->hash_iterator, file_path, data, data_size); + + if (!rc_hash_iterate(hash, &load_state->hash_iterator)) { + rc_client_load_error(load_state, RC_INVALID_STATE, "hash generation failed"); + return NULL; + } + + load_state->hash_console_id = load_state->hash_iterator.consoles[load_state->hash_iterator.index - 1]; + } + else { + /* ASSERT: hash_iterator->index and hash_iterator->consoles[0] will be 0 from calloc */ + load_state->hash_console_id = console_id; + + if (data != NULL) { + if (!rc_hash_generate_from_buffer(hash, console_id, data, data_size)) { + rc_client_load_error(load_state, RC_INVALID_STATE, "hash generation failed"); + return NULL; + } + } + else { + if (!rc_hash_generate_from_file(hash, console_id, file_path)) { + rc_client_load_error(load_state, RC_INVALID_STATE, "hash generation failed"); + return NULL; + } + } + } + + return rc_client_load_game(load_state, hash, file_path); +} + +static void rc_client_game_mark_ui_to_be_hidden(rc_client_t* client, rc_client_game_info_t* game) +{ + rc_client_achievement_info_t* achievement; + rc_client_achievement_info_t* achievement_stop; + rc_client_leaderboard_info_t* leaderboard; + rc_client_leaderboard_info_t* leaderboard_stop; + rc_client_subset_info_t* subset; + + for (subset = game->subsets; subset; subset = subset->next) { + achievement = subset->achievements; + achievement_stop = achievement + subset->public_.num_achievements; + for (; achievement < achievement_stop; ++achievement) { + if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE && + achievement->trigger && achievement->trigger->state == RC_TRIGGER_STATE_PRIMED) { + achievement->pending_events |= RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE; + subset->pending_events |= RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT; + } + } + + leaderboard = subset->leaderboards; + leaderboard_stop = leaderboard + subset->public_.num_leaderboards; + for (; leaderboard < leaderboard_stop; ++leaderboard) { + if (leaderboard->public_.state == RC_CLIENT_LEADERBOARD_STATE_TRACKING) + rc_client_release_leaderboard_tracker(game, leaderboard); + } + } + + rc_client_hide_progress_tracker(client, game); +} + +void rc_client_unload_game(rc_client_t* client) +{ + rc_client_game_info_t* game; + rc_client_scheduled_callback_data_t** last; + rc_client_scheduled_callback_data_t* next; + + if (!client) + return; + + rc_mutex_lock(&client->state.mutex); + + game = client->game; + client->game = NULL; + client->state.load = NULL; + + if (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_LOCKED) + client->state.spectator_mode = RC_CLIENT_SPECTATOR_MODE_ON; + + if (game != NULL) + rc_client_game_mark_ui_to_be_hidden(client, game); + + last = &client->state.scheduled_callbacks; + do { + next = *last; + if (!next) + break; + + /* remove rich presence ping scheduled event for game */ + if (next->callback == rc_client_ping && game && next->related_id == game->public_.id) { + *last = next->next; + continue; + } + + last = &next->next; + } while (1); + + rc_mutex_unlock(&client->state.mutex); + + if (game != NULL) { + rc_client_raise_pending_events(client, game); + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Unloading game %u", game->public_.id); + rc_client_free_game(game); + } +} + +static void rc_client_change_media(rc_client_t* client, const rc_client_game_hash_t* game_hash, rc_client_callback_t callback, void* callback_userdata) +{ + if (game_hash->game_id == client->game->public_.id) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to valid media for game %u: %s", game_hash->game_id, game_hash->hash); + } + else if (game_hash->game_id == RC_CLIENT_UNKNOWN_GAME_ID) { + RC_CLIENT_LOG_INFO(client, "Switching to unknown media"); + } + else if (game_hash->game_id == 0) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to unrecognized media: %s", game_hash->hash); + } + else { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Switching to known media for game %u: %s", game_hash->game_id, game_hash->hash); + } + + client->game->public_.hash = game_hash->hash; + callback(RC_OK, NULL, client, callback_userdata); +} + +static void rc_client_identify_changed_media_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_load_state_t* load_state = (rc_client_load_state_t*)callback_data; + rc_client_t* client = load_state->client; + rc_api_resolve_hash_response_t resolve_hash_response; + + int result = rc_api_process_resolve_hash_server_response(&resolve_hash_response, server_response); + const char* error_message = rc_client_server_error_message(&result, server_response->http_status_code, &resolve_hash_response.response); + + if (rc_client_async_handle_aborted(client, &load_state->async_handle)) { + RC_CLIENT_LOG_VERBOSE(client, "Media change aborted"); + /* if lookup succeeded, still capture the new hash */ + if (result == RC_OK) + load_state->hash->game_id = resolve_hash_response.game_id; + } + else if (client->game != load_state->game) { + /* loaded game changed. return success regardless of result */ + load_state->callback(RC_ABORTED, "The requested game is no longer active", client, load_state->callback_userdata); + } + else if (error_message) { + load_state->callback(result, error_message, client, load_state->callback_userdata); + } + else { + load_state->hash->game_id = resolve_hash_response.game_id; + + if (resolve_hash_response.game_id == 0 && client->state.hardcore) { + RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabling hardcore for unidentified media: %s", load_state->hash->hash); + rc_client_set_hardcore_enabled(client, 0); + client->game->public_.hash = load_state->hash->hash; /* do still update the loaded hash */ + load_state->callback(RC_HARDCORE_DISABLED, "Hardcore disabled. Unidentified media inserted.", client, load_state->callback_userdata); + } + else { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Identified game: %u (%s)", load_state->hash->game_id, load_state->hash->hash); + rc_client_change_media(client, load_state->hash, load_state->callback, load_state->callback_userdata); + } + } + + free(load_state); + rc_api_destroy_resolve_hash_response(&resolve_hash_response); +} + +rc_client_async_handle_t* rc_client_begin_change_media(rc_client_t* client, const char* file_path, + const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata) +{ + rc_client_game_hash_t* game_hash = NULL; + rc_client_media_hash_t* media_hash; + rc_client_game_info_t* game; + rc_client_pending_media_t* pending_media = NULL; + uint32_t path_djb2; + + if (!client) { + callback(RC_INVALID_STATE, "client is required", client, callback_userdata); + return NULL; + } + + if (!data && !file_path) { + callback(RC_INVALID_STATE, "either data or file_path is required", client, callback_userdata); + return NULL; + } + + rc_mutex_lock(&client->state.mutex); + if (client->state.load) { + game = client->state.load->game; + if (game->public_.console_id == 0) { + /* still waiting for game data */ + pending_media = client->state.load->pending_media; + if (pending_media) { + if (pending_media->data) + free(pending_media->data); + free((void*)pending_media->file_path); + free(pending_media); + } + + pending_media = (rc_client_pending_media_t*)calloc(1, sizeof(*pending_media)); + if (!pending_media) { + rc_mutex_unlock(&client->state.mutex); + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return NULL; + } + + pending_media->file_path = strdup(file_path); + pending_media->callback = callback; + pending_media->callback_userdata = callback_userdata; + if (data && data_size) { + pending_media->data_size = data_size; + pending_media->data = (uint8_t*)malloc(data_size); + if (!pending_media->data) { + rc_mutex_unlock(&client->state.mutex); + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return NULL; + } + memcpy(pending_media->data, data, data_size); + } + + client->state.load->pending_media = pending_media; + } + } + else { + game = client->game; + } + rc_mutex_unlock(&client->state.mutex); + + if (!game) { + callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata); + return NULL; + } + + /* still waiting for game data */ + if (pending_media) + return NULL; + + /* check to see if we've already hashed this file */ + path_djb2 = rc_djb2(file_path); + rc_mutex_lock(&client->state.mutex); + for (media_hash = game->media_hash; media_hash; media_hash = media_hash->next) { + if (media_hash->path_djb2 == path_djb2) { + game_hash = media_hash->game_hash; + break; + } + } + rc_mutex_unlock(&client->state.mutex); + + if (!game_hash) { + char hash[33]; + int result; + + if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) { + g_hash_client = client; + rc_hash_init_error_message_callback(rc_client_log_hash_message); + rc_hash_init_verbose_message_callback(rc_client_log_hash_message); + } + + if (data != NULL) + result = rc_hash_generate_from_buffer(hash, game->public_.console_id, data, data_size); + else + result = rc_hash_generate_from_file(hash, game->public_.console_id, file_path); + + g_hash_client = NULL; + + if (!result) { + /* when changing discs, if the disc is not supported by the system, allow it. this is + * primarily for games that support user-provided audio CDs, but does allow using discs + * from other systems for games that leverage user-provided discs. */ + strcpy_s(hash, sizeof(hash), "[NO HASH]"); + } + + game_hash = rc_client_find_game_hash(client, hash); + + media_hash = (rc_client_media_hash_t*)rc_buf_alloc(&game->buffer, sizeof(*media_hash)); + media_hash->game_hash = game_hash; + media_hash->path_djb2 = path_djb2; + + rc_mutex_lock(&client->state.mutex); + media_hash->next = game->media_hash; + game->media_hash = media_hash; + rc_mutex_unlock(&client->state.mutex); + + if (!result) { + rc_client_change_media(client, game_hash, callback, callback_userdata); + return NULL; + } + } + + if (game_hash->game_id != RC_CLIENT_UNKNOWN_GAME_ID) { + rc_client_change_media(client, game_hash, callback, callback_userdata); + return NULL; + } + else { + /* call the server to make sure the hash is valid for the loaded game */ + rc_client_load_state_t* callback_data; + rc_api_resolve_hash_request_t resolve_hash_request; + rc_api_request_t request; + int result; + + memset(&resolve_hash_request, 0, sizeof(resolve_hash_request)); + resolve_hash_request.game_hash = game_hash->hash; + + result = rc_api_init_resolve_hash_request(&request, &resolve_hash_request); + if (result != RC_OK) { + callback(result, rc_error_str(result), client, callback_userdata); + return NULL; + } + + callback_data = (rc_client_load_state_t*)calloc(1, sizeof(rc_client_load_state_t)); + if (!callback_data) { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return NULL; + } + + callback_data->callback = callback; + callback_data->callback_userdata = callback_userdata; + callback_data->client = client; + callback_data->hash = game_hash; + callback_data->game = game; + + client->callbacks.server_call(&request, rc_client_identify_changed_media_callback, callback_data, client); + + rc_api_destroy_request(&request); + + return &callback_data->async_handle; + } +} + +const rc_client_game_t* rc_client_get_game_info(const rc_client_t* client) +{ + return (client && client->game) ? &client->game->public_ : NULL; +} + +int rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size) +{ + if (!game) + return RC_INVALID_STATE; + + return rc_client_get_image_url(buffer, buffer_size, RC_IMAGE_TYPE_GAME, game->badge_name); +} + +/* ===== Subsets ===== */ + +void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata) +{ + char buffer[32]; + rc_client_load_state_t* load_state; + + if (!client) { + callback(RC_INVALID_STATE, "client is required", client, callback_userdata); + return; + } + + if (!client->game) { + callback(RC_NO_GAME_LOADED, rc_error_str(RC_NO_GAME_LOADED), client, callback_userdata); + return; + } + + snprintf(buffer, sizeof(buffer), "[SUBSET%u]", subset_id); + + load_state = (rc_client_load_state_t*)calloc(1, sizeof(*load_state)); + if (!load_state) { + callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata); + return; + } + + load_state->client = client; + load_state->callback = callback; + load_state->callback_userdata = callback_userdata; + load_state->game = client->game; + load_state->hash = rc_client_find_game_hash(client, buffer); + load_state->hash->game_id = subset_id; + client->state.load = load_state; + + rc_client_begin_fetch_game_data(load_state); +} + +const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id) +{ + rc_client_subset_info_t* subset; + + if (!client || !client->game) + return NULL; + + for (subset = client->game->subsets; subset; subset = subset->next) { + if (subset->public_.id == subset_id) + return &subset->public_; + } + + return NULL; +} + +/* ===== Achievements ===== */ + +static void rc_client_update_achievement_display_information(rc_client_t* client, rc_client_achievement_info_t* achievement, time_t recent_unlock_time) +{ + uint8_t new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN; + uint32_t new_measured_value = 0; + + if (achievement->public_.bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED) + return; + + achievement->public_.measured_progress[0] = '\0'; + + if (achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) { + /* achievement unlocked */ + new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED; + } + else { + /* active achievement */ + new_bucket = (achievement->public_.category == RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL) ? + RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL : RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED; + + if (achievement->trigger) { + if (achievement->trigger->measured_target) { + if (achievement->trigger->measured_value == RC_MEASURED_UNKNOWN) { + /* value hasn't been initialized yet, leave progress string empty */ + } + else if (achievement->trigger->measured_value == 0) { + /* value is 0, leave progress string empty. update progress to 0.0 */ + achievement->public_.measured_percent = 0.0; + } + else { + /* clamp measured value at target (can't get more than 100%) */ + new_measured_value = (achievement->trigger->measured_value > achievement->trigger->measured_target) ? + achievement->trigger->measured_target : achievement->trigger->measured_value; + + achievement->public_.measured_percent = ((float)new_measured_value * 100) / (float)achievement->trigger->measured_target; + + if (!achievement->trigger->measured_as_percent) { + snprintf(achievement->public_.measured_progress, sizeof(achievement->public_.measured_progress), + "%u/%u", new_measured_value, achievement->trigger->measured_target); + } + else if (achievement->public_.measured_percent >= 1.0) { + snprintf(achievement->public_.measured_progress, sizeof(achievement->public_.measured_progress), + "%u%%", (uint32_t)achievement->public_.measured_percent); + } + } + } + + if (achievement->trigger->state == RC_TRIGGER_STATE_PRIMED) + new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE; + else if (achievement->public_.measured_percent >= 80.0) + new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE; + } + } + + if (new_bucket == RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED && achievement->public_.unlock_time >= recent_unlock_time) + new_bucket = RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED; + + achievement->public_.bucket = new_bucket; +} + +static const char* rc_client_get_achievement_bucket_label(uint8_t bucket_type) +{ + switch (bucket_type) { + case RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED: return "Locked"; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED: return "Unlocked"; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED: return "Unsupported"; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL: return "Unofficial"; + case RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED: return "Recently Unlocked"; + case RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE: return "Active Challenges"; + case RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE: return "Almost There"; + default: return "Unknown"; + } +} + +static const char* rc_client_get_subset_achievement_bucket_label(uint8_t bucket_type, rc_client_game_info_t* game, rc_client_subset_info_t* subset) +{ + const char** ptr; + const char* label; + char* new_label; + size_t new_label_len; + + switch (bucket_type) { + case RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED: ptr = &subset->locked_label; break; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED: ptr = &subset->unlocked_label; break; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED: ptr = &subset->unsupported_label; break; + case RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL: ptr = &subset->unofficial_label; break; + default: return rc_client_get_achievement_bucket_label(bucket_type); + } + + if (*ptr) + return *ptr; + + label = rc_client_get_achievement_bucket_label(bucket_type); + new_label_len = strlen(subset->public_.title) + strlen(label) + 4; + new_label = (char*)rc_buf_alloc(&game->buffer, new_label_len); + snprintf(new_label, new_label_len, "%s - %s", subset->public_.title, label); + + *ptr = new_label; + return new_label; +} + +static int rc_client_compare_achievement_unlock_times(const void* a, const void* b) +{ + const rc_client_achievement_t* unlock_a = *(const rc_client_achievement_t**)a; + const rc_client_achievement_t* unlock_b = *(const rc_client_achievement_t**)b; + return (int)(unlock_b->unlock_time - unlock_a->unlock_time); +} + +static uint8_t rc_client_map_bucket(uint8_t bucket, int grouping) +{ + if (grouping == RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE) { + switch (bucket) { + case RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED: + return RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED; + + case RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE: + case RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE: + return RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED; + + default: + return bucket; + } + } + + return bucket; +} + +rc_client_achievement_list_t* rc_client_create_achievement_list(rc_client_t* client, int category, int grouping) +{ + rc_client_achievement_info_t* achievement; + rc_client_achievement_info_t* stop; + rc_client_achievement_t** bucket_achievements; + rc_client_achievement_t** achievement_ptr; + rc_client_achievement_bucket_t* bucket_ptr; + rc_client_achievement_list_t* list; + rc_client_subset_info_t* subset; + const uint32_t list_size = RC_ALIGN(sizeof(*list)); + uint32_t bucket_counts[16]; + uint32_t num_buckets; + uint32_t num_achievements; + size_t buckets_size; + uint8_t bucket_type; + uint32_t num_subsets = 0; + uint32_t i, j; + const uint8_t shared_bucket_order[] = { + RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE, + RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED, + RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE + }; + const uint8_t subset_bucket_order[] = { + RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED, + RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL, + RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED, + RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED + }; + const time_t recent_unlock_time = time(NULL) - RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS; + + if (!client || !client->game) + return calloc(1, sizeof(rc_client_achievement_list_t)); + + memset(&bucket_counts, 0, sizeof(bucket_counts)); + + rc_mutex_lock(&client->state.mutex); + + subset = client->game->subsets; + for (; subset; subset = subset->next) { + if (!subset->active) + continue; + + num_subsets++; + achievement = subset->achievements; + stop = achievement + subset->public_.num_achievements; + for (; achievement < stop; ++achievement) { + if (achievement->public_.category & category) { + rc_client_update_achievement_display_information(client, achievement, recent_unlock_time); + bucket_counts[rc_client_map_bucket(achievement->public_.bucket, grouping)]++; + } + } + } + + num_buckets = 0; + num_achievements = 0; + for (i = 0; i < sizeof(bucket_counts) / sizeof(bucket_counts[0]); ++i) { + if (bucket_counts[i]) { + int needs_split = 0; + + num_achievements += bucket_counts[i]; + + if (num_subsets > 1) { + for (j = 0; j < sizeof(subset_bucket_order) / sizeof(subset_bucket_order[0]); ++j) { + if (subset_bucket_order[j] == i) { + needs_split = 1; + break; + } + } + } + + if (!needs_split) { + ++num_buckets; + continue; + } + + subset = client->game->subsets; + for (; subset; subset = subset->next) { + if (!subset->active) + continue; + + achievement = subset->achievements; + stop = achievement + subset->public_.num_achievements; + for (; achievement < stop; ++achievement) { + if (achievement->public_.category & category) { + if (rc_client_map_bucket(achievement->public_.bucket, grouping) == i) { + ++num_buckets; + break; + } + } + } + } + } + } + + buckets_size = RC_ALIGN(num_buckets * sizeof(rc_client_achievement_bucket_t)); + + list = (rc_client_achievement_list_t*)malloc(list_size + buckets_size + num_achievements * sizeof(rc_client_achievement_t*)); + bucket_ptr = list->buckets = (rc_client_achievement_bucket_t*)((uint8_t*)list + list_size); + achievement_ptr = (rc_client_achievement_t**)((uint8_t*)bucket_ptr + buckets_size); + + if (grouping == RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS) { + for (i = 0; i < sizeof(shared_bucket_order) / sizeof(shared_bucket_order[0]); ++i) { + bucket_type = shared_bucket_order[i]; + if (!bucket_counts[bucket_type]) + continue; + + bucket_achievements = achievement_ptr; + for (subset = client->game->subsets; subset; subset = subset->next) { + if (!subset->active) + continue; + + achievement = subset->achievements; + stop = achievement + subset->public_.num_achievements; + for (; achievement < stop; ++achievement) { + if (achievement->public_.category & category && + rc_client_map_bucket(achievement->public_.bucket, grouping) == bucket_type) { + *achievement_ptr++ = &achievement->public_; + } + } + } + + if (achievement_ptr > bucket_achievements) { + bucket_ptr->achievements = bucket_achievements; + bucket_ptr->num_achievements = (uint32_t)(achievement_ptr - bucket_achievements); + bucket_ptr->subset_id = 0; + bucket_ptr->label = rc_client_get_achievement_bucket_label(bucket_type); + bucket_ptr->bucket_type = bucket_type; + + if (bucket_type == RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED) + qsort(bucket_ptr->achievements, bucket_ptr->num_achievements, sizeof(rc_client_achievement_t*), rc_client_compare_achievement_unlock_times); + + ++bucket_ptr; + } + } + } + + for (subset = client->game->subsets; subset; subset = subset->next) { + if (!subset->active) + continue; + + for (i = 0; i < sizeof(subset_bucket_order) / sizeof(subset_bucket_order[0]); ++i) { + bucket_type = subset_bucket_order[i]; + if (!bucket_counts[bucket_type]) + continue; + + bucket_achievements = achievement_ptr; + + achievement = subset->achievements; + stop = achievement + subset->public_.num_achievements; + for (; achievement < stop; ++achievement) { + if (achievement->public_.category & category && + rc_client_map_bucket(achievement->public_.bucket, grouping) == bucket_type) { + *achievement_ptr++ = &achievement->public_; + } + } + + if (achievement_ptr > bucket_achievements) { + bucket_ptr->achievements = bucket_achievements; + bucket_ptr->num_achievements = (uint32_t)(achievement_ptr - bucket_achievements); + bucket_ptr->subset_id = (num_subsets > 1) ? subset->public_.id : 0; + bucket_ptr->bucket_type = bucket_type; + + if (num_subsets > 1) + bucket_ptr->label = rc_client_get_subset_achievement_bucket_label(bucket_type, client->game, subset); + else + bucket_ptr->label = rc_client_get_achievement_bucket_label(bucket_type); + + ++bucket_ptr; + } + } + } + + rc_mutex_unlock(&client->state.mutex); + + list->num_buckets = (uint32_t)(bucket_ptr - list->buckets); + return list; +} + +void rc_client_destroy_achievement_list(rc_client_achievement_list_t* list) +{ + if (list) + free(list); +} + +static const rc_client_achievement_t* rc_client_subset_get_achievement_info( + rc_client_t* client, rc_client_subset_info_t* subset, uint32_t id) +{ + rc_client_achievement_info_t* achievement = subset->achievements; + rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements; + + for (; achievement < stop; ++achievement) { + if (achievement->public_.id == id) { + const time_t recent_unlock_time = time(NULL) - RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS; + rc_mutex_lock((rc_mutex_t*)(&client->state.mutex)); + rc_client_update_achievement_display_information(client, achievement, recent_unlock_time); + rc_mutex_unlock((rc_mutex_t*)(&client->state.mutex)); + return &achievement->public_; + } + } + + return NULL; +} + +const rc_client_achievement_t* rc_client_get_achievement_info(rc_client_t* client, uint32_t id) +{ + rc_client_subset_info_t* subset; + + if (!client || !client->game) + return NULL; + + for (subset = client->game->subsets; subset; subset = subset->next) { + const rc_client_achievement_t* achievement = rc_client_subset_get_achievement_info(client, subset, id); + if (achievement != NULL) + return achievement; + } + + return NULL; +} + +int rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size) +{ + const int image_type = (state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED) ? + RC_IMAGE_TYPE_ACHIEVEMENT : RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED; + + if (!achievement || !achievement->badge_name[0]) + return rc_client_get_image_url(buffer, buffer_size, image_type, "00000"); + + return rc_client_get_image_url(buffer, buffer_size, image_type, achievement->badge_name); +} + +typedef struct rc_client_award_achievement_callback_data_t +{ + uint32_t id; + uint32_t retry_count; + uint8_t hardcore; + const char* game_hash; + time_t unlock_time; + rc_client_t* client; + rc_client_scheduled_callback_data_t* scheduled_callback_data; +} rc_client_award_achievement_callback_data_t; + +static void rc_client_award_achievement_server_call(rc_client_award_achievement_callback_data_t* ach_data); + +static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now) +{ + rc_client_award_achievement_callback_data_t* ach_data = + (rc_client_award_achievement_callback_data_t*)callback_data->data; + + rc_client_award_achievement_server_call(ach_data); +} + +static void rc_client_award_achievement_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_award_achievement_callback_data_t* ach_data = + (rc_client_award_achievement_callback_data_t*)callback_data; + rc_api_award_achievement_response_t award_achievement_response; + + int result = rc_api_process_award_achievement_server_response(&award_achievement_response, server_response); + const char* error_message = rc_client_server_error_message(&result, server_response->http_status_code, &award_achievement_response.response); + + if (error_message) { + if (award_achievement_response.response.error_message && !rc_client_should_retry(server_response)) { + /* actual error from server */ + RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Error awarding achievement %u: %s", ach_data->id, error_message); + rc_client_raise_server_error_event(ach_data->client, "award_achievement", award_achievement_response.response.error_message); + } + else if (ach_data->retry_count++ == 0) { + /* first retry is immediate */ + RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Error awarding achievement %u: %s, retrying immediately", ach_data->id, error_message); + rc_client_award_achievement_server_call(ach_data); + return; + } + else { + /* double wait time between each attempt until we hit a maximum delay of two minutes */ + /* 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s -> 120s -> 120s -> 120s ...*/ + const uint32_t delay = (ach_data->retry_count > 7) ? 120 : (1 << (ach_data->retry_count - 1)); + RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Error awarding achievement %u: %s, retrying in %u seconds", ach_data->id, error_message, delay); + + if (!ach_data->scheduled_callback_data) { + ach_data->scheduled_callback_data = (rc_client_scheduled_callback_data_t*)calloc(1, sizeof(*ach_data->scheduled_callback_data)); + if (!ach_data->scheduled_callback_data) { + RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Failed to allocate scheduled callback data for reattempt to unlock achievement %u", ach_data->id); + rc_client_raise_server_error_event(ach_data->client, "award_achievement", rc_error_str(RC_OUT_OF_MEMORY)); + return; + } + ach_data->scheduled_callback_data->callback = rc_client_award_achievement_retry; + ach_data->scheduled_callback_data->data = ach_data; + ach_data->scheduled_callback_data->related_id = ach_data->id; + } + + ach_data->scheduled_callback_data->when = clock() + delay * CLOCKS_PER_SEC; + + rc_client_schedule_callback(ach_data->client, ach_data->scheduled_callback_data); + return; + } + } + else { + ach_data->client->user.score = award_achievement_response.new_player_score; + ach_data->client->user.score_softcore = award_achievement_response.new_player_score_softcore; + + if (award_achievement_response.awarded_achievement_id != ach_data->id) { + RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Awarded achievement %u instead of %u", award_achievement_response.awarded_achievement_id, error_message); + } + else { + if (award_achievement_response.response.error_message) { + /* previously unlocked achievements are returned as a success with an error message */ + RC_CLIENT_LOG_INFO_FORMATTED(ach_data->client, "Achievement %u: %s", ach_data->id, award_achievement_response.response.error_message); + } + else if (ach_data->retry_count) { + RC_CLIENT_LOG_INFO_FORMATTED(ach_data->client, "Achievement %u awarded after %u attempts, new score: %u", + ach_data->id, ach_data->retry_count + 1, + ach_data->hardcore ? award_achievement_response.new_player_score : award_achievement_response.new_player_score_softcore); + } + else { + RC_CLIENT_LOG_INFO_FORMATTED(ach_data->client, "Achievement %u awarded, new score: %u", + ach_data->id, + ach_data->hardcore ? award_achievement_response.new_player_score : award_achievement_response.new_player_score_softcore); + } + + if (award_achievement_response.achievements_remaining == 0) { + rc_client_subset_info_t* subset; + for (subset = ach_data->client->game->subsets; subset; subset = subset->next) { + if (subset->mastery == RC_CLIENT_MASTERY_STATE_NONE && + rc_client_subset_get_achievement_info(ach_data->client, subset, ach_data->id)) { + if (subset->public_.id == ach_data->client->game->public_.id) { + RC_CLIENT_LOG_INFO_FORMATTED(ach_data->client, "Game %u %s", ach_data->client->game->public_.id, + ach_data->client->state.hardcore ? "mastered" : "completed"); + subset->mastery = RC_CLIENT_MASTERY_STATE_PENDING; + } + else { + RC_CLIENT_LOG_INFO_FORMATTED(ach_data->client, "Subset %u %s", ach_data->client->game->public_.id, + ach_data->client->state.hardcore ? "mastered" : "completed"); + + /* TODO: subset mastery notification */ + subset->mastery = RC_CLIENT_MASTERY_STATE_SHOWN; + } + } + } + } + } + } + + if (ach_data->scheduled_callback_data) + free(ach_data->scheduled_callback_data); + free(ach_data); +} + +static void rc_client_award_achievement_server_call(rc_client_award_achievement_callback_data_t* ach_data) +{ + rc_api_award_achievement_request_t api_params; + rc_api_request_t request; + int result; + + memset(&api_params, 0, sizeof(api_params)); + api_params.username = ach_data->client->user.username; + api_params.api_token = ach_data->client->user.token; + api_params.achievement_id = ach_data->id; + api_params.hardcore = ach_data->hardcore; + api_params.game_hash = ach_data->game_hash; + + result = rc_api_init_award_achievement_request(&request, &api_params); + if (result != RC_OK) { + RC_CLIENT_LOG_ERR_FORMATTED(ach_data->client, "Error constructing unlock request for achievement %u: %s", ach_data->id, rc_error_str(result)); + free(ach_data); + return; + } + + ach_data->client->callbacks.server_call(&request, rc_client_award_achievement_callback, ach_data, ach_data->client); + + rc_api_destroy_request(&request); +} + +static void rc_client_award_achievement(rc_client_t* client, rc_client_achievement_info_t* achievement) +{ + rc_client_award_achievement_callback_data_t* callback_data; + + rc_mutex_lock(&client->state.mutex); + + if (client->state.hardcore) { + achievement->public_.unlock_time = achievement->unlock_time_hardcore = time(NULL); + if (achievement->unlock_time_softcore == 0) + achievement->unlock_time_softcore = achievement->unlock_time_hardcore; + + /* adjust score now - will get accurate score back from server */ + client->user.score += achievement->public_.points; + } + else { + achievement->public_.unlock_time = achievement->unlock_time_softcore = time(NULL); + + /* adjust score now - will get accurate score back from server */ + client->user.score_softcore += achievement->public_.points; + } + + achievement->public_.state = RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED; + achievement->public_.unlocked |= (client->state.hardcore) ? + RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH : RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE; + + rc_mutex_unlock(&client->state.mutex); + + /* can't unlock unofficial achievements on the server */ + if (achievement->public_.category != RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Unlocked unofficial achievement %u: %s", achievement->public_.id, achievement->public_.title); + return; + } + + /* don't actually unlock achievements when spectating */ + if (client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Spectated achievement %u: %s", achievement->public_.id, achievement->public_.title); + return; + } + + callback_data = (rc_client_award_achievement_callback_data_t*)calloc(1, sizeof(*callback_data)); + if (!callback_data) { + RC_CLIENT_LOG_ERR_FORMATTED(client, "Failed to allocate callback data for unlocking achievement %u", achievement->public_.id); + rc_client_raise_server_error_event(client, "award_achievement", rc_error_str(RC_OUT_OF_MEMORY)); + return; + } + 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 = achievement->public_.unlock_time; + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Awarding achievement %u: %s", achievement->public_.id, achievement->public_.title); + rc_client_award_achievement_server_call(callback_data); +} + +static void rc_client_subset_reset_achievements(rc_client_subset_info_t* subset) +{ + rc_client_achievement_info_t* achievement = subset->achievements; + rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements; + + for (; achievement < stop; ++achievement) { + rc_trigger_t* trigger = achievement->trigger; + if (!trigger || achievement->public_.state != RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE) + continue; + + if (trigger->state == RC_TRIGGER_STATE_PRIMED) { + achievement->pending_events |= RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE; + subset->pending_events |= RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT; + } + + rc_reset_trigger(trigger); + } +} + +static void rc_client_reset_achievements(rc_client_t* client) +{ + rc_client_subset_info_t* subset; + for (subset = client->game->subsets; subset; subset = subset->next) + rc_client_subset_reset_achievements(subset); +} + +/* ===== Leaderboards ===== */ + +static const rc_client_leaderboard_t* rc_client_subset_get_leaderboard_info(const rc_client_subset_info_t* subset, uint32_t id) +{ + rc_client_leaderboard_info_t* leaderboard = subset->leaderboards; + rc_client_leaderboard_info_t* stop = leaderboard + subset->public_.num_leaderboards; + + for (; leaderboard < stop; ++leaderboard) { + if (leaderboard->public_.id == id) + return &leaderboard->public_; + } + + return NULL; +} + +const rc_client_leaderboard_t* rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id) +{ + rc_client_subset_info_t* subset; + + if (!client || !client->game) + return NULL; + + for (subset = client->game->subsets; subset; subset = subset->next) { + const rc_client_leaderboard_t* leaderboard = rc_client_subset_get_leaderboard_info(subset, id); + if (leaderboard != NULL) + return leaderboard; + } + + return NULL; +} + +static const char* rc_client_get_leaderboard_bucket_label(uint8_t bucket_type) +{ + switch (bucket_type) { + case RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE: return "Inactive"; + case RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE: return "Active"; + case RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED: return "Unsupported"; + case RC_CLIENT_LEADERBOARD_BUCKET_ALL: return "All"; + default: return "Unknown"; + } +} + +static const char* rc_client_get_subset_leaderboard_bucket_label(uint8_t bucket_type, rc_client_game_info_t* game, rc_client_subset_info_t* subset) +{ + const char** ptr; + const char* label; + char* new_label; + size_t new_label_len; + + switch (bucket_type) { + case RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE: ptr = &subset->inactive_label; break; + case RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED: ptr = &subset->unsupported_label; break; + case RC_CLIENT_LEADERBOARD_BUCKET_ALL: ptr = &subset->all_label; break; + default: return rc_client_get_achievement_bucket_label(bucket_type); + } + + if (*ptr) + return *ptr; + + label = rc_client_get_leaderboard_bucket_label(bucket_type); + new_label_len = strlen(subset->public_.title) + strlen(label) + 4; + new_label = (char*)rc_buf_alloc(&game->buffer, new_label_len); + snprintf(new_label, new_label_len, "%s - %s", subset->public_.title, label); + + *ptr = new_label; + return new_label; +} + +static uint8_t rc_client_get_leaderboard_bucket(const rc_client_leaderboard_info_t* leaderboard, int grouping) +{ + switch (leaderboard->public_.state) { + case RC_CLIENT_LEADERBOARD_STATE_TRACKING: + return (grouping == RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE) ? + RC_CLIENT_LEADERBOARD_BUCKET_ALL : RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE; + + case RC_CLIENT_LEADERBOARD_STATE_DISABLED: + return RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED; + + default: + return (grouping == RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE) ? + RC_CLIENT_LEADERBOARD_BUCKET_ALL : RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE; + } +} + +rc_client_leaderboard_list_t* rc_client_create_leaderboard_list(rc_client_t* client, int grouping) +{ + rc_client_leaderboard_info_t* leaderboard; + rc_client_leaderboard_info_t* stop; + rc_client_leaderboard_t** bucket_leaderboards; + rc_client_leaderboard_t** leaderboard_ptr; + rc_client_leaderboard_bucket_t* bucket_ptr; + rc_client_leaderboard_list_t* list; + rc_client_subset_info_t* subset; + const uint32_t list_size = RC_ALIGN(sizeof(*list)); + uint32_t bucket_counts[8]; + uint32_t num_buckets; + uint32_t num_leaderboards; + size_t buckets_size; + uint8_t bucket_type; + uint32_t num_subsets = 0; + uint32_t i, j; + const uint8_t shared_bucket_order[] = { + RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE + }; + const uint8_t subset_bucket_order[] = { + RC_CLIENT_LEADERBOARD_BUCKET_ALL, + RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE, + RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED + }; + + if (!client || !client->game) + return calloc(1, sizeof(rc_client_leaderboard_list_t)); + + memset(&bucket_counts, 0, sizeof(bucket_counts)); + + rc_mutex_lock(&client->state.mutex); + + subset = client->game->subsets; + for (; subset; subset = subset->next) { + if (!subset->active) + continue; + + num_subsets++; + leaderboard = subset->leaderboards; + stop = leaderboard + subset->public_.num_leaderboards; + for (; leaderboard < stop; ++leaderboard) { + if (leaderboard->hidden) + continue; + + leaderboard->bucket = rc_client_get_leaderboard_bucket(leaderboard, grouping); + bucket_counts[leaderboard->bucket]++; + } + } + + num_buckets = 0; + num_leaderboards = 0; + for (i = 0; i < sizeof(bucket_counts) / sizeof(bucket_counts[0]); ++i) { + if (bucket_counts[i]) { + int needs_split = 0; + + num_leaderboards += bucket_counts[i]; + + if (num_subsets > 1) { + for (j = 0; j < sizeof(subset_bucket_order) / sizeof(subset_bucket_order[0]); ++j) { + if (subset_bucket_order[j] == i) { + needs_split = 1; + break; + } + } + } + + if (!needs_split) { + ++num_buckets; + continue; + } + + subset = client->game->subsets; + for (; subset; subset = subset->next) { + if (!subset->active) + continue; + + leaderboard = subset->leaderboards; + stop = leaderboard + subset->public_.num_leaderboards; + for (; leaderboard < stop; ++leaderboard) { + if (leaderboard->bucket == i) { + ++num_buckets; + break; + } + } + } + } + } + + buckets_size = RC_ALIGN(num_buckets * sizeof(rc_client_leaderboard_bucket_t)); + + list = (rc_client_leaderboard_list_t*)malloc(list_size + buckets_size + num_leaderboards * sizeof(rc_client_leaderboard_t*)); + bucket_ptr = list->buckets = (rc_client_leaderboard_bucket_t*)((uint8_t*)list + list_size); + leaderboard_ptr = (rc_client_leaderboard_t**)((uint8_t*)bucket_ptr + buckets_size); + + if (grouping == RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING) { + for (i = 0; i < sizeof(shared_bucket_order) / sizeof(shared_bucket_order[0]); ++i) { + bucket_type = shared_bucket_order[i]; + if (!bucket_counts[bucket_type]) + continue; + + bucket_leaderboards = leaderboard_ptr; + for (subset = client->game->subsets; subset; subset = subset->next) { + if (!subset->active) + continue; + + leaderboard = subset->leaderboards; + stop = leaderboard + subset->public_.num_leaderboards; + for (; leaderboard < stop; ++leaderboard) { + if (leaderboard->bucket == bucket_type && !leaderboard->hidden) + *leaderboard_ptr++ = &leaderboard->public_; + } + } + + if (leaderboard_ptr > bucket_leaderboards) { + bucket_ptr->leaderboards = bucket_leaderboards; + bucket_ptr->num_leaderboards = (uint32_t)(leaderboard_ptr - bucket_leaderboards); + bucket_ptr->subset_id = 0; + bucket_ptr->label = rc_client_get_leaderboard_bucket_label(bucket_type); + bucket_ptr->bucket_type = bucket_type; + ++bucket_ptr; + } + } + } + + for (subset = client->game->subsets; subset; subset = subset->next) { + if (!subset->active) + continue; + + for (i = 0; i < sizeof(subset_bucket_order) / sizeof(subset_bucket_order[0]); ++i) { + bucket_type = subset_bucket_order[i]; + if (!bucket_counts[bucket_type]) + continue; + + bucket_leaderboards = leaderboard_ptr; + + leaderboard = subset->leaderboards; + stop = leaderboard + subset->public_.num_leaderboards; + for (; leaderboard < stop; ++leaderboard) { + if (leaderboard->bucket == bucket_type && !leaderboard->hidden) + *leaderboard_ptr++ = &leaderboard->public_; + } + + if (leaderboard_ptr > bucket_leaderboards) { + bucket_ptr->leaderboards = bucket_leaderboards; + bucket_ptr->num_leaderboards = (uint32_t)(leaderboard_ptr - bucket_leaderboards); + bucket_ptr->subset_id = (num_subsets > 1) ? subset->public_.id : 0; + bucket_ptr->bucket_type = bucket_type; + + if (num_subsets > 1) + bucket_ptr->label = rc_client_get_subset_leaderboard_bucket_label(bucket_type, client->game, subset); + else + bucket_ptr->label = rc_client_get_leaderboard_bucket_label(bucket_type); + + ++bucket_ptr; + } + } + } + + rc_mutex_unlock(&client->state.mutex); + + list->num_buckets = (uint32_t)(bucket_ptr - list->buckets); + return list; +} + +void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list) +{ + if (list) + free(list); +} + +static void rc_client_allocate_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard) +{ + rc_client_leaderboard_tracker_info_t* tracker; + rc_client_leaderboard_tracker_info_t* available_tracker = NULL; + + for (tracker = game->leaderboard_trackers; tracker; tracker = tracker->next) { + if (tracker->reference_count == 0) { + if (available_tracker == NULL && tracker->pending_events == RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_NONE) + available_tracker = tracker; + + continue; + } + + if (tracker->value_djb2 != leaderboard->value_djb2 || tracker->format != leaderboard->format) + continue; + + if (tracker->raw_value != leaderboard->value) { + /* if the value comes from tracking hits, we can't assume the trackers started in the + * same frame, so we can't share the tracker */ + if (tracker->value_from_hits) + continue; + + /* value has changed. prepare an update event */ + tracker->raw_value = leaderboard->value; + tracker->pending_events |= RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_UPDATE; + game->pending_events |= RC_CLIENT_GAME_PENDING_EVENT_LEADERBOARD_TRACKER; + } + + /* attach to the existing tracker */ + ++tracker->reference_count; + tracker->pending_events &= ~RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_HIDE; + leaderboard->tracker = tracker; + leaderboard->public_.tracker_value = tracker->public_.display; + return; + } + + if (!available_tracker) { + rc_client_leaderboard_tracker_info_t** next = &game->leaderboard_trackers; + + available_tracker = (rc_client_leaderboard_tracker_info_t*)rc_buf_alloc(&game->buffer, sizeof(*available_tracker)); + memset(available_tracker, 0, sizeof(*available_tracker)); + available_tracker->public_.id = 1; + + for (tracker = *next; tracker; next = &tracker->next, tracker = *next) + available_tracker->public_.id++; + + *next = available_tracker; + } + + /* update the claimed tracker */ + available_tracker->reference_count = 1; + available_tracker->value_djb2 = leaderboard->value_djb2; + available_tracker->format = leaderboard->format; + available_tracker->raw_value = leaderboard->value; + available_tracker->pending_events = RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_SHOW; + available_tracker->value_from_hits = rc_value_from_hits(&leaderboard->lboard->value); + leaderboard->tracker = available_tracker; + leaderboard->public_.tracker_value = available_tracker->public_.display; + game->pending_events |= RC_CLIENT_GAME_PENDING_EVENT_LEADERBOARD_TRACKER; +} + +static void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard) +{ + rc_client_leaderboard_tracker_info_t* tracker = leaderboard->tracker; + leaderboard->tracker = NULL; + + if (tracker && --tracker->reference_count == 0) { + tracker->pending_events |= RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_HIDE; + game->pending_events |= RC_CLIENT_GAME_PENDING_EVENT_LEADERBOARD_TRACKER; + } +} + +static void rc_client_update_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard) +{ + rc_client_leaderboard_tracker_info_t* tracker = leaderboard->tracker; + if (tracker && tracker->raw_value != leaderboard->value) { + tracker->raw_value = leaderboard->value; + tracker->pending_events |= RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_UPDATE; + game->pending_events |= RC_CLIENT_GAME_PENDING_EVENT_LEADERBOARD_TRACKER; + } +} + +typedef struct rc_client_submit_leaderboard_entry_callback_data_t +{ + uint32_t id; + int32_t score; + uint32_t retry_count; + const char* game_hash; + time_t submit_time; + rc_client_t* client; + rc_client_scheduled_callback_data_t* scheduled_callback_data; +} rc_client_submit_leaderboard_entry_callback_data_t; + +static void rc_client_submit_leaderboard_entry_server_call(rc_client_submit_leaderboard_entry_callback_data_t* lboard_data); + +static void rc_client_submit_leaderboard_entry_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now) +{ + rc_client_submit_leaderboard_entry_callback_data_t* lboard_data = + (rc_client_submit_leaderboard_entry_callback_data_t*)callback_data->data; + + rc_client_submit_leaderboard_entry_server_call(lboard_data); +} + +static void rc_client_submit_leaderboard_entry_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_submit_leaderboard_entry_callback_data_t* lboard_data = + (rc_client_submit_leaderboard_entry_callback_data_t*)callback_data; + rc_api_submit_lboard_entry_response_t submit_lboard_entry_response; + + int result = rc_api_process_submit_lboard_entry_server_response(&submit_lboard_entry_response, server_response); + const char* error_message = rc_client_server_error_message(&result, server_response->http_status_code, &submit_lboard_entry_response.response); + + if (error_message) { + if (submit_lboard_entry_response.response.error_message && !rc_client_should_retry(server_response)) { + /* actual error from server */ + RC_CLIENT_LOG_ERR_FORMATTED(lboard_data->client, "Error submitting leaderboard entry %u: %s", lboard_data->id, error_message); + rc_client_raise_server_error_event(lboard_data->client, "submit_lboard_entry", submit_lboard_entry_response.response.error_message); + } + else if (lboard_data->retry_count++ == 0) { + /* first retry is immediate */ + RC_CLIENT_LOG_ERR_FORMATTED(lboard_data->client, "Error submitting leaderboard entry %u: %s, retrying immediately", lboard_data->id, error_message); + rc_client_submit_leaderboard_entry_server_call(lboard_data); + return; + } + else { + /* double wait time between each attempt until we hit a maximum delay of two minutes */ + /* 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s -> 120s -> 120s -> 120s ...*/ + const uint32_t delay = (lboard_data->retry_count > 7) ? 120 : (1 << (lboard_data->retry_count - 1)); + RC_CLIENT_LOG_ERR_FORMATTED(lboard_data->client, "Error submitting leaderboard entry %u: %s, retrying in %u seconds", lboard_data->id, error_message, delay); + + if (!lboard_data->scheduled_callback_data) { + lboard_data->scheduled_callback_data = (rc_client_scheduled_callback_data_t*)calloc(1, sizeof(*lboard_data->scheduled_callback_data)); + if (!lboard_data->scheduled_callback_data) { + RC_CLIENT_LOG_ERR_FORMATTED(lboard_data->client, "Failed to allocate scheduled callback data for reattempt to submit entry for leaderboard %u", lboard_data->id); + rc_client_raise_server_error_event(lboard_data->client, "submit_lboard_entry", rc_error_str(RC_OUT_OF_MEMORY)); + return; + } + lboard_data->scheduled_callback_data->callback = rc_client_submit_leaderboard_entry_retry; + lboard_data->scheduled_callback_data->data = lboard_data; + lboard_data->scheduled_callback_data->related_id = lboard_data->id; + } + + lboard_data->scheduled_callback_data->when = clock() + delay * CLOCKS_PER_SEC; + + rc_client_schedule_callback(lboard_data->client, lboard_data->scheduled_callback_data); + return; + } + } + else { + /* TODO: raise event for scoreboard (if retry_count < 2) */ + + /* not currently doing anything with the response */ + if (lboard_data->retry_count) { + RC_CLIENT_LOG_INFO_FORMATTED(lboard_data->client, "Leaderboard %u submission %d completed after %u attempts", + lboard_data->id, lboard_data->score, lboard_data->retry_count); + } + } + + if (lboard_data->scheduled_callback_data) + free(lboard_data->scheduled_callback_data); + free(lboard_data); +} + +static void rc_client_submit_leaderboard_entry_server_call(rc_client_submit_leaderboard_entry_callback_data_t* lboard_data) +{ + rc_api_submit_lboard_entry_request_t api_params; + rc_api_request_t request; + int result; + + memset(&api_params, 0, sizeof(api_params)); + api_params.username = lboard_data->client->user.username; + api_params.api_token = lboard_data->client->user.token; + api_params.leaderboard_id = lboard_data->id; + api_params.score = lboard_data->score; + api_params.game_hash = lboard_data->game_hash; + + result = rc_api_init_submit_lboard_entry_request(&request, &api_params); + if (result != RC_OK) { + RC_CLIENT_LOG_ERR_FORMATTED(lboard_data->client, "Error constructing submit leaderboard entry for leaderboard %u: %s", lboard_data->id, rc_error_str(result)); + return; + } + + lboard_data->client->callbacks.server_call(&request, rc_client_submit_leaderboard_entry_callback, lboard_data, lboard_data->client); + + rc_api_destroy_request(&request); +} + +static void rc_client_submit_leaderboard_entry(rc_client_t* client, rc_client_leaderboard_info_t* leaderboard) +{ + rc_client_submit_leaderboard_entry_callback_data_t* callback_data; + + /* don't actually submit leaderboard entries when spectating */ + if (client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Spectated %s (%d) for leaderboard %u: %s", + leaderboard->public_.tracker_value, leaderboard->value, leaderboard->public_.id, leaderboard->public_.title); + return; + } + + callback_data = (rc_client_submit_leaderboard_entry_callback_data_t*)calloc(1, sizeof(*callback_data)); + if (!callback_data) { + RC_CLIENT_LOG_ERR_FORMATTED(client, "Failed to allocate callback data for submitting entry for leaderboard %u", leaderboard->public_.id); + rc_client_raise_server_error_event(client, "submit_lboard_entry", rc_error_str(RC_OUT_OF_MEMORY)); + return; + } + callback_data->client = client; + callback_data->id = leaderboard->public_.id; + callback_data->score = leaderboard->value; + callback_data->game_hash = client->game->public_.hash; + callback_data->submit_time = time(NULL); + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Submitting %s (%d) for leaderboard %u: %s", + leaderboard->public_.tracker_value, leaderboard->value, leaderboard->public_.id, leaderboard->public_.title); + rc_client_submit_leaderboard_entry_server_call(callback_data); +} + +static void rc_client_subset_reset_leaderboards(rc_client_game_info_t* game, rc_client_subset_info_t* subset) +{ + rc_client_leaderboard_info_t* leaderboard = subset->leaderboards; + rc_client_leaderboard_info_t* stop = leaderboard + subset->public_.num_leaderboards; + + for (; leaderboard < stop; ++leaderboard) { + rc_lboard_t* lboard = leaderboard->lboard; + if (!lboard) + continue; + + switch (leaderboard->public_.state) { + case RC_CLIENT_LEADERBOARD_STATE_INACTIVE: + case RC_CLIENT_LEADERBOARD_STATE_DISABLED: + continue; + + case RC_CLIENT_LEADERBOARD_STATE_TRACKING: + rc_client_release_leaderboard_tracker(game, leaderboard); + /* fallthrough to default */ + default: + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE; + rc_reset_lboard(lboard); + break; + } + } +} + +static void rc_client_reset_leaderboards(rc_client_t* client) +{ + rc_client_subset_info_t* subset; + for (subset = client->game->subsets; subset; subset = subset->next) + rc_client_subset_reset_leaderboards(client->game, subset); +} + +typedef struct rc_client_fetch_leaderboard_entries_callback_data_t { + rc_client_t* client; + rc_client_fetch_leaderboard_entries_callback_t callback; + void* callback_userdata; + uint32_t leaderboard_id; + rc_client_async_handle_t async_handle; +} rc_client_fetch_leaderboard_entries_callback_data_t; + +static void rc_client_fetch_leaderboard_entries_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_fetch_leaderboard_entries_callback_data_t* lbinfo_callback_data = (rc_client_fetch_leaderboard_entries_callback_data_t*)callback_data; + rc_client_t* client = lbinfo_callback_data->client; + rc_api_fetch_leaderboard_info_response_t lbinfo_response; + const char* error_message; + int result; + + if (rc_client_async_handle_aborted(client, &lbinfo_callback_data->async_handle)) { + RC_CLIENT_LOG_VERBOSE(client, "Fetch leaderbord entries aborted"); + free(lbinfo_callback_data); + return; + } + + result = rc_api_process_fetch_leaderboard_info_server_response(&lbinfo_response, server_response); + error_message = rc_client_server_error_message(&result, server_response->http_status_code, &lbinfo_response.response); + if (error_message) { + RC_CLIENT_LOG_ERR_FORMATTED(client, "Fetch leaderboard %u info failed: %s", lbinfo_callback_data->leaderboard_id, error_message); + lbinfo_callback_data->callback(result, error_message, NULL, client, lbinfo_callback_data->callback_userdata); + } + else { + rc_client_leaderboard_entry_list_t* list; + const size_t list_size = sizeof(*list) + sizeof(rc_client_leaderboard_entry_t) * lbinfo_response.num_entries; + size_t needed_size = list_size; + unsigned i; + + for (i = 0; i < lbinfo_response.num_entries; i++) + needed_size += strlen(lbinfo_response.entries[i].username) + 1; + + list = (rc_client_leaderboard_entry_list_t*)malloc(needed_size); + if (!list) { + lbinfo_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, lbinfo_callback_data->callback_userdata); + } + else { + rc_client_leaderboard_entry_t* entry = list->entries = (rc_client_leaderboard_entry_t*)((uint8_t*)list + sizeof(*list)); + char* user = (char*)((uint8_t*)list + list_size); + const rc_api_lboard_info_entry_t* lbentry = lbinfo_response.entries; + const rc_api_lboard_info_entry_t* stop = lbentry + lbinfo_response.num_entries; + const size_t logged_in_user_len = strlen(client->user.display_name) + 1; + list->user_index = -1; + + for (; lbentry < stop; ++lbentry, ++entry) { + const size_t len = strlen(lbentry->username) + 1; + entry->user = user; + memcpy(user, lbentry->username, len); + user += len; + + if (len == logged_in_user_len && memcmp(entry->user, client->user.display_name, len) == 0) + list->user_index = (int)(entry - list->entries); + + entry->index = lbentry->index; + entry->rank = lbentry->rank; + entry->submitted = lbentry->submitted; + + rc_format_value(entry->display, sizeof(entry->display), lbentry->score, lbinfo_response.format); + } + + list->num_entries = lbinfo_response.num_entries; + + lbinfo_callback_data->callback(RC_OK, NULL, list, client, lbinfo_callback_data->callback_userdata); + } + } + + rc_api_destroy_fetch_leaderboard_info_response(&lbinfo_response); + free(lbinfo_callback_data); +} + +static rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_info(rc_client_t* client, + const rc_api_fetch_leaderboard_info_request_t* lbinfo_request, + rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata) +{ + rc_client_fetch_leaderboard_entries_callback_data_t* callback_data; + rc_api_request_t request; + int result; + const char* error_message; + + result = rc_api_init_fetch_leaderboard_info_request(&request, lbinfo_request); + + 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_leaderboard_entries_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; + callback_data->leaderboard_id = lbinfo_request->leaderboard_id; + + client->callbacks.server_call(&request, rc_client_fetch_leaderboard_entries_callback, callback_data, client); + rc_api_destroy_request(&request); + + return &callback_data->async_handle; +} + +rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id, + uint32_t first_entry, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata) +{ + rc_api_fetch_leaderboard_info_request_t lbinfo_request; + + memset(&lbinfo_request, 0, sizeof(lbinfo_request)); + lbinfo_request.leaderboard_id = leaderboard_id; + lbinfo_request.first_entry = first_entry; + lbinfo_request.count = count; + + return rc_client_begin_fetch_leaderboard_info(client, &lbinfo_request, callback, callback_userdata); +} + +rc_client_async_handle_t* rc_client_begin_fetch_leaderboard_entries_around_user(rc_client_t* client, uint32_t leaderboard_id, + uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata) +{ + rc_api_fetch_leaderboard_info_request_t lbinfo_request; + + memset(&lbinfo_request, 0, sizeof(lbinfo_request)); + lbinfo_request.leaderboard_id = leaderboard_id; + lbinfo_request.username = client->user.username; + lbinfo_request.count = count; + + if (!lbinfo_request.username) { + callback(RC_LOGIN_REQUIRED, rc_error_str(RC_LOGIN_REQUIRED), NULL, client, callback_userdata); + return NULL; + } + + return rc_client_begin_fetch_leaderboard_info(client, &lbinfo_request, callback, callback_userdata); +} + +void rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list) +{ + if (list) + free(list); +} + +int rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_entry_t* entry, char buffer[], size_t buffer_size) +{ + if (!entry) + return RC_INVALID_STATE; + + return rc_client_get_image_url(buffer, buffer_size, RC_IMAGE_TYPE_USER, entry->user); +} + +/* ===== Rich Presence ===== */ + +static void rc_client_ping_callback(const rc_api_server_response_t* server_response, void* callback_data) +{ + rc_client_t* client = (rc_client_t*)callback_data; + rc_api_ping_response_t response; + + int result = rc_api_process_ping_server_response(&response, server_response); + const char* error_message = rc_client_server_error_message(&result, server_response->http_status_code, &response.response); + if (error_message) { + RC_CLIENT_LOG_WARN_FORMATTED(client, "Ping response error: %s", error_message); + } + + rc_api_destroy_ping_response(&response); +} + +static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now) +{ + rc_api_ping_request_t api_params; + rc_api_request_t request; + char buffer[256]; + int result; + + rc_runtime_get_richpresence(&client->game->runtime, buffer, sizeof(buffer), + client->state.legacy_peek, client, NULL); + + memset(&api_params, 0, sizeof(api_params)); + api_params.username = client->user.username; + api_params.api_token = client->user.token; + api_params.game_id = client->game->public_.id; + api_params.rich_presence = buffer; + + result = rc_api_init_ping_request(&request, &api_params); + if (result != RC_OK) { + RC_CLIENT_LOG_WARN_FORMATTED(client, "Error generating ping request: %s", rc_error_str(result)); + } + else { + client->callbacks.server_call(&request, rc_client_ping_callback, client, client); + } + + callback_data->when = now + 120 * CLOCKS_PER_SEC; + rc_client_schedule_callback(client, callback_data); +} + +size_t rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size) +{ + int result; + + if (!client || !client->game || !buffer) + return 0; + + result = rc_runtime_get_richpresence(&client->game->runtime, buffer, (unsigned)buffer_size, + client->state.legacy_peek, client, NULL); + + if (result == 0) + result = snprintf(buffer, buffer_size, "Playing %s", client->game->public_.title); + + return result; +} + +/* ===== Processing ===== */ + +void rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler) +{ + if (client) + client->callbacks.event_handler = handler; +} + +void rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler) +{ + if (client) + client->callbacks.read_memory = handler; +} + +static void rc_client_invalidate_processing_memref(rc_client_t* client) +{ + rc_memref_t** next_memref = &client->game->runtime.memrefs; + rc_memref_t* memref; + + /* if processing_memref is not set, this occurred following a pointer chain. ignore it. */ + if (!client->state.processing_memref) + return; + + /* invalid memref. remove from chain so we don't have to evaluate it in the future. + * it's still there, so anything referencing it will always fetch the current value. */ + while ((memref = *next_memref) != NULL) { + if (memref == client->state.processing_memref) { + *next_memref = memref->next; + break; + } + next_memref = &memref->next; + } + + rc_client_invalidate_memref_achievements(client->game, client, client->state.processing_memref); + rc_client_invalidate_memref_leaderboards(client->game, client, client->state.processing_memref); + + client->state.processing_memref = NULL; +} + +static unsigned rc_client_peek_le(unsigned address, unsigned num_bytes, void* ud) +{ + rc_client_t* client = (rc_client_t*)ud; + unsigned value = 0; + uint32_t num_read = 0; + + /* if we know the address is out of range, and it's part of a pointer chain + * (processing_memref is null), don't bother processing it. */ + if (address > client->game->max_valid_address && !client->state.processing_memref) + return 0; + + if (num_bytes <= sizeof(value)) { + num_read = client->callbacks.read_memory(address, (uint8_t*)&value, num_bytes, client); + if (num_read == num_bytes) + return value; + } + + if (num_read < num_bytes) + rc_client_invalidate_processing_memref(client); + + return 0; +} + +static unsigned rc_client_peek(unsigned address, unsigned num_bytes, void* ud) +{ + rc_client_t* client = (rc_client_t*)ud; + uint8_t buffer[4]; + uint32_t num_read = 0; + + /* if we know the address is out of range, and it's part of a pointer chain + * (processing_memref is null), don't bother processing it. */ + if (address > client->game->max_valid_address && !client->state.processing_memref) + return 0; + + switch (num_bytes) { + case 1: + num_read = client->callbacks.read_memory(address, buffer, 1, client); + if (num_read == 1) + return buffer[0]; + break; + case 2: + num_read = client->callbacks.read_memory(address, buffer, 2, client); + if (num_read == 2) + return buffer[0] | (buffer[1] << 8); + break; + case 3: + num_read = client->callbacks.read_memory(address, buffer, 3, client); + if (num_read == 3) + return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16); + break; + case 4: + num_read = client->callbacks.read_memory(address, buffer, 4, client); + if (num_read == 4) + return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + break; + default: + break; + } + + if (num_read < num_bytes) + rc_client_invalidate_processing_memref(client); + + return 0; +} + +void rc_client_set_legacy_peek(rc_client_t* client, int method) +{ + if (method == RC_CLIENT_LEGACY_PEEK_AUTO) { + uint8_t buffer[4] = { 1,0,0,0 }; + method = (*((uint32_t*)buffer) == 1) ? + RC_CLIENT_LEGACY_PEEK_LITTLE_ENDIAN_READS : RC_CLIENT_LEGACY_PEEK_CONSTRUCTED; + } + + client->state.legacy_peek = (method == RC_CLIENT_LEGACY_PEEK_LITTLE_ENDIAN_READS) ? + rc_client_peek_le : rc_client_peek; +} + +int rc_client_is_processing_required(rc_client_t* client) +{ + if (!client || !client->game) + return 0; + + if (client->game->runtime.trigger_count || client->game->runtime.lboard_count) + return 1; + + return (client->game->runtime.richpresence && client->game->runtime.richpresence->richpresence); +} + +static void rc_client_update_memref_values(rc_client_t* client) +{ + rc_memref_t* memref = client->game->runtime.memrefs; + unsigned value; + int invalidated_memref = 0; + + for (; memref; memref = memref->next) { + if (memref->value.is_indirect) + continue; + + client->state.processing_memref = memref; + + value = rc_peek_value(memref->address, memref->value.size, client->state.legacy_peek, client); + + if (client->state.processing_memref) { + rc_update_memref_value(&memref->value, value); + } + else { + /* if the peek function cleared the processing_memref, the memref was invalidated */ + invalidated_memref = 1; + } + } + + client->state.processing_memref = NULL; + + if (invalidated_memref) + rc_client_update_active_achievements(client->game); +} + +static void rc_client_do_frame_process_achievements(rc_client_t* client, rc_client_subset_info_t* subset) +{ + rc_client_achievement_info_t* achievement = subset->achievements; + rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements; + + for (; achievement < stop; ++achievement) { + rc_trigger_t* trigger = achievement->trigger; + int old_state, new_state; + unsigned old_measured_value; + + if (!trigger || achievement->public_.state != RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE) + continue; + + old_measured_value = trigger->measured_value; + old_state = trigger->state; + new_state = rc_evaluate_trigger(trigger, client->state.legacy_peek, client, NULL); + + /* 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 && + rc_trigger_state_active(new_state) && new_state != RC_TRIGGER_STATE_WAITING) { + + /* only show a popup for the achievement closest to triggering */ + float progress = (float)trigger->measured_value / (float)trigger->measured_target; + + if (trigger->measured_as_percent) { + /* if reporting the measured value as a percentage, only show the popup if the percentage changes */ + const unsigned old_percent = (unsigned)(((unsigned long long)old_measured_value * 100) / trigger->measured_target); + const unsigned new_percent = (unsigned)(((unsigned long long)trigger->measured_value * 100) / trigger->measured_target); + if (old_percent == new_percent) + progress = -1.0; + } + + if (progress > client->game->progress_tracker.progress) { + client->game->progress_tracker.progress = progress; + client->game->progress_tracker.achievement = achievement; + client->game->pending_events |= RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER; + subset->pending_events |= RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT; + achievement->pending_events |= RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_UPDATE; + } + } + + /* if the state hasn't changed, there won't be any events raised */ + if (new_state == old_state) + continue; + + /* raise a CHALLENGE_INDICATOR_HIDE event when changing from PRIMED to anything else */ + if (old_state == RC_TRIGGER_STATE_PRIMED) + achievement->pending_events |= RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE; + + /* raise events for each of the possible new states */ + if (new_state == RC_TRIGGER_STATE_TRIGGERED) + achievement->pending_events |= RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_TRIGGERED; + else if (new_state == RC_TRIGGER_STATE_PRIMED) + achievement->pending_events |= RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_SHOW; + + subset->pending_events |= RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT; + } +} + +static void rc_client_hide_progress_tracker(rc_client_t* client, rc_client_game_info_t* game) +{ + /* ASSERT: this should only be called if the mutex is held */ + + if (game->progress_tracker.hide_callback && + game->progress_tracker.hide_callback->when && + game->progress_tracker.action == RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE) { + rc_client_reschedule_callback(client, game->progress_tracker.hide_callback, 0); + game->progress_tracker.action = RC_CLIENT_PROGRESS_TRACKER_ACTION_HIDE; + game->pending_events |= RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER; + } +} + +static void rc_client_progress_tracker_timer_elapsed(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now) +{ + rc_client_event_t client_event; + memset(&client_event, 0, sizeof(client_event)); + + rc_mutex_lock(&client->state.mutex); + if (client->game->progress_tracker.action == RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE) { + client->game->progress_tracker.hide_callback->when = 0; + client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE; + } + rc_mutex_unlock(&client->state.mutex); + + if (client_event.type) + client->callbacks.event_handler(&client_event, client); +} + +static void rc_client_do_frame_update_progress_tracker(rc_client_t* client, rc_client_game_info_t* game) +{ + if (!game->progress_tracker.hide_callback) { + game->progress_tracker.hide_callback = (rc_client_scheduled_callback_data_t*) + rc_buf_alloc(&game->buffer, sizeof(rc_client_scheduled_callback_data_t)); + memset(game->progress_tracker.hide_callback, 0, sizeof(rc_client_scheduled_callback_data_t)); + game->progress_tracker.hide_callback->callback = rc_client_progress_tracker_timer_elapsed; + } + + if (game->progress_tracker.hide_callback->when == 0) + game->progress_tracker.action = RC_CLIENT_PROGRESS_TRACKER_ACTION_SHOW; + else + game->progress_tracker.action = RC_CLIENT_PROGRESS_TRACKER_ACTION_UPDATE; + + rc_client_reschedule_callback(client, game->progress_tracker.hide_callback, clock() + 2 * CLOCKS_PER_SEC); +} + +static void rc_client_raise_progress_tracker_events(rc_client_t* client, rc_client_game_info_t* game) +{ + rc_client_event_t client_event; + + memset(&client_event, 0, sizeof(client_event)); + + switch (game->progress_tracker.action) { + case RC_CLIENT_PROGRESS_TRACKER_ACTION_SHOW: + client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW; + break; + case RC_CLIENT_PROGRESS_TRACKER_ACTION_HIDE: + client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE; + break; + default: + client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE; + break; + } + game->progress_tracker.action = RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE; + + client_event.achievement = &game->progress_tracker.achievement->public_; + client->callbacks.event_handler(&client_event, client); +} + +static void rc_client_raise_achievement_events(rc_client_t* client, rc_client_subset_info_t* subset) +{ + rc_client_achievement_info_t* achievement = subset->achievements; + rc_client_achievement_info_t* stop = achievement + subset->public_.num_achievements; + rc_client_event_t client_event; + time_t recent_unlock_time = 0; + + memset(&client_event, 0, sizeof(client_event)); + + for (; achievement < stop; ++achievement) { + if (achievement->pending_events == RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_NONE) + continue; + + /* kick off award achievement request first */ + if (achievement->pending_events & RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_TRIGGERED) { + rc_client_award_achievement(client, achievement); + client->game->pending_events |= RC_CLIENT_GAME_PENDING_EVENT_UPDATE_ACTIVE_ACHIEVEMENTS; + } + + /* update display state */ + if (recent_unlock_time == 0) + recent_unlock_time = time(NULL) - RC_CLIENT_RECENT_UNLOCK_DELAY_SECONDS; + rc_client_update_achievement_display_information(client, achievement, recent_unlock_time); + + /* raise events */ + client_event.achievement = &achievement->public_; + + if (achievement->pending_events & RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE) { + client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE; + client->callbacks.event_handler(&client_event, client); + } + else if (achievement->pending_events & RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_SHOW) { + client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW; + client->callbacks.event_handler(&client_event, client); + } + + if (achievement->pending_events & RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_TRIGGERED) { + client_event.type = RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED; + client->callbacks.event_handler(&client_event, client); + } + + /* clear pending flags */ + achievement->pending_events = RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_NONE; + } +} + +static void rc_client_raise_mastery_event(rc_client_t* client, rc_client_subset_info_t* subset) +{ + rc_client_event_t client_event; + + memset(&client_event, 0, sizeof(client_event)); + client_event.type = RC_CLIENT_EVENT_GAME_COMPLETED; + + subset->mastery = RC_CLIENT_MASTERY_STATE_SHOWN; + + client->callbacks.event_handler(&client_event, client); +} + +static void rc_client_do_frame_process_leaderboards(rc_client_t* client, rc_client_subset_info_t* subset) +{ + rc_client_leaderboard_info_t* leaderboard = subset->leaderboards; + rc_client_leaderboard_info_t* stop = leaderboard + subset->public_.num_leaderboards; + + for (; leaderboard < stop; ++leaderboard) { + rc_lboard_t* lboard = leaderboard->lboard; + int old_state, new_state; + + switch (leaderboard->public_.state) { + case RC_CLIENT_LEADERBOARD_STATE_INACTIVE: + case RC_CLIENT_LEADERBOARD_STATE_DISABLED: + continue; + + default: + if (!lboard) + continue; + + break; + } + + old_state = lboard->state; + new_state = rc_evaluate_lboard(lboard, &leaderboard->value, client->state.legacy_peek, client, NULL); + + switch (new_state) { + case RC_LBOARD_STATE_STARTED: /* leaderboard is running */ + if (old_state != RC_LBOARD_STATE_STARTED) { + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_TRACKING; + leaderboard->pending_events |= RC_CLIENT_LEADERBOARD_PENDING_EVENT_STARTED; + rc_client_allocate_leaderboard_tracker(client->game, leaderboard); + } + else { + rc_client_update_leaderboard_tracker(client->game, leaderboard); + } + break; + + case RC_LBOARD_STATE_CANCELED: + if (old_state != RC_LBOARD_STATE_CANCELED) { + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE; + leaderboard->pending_events |= RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED; + rc_client_release_leaderboard_tracker(client->game, leaderboard); + } + break; + + case RC_LBOARD_STATE_TRIGGERED: + if (old_state != RC_RUNTIME_EVENT_LBOARD_TRIGGERED) { + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE; + leaderboard->pending_events |= RC_CLIENT_LEADERBOARD_PENDING_EVENT_SUBMITTED; + + if (old_state != RC_LBOARD_STATE_STARTED) + rc_client_allocate_leaderboard_tracker(client->game, leaderboard); + else + rc_client_update_leaderboard_tracker(client->game, leaderboard); + + rc_client_release_leaderboard_tracker(client->game, leaderboard); + } + break; + } + + if (leaderboard->pending_events) + subset->pending_events |= RC_CLIENT_SUBSET_PENDING_EVENT_LEADERBOARD; + } +} + +static void rc_client_raise_leaderboard_tracker_events(rc_client_t* client, rc_client_game_info_t* game) +{ + rc_client_leaderboard_tracker_info_t* tracker = game->leaderboard_trackers; + rc_client_event_t client_event; + + memset(&client_event, 0, sizeof(client_event)); + + tracker = game->leaderboard_trackers; + for (; tracker; tracker = tracker->next) { + if (tracker->pending_events == RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_NONE) + continue; + + client_event.leaderboard_tracker = &tracker->public_; + + /* update display text for new trackers or updated trackers */ + if (tracker->pending_events & (RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_SHOW | RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_UPDATE)) + rc_format_value(tracker->public_.display, sizeof(tracker->public_.display), tracker->raw_value, tracker->format); + + if (tracker->pending_events & RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_HIDE) { + if (tracker->pending_events & RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_SHOW) { + /* request to show and hide in the same frame - ignore the event */ + } + else { + client_event.type = RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE; + client->callbacks.event_handler(&client_event, client); + } + } + else if (tracker->pending_events & RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_SHOW) { + client_event.type = RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW; + client->callbacks.event_handler(&client_event, client); + } + else if (tracker->pending_events & RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_UPDATE) { + client_event.type = RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE; + client->callbacks.event_handler(&client_event, client); + } + + tracker->pending_events = RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_NONE; + } +} + +static void rc_client_raise_leaderboard_events(rc_client_t* client, rc_client_subset_info_t* subset) +{ + rc_client_leaderboard_info_t* leaderboard = subset->leaderboards; + rc_client_leaderboard_info_t* leaderboard_stop = leaderboard + subset->public_.num_leaderboards; + rc_client_event_t client_event; + + memset(&client_event, 0, sizeof(client_event)); + + for (; leaderboard < leaderboard_stop; ++leaderboard) { + if (leaderboard->pending_events == RC_CLIENT_LEADERBOARD_PENDING_EVENT_NONE) + continue; + + client_event.leaderboard = &leaderboard->public_; + + if (leaderboard->pending_events & RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED) { + RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Leaderboard %u canceled: %s", leaderboard->public_.id, leaderboard->public_.title); + client_event.type = RC_CLIENT_EVENT_LEADERBOARD_FAILED; + client->callbacks.event_handler(&client_event, client); + } + else if (leaderboard->pending_events & RC_CLIENT_LEADERBOARD_PENDING_EVENT_SUBMITTED) { + /* kick off submission request before raising event */ + rc_client_submit_leaderboard_entry(client, leaderboard); + + client_event.type = RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED; + client->callbacks.event_handler(&client_event, client); + } + else if (leaderboard->pending_events & RC_CLIENT_LEADERBOARD_PENDING_EVENT_STARTED) { + RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Leaderboard %u started: %s", leaderboard->public_.id, leaderboard->public_.title); + client_event.type = RC_CLIENT_EVENT_LEADERBOARD_STARTED; + client->callbacks.event_handler(&client_event, client); + } + + leaderboard->pending_events = RC_CLIENT_LEADERBOARD_PENDING_EVENT_NONE; + } +} + +static void rc_client_reset_pending_events(rc_client_t* client) +{ + rc_client_subset_info_t* subset; + + client->game->pending_events = RC_CLIENT_GAME_PENDING_EVENT_NONE; + + for (subset = client->game->subsets; subset; subset = subset->next) + subset->pending_events = RC_CLIENT_SUBSET_PENDING_EVENT_NONE; +} + +static void rc_client_subset_raise_pending_events(rc_client_t* client, rc_client_subset_info_t* subset) +{ + /* raise any pending achievement events */ + if (subset->pending_events & RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT) + rc_client_raise_achievement_events(client, subset); + + /* raise any pending leaderboard events */ + if (subset->pending_events & RC_CLIENT_SUBSET_PENDING_EVENT_LEADERBOARD) + rc_client_raise_leaderboard_events(client, subset); + + /* raise mastery event if pending */ + if (subset->mastery == RC_CLIENT_MASTERY_STATE_PENDING) + rc_client_raise_mastery_event(client, subset); +} + +static void rc_client_raise_pending_events(rc_client_t* client, rc_client_game_info_t* game) +{ + rc_client_subset_info_t* subset; + + /* raise tracker events before leaderboard events so formatted values are updated for leaderboard events */ + if (game->pending_events & RC_CLIENT_GAME_PENDING_EVENT_LEADERBOARD_TRACKER) + rc_client_raise_leaderboard_tracker_events(client, game); + + for (subset = game->subsets; subset; subset = subset->next) + rc_client_subset_raise_pending_events(client, subset); + + /* raise progress tracker events after achievement events so formatted values are updated for tracker event */ + if (game->pending_events & RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER) + rc_client_raise_progress_tracker_events(client, game); + + /* if any achievements were unlocked, resync the active achievements list */ + if (game->pending_events & RC_CLIENT_GAME_PENDING_EVENT_UPDATE_ACTIVE_ACHIEVEMENTS) { + rc_mutex_lock(&client->state.mutex); + rc_client_update_active_achievements(game); + rc_mutex_unlock(&client->state.mutex); + } + + game->pending_events = RC_CLIENT_GAME_PENDING_EVENT_NONE; +} + +void rc_client_do_frame(rc_client_t* client) +{ + if (!client) + return; + + if (client->game && !client->game->waiting_for_reset) { + rc_runtime_richpresence_t* richpresence; + rc_client_subset_info_t* subset; + + rc_mutex_lock(&client->state.mutex); + + rc_client_reset_pending_events(client); + + rc_client_update_memref_values(client); + rc_update_variables(client->game->runtime.variables, client->state.legacy_peek, client, NULL); + + client->game->progress_tracker.progress = 0.0; + for (subset = client->game->subsets; subset; subset = subset->next) { + if (subset->active) + rc_client_do_frame_process_achievements(client, subset); + } + if (client->game->pending_events & RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER) + rc_client_do_frame_update_progress_tracker(client, client->game); + + if (client->state.hardcore) { + for (subset = client->game->subsets; subset; subset = subset->next) { + if (subset->active) + rc_client_do_frame_process_leaderboards(client, subset); + } + } + + richpresence = client->game->runtime.richpresence; + if (richpresence && richpresence->richpresence) + rc_update_richpresence(richpresence->richpresence, client->state.legacy_peek, client, NULL); + + rc_mutex_unlock(&client->state.mutex); + + rc_client_raise_pending_events(client, client->game); + } + + rc_client_idle(client); +} + +void rc_client_idle(rc_client_t* client) +{ + rc_client_scheduled_callback_data_t* scheduled_callback; + + if (!client) + return; + + scheduled_callback = client->state.scheduled_callbacks; + if (scheduled_callback) { + const clock_t now = clock(); + + do { + rc_mutex_lock(&client->state.mutex); + scheduled_callback = client->state.scheduled_callbacks; + if (scheduled_callback) { + if (RC_CLIENT_CLOCK_IS_BEFORE(now, scheduled_callback->when)) { + /* not time for next callback yet, ignore it */ + scheduled_callback = NULL; + } + else { + /* remove the callback from the queue while we process it. callback can requeue if desired */ + client->state.scheduled_callbacks = scheduled_callback->next; + } + } + rc_mutex_unlock(&client->state.mutex); + + if (!scheduled_callback) + break; + + scheduled_callback->callback(scheduled_callback, client, now); + } while (1); + } +} + +void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* scheduled_callback) +{ + rc_client_scheduled_callback_data_t** last; + rc_client_scheduled_callback_data_t* next; + + rc_mutex_lock(&client->state.mutex); + + last = &client->state.scheduled_callbacks; + do { + next = *last; + if (!next || RC_CLIENT_CLOCK_IS_BEFORE(scheduled_callback->when, next->when)) { + scheduled_callback->next = next; + *last = scheduled_callback; + break; + } + + last = &next->next; + } while (1); + + rc_mutex_unlock(&client->state.mutex); +} + +static void rc_client_reschedule_callback(rc_client_t* client, + rc_client_scheduled_callback_data_t* callback, clock_t when) +{ + rc_client_scheduled_callback_data_t** last; + rc_client_scheduled_callback_data_t* next; + + /* ASSERT: this should only be called if the mutex is held */ + + callback->when = when; + + last = &client->state.scheduled_callbacks; + do { + next = *last; + + if (next == callback) { + if (when == 0) { + /* request to unschedule the callback */ + *last = next->next; + next->next = NULL; + break; + } + + if (!next->next) { + /* end of list, just append it */ + break; + } + + if (RC_CLIENT_CLOCK_IS_BEFORE(when, next->next->when)) { + /* already in the correct place */ + break; + } + + /* remove from current position - will insert later */ + *last = next->next; + next->next = NULL; + continue; + } + + if (!next || RC_CLIENT_CLOCK_IS_BEFORE(when, next->when)) { + /* insert here */ + callback->next = next; + *last = callback; + break; + } + + last = &next->next; + } while (1); +} + +static void rc_client_reset_richpresence(rc_client_t* client) +{ + rc_runtime_richpresence_t* richpresence = client->game->runtime.richpresence; + if (richpresence && richpresence->richpresence) + rc_reset_richpresence(richpresence->richpresence); +} + +static void rc_client_reset_variables(rc_client_t* client) +{ + rc_value_t* variable = client->game->runtime.variables; + for (; variable; variable = variable->next) + rc_reset_value(variable); +} + +static void rc_client_reset_all(rc_client_t* client) +{ + rc_client_reset_achievements(client); + rc_client_reset_leaderboards(client); + rc_client_reset_richpresence(client); + rc_client_reset_variables(client); +} + +void rc_client_reset(rc_client_t* client) +{ + rc_client_game_hash_t* game_hash; + if (!client || !client->game) + return; + + game_hash = rc_client_find_game_hash(client, client->game->public_.hash); + if (game_hash && game_hash->game_id != client->game->public_.id) { + /* current media is not for loaded game. unload game */ + RC_CLIENT_LOG_WARN_FORMATTED(client, "Disabling runtime. Reset with non-game media loaded: %u (%s)", + (game_hash->game_id == RC_CLIENT_UNKNOWN_GAME_ID) ? 0 : game_hash->game_id, game_hash->hash); + rc_client_unload_game(client); + return; + } + + RC_CLIENT_LOG_INFO(client, "Resetting runtime"); + + rc_mutex_lock(&client->state.mutex); + + client->game->waiting_for_reset = 0; + rc_client_reset_pending_events(client); + + rc_client_hide_progress_tracker(client, client->game); + rc_client_reset_all(client); + + rc_mutex_unlock(&client->state.mutex); + + rc_client_raise_pending_events(client, client->game); +} + +size_t rc_client_progress_size(rc_client_t* client) +{ + size_t result; + + if (!client || !client->game) + return 0; + + rc_mutex_lock(&client->state.mutex); + result = rc_runtime_progress_size(&client->game->runtime, NULL); + rc_mutex_unlock(&client->state.mutex); + + return result; +} + +int rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer) +{ + int result; + + if (!client || !client->game) + return RC_NO_GAME_LOADED; + + if (!buffer) + return RC_INVALID_STATE; + + rc_mutex_lock(&client->state.mutex); + result = rc_runtime_serialize_progress(buffer, &client->game->runtime, NULL); + rc_mutex_unlock(&client->state.mutex); + + return result; +} + +static void rc_client_subset_before_deserialize_progress(rc_client_subset_info_t* subset) +{ + rc_client_achievement_info_t* achievement; + rc_client_achievement_info_t* achievement_stop; + rc_client_leaderboard_info_t* leaderboard; + rc_client_leaderboard_info_t* leaderboard_stop; + + /* flag any visible challenge indicators to be hidden */ + achievement = subset->achievements; + achievement_stop = achievement + subset->public_.num_achievements; + for (; achievement < achievement_stop; ++achievement) { + rc_trigger_t* trigger = achievement->trigger; + if (trigger && trigger->state == RC_TRIGGER_STATE_PRIMED && + achievement->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE) { + achievement->pending_events |= RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE; + subset->pending_events |= RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT; + } + } + + /* flag any visible trackers to be hidden */ + leaderboard = subset->leaderboards; + leaderboard_stop = leaderboard + subset->public_.num_leaderboards; + for (; leaderboard < leaderboard_stop; ++leaderboard) { + rc_lboard_t* lboard = leaderboard->lboard; + if (lboard && lboard->state == RC_LBOARD_STATE_STARTED && + leaderboard->public_.state == RC_CLIENT_LEADERBOARD_STATE_TRACKING) { + leaderboard->pending_events |= RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED; + subset->pending_events |= RC_CLIENT_SUBSET_PENDING_EVENT_LEADERBOARD; + } + } +} + +static void rc_client_subset_after_deserialize_progress(rc_client_game_info_t* game, rc_client_subset_info_t* subset) +{ + rc_client_achievement_info_t* achievement; + rc_client_achievement_info_t* achievement_stop; + rc_client_leaderboard_info_t* leaderboard; + rc_client_leaderboard_info_t* leaderboard_stop; + + /* flag any challenge indicators that should be shown */ + achievement = subset->achievements; + achievement_stop = achievement + subset->public_.num_achievements; + for (; achievement < achievement_stop; ++achievement) { + rc_trigger_t* trigger = achievement->trigger; + if (!trigger || achievement->public_.state != RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE) + continue; + + if (trigger->state == RC_TRIGGER_STATE_PRIMED) { + /* if it's already shown, just keep it. otherwise flag it to be shown */ + if (achievement->pending_events & RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE) { + achievement->pending_events &= ~RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE; + } + else { + achievement->pending_events |= RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_SHOW; + subset->pending_events |= RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT; + } + } + /* ASSERT: only active achievements are serialized, so we don't have to worry about + * deserialization deactiving them. */ + } + + /* flag any trackers that need to be shown */ + leaderboard = subset->leaderboards; + leaderboard_stop = leaderboard + subset->public_.num_leaderboards; + for (; leaderboard < leaderboard_stop; ++leaderboard) { + rc_lboard_t* lboard = leaderboard->lboard; + if (!lboard || + leaderboard->public_.state == RC_CLIENT_LEADERBOARD_STATE_INACTIVE || + leaderboard->public_.state == RC_CLIENT_LEADERBOARD_STATE_DISABLED) + continue; + + if (lboard->state == RC_LBOARD_STATE_STARTED) { + leaderboard->value = (int)lboard->value.value.value; + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_TRACKING; + + /* if it's already being tracked, just update tracker. otherwise, allocate one */ + if (leaderboard->pending_events & RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED) { + leaderboard->pending_events &= ~RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED; + rc_client_update_leaderboard_tracker(game, leaderboard); + } + else { + rc_client_allocate_leaderboard_tracker(game, leaderboard); + } + } + else if (leaderboard->pending_events & RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED) { + /* deallocate the tracker (don't actually raise the failed event) */ + leaderboard->pending_events &= ~RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED; + leaderboard->public_.state = RC_CLIENT_LEADERBOARD_STATE_ACTIVE; + rc_client_release_leaderboard_tracker(game, leaderboard); + } + } +} + +int rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized) +{ + rc_client_subset_info_t* subset; + int result; + + if (!client || !client->game) + return RC_NO_GAME_LOADED; + + rc_mutex_lock(&client->state.mutex); + + rc_client_reset_pending_events(client); + + for (subset = client->game->subsets; subset; subset = subset->next) + rc_client_subset_before_deserialize_progress(subset); + + rc_client_hide_progress_tracker(client, client->game); + + if (!serialized) { + rc_client_reset_all(client); + result = RC_OK; + } + else { + result = rc_runtime_deserialize_progress(&client->game->runtime, serialized, NULL); + } + + for (subset = client->game->subsets; subset; subset = subset->next) + rc_client_subset_after_deserialize_progress(client->game, subset); + + rc_mutex_unlock(&client->state.mutex); + + rc_client_raise_pending_events(client, client->game); + + return result; +} + +/* ===== Toggles ===== */ + +static void rc_client_enable_hardcore(rc_client_t* client) +{ + client->state.hardcore = 1; + + if (client->game) { + rc_client_toggle_hardcore_achievements(client->game, client, RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE); + rc_client_activate_leaderboards(client->game, client); + + /* disable processing until the client acknowledges the reset event by calling rc_runtime_reset() */ + RC_CLIENT_LOG_INFO(client, "Hardcore enabled, waiting for reset"); + client->game->waiting_for_reset = 1; + } + else { + RC_CLIENT_LOG_INFO(client, "Hardcore enabled"); + } +} + +static void rc_client_disable_hardcore(rc_client_t* client) +{ + client->state.hardcore = 0; + RC_CLIENT_LOG_INFO(client, "Hardcore disabled"); + + if (client->game) { + rc_client_toggle_hardcore_achievements(client->game, client, RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE); + rc_client_deactivate_leaderboards(client->game, client); + } +} + +void rc_client_set_hardcore_enabled(rc_client_t* client, int enabled) +{ + int changed = 0; + + if (!client) + return; + + rc_mutex_lock(&client->state.mutex); + + enabled = enabled ? 1 : 0; + if (client->state.hardcore != enabled) { + if (enabled) + rc_client_enable_hardcore(client); + else + rc_client_disable_hardcore(client); + + changed = 1; + } + + rc_mutex_unlock(&client->state.mutex); + + /* events must be raised outside of lock */ + if (changed && client->game) { + if (enabled) { + /* if enabling hardcore, notify client that a reset is requested */ + if (client->game->waiting_for_reset) { + rc_client_event_t client_event; + memset(&client_event, 0, sizeof(client_event)); + client_event.type = RC_CLIENT_EVENT_RESET; + client->callbacks.event_handler(&client_event, client); + } + } + else { + /* if disabling hardcore, leaderboards will be deactivated. raise events for hiding trackers */ + rc_client_raise_pending_events(client, client->game); + } + } +} + +int rc_client_get_hardcore_enabled(const rc_client_t* client) +{ + return client && client->state.hardcore; +} + +void rc_client_set_unofficial_enabled(rc_client_t* client, int enabled) +{ + if (client) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Unofficial %s", enabled ? "enabled" : "disabled"); + client->state.unofficial_enabled = enabled ? 1 : 0; + } +} + +int rc_client_get_unofficial_enabled(const rc_client_t* client) +{ + return client && client->state.unofficial_enabled; +} + +void rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled) +{ + if (client) { + RC_CLIENT_LOG_INFO_FORMATTED(client, "Encore mode %s", enabled ? "enabled" : "disabled"); + client->state.encore_mode = enabled ? 1 : 0; + } +} + +int rc_client_get_encore_mode_enabled(const rc_client_t* client) +{ + return client && client->state.encore_mode; +} + +void rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled) +{ + if (client) { + if (!enabled && client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_LOCKED) { + RC_CLIENT_LOG_WARN(client, "Spectator mode cannot be disabled if it was enabled prior to loading game."); + return; + } + + RC_CLIENT_LOG_INFO_FORMATTED(client, "Spectator mode %s", enabled ? "enabled" : "disabled"); + client->state.spectator_mode = enabled ? RC_CLIENT_SPECTATOR_MODE_ON : RC_CLIENT_SPECTATOR_MODE_OFF; + } +} + +int rc_client_get_spectator_mode_enabled(const rc_client_t* client) +{ + return client && (client->state.spectator_mode == RC_CLIENT_SPECTATOR_MODE_OFF) ? 0 : 1; +} + +void rc_client_set_userdata(rc_client_t* client, void* userdata) +{ + if (client) + client->callbacks.client_data = userdata; +} + +void* rc_client_get_userdata(const rc_client_t* client) +{ + return client ? client->callbacks.client_data : NULL; +} + +void rc_client_set_host(const rc_client_t* client, const char* hostname) +{ + /* if empty, just pass NULL */ + if (hostname && !hostname[0]) + hostname = NULL; + + /* clear the image host so it'll use the custom host for images too */ + rc_api_set_image_host(NULL); + + /* set the custom host */ + if (hostname && client) { + RC_CLIENT_LOG_VERBOSE_FORMATTED(client, "Using host: %s", hostname); + } + rc_api_set_host(hostname); +} diff --git a/dep/rcheevos/src/rcheevos/rc_client_internal.h b/dep/rcheevos/src/rcheevos/rc_client_internal.h new file mode 100644 index 0000000000..9d0dd9f992 --- /dev/null +++ b/dep/rcheevos/src/rcheevos/rc_client_internal.h @@ -0,0 +1,290 @@ +#ifndef RC_CLIENT_INTERNAL_H +#define RC_CLIENT_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "rc_client.h" + +#include "rc_compat.h" +#include "rc_runtime.h" +#include "rc_runtime_types.h" + +typedef struct rc_client_callbacks_t { + rc_client_read_memory_func_t read_memory; + rc_client_event_handler_t event_handler; + rc_client_server_call_t server_call; + rc_client_message_callback_t log_call; + + void* client_data; +} rc_client_callbacks_t; + +struct rc_client_scheduled_callback_data_t; +typedef void (*rc_client_scheduled_callback_t)(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, clock_t now); + +typedef struct rc_client_scheduled_callback_data_t +{ + clock_t when; + unsigned related_id; + rc_client_scheduled_callback_t callback; + void* data; + struct rc_client_scheduled_callback_data_t* next; +} rc_client_scheduled_callback_data_t; + +void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* scheduled_callback); + +enum { + RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_NONE = 0, + RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_TRIGGERED = (1 << 1), + RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_SHOW = (1 << 2), + RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE = (1 << 3), + RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_UPDATE = (1 << 4) /* not a real event, just triggers update */ +}; + +typedef struct rc_client_achievement_info_t { + rc_client_achievement_t public_; + + rc_trigger_t* trigger; + uint8_t md5[16]; + + time_t unlock_time_hardcore; + time_t unlock_time_softcore; + + uint8_t pending_events; + + const char* author; + time_t created_time; + time_t updated_time; +} rc_client_achievement_info_t; + +enum { + RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE, + RC_CLIENT_PROGRESS_TRACKER_ACTION_SHOW, + RC_CLIENT_PROGRESS_TRACKER_ACTION_UPDATE, + RC_CLIENT_PROGRESS_TRACKER_ACTION_HIDE +}; + +typedef struct rc_client_progress_tracker_t { + rc_client_achievement_info_t* achievement; + float progress; + + rc_client_scheduled_callback_data_t* hide_callback; + uint8_t action; +} rc_client_progress_tracker_t; + +enum { + RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_NONE = 0, + RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_UPDATE = (1 << 1), + RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_SHOW = (1 << 2), + RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_HIDE = (1 << 3) +}; + +typedef struct rc_client_leaderboard_tracker_info_t { + rc_client_leaderboard_tracker_t public_; + struct rc_client_leaderboard_tracker_info_t* next; + int raw_value; + + uint32_t value_djb2; + + uint8_t format; + uint8_t pending_events; + uint8_t reference_count; + uint8_t value_from_hits; +} rc_client_leaderboard_tracker_info_t; + +enum { + RC_CLIENT_LEADERBOARD_PENDING_EVENT_NONE = 0, + RC_CLIENT_LEADERBOARD_PENDING_EVENT_STARTED = (1 << 1), + RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED = (1 << 2), + RC_CLIENT_LEADERBOARD_PENDING_EVENT_SUBMITTED = (1 << 3) +}; + +typedef struct rc_client_leaderboard_info_t { + rc_client_leaderboard_t public_; + + rc_lboard_t* lboard; + uint8_t md5[16]; + + rc_client_leaderboard_tracker_info_t* tracker; + + uint32_t value_djb2; + int value; + + uint8_t format; + uint8_t pending_events; + uint8_t bucket; + uint8_t hidden; +} rc_client_leaderboard_info_t; + +enum { + RC_CLIENT_SUBSET_PENDING_EVENT_NONE = 0, + RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT = (1 << 1), + RC_CLIENT_SUBSET_PENDING_EVENT_LEADERBOARD = (1 << 2) +}; + +enum { + RC_CLIENT_GAME_PENDING_EVENT_NONE = 0, + RC_CLIENT_GAME_PENDING_EVENT_LEADERBOARD_TRACKER = (1 << 1), + RC_CLIENT_GAME_PENDING_EVENT_UPDATE_ACTIVE_ACHIEVEMENTS = (1 << 2), + RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER = (1 << 3) +}; + +typedef struct rc_client_subset_info_t { + rc_client_subset_t public_; + + rc_client_achievement_info_t* achievements; + rc_client_leaderboard_info_t* leaderboards; + + struct rc_client_subset_info_t* next; + + const char* all_label; + const char* inactive_label; + const char* locked_label; + const char* unlocked_label; + const char* unofficial_label; + const char* unsupported_label; + + uint8_t active; + uint8_t mastery; + uint8_t pending_events; +} rc_client_subset_info_t; + +typedef struct rc_client_game_hash_t { + char hash[33]; + uint32_t game_id; + struct rc_client_game_hash_t* next; +} rc_client_game_hash_t; + +rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char* hash); + +typedef struct rc_client_media_hash_t { + rc_client_game_hash_t* game_hash; + struct rc_client_media_hash_t* next; + uint32_t path_djb2; +} rc_client_media_hash_t; + +typedef struct rc_client_game_info_t { + rc_client_game_t public_; + rc_client_leaderboard_tracker_info_t* leaderboard_trackers; + rc_client_progress_tracker_t progress_tracker; + + rc_client_subset_info_t* subsets; + + rc_client_media_hash_t* media_hash; + + rc_runtime_t runtime; + + uint32_t max_valid_address; + + uint8_t waiting_for_reset; + uint8_t pending_events; + + rc_api_buffer_t buffer; +} rc_client_game_info_t; + +enum { + RC_CLIENT_LOAD_STATE_NONE, + RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME, + RC_CLIENT_LOAD_STATE_AWAIT_LOGIN, + RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA, + RC_CLIENT_LOAD_STATE_STARTING_SESSION, + RC_CLIENT_LOAD_STATE_DONE, + RC_CLIENT_LOAD_STATE_UNKNOWN_GAME +}; + +enum { + RC_CLIENT_USER_STATE_NONE, + RC_CLIENT_USER_STATE_LOGIN_REQUESTED, + RC_CLIENT_USER_STATE_LOGGED_IN +}; + +enum { + RC_CLIENT_MASTERY_STATE_NONE, + RC_CLIENT_MASTERY_STATE_PENDING, + RC_CLIENT_MASTERY_STATE_SHOWN +}; + +enum { + RC_CLIENT_SPECTATOR_MODE_OFF, + RC_CLIENT_SPECTATOR_MODE_ON, + RC_CLIENT_SPECTATOR_MODE_LOCKED +}; + +struct rc_client_load_state_t; + +typedef struct rc_client_state_t { + rc_mutex_t mutex; + rc_api_buffer_t buffer; + + rc_client_scheduled_callback_data_t* scheduled_callbacks; + + uint8_t hardcore; + uint8_t encore_mode; + uint8_t spectator_mode; + uint8_t unofficial_enabled; + uint8_t log_level; + uint8_t user; + + struct rc_client_load_state_t* load; + rc_memref_t* processing_memref; + + rc_peek_t legacy_peek; +} rc_client_state_t; + +struct rc_client_t { + rc_client_game_info_t* game; + rc_client_game_hash_t* hashes; + + rc_client_user_t user; + + rc_client_callbacks_t callbacks; + + rc_client_state_t state; +}; + +#ifdef RC_NO_VARIADIC_MACROS + void RC_CLIENT_LOG_ERR_FORMATTED(const rc_client_t* client, const char* format, ...); + void RC_CLIENT_LOG_WARN_FORMATTED(const rc_client_t* client, const char* format, ...); + void RC_CLIENT_LOG_INFO_FORMATTED(const rc_client_t* client, const char* format, ...); + void RC_CLIENT_LOG_VERBOSE_FORMATTED(const rc_client_t* client, const char* format, ...); +#else + void rc_client_log_message_formatted(const rc_client_t* client, const char* format, ...); + #define RC_CLIENT_LOG_ERR_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR) rc_client_log_message_formatted(client, format, __VA_ARGS__); } + #define RC_CLIENT_LOG_WARN_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_WARN) rc_client_log_message_formatted(client, format, __VA_ARGS__); } + #define RC_CLIENT_LOG_INFO_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) rc_client_log_message_formatted(client, format, __VA_ARGS__); } + #define RC_CLIENT_LOG_VERBOSE_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_VERBOSE) rc_client_log_message_formatted(client, format, __VA_ARGS__); } +#endif + +void rc_client_log_message(const rc_client_t* client, const char* message); +#define RC_CLIENT_LOG_ERR(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR) rc_client_log_message(client, message); } +#define RC_CLIENT_LOG_WARN(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_WARN) rc_client_log_message(client, message); } +#define RC_CLIENT_LOG_INFO(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) rc_client_log_message(client, message); } +#define RC_CLIENT_LOG_VERBOSE(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_VERBOSE) rc_client_log_message(client, message); } + +/* internals pulled from runtime.c */ +void rc_runtime_checksum(const char* memaddr, unsigned char* md5); +int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref); +int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref); +/* end runtime.c internals */ + +/* helper functions for unit tests */ +struct rc_hash_iterator; +struct rc_hash_iterator* rc_client_get_load_state_hash_iterator(rc_client_t* client); +/* end helper functions for unit tests */ + +enum { + RC_CLIENT_LEGACY_PEEK_AUTO, + RC_CLIENT_LEGACY_PEEK_CONSTRUCTED, + RC_CLIENT_LEGACY_PEEK_LITTLE_ENDIAN_READS +}; + +void rc_client_set_legacy_peek(rc_client_t* client, int method); + +void rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata); + +#ifdef __cplusplus +} +#endif + +#endif /* RC_CLIENT_INTERNAL_H */ diff --git a/dep/rcheevos/src/rcheevos/rc_compat.h b/dep/rcheevos/src/rcheevos/rc_compat.h index 62c362f47e..e22f9b84e5 100644 --- a/dep/rcheevos/src/rcheevos/rc_compat.h +++ b/dep/rcheevos/src/rcheevos/rc_compat.h @@ -13,6 +13,8 @@ extern "C" { /* MinGW redefinitions */ +#define RC_NO_VARIADIC_MACROS 1 + #elif defined(_MSC_VER) /* Visual Studio redefinitions */ @@ -30,6 +32,9 @@ extern "C" { #elif __STDC_VERSION__ < 199901L /* C89 redefinitions */ +#define RC_C89_HELPERS 1 + +#define RC_NO_VARIADIC_MACROS 1 #ifndef snprintf extern int rc_snprintf(char* buffer, size_t size, const char* format, ...); @@ -53,6 +58,40 @@ extern "C" { #endif /* __STDC_VERSION__ < 199901L */ +#ifndef __STDC_WANT_SECURE_LIB__ + /* _CRT_SECURE_NO_WARNINGS redefinitions */ + #define strcpy_s(dest, sz, src) strcpy(dest, src) + #define sscanf_s sscanf + + /* NOTE: Microsoft secure gmtime_s parameter order differs from C11 standard */ + #include + extern struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer); + #define gmtime_s rc_gmtime_s +#endif + +#ifdef RC_NO_THREADS + typedef int rc_mutex_t; + + #define rc_mutex_init(mutex) + #define rc_mutex_destroy(mutex) + #define rc_mutex_lock(mutex) + #define rc_mutex_unlock(mutex) +#else + #ifdef _WIN32 + typedef struct rc_mutex_t { + void* handle; /* HANDLE is defined as "void*" */ + } rc_mutex_t; + #else + #include + typedef pthread_mutex_t rc_mutex_t; + #endif + + void rc_mutex_init(rc_mutex_t* mutex); + void rc_mutex_destroy(rc_mutex_t* mutex); + void rc_mutex_lock(rc_mutex_t* mutex); + void rc_mutex_unlock(rc_mutex_t* mutex); +#endif + #ifdef __cplusplus } #endif diff --git a/dep/rcheevos/src/rcheevos/rc_internal.h b/dep/rcheevos/src/rcheevos/rc_internal.h index c98f713094..e58ca53dea 100644 --- a/dep/rcheevos/src/rcheevos/rc_internal.h +++ b/dep/rcheevos/src/rcheevos/rc_internal.h @@ -36,6 +36,9 @@ RC_ALLOW_ALIGN(char) #define RC_ALLOC(t, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t))) #define RC_ALLOC_SCRATCH(t, p) ((t*)rc_alloc_scratch((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t))) +/* force alignment to 4 bytes on 32-bit systems, or 8 bytes on 64-bit systems */ +#define RC_ALIGN(n) (((n) + (sizeof(void*)-1)) & ~(sizeof(void*)-1)) + typedef struct rc_scratch_buffer { struct rc_scratch_buffer* next; int offset; @@ -83,6 +86,8 @@ typedef struct { } rc_typed_value_t; +#define RC_MEASURED_UNKNOWN 0xFFFFFFFF + typedef struct { rc_typed_value_t add_value;/* AddSource/SubSource */ int add_hits; /* AddHits */ @@ -130,6 +135,8 @@ void* rc_alloc(void* pointer, int* offset, int size, int alignment, rc_scratch_t void* rc_alloc_scratch(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset); char* rc_alloc_str(rc_parse_state_t* parse, const char* text, int length); +unsigned rc_djb2(const char* input); + rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, unsigned address, char size, char is_indirect); int rc_parse_memref(const char** memaddr, char* size, unsigned* address); void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud); @@ -138,6 +145,7 @@ unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_stat char rc_memref_shared_size(char size); unsigned rc_memref_mask(char size); void rc_transform_memref_value(rc_typed_value_t* value, char size); +unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud); void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse); int rc_trigger_state_active(int state); @@ -146,6 +154,22 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state); void rc_reset_condset(rc_condset_t* self); +enum { + RC_PROCESSING_COMPARE_DEFAULT = 0, + RC_PROCESSING_COMPARE_MEMREF_TO_CONST, + RC_PROCESSING_COMPARE_MEMREF_TO_DELTA, + RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF, + RC_PROCESSING_COMPARE_DELTA_TO_MEMREF, + RC_PROCESSING_COMPARE_DELTA_TO_CONST, + RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED, + RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED, + RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED, + RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED, + RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED, + RC_PROCESSING_COMPARE_ALWAYS_TRUE, + RC_PROCESSING_COMPARE_ALWAYS_FALSE +}; + rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, int is_indirect); int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state); void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state); @@ -154,10 +178,12 @@ int rc_condition_is_combining(const rc_condition_t* self); int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse); void rc_evaluate_operand(rc_typed_value_t* value, rc_operand_t* self, rc_eval_state_t* eval_state); int rc_operand_is_float_memref(const rc_operand_t* self); +int rc_operand_is_float(const rc_operand_t* self); void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse); int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L); void rc_reset_value(rc_value_t* self); +int rc_value_from_hits(rc_value_t* self); rc_value_t* rc_alloc_helper_variable(const char* memaddr, int memaddr_len, rc_parse_state_t* parse); void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L); @@ -165,6 +191,7 @@ void rc_typed_value_convert(rc_typed_value_t* value, char new_type); void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount); void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount); void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount); +void rc_typed_value_negate(rc_typed_value_t* value); int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper); void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref); diff --git a/dep/rcheevos/src/rcheevos/rc_libretro.c b/dep/rcheevos/src/rcheevos/rc_libretro.c new file mode 100644 index 0000000000..eab44ef1b1 --- /dev/null +++ b/dep/rcheevos/src/rcheevos/rc_libretro.c @@ -0,0 +1,816 @@ +/* This file provides a series of functions for integrating RetroAchievements with libretro. + * These functions will be called by a libretro frontend to validate certain expected behaviors + * and simplify mapping core data to the RAIntegration DLL. + * + * Originally designed to be shared between RALibretro and RetroArch, but will simplify + * integrating with any other frontends. + */ + +#include "rc_libretro.h" + +#include "rc_consoles.h" +#include "rc_compat.h" + +#include +#include + +/* internal helper functions in hash.c */ +extern void* rc_file_open(const char* path); +extern void rc_file_seek(void* file_handle, int64_t offset, int origin); +extern int64_t rc_file_tell(void* file_handle); +extern size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes); +extern void rc_file_close(void* file_handle); +extern int rc_path_compare_extension(const char* path, const char* ext); +extern int rc_hash_error(const char* message); + + +static rc_libretro_message_callback rc_libretro_verbose_message_callback = NULL; + +/* a value that starts with a comma is a CSV. + * if it starts with an exclamation point, it's everything but the provided value. + * if it starts with an exclamntion point followed by a comma, it's everything but the CSV values. + * values are case-insensitive */ +typedef struct rc_disallowed_core_settings_t +{ + const char* library_name; + const rc_disallowed_setting_t* disallowed_settings; +} rc_disallowed_core_settings_t; + +static const rc_disallowed_setting_t _rc_disallowed_bsnes_settings[] = { + { "bsnes_region", "pal" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_cap32_settings[] = { + { "cap32_autorun", "disabled" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings[] = { + { "dolphin_cheats_enabled", "enabled" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_duckstation_settings[] = { + { "duckstation_CDROM.LoadImagePatches", "true" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings[] = { + { "ecwolf-invulnerability", "enabled" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = { + { "fbneo-allow-patched-romsets", "enabled" }, + { "fbneo-cheat-*", "!,Disabled,0 - Disabled" }, + { "fbneo-dipswitch-*", "Universe BIOS*" }, + { "fbneo-neogeo-mode", "UNIBIOS" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = { + { "fceumm_region", ",PAL,Dendy" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_gpgx_settings[] = { + { "genesis_plus_gx_lock_on", ",action replay (pro),game genie" }, + { "genesis_plus_gx_region_detect", "pal" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_gpgx_wide_settings[] = { + { "genesis_plus_gx_wide_lock_on", ",action replay (pro),game genie" }, + { "genesis_plus_gx_wide_region_detect", "pal" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_mesen_settings[] = { + { "mesen_region", ",PAL,Dendy" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_mesen_s_settings[] = { + { "mesen-s_region", "PAL" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = { + { "pcsx_rearmed_region", "pal" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_picodrive_settings[] = { + { "picodrive_region", ",Europe,Japan PAL" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_ppsspp_settings[] = { + { "ppsspp_cheats", "enabled" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_quasi88_settings[] = { + { "q88_cpu_clock", ",1,2" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_smsplus_settings[] = { + { "smsplus_region", "pal" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = { + { "snes9x_gfx_clip", "disabled" }, + { "snes9x_gfx_transp", "disabled" }, + { "snes9x_layer_*", "disabled" }, + { "snes9x_region", "pal" }, + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_vice_settings[] = { + { "vice_autostart", "disabled" }, /* autostart dictates initial load and reset from menu */ + { "vice_reset", "!autostart" }, /* reset dictates behavior when pressing reset button (END) */ + { NULL, NULL } +}; + +static const rc_disallowed_setting_t _rc_disallowed_virtual_jaguar_settings[] = { + { "virtualjaguar_pal", "enabled" }, + { NULL, NULL } +}; + +static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = { + { "bsnes-mercury", _rc_disallowed_bsnes_settings }, + { "cap32", _rc_disallowed_cap32_settings }, + { "dolphin-emu", _rc_disallowed_dolphin_settings }, + { "DuckStation", _rc_disallowed_duckstation_settings }, + { "ecwolf", _rc_disallowed_ecwolf_settings }, + { "FCEUmm", _rc_disallowed_fceumm_settings }, + { "FinalBurn Neo", _rc_disallowed_fbneo_settings }, + { "Genesis Plus GX", _rc_disallowed_gpgx_settings }, + { "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings }, + { "Mesen", _rc_disallowed_mesen_settings }, + { "Mesen-S", _rc_disallowed_mesen_s_settings }, + { "PPSSPP", _rc_disallowed_ppsspp_settings }, + { "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings }, + { "PicoDrive", _rc_disallowed_picodrive_settings }, + { "QUASI88", _rc_disallowed_quasi88_settings }, + { "SMS Plus GX", _rc_disallowed_smsplus_settings }, + { "Snes9x", _rc_disallowed_snes9x_settings }, + { "VICE x64", _rc_disallowed_vice_settings }, + { "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings }, + { NULL, NULL } +}; + +static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char* value) { + char c1, c2; + while ((c1 = *test++)) { + if (tolower(c1) != tolower(c2 = *value++)) + return (c2 == '*'); + } + + return (*value == '\0'); +} + +static int rc_libretro_match_value(const char* val, const char* match) { + /* if value starts with a comma, it's a CSV list of potential matches */ + if (*match == ',') { + do { + const char* ptr = ++match; + size_t size; + + while (*match && *match != ',') + ++match; + + size = match - ptr; + if (val[size] == '\0') { + if (memcmp(ptr, val, size) == 0) { + return 1; + } + else { + char buffer[128]; + memcpy(buffer, ptr, size); + buffer[size] = '\0'; + if (rc_libretro_string_equal_nocase_wildcard(buffer, val)) + return 1; + } + } + } while (*match == ','); + + return 0; + } + + /* a leading exclamation point means the provided value(s) are not forbidden (are allowed) */ + if (*match == '!') + return !rc_libretro_match_value(val, &match[1]); + + /* just a single value, attempt to match it */ + return rc_libretro_string_equal_nocase_wildcard(val, match); +} + +int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value) { + const char* key; + size_t key_len; + + for (; disallowed_settings->setting; ++disallowed_settings) { + key = disallowed_settings->setting; + key_len = strlen(key); + + if (key[key_len - 1] == '*') { + if (memcmp(setting, key, key_len - 1) == 0) { + if (rc_libretro_match_value(value, disallowed_settings->value)) + return 0; + } + } + else { + if (memcmp(setting, key, key_len + 1) == 0) { + if (rc_libretro_match_value(value, disallowed_settings->value)) + return 0; + } + } + } + + return 1; +} + +const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name) { + const rc_disallowed_core_settings_t* core_filter = rc_disallowed_core_settings; + size_t library_name_length; + + if (!library_name || !library_name[0]) + return NULL; + + library_name_length = strlen(library_name) + 1; + while (core_filter->library_name) { + if (memcmp(core_filter->library_name, library_name, library_name_length) == 0) + return core_filter->disallowed_settings; + + ++core_filter; + } + + return NULL; +} + +typedef struct rc_disallowed_core_systems_t +{ + const char* library_name; + const int disallowed_consoles[4]; +} rc_disallowed_core_systems_t; + +static const rc_disallowed_core_systems_t rc_disallowed_core_systems[] = { + /* https://github.com/libretro/Mesen-S/issues/8 */ + { "Mesen-S", { RC_CONSOLE_GAMEBOY, RC_CONSOLE_GAMEBOY_COLOR, 0 }}, + { NULL, { 0 } } +}; + +int rc_libretro_is_system_allowed(const char* library_name, int console_id) { + const rc_disallowed_core_systems_t* core_filter = rc_disallowed_core_systems; + size_t library_name_length; + size_t i; + + if (!library_name || !library_name[0]) + return 1; + + library_name_length = strlen(library_name) + 1; + while (core_filter->library_name) { + if (memcmp(core_filter->library_name, library_name, library_name_length) == 0) { + for (i = 0; i < sizeof(core_filter->disallowed_consoles) / sizeof(core_filter->disallowed_consoles[0]); ++i) { + if (core_filter->disallowed_consoles[i] == console_id) + return 0; + } + break; + } + + ++core_filter; + } + + return 1; +} + +unsigned char* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, unsigned address, unsigned* avail) { + unsigned i; + + for (i = 0; i < regions->count; ++i) { + const size_t size = regions->size[i]; + if (address < size) { + if (regions->data[i] == NULL) + break; + + if (avail) + *avail = (unsigned)(size - address); + + return ®ions->data[i][address]; + } + + address -= (unsigned)size; + } + + if (avail) + *avail = 0; + + return NULL; +} + +unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address) { + return rc_libretro_memory_find_avail(regions, address, NULL); +} + +uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, unsigned address, + uint8_t* buffer, uint32_t num_bytes) { + unsigned i; + uint32_t avail; + + for (i = 0; i < regions->count; ++i) { + const size_t size = regions->size[i]; + if (address < size) { + if (regions->data[i] == NULL) + break; + + avail = (unsigned)(size - address); + if (avail < num_bytes) + return avail; + + memcpy(buffer, ®ions->data[i][address], num_bytes); + return num_bytes; + } + + address -= (unsigned)size; + } + + return 0; +} + +void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback) { + rc_libretro_verbose_message_callback = callback; +} + +static void rc_libretro_verbose(const char* message) { + if (rc_libretro_verbose_message_callback) + rc_libretro_verbose_message_callback(message); +} + +static const char* rc_memory_type_str(int type) { + switch (type) + { + case RC_MEMORY_TYPE_SAVE_RAM: + return "SRAM"; + case RC_MEMORY_TYPE_VIDEO_RAM: + return "VRAM"; + case RC_MEMORY_TYPE_UNUSED: + return "UNUSED"; + default: + break; + } + + return "SYSTEM RAM"; +} + +static void rc_libretro_memory_register_region(rc_libretro_memory_regions_t* regions, int type, + unsigned char* data, size_t size, const char* description) { + if (size == 0) + return; + + if (regions->count == (sizeof(regions->size) / sizeof(regions->size[0]))) { + rc_libretro_verbose("Too many memory memory regions to register"); + return; + } + + if (!data && regions->count > 0 && !regions->data[regions->count - 1]) { + /* extend null region */ + regions->size[regions->count - 1] += size; + } + else if (data && regions->count > 0 && + data == (regions->data[regions->count - 1] + regions->size[regions->count - 1])) { + /* extend non-null region */ + regions->size[regions->count - 1] += size; + } + else { + /* create new region */ + regions->data[regions->count] = data; + regions->size[regions->count] = size; + ++regions->count; + } + + regions->total_size += size; + + if (rc_libretro_verbose_message_callback) { + char message[128]; + snprintf(message, sizeof(message), "Registered 0x%04X bytes of %s at $%06X (%s)", (unsigned)size, + rc_memory_type_str(type), (unsigned)(regions->total_size - size), description); + rc_libretro_verbose_message_callback(message); + } +} + +static void rc_libretro_memory_init_without_regions(rc_libretro_memory_regions_t* regions, + rc_libretro_get_core_memory_info_func get_core_memory_info) { + /* no regions specified, assume system RAM followed by save RAM */ + char description[64]; + rc_libretro_core_memory_info_t info; + + snprintf(description, sizeof(description), "offset 0x%06x", 0); + + get_core_memory_info(RETRO_MEMORY_SYSTEM_RAM, &info); + if (info.size) + rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SYSTEM_RAM, info.data, info.size, description); + + get_core_memory_info(RETRO_MEMORY_SAVE_RAM, &info); + if (info.size) + rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SAVE_RAM, info.data, info.size, description); +} + +static const struct retro_memory_descriptor* rc_libretro_memory_get_descriptor(const struct retro_memory_map* mmap, unsigned real_address, size_t* offset) +{ + const struct retro_memory_descriptor* desc = mmap->descriptors; + const struct retro_memory_descriptor* end = desc + mmap->num_descriptors; + + for (; desc < end; desc++) { + if (desc->select == 0) { + /* if select is 0, attempt to explcitly match the address */ + if (real_address >= desc->start && real_address < desc->start + desc->len) { + *offset = real_address - desc->start; + return desc; + } + } + else { + /* otherwise, attempt to match the address by matching the select bits */ + /* address is in the block if (addr & select) == (start & select) */ + if (((desc->start ^ real_address) & desc->select) == 0) { + /* get the relative offset of the address from the start of the memory block */ + unsigned reduced_address = real_address - (unsigned)desc->start; + + /* remove any bits from the reduced_address that correspond to the bits in the disconnect + * mask and collapse the remaining bits. this code was copied from the mmap_reduce function + * in RetroArch. i'm not exactly sure how it works, but it does. */ + unsigned disconnect_mask = (unsigned)desc->disconnect; + while (disconnect_mask) { + const unsigned tmp = (disconnect_mask - 1) & ~disconnect_mask; + reduced_address = (reduced_address & tmp) | ((reduced_address >> 1) & ~tmp); + disconnect_mask = (disconnect_mask & (disconnect_mask - 1)) >> 1; + } + + /* calculate the offset within the descriptor */ + *offset = reduced_address; + + /* sanity check - make sure the descriptor is large enough to hold the target address */ + if (reduced_address < desc->len) + return desc; + } + } + } + + *offset = 0; + return NULL; +} + +static void rc_libretro_memory_init_from_memory_map(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap, + const rc_memory_regions_t* console_regions) { + char description[64]; + unsigned i; + unsigned char* region_start; + unsigned char* desc_start; + size_t desc_size; + size_t offset; + + for (i = 0; i < console_regions->num_regions; ++i) { + const rc_memory_region_t* console_region = &console_regions->region[i]; + size_t console_region_size = console_region->end_address - console_region->start_address + 1; + unsigned real_address = console_region->real_address; + unsigned disconnect_size = 0; + + while (console_region_size > 0) { + const struct retro_memory_descriptor* desc = rc_libretro_memory_get_descriptor(mmap, real_address, &offset); + if (!desc) { + if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) { + snprintf(description, sizeof(description), "Could not map region starting at $%06X", + real_address - console_region->real_address + console_region->start_address); + rc_libretro_verbose(description); + } + + if (disconnect_size && console_region_size > disconnect_size) { + rc_libretro_memory_register_region(regions, console_region->type, NULL, disconnect_size, "null filler"); + console_region_size -= disconnect_size; + real_address += disconnect_size; + disconnect_size = 0; + continue; + } + + rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler"); + break; + } + + snprintf(description, sizeof(description), "descriptor %u, offset 0x%06X%s", + (unsigned)(desc - mmap->descriptors) + 1, (int)offset, desc->ptr ? "" : " [no pointer]"); + + if (desc->ptr) { + desc_start = (uint8_t*)desc->ptr + desc->offset; + region_start = desc_start + offset; + } + else { + region_start = NULL; + } + + desc_size = desc->len - offset; + if (desc->disconnect && desc_size > desc->disconnect) { + /* if we need to extract a disconnect bit, the largest block we can read is up to + * the next time that bit flips */ + /* https://stackoverflow.com/questions/12247186/find-the-lowest-set-bit */ + disconnect_size = (desc->disconnect & -((int)desc->disconnect)); + desc_size = disconnect_size - (real_address & (disconnect_size - 1)); + } + + if (console_region_size > desc_size) { + if (desc_size == 0) { + if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) { + snprintf(description, sizeof(description), "Could not map region starting at $%06X", + real_address - console_region->real_address + console_region->start_address); + rc_libretro_verbose(description); + } + + rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler"); + console_region_size = 0; + } + else { + rc_libretro_memory_register_region(regions, console_region->type, region_start, desc_size, description); + console_region_size -= desc_size; + real_address += (unsigned)desc_size; + } + } + else { + rc_libretro_memory_register_region(regions, console_region->type, region_start, console_region_size, description); + console_region_size = 0; + } + } + } +} + +static unsigned rc_libretro_memory_console_region_to_ram_type(int region_type) { + switch (region_type) + { + case RC_MEMORY_TYPE_SAVE_RAM: + return RETRO_MEMORY_SAVE_RAM; + case RC_MEMORY_TYPE_VIDEO_RAM: + return RETRO_MEMORY_VIDEO_RAM; + default: + break; + } + + return RETRO_MEMORY_SYSTEM_RAM; +} + +static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regions_t* regions, + rc_libretro_get_core_memory_info_func get_core_memory_info, const rc_memory_regions_t* console_regions) { + char description[64]; + unsigned i, j; + rc_libretro_core_memory_info_t info; + size_t offset; + + for (i = 0; i < console_regions->num_regions; ++i) { + const rc_memory_region_t* console_region = &console_regions->region[i]; + const size_t console_region_size = console_region->end_address - console_region->start_address + 1; + const unsigned type = rc_libretro_memory_console_region_to_ram_type(console_region->type); + unsigned base_address = 0; + + for (j = 0; j <= i; ++j) { + const rc_memory_region_t* console_region2 = &console_regions->region[j]; + if (rc_libretro_memory_console_region_to_ram_type(console_region2->type) == type) { + base_address = console_region2->start_address; + break; + } + } + offset = console_region->start_address - base_address; + + get_core_memory_info(type, &info); + + if (offset < info.size) { + info.size -= offset; + + if (info.data) { + snprintf(description, sizeof(description), "offset 0x%06X", (int)offset); + info.data += offset; + } + else { + snprintf(description, sizeof(description), "null filler"); + } + } + else { + if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) { + snprintf(description, sizeof(description), "Could not map region starting at $%06X", console_region->start_address); + rc_libretro_verbose(description); + } + + info.data = NULL; + info.size = 0; + } + + if (console_region_size > info.size) { + /* want more than what is available, take what we can and null fill the rest */ + rc_libretro_memory_register_region(regions, console_region->type, info.data, info.size, description); + rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size - info.size, "null filler"); + } + else { + /* only take as much as we need */ + rc_libretro_memory_register_region(regions, console_region->type, info.data, console_region_size, description); + } + } +} + +int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap, + rc_libretro_get_core_memory_info_func get_core_memory_info, int console_id) { + const rc_memory_regions_t* console_regions = rc_console_memory_regions(console_id); + rc_libretro_memory_regions_t new_regions; + int has_valid_region = 0; + unsigned i; + + if (!regions) + return 0; + + memset(&new_regions, 0, sizeof(new_regions)); + + if (console_regions == NULL || console_regions->num_regions == 0) + rc_libretro_memory_init_without_regions(&new_regions, get_core_memory_info); + else if (mmap && mmap->num_descriptors != 0) + rc_libretro_memory_init_from_memory_map(&new_regions, mmap, console_regions); + else + rc_libretro_memory_init_from_unmapped_memory(&new_regions, get_core_memory_info, console_regions); + + /* determine if any valid regions were found */ + for (i = 0; i < new_regions.count; i++) { + if (new_regions.data[i]) { + has_valid_region = 1; + break; + } + } + + memcpy(regions, &new_regions, sizeof(*regions)); + return has_valid_region; +} + +void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions) { + memset(regions, 0, sizeof(*regions)); +} + +void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, + const char* m3u_path, rc_libretro_get_image_path_func get_image_path) { + char image_path[1024]; + char* m3u_contents; + char* ptr; + int64_t file_len; + void* file_handle; + int index = 0; + + memset(hash_set, 0, sizeof(*hash_set)); + + if (!rc_path_compare_extension(m3u_path, "m3u")) + return; + + file_handle = rc_file_open(m3u_path); + if (!file_handle) + { + rc_hash_error("Could not open playlist"); + return; + } + + rc_file_seek(file_handle, 0, SEEK_END); + file_len = rc_file_tell(file_handle); + rc_file_seek(file_handle, 0, SEEK_SET); + + m3u_contents = (char*)malloc((size_t)file_len + 1); + rc_file_read(file_handle, m3u_contents, (int)file_len); + m3u_contents[file_len] = '\0'; + + rc_file_close(file_handle); + + ptr = m3u_contents; + do + { + /* ignore whitespace */ + while (isspace((int)*ptr)) + ++ptr; + + if (*ptr == '#') + { + /* ignore comment unless it's the special SAVEDISK extension */ + if (memcmp(ptr, "#SAVEDISK:", 10) == 0) + { + /* get the path to the save disk from the frontend, assign it a bogus hash so + * it doesn't get hashed later */ + if (get_image_path(index, image_path, sizeof(image_path))) + { + const char save_disk_hash[33] = "[SAVE DISK]"; + rc_libretro_hash_set_add(hash_set, image_path, -1, save_disk_hash); + ++index; + } + } + } + else + { + /* non-empty line, tally a file */ + ++index; + } + + /* find the end of the line */ + while (*ptr && *ptr != '\n') + ++ptr; + + } while (*ptr); + + free(m3u_contents); + + if (hash_set->entries_count > 0) + { + /* at least one save disk was found. make sure the core supports the #SAVEDISK: extension by + * asking for the last expected disk. if it's not found, assume no #SAVEDISK: support */ + if (!get_image_path(index - 1, image_path, sizeof(image_path))) + hash_set->entries_count = 0; + } +} + +void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set) { + if (hash_set->entries) + free(hash_set->entries); + memset(hash_set, 0, sizeof(*hash_set)); +} + +static unsigned rc_libretro_djb2(const char* input) +{ + unsigned result = 5381; + char c; + + while ((c = *input++) != '\0') + result = ((result << 5) + result) + c; /* result = result * 33 + c */ + + return result; +} + +void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set, + const char* path, int game_id, const char hash[33]) { + const unsigned path_djb2 = (path != NULL) ? rc_libretro_djb2(path) : 0; + struct rc_libretro_hash_entry_t* entry = NULL; + struct rc_libretro_hash_entry_t* scan; + struct rc_libretro_hash_entry_t* stop = hash_set->entries + hash_set->entries_count;; + + if (path_djb2) + { + /* attempt to match the path */ + for (scan = hash_set->entries; scan < stop; ++scan) + { + if (scan->path_djb2 == path_djb2) + { + entry = scan; + break; + } + } + } + + if (!entry) + { + /* entry not found, allocate a new one */ + if (hash_set->entries_size == 0) + { + hash_set->entries_size = 4; + hash_set->entries = (struct rc_libretro_hash_entry_t*) + malloc(hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t)); + } + else if (hash_set->entries_count == hash_set->entries_size) + { + hash_set->entries_size += 4; + hash_set->entries = (struct rc_libretro_hash_entry_t*)realloc(hash_set->entries, + hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t)); + } + + entry = hash_set->entries + hash_set->entries_count++; + } + + /* update the entry */ + entry->path_djb2 = path_djb2; + entry->game_id = game_id; + memcpy(entry->hash, hash, sizeof(entry->hash)); +} + +const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path) +{ + const unsigned path_djb2 = rc_libretro_djb2(path); + struct rc_libretro_hash_entry_t* scan = hash_set->entries; + struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count; + for (; scan < stop; ++scan) + { + if (scan->path_djb2 == path_djb2) + return scan->hash; + } + + return NULL; +} + +int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash) +{ + struct rc_libretro_hash_entry_t* scan = hash_set->entries; + struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count; + for (; scan < stop; ++scan) + { + if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0) + return scan->game_id; + } + + return 0; +} diff --git a/dep/rcheevos/src/rcheevos/rc_libretro.h b/dep/rcheevos/src/rcheevos/rc_libretro.h new file mode 100644 index 0000000000..089631bae1 --- /dev/null +++ b/dep/rcheevos/src/rcheevos/rc_libretro.h @@ -0,0 +1,92 @@ +#ifndef RC_LIBRETRO_H +#define RC_LIBRETRO_H + +/* this file comes from the libretro repository, which is not an explicit submodule. + * the integration must set up paths appropriately to find it. */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/*****************************************************************************\ +| Disallowed Settings | +\*****************************************************************************/ + +typedef struct rc_disallowed_setting_t +{ + const char* setting; + const char* value; +} rc_disallowed_setting_t; + +const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name); +int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value); +int rc_libretro_is_system_allowed(const char* library_name, int console_id); + +/*****************************************************************************\ +| Memory Mapping | +\*****************************************************************************/ + +/* specifies a function to call for verbose logging */ +typedef void (*rc_libretro_message_callback)(const char*); +void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback); + +#define RC_LIBRETRO_MAX_MEMORY_REGIONS 32 +typedef struct rc_libretro_memory_regions_t +{ + unsigned char* data[RC_LIBRETRO_MAX_MEMORY_REGIONS]; + size_t size[RC_LIBRETRO_MAX_MEMORY_REGIONS]; + size_t total_size; + unsigned count; +} rc_libretro_memory_regions_t; + +typedef struct rc_libretro_core_memory_info_t +{ + unsigned char* data; + size_t size; +} rc_libretro_core_memory_info_t; + +typedef void (*rc_libretro_get_core_memory_info_func)(unsigned id, rc_libretro_core_memory_info_t* info); + +int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap, + rc_libretro_get_core_memory_info_func get_core_memory_info, int console_id); +void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions); + +unsigned char* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, unsigned address); +unsigned char* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, unsigned address, unsigned* avail); +uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, unsigned address, uint8_t* buffer, uint32_t num_bytes); + +/*****************************************************************************\ +| Disk Identification | +\*****************************************************************************/ + +typedef struct rc_libretro_hash_entry_t +{ + uint32_t path_djb2; + int game_id; + char hash[33]; +} rc_libretro_hash_entry_t; + +typedef struct rc_libretro_hash_set_t +{ + struct rc_libretro_hash_entry_t* entries; + uint16_t entries_count; + uint16_t entries_size; +} rc_libretro_hash_set_t; + +typedef int (*rc_libretro_get_image_path_func)(unsigned index, char* buffer, size_t buffer_size); + +void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set, + const char* m3u_path, rc_libretro_get_image_path_func get_image_path); +void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set); + +void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set, + const char* path, int game_id, const char hash[33]); +const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path); +int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash); + +#ifdef __cplusplus +} +#endif + +#endif /* RC_LIBRETRO_H */ diff --git a/dep/rcheevos/src/rcheevos/rc_validate.c b/dep/rcheevos/src/rcheevos/rc_validate.c new file mode 100644 index 0000000000..1751b890e5 --- /dev/null +++ b/dep/rcheevos/src/rcheevos/rc_validate.c @@ -0,0 +1,847 @@ +#include "rc_validate.h" + +#include "rc_compat.h" +#include "rc_consoles.h" +#include "rc_internal.h" + +#include +#include + +static int rc_validate_memref(const rc_memref_t* memref, char result[], const size_t result_size, int console_id, unsigned max_address) +{ + if (memref->address > max_address) { + snprintf(result, result_size, "Address %04X out of range (max %04X)", memref->address, max_address); + return 0; + } + + switch (console_id) { + case RC_CONSOLE_NINTENDO: + if (memref->address >= 0x0800 && memref->address <= 0x1FFF) { + snprintf(result, result_size, "Mirror RAM may not be exposed by emulator (address %04X)", memref->address); + return 0; + } + break; + + case RC_CONSOLE_GAMEBOY: + case RC_CONSOLE_GAMEBOY_COLOR: + if (memref->address >= 0xE000 && memref->address <= 0xFDFF) { + snprintf(result, result_size, "Echo RAM may not be exposed by emulator (address %04X)", memref->address); + return 0; + } + break; + + case RC_CONSOLE_PLAYSTATION: + if (memref->address <= 0xFFFF) { + snprintf(result, result_size, "Kernel RAM may not be initialized without real BIOS (address %04X)", memref->address); + return 0; + } + break; + } + + return 1; +} + +int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, unsigned max_address) +{ + while (memref) { + if (!rc_validate_memref(memref, result, result_size, 0, max_address)) + return 0; + + memref = memref->next; + } + + return 1; +} + +static unsigned rc_console_max_address(int console_id) +{ + const rc_memory_regions_t* memory_regions; + memory_regions = rc_console_memory_regions(console_id); + if (memory_regions && memory_regions->num_regions > 0) + return memory_regions->region[memory_regions->num_regions - 1].end_address; + + return 0xFFFFFFFF; +} + +int rc_validate_memrefs_for_console(const rc_memref_t* memref, char result[], const size_t result_size, int console_id) +{ + const unsigned max_address = rc_console_max_address(console_id); + while (memref) { + if (!rc_validate_memref(memref, result, result_size, console_id, max_address)) + return 0; + + memref = memref->next; + } + + return 1; +} + +static unsigned rc_max_value(const rc_operand_t* operand) +{ + if (operand->type == RC_OPERAND_CONST) + return operand->value.num; + + if (!rc_operand_is_memref(operand)) + return 0xFFFFFFFF; + + switch (operand->size) { + case RC_MEMSIZE_BIT_0: + case RC_MEMSIZE_BIT_1: + case RC_MEMSIZE_BIT_2: + case RC_MEMSIZE_BIT_3: + case RC_MEMSIZE_BIT_4: + case RC_MEMSIZE_BIT_5: + case RC_MEMSIZE_BIT_6: + case RC_MEMSIZE_BIT_7: + return 1; + + case RC_MEMSIZE_LOW: + case RC_MEMSIZE_HIGH: + return 0xF; + + case RC_MEMSIZE_BITCOUNT: + return 8; + + case RC_MEMSIZE_8_BITS: + return (operand->type == RC_OPERAND_BCD) ? 165 : 0xFF; + + case RC_MEMSIZE_16_BITS: + case RC_MEMSIZE_16_BITS_BE: + return (operand->type == RC_OPERAND_BCD) ? 16665 : 0xFFFF; + + case RC_MEMSIZE_24_BITS: + case RC_MEMSIZE_24_BITS_BE: + return (operand->type == RC_OPERAND_BCD) ? 1666665 : 0xFFFFFF; + + default: + return (operand->type == RC_OPERAND_BCD) ? 166666665 : 0xFFFFFFFF; + } +} + +static unsigned rc_scale_value(unsigned value, char oper, const rc_operand_t* operand) +{ + switch (oper) { + case RC_OPERATOR_MULT: + { + unsigned long long scaled = ((unsigned long long)value) * rc_max_value(operand); + if (scaled > 0xFFFFFFFF) + return 0xFFFFFFFF; + + return (unsigned)scaled; + } + + case RC_OPERATOR_DIV: + { + const unsigned min_val = (operand->type == RC_OPERAND_CONST) ? operand->value.num : 1; + return value / min_val; + } + + case RC_OPERATOR_AND: + return rc_max_value(operand); + + case RC_OPERATOR_XOR: + return value | rc_max_value(operand); + + default: + return value; + } +} + +static int rc_validate_get_condition_index(const rc_condset_t* condset, const rc_condition_t* condition) +{ + int index = 1; + const rc_condition_t* scan; + for (scan = condset->conditions; scan != NULL; scan = scan->next) + { + if (scan == condition) + return index; + + ++index; + } + + return 0; +} + +static int rc_validate_range(unsigned min_val, unsigned max_val, char oper, unsigned max, char result[], const size_t result_size) +{ + switch (oper) { + case RC_OPERATOR_AND: + if (min_val > max) { + snprintf(result, result_size, "Mask has more bits than source"); + return 0; + } + else if (min_val == 0 && max_val == 0) { + snprintf(result, result_size, "Result of mask always 0"); + return 0; + } + break; + + case RC_OPERATOR_EQ: + if (min_val > max) { + snprintf(result, result_size, "Comparison is never true"); + return 0; + } + break; + + case RC_OPERATOR_NE: + if (min_val > max) { + snprintf(result, result_size, "Comparison is always true"); + return 0; + } + break; + + case RC_OPERATOR_GE: + if (min_val > max) { + snprintf(result, result_size, "Comparison is never true"); + return 0; + } + if (max_val == 0) { + snprintf(result, result_size, "Comparison is always true"); + return 0; + } + break; + + case RC_OPERATOR_GT: + if (min_val >= max) { + snprintf(result, result_size, "Comparison is never true"); + return 0; + } + break; + + case RC_OPERATOR_LE: + if (min_val >= max) { + snprintf(result, result_size, "Comparison is always true"); + return 0; + } + break; + + case RC_OPERATOR_LT: + if (min_val > max) { + snprintf(result, result_size, "Comparison is always true"); + return 0; + } + if (max_val == 0) { + snprintf(result, result_size, "Comparison is never true"); + return 0; + } + break; + } + + return 1; +} + +int rc_validate_condset_internal(const rc_condset_t* condset, char result[], const size_t result_size, int console_id, unsigned max_address) +{ + const rc_condition_t* cond; + char buffer[128]; + unsigned max_val; + int index = 1; + unsigned long long add_source_max = 0; + int in_add_hits = 0; + int in_add_address = 0; + int is_combining = 0; + + if (!condset) { + *result = '\0'; + return 1; + } + + for (cond = condset->conditions; cond; cond = cond->next, ++index) { + unsigned max = rc_max_value(&cond->operand1); + const int is_memref1 = rc_operand_is_memref(&cond->operand1); + const int is_memref2 = rc_operand_is_memref(&cond->operand2); + + if (!in_add_address) { + if (is_memref1 && !rc_validate_memref(cond->operand1.value.memref, buffer, sizeof(buffer), console_id, max_address)) { + snprintf(result, result_size, "Condition %d: %s", index, buffer); + return 0; + } + if (is_memref2 && !rc_validate_memref(cond->operand2.value.memref, buffer, sizeof(buffer), console_id, max_address)) { + snprintf(result, result_size, "Condition %d: %s", index, buffer); + return 0; + } + } + else { + in_add_address = 0; + } + + switch (cond->type) { + case RC_CONDITION_ADD_SOURCE: + max = rc_scale_value(max, cond->oper, &cond->operand2); + add_source_max += max; + is_combining = 1; + continue; + + case RC_CONDITION_SUB_SOURCE: + max = rc_scale_value(max, cond->oper, &cond->operand2); + if (add_source_max < max) /* potential underflow - may be expected */ + add_source_max = 0xFFFFFFFF; + is_combining = 1; + continue; + + case RC_CONDITION_ADD_ADDRESS: + if (cond->operand1.type == RC_OPERAND_DELTA || cond->operand1.type == RC_OPERAND_PRIOR) { + snprintf(result, result_size, "Condition %d: Using pointer from previous frame", index); + return 0; + } + in_add_address = 1; + is_combining = 1; + continue; + + case RC_CONDITION_ADD_HITS: + case RC_CONDITION_SUB_HITS: + in_add_hits = 1; + is_combining = 1; + break; + + case RC_CONDITION_AND_NEXT: + case RC_CONDITION_OR_NEXT: + case RC_CONDITION_RESET_NEXT_IF: + is_combining = 1; + break; + + default: + if (in_add_hits) { + if (cond->required_hits == 0) { + snprintf(result, result_size, "Condition %d: Final condition in AddHits chain must have a hit target", index); + return 0; + } + + in_add_hits = 0; + } + + is_combining = 0; + break; + } + + /* if we're in an add source chain, check for overflow */ + if (add_source_max) { + const unsigned long long overflow = add_source_max + max; + if (overflow > 0xFFFFFFFFUL) + max = 0xFFFFFFFF; + else + max += (unsigned)add_source_max; + } + + /* check for comparing two differently sized memrefs */ + max_val = rc_max_value(&cond->operand2); + if (max_val != max && add_source_max == 0 && is_memref1 && is_memref2) { + snprintf(result, result_size, "Condition %d: Comparing different memory sizes", index); + return 0; + } + + /* if either side is a memref, or there's a running add source chain, check for impossible comparisons */ + if (is_memref1 || is_memref2 || add_source_max) { + const size_t prefix_length = snprintf(result, result_size, "Condition %d: ", index); + + unsigned min_val; + switch (cond->operand2.type) { + case RC_OPERAND_CONST: + min_val = cond->operand2.value.num; + break; + + case RC_OPERAND_FP: + min_val = (int)cond->operand2.value.dbl; + + /* cannot compare an integer memory reference to a non-integral floating point value */ + /* assert: is_memref1 (because operand2==FP means !is_memref2) */ + if (!add_source_max && !rc_operand_is_float_memref(&cond->operand1) && + (float)min_val != cond->operand2.value.dbl) { + snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true"); + return 0; + } + + break; + + default: + min_val = 0; + + /* cannot compare an integer memory reference to a non-integral floating point value */ + /* assert: is_memref2 (because operand1==FP means !is_memref1) */ + if (cond->operand1.type == RC_OPERAND_FP && !add_source_max && !rc_operand_is_float_memref(&cond->operand2) && + (float)((int)cond->operand1.value.dbl) != cond->operand1.value.dbl) { + snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true"); + return 0; + } + + break; + } + + if (rc_operand_is_float(&cond->operand2) && rc_operand_is_float(&cond->operand1)) { + /* both sides are floats, don't validate range*/ + } else if (!rc_validate_range(min_val, max_val, cond->oper, max, result + prefix_length, result_size - prefix_length)) { + return 0; + } + } + + add_source_max = 0; + } + + if (is_combining) { + snprintf(result, result_size, "Final condition type expects another condition to follow"); + return 0; + } + + *result = '\0'; + return 1; +} + +int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, unsigned max_address) +{ + return rc_validate_condset_internal(condset, result, result_size, 0, max_address); +} + +int rc_validate_condset_for_console(const rc_condset_t* condset, char result[], const size_t result_size, int console_id) +{ + const unsigned max_address = rc_console_max_address(console_id); + return rc_validate_condset_internal(condset, result, result_size, console_id, max_address); +} + +static int rc_validate_is_combining_condition(const rc_condition_t* condition) +{ + switch (condition->type) + { + case RC_CONDITION_ADD_ADDRESS: + case RC_CONDITION_ADD_HITS: + case RC_CONDITION_ADD_SOURCE: + case RC_CONDITION_AND_NEXT: + case RC_CONDITION_OR_NEXT: + case RC_CONDITION_RESET_NEXT_IF: + case RC_CONDITION_SUB_HITS: + case RC_CONDITION_SUB_SOURCE: + return 1; + + default: + return 0; + } +} + +static const rc_condition_t* rc_validate_next_non_combining_condition(const rc_condition_t* condition) +{ + int is_combining = rc_validate_is_combining_condition(condition); + for (condition = condition->next; condition != NULL; condition = condition->next) + { + if (rc_validate_is_combining_condition(condition)) + is_combining = 1; + else if (is_combining) + is_combining = 0; + else + return condition; + } + + return NULL; +} + +static int rc_validate_get_opposite_comparison(int oper) +{ + switch (oper) + { + case RC_OPERATOR_EQ: return RC_OPERATOR_NE; + case RC_OPERATOR_NE: return RC_OPERATOR_EQ; + case RC_OPERATOR_LT: return RC_OPERATOR_GE; + case RC_OPERATOR_LE: return RC_OPERATOR_GT; + case RC_OPERATOR_GT: return RC_OPERATOR_LE; + case RC_OPERATOR_GE: return RC_OPERATOR_LT; + default: return oper; + } +} + +static const rc_operand_t* rc_validate_get_comparison(const rc_condition_t* condition, int* comparison, unsigned* value) +{ + if (rc_operand_is_memref(&condition->operand1)) + { + if (condition->operand2.type != RC_OPERAND_CONST) + return NULL; + + *comparison = condition->oper; + *value = condition->operand2.value.num; + return &condition->operand1; + } + + if (condition->operand1.type != RC_OPERAND_CONST) + return NULL; + + if (!rc_operand_is_memref(&condition->operand2)) + return NULL; + + *comparison = rc_validate_get_opposite_comparison(condition->oper); + *value = condition->operand1.value.num; + return &condition->operand2; +} + +enum { + RC_OVERLAP_NONE = 0, + RC_OVERLAP_CONFLICTING, + RC_OVERLAP_REDUNDANT, + RC_OVERLAP_DEFER +}; + +static int rc_validate_comparison_overlap(int comparison1, unsigned value1, int comparison2, unsigned value2) +{ + /* NOTE: this only cares if comp2 conflicts with comp1. + * If comp1 conflicts with comp2, we'll catch that later (return RC_OVERLAP_NONE for now) */ + switch (comparison2) + { + case RC_OPERATOR_EQ: + switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */ + { /* value1 = value2 value1 < value2 value1 > value2 */ + case RC_OPERATOR_EQ: /* a == 1 && a == 1 | a == 1 && a == 2 | a == 2 && a == 1 */ + /* redundant conflict conflict */ + return (value1 == value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING; + case RC_OPERATOR_LE: /* a <= 1 && a == 1 | a <= 1 && a == 2 | a <= 2 && a == 1 */ + /* defer conflict defer */ + return (value1 < value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER; + case RC_OPERATOR_GE: /* a >= 1 && a == 1 | a >= 1 && a == 2 | a >= 2 && a == 1 */ + /* defer defer conflict */ + return (value1 > value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER; + case RC_OPERATOR_NE: /* a != 1 && a == 1 | a != 1 && a == 2 | a != 2 && a == 1 */ + /* conflict defer defer */ + return (value1 == value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER; + case RC_OPERATOR_LT: /* a < 1 && a == 1 | a < 1 && a == 2 | a < 2 && a == 1 */ + /* conflict conflict defer */ + return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER; + case RC_OPERATOR_GT: /* a > 1 && a == 1 | a > 1 && a == 2 | a > 2 && a == 1 */ + /* conflict defer conflict */ + return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER; + } + break; + + case RC_OPERATOR_NE: + switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */ + { /* value1 = value2 value1 < value2 value1 > value2 */ + case RC_OPERATOR_EQ: /* a == 1 && a != 1 | a == 1 && a != 2 | a == 2 && a != 1 */ + /* conflict redundant redundant */ + return (value1 == value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_REDUNDANT; + case RC_OPERATOR_LE: /* a <= 1 && a != 1 | a <= 1 && a != 2 | a <= 2 && a != 1 */ + /* none redundant none */ + return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE; + case RC_OPERATOR_GE: /* a >= 1 && a != 1 | a >= 1 && a != 2 | a >= 2 && a != 1 */ + /* none none redundant */ + return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE; + case RC_OPERATOR_NE: /* a != 1 && a != 1 | a != 1 && a != 2 | a != 2 && a != 1 */ + /* redundant none none */ + return (value1 == value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE; + case RC_OPERATOR_LT: /* a < 1 && a != 1 | a < 1 && a != 2 | a < 2 && a != 1 */ + /* redundant redundant none */ + return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE; + case RC_OPERATOR_GT: /* a > 1 && a != 1 | a > 1 && a != 2 | a > 2 && a != 1 */ + /* redundant none redundant */ + return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE; + } + break; + + case RC_OPERATOR_LT: + switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */ + { /* value1 = value2 value1 < value2 value1 > value2 */ + case RC_OPERATOR_EQ: /* a == 1 && a < 1 | a == 1 && a < 2 | a == 2 && a < 1 */ + /* conflict redundant conflict */ + return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING; + case RC_OPERATOR_LE: /* a <= 1 && a < 1 | a <= 1 && a < 2 | a <= 2 && a < 1 */ + /* defer redundant defer */ + return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER; + case RC_OPERATOR_GE: /* a >= 1 && a < 1 | a >= 1 && a < 2 | a >= 2 && a < 1 */ + /* conflict none conflict */ + return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE; + case RC_OPERATOR_NE: /* a != 1 && a < 1 | a != 1 && a < 2 | a != 2 && a < 1 */ + /* defer none defer */ + return (value1 >= value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE; + case RC_OPERATOR_LT: /* a < 1 && a < 1 | a < 1 && a < 2 | a < 2 && a < 1 */ + /* redundant redundant defer */ + return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER; + case RC_OPERATOR_GT: /* a > 1 && a < 1 | a > 1 && a < 2 | a > 2 && a < 1 */ + /* conflict none conflict */ + return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE; + } + break; + + case RC_OPERATOR_LE: + switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */ + { /* value1 = value2 value1 < value2 value1 > value2 */ + case RC_OPERATOR_EQ: /* a == 1 && a <= 1 | a == 1 && a <= 2 | a == 2 && a <= 1 */ + /* redundant redundant conflict */ + return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING; + case RC_OPERATOR_LE: /* a <= 1 && a <= 1 | a <= 1 && a <= 2 | a <= 2 && a <= 1 */ + /* redundant redundant defer */ + return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER; + case RC_OPERATOR_GE: /* a >= 1 && a <= 1 | a >= 1 && a <= 2 | a >= 2 && a <= 1 */ + /* none none conflict */ + return (value1 > value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE; + case RC_OPERATOR_NE: /* a != 1 && a <= 1 | a != 1 && a <= 2 | a != 2 && a <= 1 */ + /* none none defer */ + return (value1 > value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE; + case RC_OPERATOR_LT: /* a < 1 && a <= 1 | a < 1 && a <= 2 | a < 2 && a <= 1 */ + /* redundant redundant defer */ + return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER; + case RC_OPERATOR_GT: /* a > 1 && a <= 1 | a > 1 && a <= 2 | a > 2 && a <= 1 */ + /* conflict none conflict */ + return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE; + } + break; + + case RC_OPERATOR_GT: + switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */ + { /* value1 = value2 value1 < value2 value1 > value2 */ + case RC_OPERATOR_EQ: /* a == 1 && a > 1 | a == 1 && a > 2 | a == 2 && a > 1 */ + /* conflict conflict redundant */ + return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING; + case RC_OPERATOR_LE: /* a <= 1 && a > 1 | a <= 1 && a > 2 | a <= 2 && a > 1 */ + /* conflict conflict defer */ + return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER; + case RC_OPERATOR_GE: /* a >= 1 && a > 1 | a >= 1 && a > 2 | a >= 2 && a > 1 */ + /* defer defer redundant */ + return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER; + case RC_OPERATOR_NE: /* a != 1 && a > 1 | a != 1 && a > 2 | a != 2 && a > 1 */ + /* defer defer none */ + return (value1 <= value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE; + case RC_OPERATOR_LT: /* a < 1 && a > 1 | a < 1 && a > 2 | a < 2 && a > 1 */ + /* conflict conflict none */ + return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE; + case RC_OPERATOR_GT: /* a > 1 && a > 1 | a > 1 && a > 2 | a > 2 && a > 1 */ + /* redundant defer redundant */ + return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER; + } + break; + + case RC_OPERATOR_GE: + switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */ + { /* value1 = value2 value1 < value2 value1 > value2 */ + case RC_OPERATOR_EQ: /* a == 1 && a >= 1 | a == 1 && a >= 2 | a == 2 && a >= 1 */ + /* redundant conflict redundant */ + return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING; + case RC_OPERATOR_LE: /* a <= 1 && a >= 1 | a <= 1 && a >= 2 | a <= 2 && a >= 1 */ + /* none conflict none */ + return (value1 < value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE; + case RC_OPERATOR_GE: /* a >= 1 && a >= 1 | a >= 1 && a >= 2 | a >= 2 && a >= 1 */ + /* redundant redundant defer */ + return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER; + case RC_OPERATOR_NE: /* a != 1 && a >= 1 | a != 1 && a >= 2 | a != 2 && a >= 1 */ + /* none defer none */ + return (value1 < value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE; + case RC_OPERATOR_LT: /* a < 1 && a >= 1 | a < 1 && a >= 2 | a < 2 && a >= 1 */ + /* conflict conflict none */ + return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE; + case RC_OPERATOR_GT: /* a > 1 && a >= 1 | a > 1 && a >= 2 | a > 2 && a >= 1 */ + /* redundant defer redundant */ + return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER; + } + break; + } + + return RC_OVERLAP_NONE; +} + +static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, const rc_condset_t* compare_conditions, + const char* prefix, const char* compare_prefix, char result[], const size_t result_size) +{ + int comparison1, comparison2; + unsigned value1, value2; + const rc_operand_t* operand1; + const rc_operand_t* operand2; + const rc_condition_t* compare_condition; + const rc_condition_t* condition; + int overlap; + + /* empty group */ + if (conditions == NULL || compare_conditions == NULL) + return 1; + + /* outer loop is the source conditions */ + for (condition = conditions->conditions; condition != NULL; + condition = rc_validate_next_non_combining_condition(condition)) + { + /* hits can be captured at any time, so any potential conflict will not be conflicting at another time */ + if (condition->required_hits) + continue; + + operand1 = rc_validate_get_comparison(condition, &comparison1, &value1); + if (!operand1) + continue; + + switch (condition->type) + { + case RC_CONDITION_PAUSE_IF: + if (conditions != compare_conditions) + break; + /* fallthrough */ + case RC_CONDITION_RESET_IF: + comparison1 = rc_validate_get_opposite_comparison(comparison1); + break; + default: + if (rc_validate_is_combining_condition(condition)) + continue; + break; + } + + /* inner loop is the potentially conflicting conditions */ + for (compare_condition = compare_conditions->conditions; compare_condition != NULL; + compare_condition = rc_validate_next_non_combining_condition(compare_condition)) + { + if (compare_condition == condition) + continue; + + if (compare_condition->required_hits) + continue; + + operand2 = rc_validate_get_comparison(compare_condition, &comparison2, &value2); + if (!operand2 || operand2->value.memref->address != operand1->value.memref->address || + operand2->size != operand1->size || operand2->type != operand1->type) + continue; + + switch (compare_condition->type) + { + case RC_CONDITION_PAUSE_IF: + if (conditions != compare_conditions) + break; + /* fallthrough */ + case RC_CONDITION_RESET_IF: + comparison2 = rc_validate_get_opposite_comparison(comparison2); + break; + default: + if (rc_validate_is_combining_condition(compare_condition)) + continue; + break; + } + + overlap = rc_validate_comparison_overlap(comparison1, value1, comparison2, value2); + switch (overlap) + { + case RC_OVERLAP_CONFLICTING: + if (compare_condition->type == RC_CONDITION_PAUSE_IF || condition->type == RC_CONDITION_PAUSE_IF) + { + /* ignore PauseIf conflicts between groups, unless both conditions are PauseIfs */ + if (conditions != compare_conditions && compare_condition->type != condition->type) + continue; + } + break; + + case RC_OVERLAP_REDUNDANT: + if (prefix != compare_prefix && strcmp(compare_prefix, "Core") == 0) + { + /* if the alt condition is more restrictive than the core condition, ignore it */ + if (rc_validate_comparison_overlap(comparison2, value2, comparison1, value1) != RC_OVERLAP_REDUNDANT) + continue; + } + + if (compare_condition->type == RC_CONDITION_PAUSE_IF || condition->type == RC_CONDITION_PAUSE_IF) + { + /* ignore PauseIf redundancies between groups */ + if (conditions != compare_conditions) + continue; + + /* if the PauseIf is less restrictive than the other condition, it's just a guard. ignore it */ + if (rc_validate_comparison_overlap(comparison2, value2, comparison1, value1) != RC_OVERLAP_REDUNDANT) + continue; + + /* PauseIf redundant with ResetIf is a conflict (both are inverted comparisons) */ + if (compare_condition->type == RC_CONDITION_RESET_IF || condition->type == RC_CONDITION_RESET_IF) + overlap = RC_OVERLAP_CONFLICTING; + } + else if (compare_condition->type == RC_CONDITION_RESET_IF && condition->type != RC_CONDITION_RESET_IF) + { + /* only ever report the redundancy on the non-ResetIf condition. The ResetIf is allowed to + * fire when the non-ResetIf condition is not true. */ + continue; + } + else if (compare_condition->type == RC_CONDITION_MEASURED_IF || condition->type == RC_CONDITION_MEASURED_IF) + { + /* ignore MeasuredIf redundancies between groups */ + if (conditions != compare_conditions) + continue; + + if (compare_condition->type == RC_CONDITION_MEASURED_IF && condition->type != RC_CONDITION_MEASURED_IF) + { + /* only ever report the redundancy on the non-MeasuredIf condition. The MeasuredIf provides + * additional functionality. */ + continue; + } + } + else if (compare_condition->type == RC_CONDITION_TRIGGER || condition->type == RC_CONDITION_TRIGGER) + { + /* Trigger is allowed to be redundant with non-trigger conditions as there may be limits that start a + * challenge that are furhter reduced for the completion of the challenge */ + if (compare_condition->type != condition->type) + continue; + } + break; + + default: + continue; + } + + if (compare_prefix && *compare_prefix) + { + snprintf(result, result_size, "%s Condition %d: %s with %s Condition %d", + compare_prefix, rc_validate_get_condition_index(compare_conditions, compare_condition), + (overlap == RC_OVERLAP_REDUNDANT) ? "Redundant" : "Conflicts", + prefix, rc_validate_get_condition_index(conditions, condition)); + } + else + { + snprintf(result, result_size, "Condition %d: %s with Condition %d", + rc_validate_get_condition_index(compare_conditions, compare_condition), + (overlap == RC_OVERLAP_REDUNDANT) ? "Redundant" : "Conflicts", + rc_validate_get_condition_index(conditions, condition)); + } + return 0; + } + } + + return 1; +} + +static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result[], const size_t result_size, int console_id, unsigned max_address) +{ + const rc_condset_t* alt; + int index; + + if (!trigger->alternative) + { + if (!rc_validate_condset_internal(trigger->requirement, result, result_size, console_id, max_address)) + return 0; + + return rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "", "", result, result_size); + } + + snprintf(result, result_size, "Core "); + if (!rc_validate_condset_internal(trigger->requirement, result + 5, result_size - 5, console_id, max_address)) + return 0; + + /* compare core to itself */ + if (!rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "Core", "Core", result, result_size)) + return 0; + + index = 1; + for (alt = trigger->alternative; alt; alt = alt->next, ++index) { + char altname[16]; + const size_t prefix_length = snprintf(result, result_size, "Alt%d ", index); + if (!rc_validate_condset_internal(alt, result + prefix_length, result_size - prefix_length, console_id, max_address)) + return 0; + + /* compare alt to itself */ + snprintf(altname, sizeof(altname), "Alt%d", index); + if (!rc_validate_conflicting_conditions(alt, alt, altname, altname, result, result_size)) + return 0; + + /* compare alt to core */ + if (!rc_validate_conflicting_conditions(trigger->requirement, alt, "Core", altname, result, result_size)) + return 0; + + /* compare core to alt */ + if (!rc_validate_conflicting_conditions(alt, trigger->requirement, altname, "Core", result, result_size)) + return 0; + } + + *result = '\0'; + return 1; +} + +int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, unsigned max_address) +{ + return rc_validate_trigger_internal(trigger, result, result_size, 0, max_address); +} + +int rc_validate_trigger_for_console(const rc_trigger_t* trigger, char result[], const size_t result_size, int console_id) +{ + const unsigned max_address = rc_console_max_address(console_id); + return rc_validate_trigger_internal(trigger, result, result_size, console_id, max_address); +} diff --git a/dep/rcheevos/src/rcheevos/rc_validate.h b/dep/rcheevos/src/rcheevos/rc_validate.h new file mode 100644 index 0000000000..ba7df325ec --- /dev/null +++ b/dep/rcheevos/src/rcheevos/rc_validate.h @@ -0,0 +1,26 @@ +#ifndef RC_VALIDATE_H +#define RC_VALIDATE_H + +#include "rc_runtime_types.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, unsigned max_address); + +int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, unsigned max_address); +int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, unsigned max_address); + +int rc_validate_memrefs_for_console(const rc_memref_t* memref, char result[], const size_t result_size, int console_id); + +int rc_validate_condset_for_console(const rc_condset_t* condset, char result[], const size_t result_size, int console_id); +int rc_validate_trigger_for_console(const rc_trigger_t* trigger, char result[], const size_t result_size, int console_id); + +#ifdef __cplusplus +} +#endif + +#endif /* RC_VALIDATE_H */ diff --git a/dep/rcheevos/src/rcheevos/rc_version.h b/dep/rcheevos/src/rcheevos/rc_version.h new file mode 100644 index 0000000000..cdf857d5f5 --- /dev/null +++ b/dep/rcheevos/src/rcheevos/rc_version.h @@ -0,0 +1,29 @@ +#ifndef RC_VERSION_H +#define RC_VERSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define RCHEEVOS_VERSION_MAJOR 10 +#define RCHEEVOS_VERSION_MINOR 7 +#define RCHEEVOS_VERSION_PATCH 0 + +#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch) +#define RCHEEVOS_VERSION RCHEEVOS_MAKE_VERSION(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR, RCHEEVOS_VERSION_PATCH) + +#define RCHEEVOS_MAKE_STRING(num) #num +#define RCHEEVOS_MAKE_VERSION_STRING(major, minor, patch) RCHEEVOS_MAKE_STRING(major) "." RCHEEVOS_MAKE_STRING(minor) "." RCHEEVOS_MAKE_STRING(patch) +#define RCHEEVOS_MAKE_VERSION_STRING_SHORT(major, minor) RCHEEVOS_MAKE_STRING(major) "." RCHEEVOS_MAKE_STRING(minor) + +#if RCHEEVOS_VERSION_PATCH > 0 + #define RCHEEVOS_VERSION_STRING RCHEEVOS_MAKE_VERSION_STRING(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR, RCHEEVOS_VERSION_PATCH) +#else + #define RCHEEVOS_VERSION_STRING RCHEEVOS_MAKE_VERSION_STRING_SHORT(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* RC_VERSION_H */ diff --git a/dep/rcheevos/src/rcheevos/richpresence.c b/dep/rcheevos/src/rcheevos/richpresence.c index 68d54490c2..3ad6bd991b 100644 --- a/dep/rcheevos/src/rcheevos/richpresence.c +++ b/dep/rcheevos/src/rcheevos/richpresence.c @@ -280,7 +280,6 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo { rc_richpresence_lookup_item_t** items; rc_scratch_buffer_t* buffer; - const int alignment = sizeof(rc_richpresence_lookup_item_t*); int index; int size; @@ -293,7 +292,7 @@ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** roo size = count * sizeof(rc_richpresence_lookup_item_t*); buffer = &parse->scratch.buffer; do { - const int aligned_offset = (buffer->offset + alignment - 1) & ~(alignment - 1); + const int aligned_offset = RC_ALIGN(buffer->offset); const int remaining = sizeof(buffer->buffer) - aligned_offset; if (remaining >= size) { @@ -534,10 +533,11 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, display = nextline; display_line = parse->lines_read; + /* scan as long as we find conditional lines or full line comments */ do { line = nextline; nextline = rc_parse_line(line, &endline, parse); - } while (*line == '?'); + } while (*line == '?' || (line[0] == '/' && line[1] == '/')); } line = nextline; @@ -557,38 +557,48 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, nextline = rc_parse_line(line, &endline, parse); - while (*line == '?') { - /* conditional display: ?trigger?string */ - ptr = ++line; - while (ptr < endline && *ptr != '?') - ++ptr; - - if (ptr < endline) { - *nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup); - if (parse->offset < 0) - return; - trigger = &((*nextdisplay)->trigger); - rc_parse_trigger_internal(trigger, &line, parse); - trigger->memrefs = 0; - if (parse->offset < 0) - return; - if (parse->buffer) - nextdisplay = &((*nextdisplay)->next); + do { + if (line[0] == '?') { + /* conditional display: ?trigger?string */ + ptr = ++line; + while (ptr < endline && *ptr != '?') + ++ptr; + + if (ptr < endline) { + *nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup); + if (parse->offset < 0) + return; + trigger = &((*nextdisplay)->trigger); + rc_parse_trigger_internal(trigger, &line, parse); + trigger->memrefs = 0; + if (parse->offset < 0) + return; + if (parse->buffer) + nextdisplay = &((*nextdisplay)->next); + } + } + else if (line[0] != '/' || line[1] != '/') { + break; } line = nextline; nextline = rc_parse_line(line, &endline, parse); - } + } while (1); /* non-conditional display: string */ *nextdisplay = rc_parse_richpresence_display_internal(line, endline, parse, firstlookup); if (*nextdisplay) { hasdisplay = 1; nextdisplay = &((*nextdisplay)->next); - } - /* restore the parser state */ - parse->lines_read = lines_read; + /* restore the parser state */ + parse->lines_read = lines_read; + } + else { + /* this should only happen if the line is blank. + * expect parse->offset to be RC_MISSING_DISPLAY_STRING and leave parse->lines_read + * on the current line for error tracking. */ + } } /* finalize */ diff --git a/dep/rcheevos/src/rcheevos/runtime.c b/dep/rcheevos/src/rcheevos/runtime.c index 32f079a5bc..a5a66abd69 100644 --- a/dep/rcheevos/src/rcheevos/runtime.c +++ b/dep/rcheevos/src/rcheevos/runtime.c @@ -9,6 +9,17 @@ #define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256 +rc_runtime_t* rc_runtime_alloc(void) { + rc_runtime_t* self = malloc(sizeof(rc_runtime_t)); + + if (self) { + rc_runtime_init(self); + self->owns_self = 1; + } + + return self; +} + void rc_runtime_init(rc_runtime_t* self) { memset(self, 0, sizeof(rc_runtime_t)); self->next_memref = &self->memrefs; @@ -48,9 +59,13 @@ void rc_runtime_destroy(rc_runtime_t* self) { self->next_memref = 0; self->memrefs = 0; + + if (self->owns_self) { + free(self); + } } -static void rc_runtime_checksum(const char* memaddr, unsigned char* md5) { +void rc_runtime_checksum(const char* memaddr, unsigned char* md5) { md5_state_t state; md5_init(&state); md5_append(&state, (unsigned char*)memaddr, (int)strlen(memaddr)); @@ -231,7 +246,7 @@ int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id } if (rc_trigger_state_active(trigger->state)) { - *measured_value = trigger->measured_value; + *measured_value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value; *measured_target = trigger->measured_target; } else { @@ -257,7 +272,7 @@ int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned } /* cap the value at the target so we can count past the target: "107 >= 100" */ - value = trigger->measured_value; + value = (trigger->measured_value == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value; if (value > trigger->measured_target) value = trigger->measured_target; @@ -534,6 +549,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha for (i = self->trigger_count - 1; i >= 0; --i) { rc_trigger_t* trigger = self->triggers[i].trigger; int old_state, new_state; + unsigned old_measured_value; if (!trigger) continue; @@ -552,13 +568,13 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha continue; } + old_measured_value = trigger->measured_value; old_state = trigger->state; new_state = rc_evaluate_trigger(trigger, peek, ud, L); - /* the trigger state doesn't actually change to RESET, RESET just serves as a notification. + /* trigger->state doesn't actually change to RESET, RESET just serves as a notification. * handle the notification, then look at the actual state */ - if (new_state == RC_TRIGGER_STATE_RESET) - { + if (new_state == RC_TRIGGER_STATE_RESET) { runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET; runtime_event.id = self->triggers[i].id; event_handler(&runtime_event); @@ -566,6 +582,32 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha new_state = trigger->state; } + /* if the measured value changed and the achievement hasn't triggered, send a notification */ + if (trigger->measured_value != old_measured_value && old_measured_value != RC_MEASURED_UNKNOWN && + trigger->measured_target != 0 && trigger->measured_value <= trigger->measured_target && + new_state != RC_TRIGGER_STATE_TRIGGERED && + new_state != RC_TRIGGER_STATE_INACTIVE && new_state != RC_TRIGGER_STATE_WAITING) { + + runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED; + runtime_event.id = self->triggers[i].id; + + if (trigger->measured_as_percent) { + /* if reporting measured value as a percentage, only send the notification if the percentage changes */ + unsigned old_percent = (unsigned)(((unsigned long long)old_measured_value * 100) / trigger->measured_target); + unsigned new_percent = (unsigned)(((unsigned long long)trigger->measured_value * 100) / trigger->measured_target); + if (old_percent != new_percent) { + runtime_event.value = new_percent; + event_handler(&runtime_event); + } + } + else { + runtime_event.value = trigger->measured_value; + event_handler(&runtime_event); + } + + runtime_event.value = 0; /* achievement loop expects this to stay at 0 */ + } + /* if the state hasn't changed, there won't be any events raised */ if (new_state == old_state) continue; @@ -685,13 +727,8 @@ void rc_runtime_reset(rc_runtime_t* self) { rc_reset_lboard(self->lboards[i].lboard); } - if (self->richpresence && self->richpresence->richpresence) { - rc_richpresence_display_t* display = self->richpresence->richpresence->first_display; - while (display != 0) { - rc_reset_trigger(&display->trigger); - display = display->next; - } - } + if (self->richpresence && self->richpresence->richpresence) + rc_reset_richpresence(self->richpresence->richpresence); for (variable = self->variables; variable; variable = variable->next) rc_reset_value(variable); @@ -712,7 +749,7 @@ static int rc_condset_contains_memref(const rc_condset_t* condset, const rc_memr return 0; } -static int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) { +int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) { rc_condset_t* condset; if (!value) return 0; @@ -725,7 +762,7 @@ static int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* return 0; } -static int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref) { +int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref) { rc_condset_t* condset; if (!trigger) return 0; diff --git a/dep/rcheevos/src/rcheevos/runtime_progress.c b/dep/rcheevos/src/rcheevos/runtime_progress.c index cebe981c32..32353faab6 100644 --- a/dep/rcheevos/src/rcheevos/runtime_progress.c +++ b/dep/rcheevos/src/rcheevos/runtime_progress.c @@ -17,7 +17,7 @@ #define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */ typedef struct rc_runtime_progress_t { - rc_runtime_t* runtime; + const rc_runtime_t* runtime; int offset; unsigned char* buffer; @@ -81,17 +81,6 @@ static int rc_runtime_progress_match_md5(rc_runtime_progress_t* progress, unsign return result; } -static unsigned rc_runtime_progress_djb2(const char* input) -{ - unsigned result = 5381; - char c; - - while ((c = *input++) != '\0') - result = ((result << 5) + result) + c; /* result = result * 33 + c */ - - return result; -} - static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, unsigned chunk_id) { rc_runtime_progress_write_uint(progress, chunk_id); @@ -120,7 +109,7 @@ static void rc_runtime_progress_end_chunk(rc_runtime_progress_t* progress) } } -static void rc_runtime_progress_init(rc_runtime_progress_t* progress, rc_runtime_t* runtime, lua_State* L) +static void rc_runtime_progress_init(rc_runtime_progress_t* progress, const rc_runtime_t* runtime, lua_State* L) { memset(progress, 0, sizeof(rc_runtime_progress_t)); progress->runtime = runtime; @@ -352,7 +341,7 @@ static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress) for (variable = progress->runtime->variables; variable; variable = variable->next) { - unsigned djb2 = rc_runtime_progress_djb2(variable->name); + unsigned djb2 = rc_djb2(variable->name); rc_runtime_progress_write_uint(progress, djb2); rc_runtime_progress_write_variable(progress, variable); @@ -418,7 +407,7 @@ static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress) count = 0; for (variable = progress->runtime->variables; variable; variable = variable->next) { pending_variables[count].variable = variable; - pending_variables[count].djb2 = rc_runtime_progress_djb2(variable->name); + pending_variables[count].djb2 = rc_djb2(variable->name); ++count; } @@ -751,7 +740,7 @@ int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L) rc_runtime_progress_t progress; int result; - rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L); + rc_runtime_progress_init(&progress, runtime, L); result = rc_runtime_progress_serialize_internal(&progress); if (result != RC_OK) @@ -764,7 +753,10 @@ int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua { rc_runtime_progress_t progress; - rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L); + if (!buffer) + return RC_INVALID_STATE; + + rc_runtime_progress_init(&progress, runtime, L); progress.buffer = (unsigned char*)buffer; return rc_runtime_progress_serialize_internal(&progress); @@ -782,6 +774,11 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char* int seen_rich_presence = 0; int result = RC_OK; + if (!serialized) { + rc_runtime_reset(runtime); + return RC_INVALID_STATE; + } + rc_runtime_progress_init(&progress, runtime, L); progress.buffer = (unsigned char*)serialized; diff --git a/dep/rcheevos/src/rcheevos/trigger.c b/dep/rcheevos/src/rcheevos/trigger.c index dda918b536..6061ab8ee1 100644 --- a/dep/rcheevos/src/rcheevos/trigger.c +++ b/dep/rcheevos/src/rcheevos/trigger.c @@ -42,8 +42,8 @@ void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_pars *next = 0; *memaddr = aux; - self->measured_value = 0; self->measured_target = parse->measured_target; + self->measured_value = parse->measured_target ? RC_MEASURED_UNKNOWN : 0; self->measured_as_percent = parse->measured_as_percent; self->state = RC_TRIGGER_STATE_WAITING; self->has_hits = 0; @@ -197,14 +197,6 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* self->measured_value = eval_state.measured_value.value.u32; } - /* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */ - /* otherwise, if the state is WAITING, proceed to activating the trigger */ - if (self->state == RC_TRIGGER_STATE_WAITING && ret) { - rc_reset_trigger(self); - self->has_hits = 0; - return RC_TRIGGER_STATE_WAITING; - } - /* if any ResetIf condition was true, reset the hit counts */ if (eval_state.was_reset) { /* if the measured value came from a hit count, reset it. do this before calling @@ -247,6 +239,13 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* is_primed = 0; } else if (ret) { + /* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */ + if (self->state == RC_TRIGGER_STATE_WAITING) { + rc_reset_trigger(self); + self->has_hits = 0; + return RC_TRIGGER_STATE_WAITING; + } + /* trigger was triggered */ self->state = RC_TRIGGER_STATE_TRIGGERED; return RC_TRIGGER_STATE_TRIGGERED; @@ -284,6 +283,9 @@ void rc_reset_trigger(rc_trigger_t* self) { rc_reset_trigger_hitcounts(self); self->state = RC_TRIGGER_STATE_WAITING; - self->measured_value = 0; + + if (self->measured_target) + self->measured_value = RC_MEASURED_UNKNOWN; + self->has_hits = 0; } diff --git a/dep/rcheevos/src/rcheevos/value.c b/dep/rcheevos/src/rcheevos/value.c index 70444c1047..54784caabe 100644 --- a/dep/rcheevos/src/rcheevos/value.c +++ b/dep/rcheevos/src/rcheevos/value.c @@ -111,6 +111,7 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat case RC_OPERATOR_MULT: case RC_OPERATOR_DIV: case RC_OPERATOR_AND: + case RC_OPERATOR_XOR: case RC_OPERATOR_NONE: break; @@ -284,6 +285,20 @@ void rc_reset_value(rc_value_t* self) { self->value.changed = 0; } +int rc_value_from_hits(rc_value_t* self) +{ + rc_condset_t* condset = self->conditions; + for (; condset != NULL; condset = condset->next) { + rc_condition_t* condition = condset->conditions; + for (; condition != NULL; condition = condition->next) { + if (condition->type == RC_CONDITION_MEASURED) + return (condition->required_hits != 0); + } + } + + return 0; +} + void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables) { parse->variables = variables; *variables = 0; @@ -427,6 +442,26 @@ static rc_typed_value_t* rc_typed_value_convert_into(rc_typed_value_t* dest, con return dest; } +void rc_typed_value_negate(rc_typed_value_t* value) { + switch (value->type) + { + case RC_VALUE_TYPE_UNSIGNED: + rc_typed_value_convert(value, RC_VALUE_TYPE_SIGNED); + /* fallthrough to RC_VALUE_TYPE_SIGNED */ + + case RC_VALUE_TYPE_SIGNED: + value->value.i32 = -(value->value.i32); + break; + + case RC_VALUE_TYPE_FLOAT: + value->value.f32 = -(value->value.f32); + break; + + default: + break; + } +} + void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount) { rc_typed_value_t converted; diff --git a/dep/rcheevos/src/rhash/cdreader.c b/dep/rcheevos/src/rhash/cdreader.c index 9f464b1bbb..c0f5c88a2b 100644 --- a/dep/rcheevos/src/rhash/cdreader.c +++ b/dep/rcheevos/src/rhash/cdreader.c @@ -22,6 +22,7 @@ struct cdrom_t void* file_handle; /* the file handle for reading the track data */ int sector_size; /* the size of each sector in the track data */ int sector_header_size; /* the offset to the raw data within a sector block */ + int raw_data_size; /* the amount of raw data within a sector block */ int64_t file_track_offset;/* the offset of the track data within the file */ int track_first_sector; /* the first absolute sector associated to the track (includes pregap) */ int track_pregap_sectors; /* the number of pregap sectors */ @@ -58,6 +59,7 @@ static void cdreader_determine_sector_size(struct cdrom_t* cdrom) cdrom->sector_size = 0; cdrom->sector_header_size = 0; + cdrom->raw_data_size = 2048; rc_file_seek(cdrom->file_handle, toc_sector * 2352 + cdrom->file_track_offset, SEEK_SET); if (rc_file_read(cdrom->file_handle, header, sizeof(header)) < sizeof(header)) @@ -122,6 +124,8 @@ static void* cdreader_open_bin_track(const char* path, uint32_t track) return NULL; cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom)); + if (!cdrom) + return NULL; cdrom->file_handle = file_handle; #ifndef NDEBUG cdrom->track_id = track; @@ -219,6 +223,7 @@ static int cdreader_open_bin(struct cdrom_t* cdrom, const char* path, const char { cdrom->sector_size = 2352; cdrom->sector_header_size = 0; + cdrom->raw_data_size = 2352; /* no header or footer data on audio tracks */ } } @@ -282,6 +287,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track) char* bin_filename = NULL; char *ptr, *ptr2, *end; int done = 0; + int session = 1; size_t num_read = 0; struct cdrom_t* cdrom = NULL; @@ -339,7 +345,7 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track) ++ptr; /* convert mm:ss:ff to sector count */ - sscanf(ptr, "%d:%d:%d", &m, &s, &f); + sscanf_s(ptr, "%d:%d:%d", &m, &s, &f); sector_offset = ((m * 60) + s) * 75 + f; if (current_track.first_sector == -1) @@ -389,6 +395,13 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track) done = 1; break; } + + if (track == RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION && session == 2) + { + track = current_track.id; + done = 1; + break; + } } } else if (strncasecmp(ptr, "TRACK ", 6) == 0) @@ -465,6 +478,20 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track) if (ptr2 - ptr < (int)sizeof(current_track.filename)) memcpy(current_track.filename, ptr, ptr2 - ptr); } + else if (strncasecmp(ptr, "REM ", 4) == 0) + { + ptr += 4; + while (*ptr == ' ') + ++ptr; + + if (strncasecmp(ptr, "SESSION ", 8) == 0) + { + ptr += 8; + while (*ptr == ' ') + ++ptr; + session = atoi(ptr); + } + } while (*ptr && *ptr != '\n') ++ptr; @@ -694,8 +721,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track) largest_track_size = track_size; largest_track = current_track; largest_track_lba = lba; - strcpy(largest_track_file, file); - strcpy(largest_track_sector_size, sector_size); + strcpy_s(largest_track_file, sizeof(largest_track_file), file); + strcpy_s(largest_track_sector_size, sizeof(largest_track_sector_size), sector_size); } } } @@ -723,8 +750,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track) if (largest_track != 0 && largest_track != current_track) { current_track = largest_track; - strcpy(file, largest_track_file); - strcpy(sector_size, largest_track_sector_size); + strcpy_s(file, sizeof(file), largest_track_file); + strcpy_s(sector_size, sizeof(sector_size), largest_track_sector_size); lba = largest_track_lba; } @@ -794,18 +821,18 @@ static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* bu sector_start = (int64_t)(sector - cdrom->track_first_sector) * cdrom->sector_size + cdrom->sector_header_size + cdrom->file_track_offset; - while (requested_bytes > 2048) + while (requested_bytes > (size_t)cdrom->raw_data_size) { rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET); - num_read = rc_file_read(cdrom->file_handle, buffer_ptr, 2048); + num_read = rc_file_read(cdrom->file_handle, buffer_ptr, cdrom->raw_data_size); total_read += num_read; - if (num_read < 2048) + if (num_read < (size_t)cdrom->raw_data_size) return total_read; - buffer_ptr += 2048; + buffer_ptr += cdrom->raw_data_size; sector_start += cdrom->sector_size; - requested_bytes -= 2048; + requested_bytes -= cdrom->raw_data_size; } rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET); @@ -844,7 +871,7 @@ void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader) cdreader->first_track_sector = cdreader_first_track_sector; } -void rc_hash_init_default_cdreader() +void rc_hash_init_default_cdreader(void) { struct rc_hash_cdreader cdreader; rc_hash_get_default_cdreader(&cdreader); diff --git a/dep/rcheevos/src/rhash/hash.c b/dep/rcheevos/src/rhash/hash.c index 1c983146b2..e93423b864 100644 --- a/dep/rcheevos/src/rhash/hash.c +++ b/dep/rcheevos/src/rhash/hash.c @@ -48,7 +48,13 @@ static struct rc_hash_filereader* filereader = NULL; static void* filereader_open(const char* path) { +#if defined(__STDC_WANT_SECURE_LIB__) + FILE* fp; + fopen_s(&fp, path, "rb"); + return fp; +#else return fopen(path, "rb"); +#endif } static void filereader_seek(void* file_handle, int64_t offset, int origin) @@ -223,12 +229,17 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns { uint8_t buffer[2048], *tmp; int sector; + unsigned num_sectors = 0; size_t filename_length; const char* slash; if (!track_handle) return 0; + /* we start at the root. don't need to explicitly find it */ + if (*path == '\\') + ++path; + filename_length = strlen(path); slash = strrchr(path, '\\'); if (slash) @@ -247,6 +258,8 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns } else { + unsigned logical_block_size; + /* find the cd information */ if (!rc_cd_read_sector(track_handle, rc_cd_first_track_sector(track_handle) + 16, buffer, 256)) return 0; @@ -255,6 +268,15 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns * https://www.cdroller.com/htm/readdata.html */ sector = buffer[156 + 2] | (buffer[156 + 3] << 8) | (buffer[156 + 4] << 16); + + /* if the table of contents spans more than one sector, it's length of section will exceed it's logical block size */ + logical_block_size = (buffer[128] | (buffer[128 + 1] << 8)); /* logical block size */ + if (logical_block_size == 0) { + num_sectors = 1; + } else { + num_sectors = (buffer[156 + 10] | (buffer[156 + 11] << 8) | (buffer[156 + 12] << 16) | (buffer[156 + 13] << 24)); /* length of section */ + num_sectors /= logical_block_size; + } } /* fetch and process the directory record */ @@ -262,21 +284,34 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns return 0; tmp = buffer; - while (tmp < buffer + sizeof(buffer)) + do { - if (!*tmp) - return 0; + if (tmp >= buffer + sizeof(buffer) || !*tmp) + { + /* end of this path table block. if the path table spans multiple sectors, keep scanning */ + if (num_sectors > 1) + { + --num_sectors; + if (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer))) + { + tmp = buffer; + continue; + } + } + break; + } /* filename is 33 bytes into the record and the format is "FILENAME;version" or "DIRECTORY" */ - if ((tmp[33 + filename_length] == ';' || tmp[33 + filename_length] == '\0') && + if ((tmp[32] == filename_length || tmp[33 + filename_length] == ';') && strncasecmp((const char*)(tmp + 33), path, filename_length) == 0) { sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16); if (verbose_message_callback) { - snprintf((char*)buffer, sizeof(buffer), "Found %s at sector %d", path, sector); - verbose_message_callback((const char*)buffer); + char message[128]; + snprintf(message, sizeof(message), "Found %s at sector %d", path, sector); + verbose_message_callback(message); } if (size) @@ -287,7 +322,7 @@ static uint32_t rc_cd_find_file_sector(void* track_handle, const char* path, uns /* the first byte of the record is the length of the record */ tmp += *tmp; - } + } while (1); return 0; } @@ -347,6 +382,34 @@ int rc_path_compare_extension(const char* path, const char* ext) /* ===================================================== */ +static void rc_hash_byteswap16(uint8_t* buffer, const uint8_t* stop) +{ + uint32_t* ptr = (uint32_t*)buffer; + const uint32_t* stop32 = (const uint32_t*)stop; + while (ptr < stop32) + { + uint32_t temp = *ptr; + temp = (temp & 0xFF00FF00) >> 8 | + (temp & 0x00FF00FF) << 8; + *ptr++ = temp; + } +} + +static void rc_hash_byteswap32(uint8_t* buffer, const uint8_t* stop) +{ + uint32_t* ptr = (uint32_t*)buffer; + const uint32_t* stop32 = (const uint32_t*)stop; + while (ptr < stop32) + { + uint32_t temp = *ptr; + temp = (temp & 0xFF000000) >> 24 | + (temp & 0x00FF0000) >> 8 | + (temp & 0x0000FF00) << 8 | + (temp & 0x000000FF) << 24; + *ptr++ = temp; + } +} + static int rc_hash_finalize(md5_state_t* md5, char hash[33]) { md5_byte_t digest[16]; @@ -415,6 +478,9 @@ static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector verbose_message_callback(message); } + if (size < (unsigned)num_read) + size = (unsigned)num_read; + do { md5_append(md5, buffer, (int)num_read); @@ -684,6 +750,142 @@ static int rc_hash_text(char hash[33], const uint8_t* buffer, size_t buffer_size return rc_hash_finalize(&md5, hash); } +/* helper variable only used for testing */ +const char* _rc_hash_jaguar_cd_homebrew_hash = NULL; + +static int rc_hash_jaguar_cd(char hash[33], const char* path) +{ + uint8_t buffer[2352]; + char message[128]; + void* track_handle; + md5_state_t md5; + int byteswapped = 0; + unsigned size = 0; + unsigned offset = 0; + unsigned sector = 0; + unsigned remaining; + unsigned i; + + /* Jaguar CD header is in the first sector of the first data track OF THE SECOND SESSION. + * The first track must be an audio track, but may be a warning message or actual game audio */ + track_handle = rc_cd_open_track(path, RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION); + if (!track_handle) + return rc_hash_error("Could not open track"); + + /* The header is an unspecified distance into the first sector, but usually two bytes in. + * It consists of 64 bytes of "TAIR" or "ATRI" repeating, depending on whether or not the data + * is byteswapped. Then another 32 byte that reads "ATARI APPROVED DATA HEADER ATRI " + * (possibly byteswapped). Then a big-endian 32-bit value for the address where the boot code + * should be loaded, and a second big-endian 32-bit value for the size of the boot code. */ + sector = rc_cd_first_track_sector(track_handle); + rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); + + for (i = 64; i < sizeof(buffer) - 32 - 4 * 3; i++) + { + if (memcmp(&buffer[i], "TARA IPARPVODED TA AEHDAREA RT I", 32) == 0) + { + byteswapped = 1; + offset = i + 32 + 4; + size = (buffer[offset] << 16) | (buffer[offset + 1] << 24) | (buffer[offset + 2]) | (buffer[offset + 3] << 8); + break; + } + else if (memcmp(&buffer[i], "ATARI APPROVED DATA HEADER ATRI ", 32) == 0) + { + byteswapped = 0; + offset = i + 32 + 4; + size = (buffer[offset] << 24) | (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | (buffer[offset + 3]); + break; + } + } + + if (size == 0) /* did not see ATARI APPROVED DATA HEADER */ + { + rc_cd_close_track(track_handle); + return rc_hash_error("Not a Jaguar CD"); + } + + i = 0; /* only loop once */ + do + { + md5_init(&md5); + + offset += 4; + + if (verbose_message_callback) + { + snprintf(message, sizeof(message), "Hashing boot executable (%u bytes starting at %u bytes into sector %u)", size, offset, sector); + rc_hash_verbose(message); + } + + if (size > MAX_BUFFER_SIZE) + size = MAX_BUFFER_SIZE; + + do + { + if (byteswapped) + rc_hash_byteswap16(buffer, &buffer[sizeof(buffer)]); + + remaining = sizeof(buffer) - offset; + if (remaining >= size) + { + md5_append(&md5, &buffer[offset], size); + size = 0; + break; + } + + md5_append(&md5, &buffer[offset], remaining); + size -= remaining; + offset = 0; + } while (rc_cd_read_sector(track_handle, ++sector, buffer, sizeof(buffer)) == sizeof(buffer)); + + rc_cd_close_track(track_handle); + + if (size > 0) + return rc_hash_error("Not enough data"); + + rc_hash_finalize(&md5, hash); + + /* homebrew games all seem to have the same boot executable and store the actual game code in track 2. + * if we generated something other than the homebrew hash, return it. assume all homebrews are byteswapped. */ + if (strcmp(hash, "254487b59ab21bc005338e85cbf9fd2f") != 0 || !byteswapped) { + if (_rc_hash_jaguar_cd_homebrew_hash == NULL || strcmp(hash, _rc_hash_jaguar_cd_homebrew_hash) != 0) + return 1; + } + + /* if we've already been through the loop a second time, just return the hash */ + if (i == 1) + return 1; + ++i; + + if (verbose_message_callback) + { + snprintf(message, sizeof(message), "Potential homebrew at sector %u, checking for KART data in track 2", sector); + rc_hash_verbose(message); + } + + track_handle = rc_cd_open_track(path, 2); + if (!track_handle) + return rc_hash_error("Could not open track"); + + /* track 2 of the homebrew code has the 64 bytes or ATRI followed by 32 bytes of "ATARI APPROVED DATA HEADER ATRI!", + * then 64 bytes of KART repeating. */ + sector = rc_cd_first_track_sector(track_handle); + rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)); + if (memcmp(&buffer[0x5E], "RT!IRTKA", 8) != 0) + return rc_hash_error("Homebrew executable not found in track 2"); + + /* found KART data*/ + if (verbose_message_callback) + { + snprintf(message, sizeof(message), "Found KART data in track 2"); + rc_hash_verbose(message); + } + + offset = 0xA6; + size = (buffer[offset] << 16) | (buffer[offset + 1] << 24) | (buffer[offset + 2]) | (buffer[offset + 3] << 8); + } while (1); +} + static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size) { /* if the file contains a header, ignore it */ @@ -698,6 +900,70 @@ static int rc_hash_lynx(char hash[33], const uint8_t* buffer, size_t buffer_size return rc_hash_buffer(hash, buffer, buffer_size); } +static int rc_hash_neogeo_cd(char hash[33], const char* path) +{ + char buffer[1024], *ptr; + void* track_handle; + uint32_t sector; + unsigned size; + md5_state_t md5; + + track_handle = rc_cd_open_track(path, 1); + if (!track_handle) + return rc_hash_error("Could not open track"); + + /* https://wiki.neogeodev.org/index.php?title=IPL_file, https://wiki.neogeodev.org/index.php?title=PRG_file + * IPL file specifies data to be loaded before the game starts. PRG files are the executable code + */ + sector = rc_cd_find_file_sector(track_handle, "IPL.TXT", &size); + if (!sector) + { + rc_cd_close_track(track_handle); + return rc_hash_error("Not a NeoGeo CD game disc"); + } + + if (rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer)) == 0) + { + rc_cd_close_track(track_handle); + return 0; + } + + md5_init(&md5); + + buffer[sizeof(buffer) - 1] = '\0'; + ptr = &buffer[0]; + do + { + char* start = ptr; + while (*ptr && *ptr != '.') + ++ptr; + + if (strncasecmp(ptr, ".PRG", 4) == 0) + { + ptr += 4; + *ptr++ = '\0'; + + sector = rc_cd_find_file_sector(track_handle, start, &size); + if (!sector || !rc_hash_cd_file(&md5, track_handle, sector, NULL, size, start)) + { + char error[128]; + rc_cd_close_track(track_handle); + snprintf(error, sizeof(error), "Could not read %.16s", start); + return rc_hash_error(error); + } + } + + while (*ptr && *ptr != '\n') + ++ptr; + if (*ptr != '\n') + break; + ++ptr; + } while (*ptr != '\0' && *ptr != '\x1a'); + + rc_cd_close_track(track_handle); + return rc_hash_finalize(&md5, hash); +} + static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size) { /* if the file contains a header, ignore it */ @@ -719,34 +985,6 @@ static int rc_hash_nes(char hash[33], const uint8_t* buffer, size_t buffer_size) return rc_hash_buffer(hash, buffer, buffer_size); } -static void rc_hash_v64_to_z64(uint8_t* buffer, const uint8_t* stop) -{ - uint32_t* ptr = (uint32_t*)buffer; - const uint32_t* stop32 = (const uint32_t*)stop; - while (ptr < stop32) - { - uint32_t temp = *ptr; - temp = (temp & 0xFF00FF00) >> 8 | - (temp & 0x00FF00FF) << 8; - *ptr++ = temp; - } -} - -static void rc_hash_n64_to_z64(uint8_t* buffer, const uint8_t* stop) -{ - uint32_t* ptr = (uint32_t*)buffer; - const uint32_t* stop32 = (const uint32_t*)stop; - while (ptr < stop32) - { - uint32_t temp = *ptr; - temp = (temp & 0xFF000000) >> 24 | - (temp & 0x00FF0000) >> 8 | - (temp & 0x0000FF00) << 8 | - (temp & 0x000000FF) << 24; - *ptr++ = temp; - } -} - static int rc_hash_n64(char hash[33], const char* path) { uint8_t* buffer; @@ -787,6 +1025,9 @@ static int rc_hash_n64(char hash[33], const char* path) rc_hash_verbose("converting n64 to z64"); is_n64 = 1; } + else if (buffer[0] == 0xE8 || buffer[0] == 0x22) /* ndd format (don't byteswap) */ + { + } else { free(buffer); @@ -818,9 +1059,9 @@ static int rc_hash_n64(char hash[33], const char* path) rc_file_read(file_handle, buffer, (int)buffer_size); if (is_v64) - rc_hash_v64_to_z64(buffer, stop); + rc_hash_byteswap16(buffer, stop); else if (is_n64) - rc_hash_n64_to_z64(buffer, stop); + rc_hash_byteswap32(buffer, stop); md5_append(&md5, buffer, (int)buffer_size); remaining -= buffer_size; @@ -832,9 +1073,9 @@ static int rc_hash_n64(char hash[33], const char* path) stop = buffer + remaining; if (is_v64) - rc_hash_v64_to_z64(buffer, stop); + rc_hash_byteswap16(buffer, stop); else if (is_n64) - rc_hash_n64_to_z64(buffer, stop); + rc_hash_byteswap32(buffer, stop); md5_append(&md5, buffer, (int)remaining); } @@ -957,6 +1198,121 @@ static int rc_hash_nintendo_ds(char hash[33], const char* path) return rc_hash_finalize(&md5, hash); } +static int rc_hash_gamecube(char hash[33], const char* path) +{ + md5_state_t md5; + void* file_handle; + const uint32_t BASE_HEADER_SIZE = 0x2440; + const uint32_t MAX_HEADER_SIZE = 1024 * 1024; + uint32_t apploader_header_size, apploader_body_size, apploader_trailer_size, header_size; + uint8_t quad_buffer[4]; + uint8_t addr_buffer[0xD8]; + uint8_t* buffer; + uint32_t dol_offset; + uint32_t dol_offsets[18]; + uint32_t dol_sizes[18]; + uint32_t dol_buf_size = 0; + uint32_t ix; + + file_handle = rc_file_open(path); + if (!file_handle) + return rc_hash_error("Could not open file"); + + /* Verify Gamecube */ + rc_file_seek(file_handle, 0x1c, SEEK_SET); + rc_file_read(file_handle, quad_buffer, 4); + if (quad_buffer[0] != 0xC2|| quad_buffer[1] != 0x33 || quad_buffer[2] != 0x9F || quad_buffer[3] != 0x3D) + { + rc_file_close(file_handle); + return rc_hash_error("Not a Gamecube disc"); + } + + /* GetApploaderSize */ + rc_file_seek(file_handle, BASE_HEADER_SIZE + 0x14, SEEK_SET); + apploader_header_size = 0x20; + rc_file_read(file_handle, quad_buffer, 4); + apploader_body_size = + (quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3]; + rc_file_read(file_handle, quad_buffer, 4); + apploader_trailer_size = + (quad_buffer[0] << 24) | (quad_buffer[1] << 16) | (quad_buffer[2] << 8) | quad_buffer[3]; + header_size = BASE_HEADER_SIZE + apploader_header_size + apploader_body_size + apploader_trailer_size; + if (header_size > MAX_HEADER_SIZE) header_size = MAX_HEADER_SIZE; + + /* Hash headers */ + buffer = (uint8_t*)malloc(header_size); + if (!buffer) + { + rc_file_close(file_handle); + return rc_hash_error("Could not allocate temporary buffer"); + } + rc_file_seek(file_handle, 0, SEEK_SET); + rc_file_read(file_handle, buffer, header_size); + md5_init(&md5); + if (verbose_message_callback) + { + char message[128]; + snprintf(message, sizeof(message), "Hashing %u byte header", header_size); + verbose_message_callback(message); + } + md5_append(&md5, buffer, header_size); + + /* GetBootDOLOffset + * Base header size is guaranteed larger than 0x423 therefore buffer contains dol_offset right now + */ + dol_offset = (buffer[0x420] << 24) | (buffer[0x421] << 16) | (buffer[0x422] << 8) | buffer[0x423]; + free(buffer); + + /* Find offsetsand sizes for the 7 main.dol code segments and 11 main.dol data segments */ + rc_file_seek(file_handle, dol_offset, SEEK_SET); + rc_file_read(file_handle, addr_buffer, 0xD8); + for (ix = 0; ix < 18; ix++) + { + dol_offsets[ix] = + (addr_buffer[0x0 + ix * 4] << 24) | + (addr_buffer[0x1 + ix * 4] << 16) | + (addr_buffer[0x2 + ix * 4] << 8) | + addr_buffer[0x3 + ix * 4]; + dol_sizes[ix] = + (addr_buffer[0x90 + ix * 4] << 24) | + (addr_buffer[0x91 + ix * 4] << 16) | + (addr_buffer[0x92 + ix * 4] << 8) | + addr_buffer[0x93 + ix * 4]; + dol_buf_size = (dol_sizes[ix] > dol_buf_size) ? dol_sizes[ix] : dol_buf_size; + } + + /* Iterate through the 18 main.dol segments and hash each */ + buffer = (uint8_t*)malloc(dol_buf_size); + if (!buffer) + { + rc_file_close(file_handle); + return rc_hash_error("Could not allocate temporary buffer"); + } + for (ix = 0; ix < 18; ix++) + { + if (dol_sizes[ix] == 0) + continue; + rc_file_seek(file_handle, dol_offsets[ix], SEEK_SET); + rc_file_read(file_handle, buffer, dol_sizes[ix]); + if (verbose_message_callback) + { + char message[128]; + if (ix < 7) + snprintf(message, sizeof(message), "Hashing %u byte main.dol code segment %u", dol_sizes[ix], ix); + else + snprintf(message, sizeof(message), "Hashing %u byte main.dol data segment %u", dol_sizes[ix], ix - 7); + verbose_message_callback(message); + } + md5_append(&md5, buffer, dol_sizes[ix]); + } + + /* Finalize */ + rc_file_close(file_handle); + free(buffer); + + return rc_hash_finalize(&md5, hash); +} + static int rc_hash_pce(char hash[33], const uint8_t* buffer, size_t buffer_size) { /* if the file contains a header, ignore it (expect ROM data to be multiple of 128KB) */ @@ -1290,7 +1646,7 @@ static int rc_hash_find_playstation_executable(void* track_handle, const char* b if (strncmp(ptr, cdrom_prefix, cdrom_prefix_len) == 0) ptr += cdrom_prefix_len; - if (*ptr == '\\') + while (*ptr == '\\') ++ptr; start = ptr; @@ -1454,18 +1810,30 @@ static int rc_hash_psp(char hash[33], const char* path) */ sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\PARAM.SFO", &size); if (!sector) + { + rc_cd_close_track(track_handle); return rc_hash_error("Not a PSP game disc"); + } md5_init(&md5); if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\PARAM.SFO")) + { + rc_cd_close_track(track_handle); return 0; + } sector = rc_cd_find_file_sector(track_handle, "PSP_GAME\\SYSDIR\\EBOOT.BIN", &size); if (!sector) + { + rc_cd_close_track(track_handle); return rc_hash_error("Could not find primary executable"); + } if (!rc_hash_cd_file(&md5, track_handle, sector, NULL, size, "PSP_GAME\\SYSDIR\\EBOOT.BIN")) + { + rc_cd_close_track(track_handle); return 0; + } rc_cd_close_track(track_handle); return rc_hash_finalize(&md5, hash); @@ -1527,7 +1895,11 @@ static struct rc_buffered_file rc_buffered_file; static void* rc_file_open_buffered_file(const char* path) { struct rc_buffered_file* handle = (struct rc_buffered_file*)malloc(sizeof(struct rc_buffered_file)); - memcpy(handle, &rc_buffered_file, sizeof(rc_buffered_file)); + (void)path; + + if (handle) + memcpy(handle, &rc_buffered_file, sizeof(rc_buffered_file)); + return handle; } @@ -1631,7 +2003,9 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b case RC_CONSOLE_SEGA_32X: case RC_CONSOLE_SG1000: case RC_CONSOLE_SUPERVISION: + case RC_CONSOLE_TI83: case RC_CONSOLE_TIC80: + case RC_CONSOLE_UZEBOX: case RC_CONSOLE_VECTREX: case RC_CONSOLE_VIRTUAL_BOY: case RC_CONSOLE_WASM4: @@ -1659,6 +2033,7 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* b case RC_CONSOLE_NINTENDO_64: case RC_CONSOLE_NINTENDO_DS: + case RC_CONSOLE_NINTENDO_DSI: return rc_hash_file_from_buffer(hash, console_id, buffer, buffer_size); } } @@ -1922,7 +2297,9 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_SEGA_32X: case RC_CONSOLE_SG1000: case RC_CONSOLE_SUPERVISION: + case RC_CONSOLE_TI83: case RC_CONSOLE_TIC80: + case RC_CONSOLE_UZEBOX: case RC_CONSOLE_VECTREX: case RC_CONSOLE_VIRTUAL_BOY: case RC_CONSOLE_WASM4: @@ -1946,6 +2323,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_ATARI_7800: case RC_CONSOLE_ATARI_LYNX: case RC_CONSOLE_NINTENDO: + case RC_CONSOLE_PC_ENGINE: case RC_CONSOLE_SUPER_NINTENDO: /* additional logic whole-file hash - buffer then call rc_hash_generate_from_buffer */ return rc_hash_buffered_file(hash, console_id, path); @@ -1959,13 +2337,29 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_ARCADE: return rc_hash_arcade(hash, path); + case RC_CONSOLE_ATARI_JAGUAR_CD: + return rc_hash_jaguar_cd(hash, path); + + case RC_CONSOLE_DREAMCAST: + if (rc_path_compare_extension(path, "m3u")) + return rc_hash_generate_from_playlist(hash, console_id, path); + + return rc_hash_dreamcast(hash, path); + + case RC_CONSOLE_GAMECUBE: + return rc_hash_gamecube(hash, path); + + case RC_CONSOLE_NEO_GEO_CD: + return rc_hash_neogeo_cd(hash, path); + case RC_CONSOLE_NINTENDO_64: return rc_hash_n64(hash, path); case RC_CONSOLE_NINTENDO_DS: + case RC_CONSOLE_NINTENDO_DSI: return rc_hash_nintendo_ds(hash, path); - case RC_CONSOLE_PC_ENGINE: + case RC_CONSOLE_PC_ENGINE_CD: if (rc_path_compare_extension(path, "cue") || rc_path_compare_extension(path, "chd")) return rc_hash_pce_cd(hash, path); @@ -1995,12 +2389,6 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path) case RC_CONSOLE_PSP: return rc_hash_psp(hash, path); - case RC_CONSOLE_DREAMCAST: - if (rc_path_compare_extension(path, "m3u")) - return rc_hash_generate_from_playlist(hash, console_id, path); - - return rc_hash_dreamcast(hash, path); - case RC_CONSOLE_SEGA_CD: case RC_CONSOLE_SATURN: if (rc_path_compare_extension(path, "m3u")) @@ -2077,7 +2465,7 @@ static void rc_hash_initialize_dsk_iterator(struct rc_hash_iterator* iterator, c rc_hash_iterator_append_console(iterator, RC_CONSOLE_APPLE_II); } -void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, uint8_t* buffer, size_t buffer_size) +void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size) { int need_path = !buffer; @@ -2108,6 +2496,15 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } break; + case '8': + /* http://tibasicdev.wikidot.com/file-extensions */ + if (rc_path_compare_extension(ext, "83g") || + rc_path_compare_extension(ext, "83p")) + { + iterator->consoles[0] = RC_CONSOLE_TI83; + } + break; + case 'a': if (rc_path_compare_extension(ext, "a78")) { @@ -2162,9 +2559,11 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2; iterator->consoles[2] = RC_CONSOLE_DREAMCAST; iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ - iterator->consoles[4] = RC_CONSOLE_PC_ENGINE; + iterator->consoles[4] = RC_CONSOLE_PC_ENGINE_CD; iterator->consoles[5] = RC_CONSOLE_3DO; iterator->consoles[6] = RC_CONSOLE_PCFX; + iterator->consoles[7] = RC_CONSOLE_NEO_GEO_CD; + iterator->consoles[8] = RC_CONSOLE_ATARI_JAGUAR_CD; need_path = 1; } else if (rc_path_compare_extension(ext, "chd")) @@ -2173,7 +2572,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2; iterator->consoles[2] = RC_CONSOLE_DREAMCAST; iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */ - iterator->consoles[4] = RC_CONSOLE_PC_ENGINE; + iterator->consoles[4] = RC_CONSOLE_PC_ENGINE_CD; iterator->consoles[5] = RC_CONSOLE_3DO; iterator->consoles[6] = RC_CONSOLE_PCFX; need_path = 1; @@ -2199,7 +2598,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } else if (rc_path_compare_extension(ext, "d64")) { - iterator->consoles[0] = RC_CONSOLE_COMMODORE_64; + iterator->consoles[0] = RC_CONSOLE_COMMODORE_64; } else if (rc_path_compare_extension(ext, "d88")) { @@ -2219,7 +2618,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } else if (rc_path_compare_extension(ext, "fd")) { - iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */ + iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */ } break; @@ -2330,7 +2729,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } else if (rc_path_compare_extension(ext, "nds")) { - iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS; + iterator->consoles[0] = RC_CONSOLE_NINTENDO_DS; /* ASSERT: handles both DS and DSi */ } else if (rc_path_compare_extension(ext, "n64") || rc_path_compare_extension(ext, "ndd")) @@ -2412,6 +2811,13 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* } break; + case 'u': + if (rc_path_compare_extension(ext, "uze")) + { + iterator->consoles[0] = RC_CONSOLE_UZEBOX; + } + break; + case 'v': if (rc_path_compare_extension(ext, "vb")) { diff --git a/dep/rcheevos/src/rhash/md5.c b/dep/rcheevos/src/rhash/md5.c index c35d96c5ef..f3a5205669 100644 --- a/dep/rcheevos/src/rhash/md5.c +++ b/dep/rcheevos/src/rhash/md5.c @@ -52,6 +52,7 @@ */ #include "md5.h" +#include #include #undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ @@ -161,7 +162,7 @@ md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) * On little-endian machines, we can process properly aligned * data without copying it. */ - if (!((data - (const md5_byte_t *)0) & 3)) { + if (!((ptrdiff_t)data & 3)) { /* data are properly aligned */ X = (const md5_word_t *)data; } else { diff --git a/scripts/make-appimage.sh b/scripts/make-appimage.sh index 25518b4844..2d04d0df61 100755 --- a/scripts/make-appimage.sh +++ b/scripts/make-appimage.sh @@ -148,6 +148,7 @@ declare -a SYSLIBS=( "libhx509.so.5" "libsqlite3.so.0" "libcrypt.so.1" + "libdbus-1.so.3" ) declare -a DEPLIBS=( diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 44036ab25b..370a898491 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -125,6 +125,8 @@ endif() if(USE_WAYLAND) target_compile_definitions(common PRIVATE "-DUSE_WAYLAND=1") +elseif(SUPPORTS_WAYLAND) + message(WARNING "Wayland support for renderers is disabled.\nDuckStation will FAIL to start on Wayland.") endif() if(USE_DRMKMS) diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp index bfe67fb054..2e12882141 100644 --- a/src/core/analog_controller.cpp +++ b/src/core/analog_controller.cpp @@ -53,7 +53,7 @@ void AnalogController::Reset() if (m_force_analog_on_reset) { - if (g_settings.controller_disable_analog_mode_forcing || System::IsRunningBIOS()) + if (g_settings.controller_disable_analog_mode_forcing || System::IsRunningUnknownGame()) { Host::AddIconOSDMessage( fmt::format("Controller{}AnalogMode", m_index), ICON_FA_GAMEPAD, @@ -835,8 +835,7 @@ static const char* s_invert_settings[] = {TRANSLATABLE("AnalogController", "Not static const SettingInfo s_settings[] = { {SettingInfo::Type::Boolean, "ForceAnalogOnReset", TRANSLATABLE("AnalogController", "Force Analog Mode on Reset"), - TRANSLATABLE("AnalogController", "Forces the controller to analog mode when the console is reset/powered on. May " - "cause issues with games, so it is recommended to leave this option off."), + TRANSLATABLE("AnalogController", "Forces the controller to analog mode when the console is reset/powered on."), "true"}, {SettingInfo::Type::Boolean, "AnalogDPadInDigitalMode", TRANSLATABLE("AnalogController", "Use Analog Sticks for D-Pad in Digital Mode"), diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 56e7a178c6..b924082e3d 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -296,7 +296,6 @@ static Command s_command = Command::None; static Command s_command_second_response = Command::None; static DriveState s_drive_state = DriveState::Idle; static DiscRegion s_disc_region = DiscRegion::Other; -static bool s_ps1_disc = false; static StatusRegister s_status = {}; static SecondaryStatusRegister s_secondary_status = {}; @@ -666,7 +665,7 @@ DiscRegion CDROM::GetDiscRegion() bool CDROM::IsMediaPS1Disc() { - return s_ps1_disc; + return (s_disc_region != DiscRegion::NonPS1); } bool CDROM::IsMediaAudioCD() @@ -715,35 +714,21 @@ bool CDROM::CanReadMedia() return (s_drive_state != DriveState::ShellOpening && m_reader.HasMedia()); } -void CDROM::InsertMedia(std::unique_ptr media) +void CDROM::InsertMedia(std::unique_ptr media, DiscRegion region) { if (CanReadMedia()) RemoveMedia(true); - // check if it's a valid PS1 disc - std::string exe_name; - std::vector exe_buffer; - s_ps1_disc = System::ReadExecutableFromImage(media.get(), &exe_name, &exe_buffer); + Log_InfoPrintf("Inserting new media, disc region: %s, console region: %s", + Settings::GetDiscRegionName(region), Settings::GetConsoleRegionName(System::GetRegion())); - if (s_ps1_disc) - { - // set the region from the system area of the disc - s_disc_region = System::GetRegionForImage(media.get()); - Log_InfoPrintf("Inserting new media, disc region: %s, console region: %s", - Settings::GetDiscRegionName(s_disc_region), Settings::GetConsoleRegionName(System::GetRegion())); - } - else - { - s_disc_region = DiscRegion::Other; - Log_InfoPrint("Inserting new media, non-PS1 disc"); - } + s_disc_region = region; + m_reader.SetMedia(std::move(media)); + SetHoldPosition(0, true); // motor automatically spins up if (s_drive_state != DriveState::ShellOpening) StartMotor(); - - m_reader.SetMedia(std::move(media)); - SetHoldPosition(0, true); } std::unique_ptr CDROM::RemoveMedia(bool for_disc_swap) @@ -764,8 +749,7 @@ std::unique_ptr CDROM::RemoveMedia(bool for_disc_swap) s_secondary_status.motor_on = false; s_secondary_status.shell_open = true; s_secondary_status.ClearActiveBits(); - s_disc_region = DiscRegion::Other; - s_ps1_disc = false; + s_disc_region = DiscRegion::NonPS1; // If the drive was doing anything, we need to abort the command. ClearDriveState(); @@ -2683,7 +2667,7 @@ void CDROM::DoIDRead() static constexpr u32 REGION_STRING_LENGTH = 4; static constexpr std::array, static_cast(DiscRegion::Count)> - region_strings = {{{'S', 'C', 'E', 'I'}, {'S', 'C', 'E', 'A'}, {'S', 'C', 'E', 'E'}, {0, 0, 0, 0}}}; + region_strings = {{{'S', 'C', 'E', 'I'}, {'S', 'C', 'E', 'A'}, {'S', 'C', 'E', 'E'}, {0, 0, 0, 0}, {0, 0, 0, 0}}}; s_async_response_fifo.PushRange(region_strings[static_cast(s_disc_region)].data(), REGION_STRING_LENGTH); SetAsyncInterrupt((flags_byte != 0) ? Interrupt::Error : Interrupt::Complete); diff --git a/src/core/cdrom.h b/src/core/cdrom.h index 4ab7d517b2..15c942e358 100644 --- a/src/core/cdrom.h +++ b/src/core/cdrom.h @@ -25,7 +25,7 @@ bool IsMediaPS1Disc(); bool IsMediaAudioCD(); bool DoesMediaRegionMatchConsole(); -void InsertMedia(std::unique_ptr media); +void InsertMedia(std::unique_ptr media, DiscRegion region); std::unique_ptr RemoveMedia(bool for_disc_swap); bool PrecacheMedia(); diff --git a/src/core/controller.cpp b/src/core/controller.cpp index 757025a238..1d623eed17 100644 --- a/src/core/controller.cpp +++ b/src/core/controller.cpp @@ -7,13 +7,14 @@ #include "digital_controller.h" #include "fmt/format.h" #include "guncon.h" +#include "host.h" #include "negcon.h" #include "playstation_mouse.h" #include "util/state_wrapper.h" static const Controller::ControllerInfo s_none_info = {ControllerType::None, "None", - "Not Connected", + TRANSLATABLE("ControllerType", "Not Connected"), nullptr, 0, nullptr, diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp index 7e36690fe4..1b45201a6f 100644 --- a/src/core/game_database.cpp +++ b/src/core/game_database.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "game_database.h" @@ -35,7 +35,6 @@ enum : u32 }; static Entry* GetMutableEntry(const std::string_view& serial); -static const Entry* GetEntryForId(const std::string_view& code); static bool LoadFromCache(); static bool SaveToCache(); @@ -111,6 +110,9 @@ void GameDatabase::Unload() const GameDatabase::Entry* GameDatabase::GetEntryForId(const std::string_view& code) { + if (code.empty()) + return nullptr; + EnsureLoaded(); auto iter = UnorderedStringMapFind(s_code_lookup, code); @@ -144,7 +146,8 @@ std::string GameDatabase::GetSerialForPath(const char* path) const GameDatabase::Entry* GameDatabase::GetEntryForDisc(CDImage* image) { - std::string id(System::GetGameIdFromImage(image, false)); + std::string id; + System::GetGameDetailsFromImage(image, &id, nullptr); if (!id.empty()) { const Entry* entry = GetEntryForId(id); @@ -152,15 +155,7 @@ const GameDatabase::Entry* GameDatabase::GetEntryForDisc(CDImage* image) return entry; } - std::string hash_id(System::GetGameHashIdFromImage(image)); - if (!hash_id.empty()) - { - const Entry* entry = GetEntryForId(hash_id); - if (entry) - return entry; - } - - Log_WarningPrintf("No entry found for disc (exe code: '%s', hash code: '%s')", id.c_str(), hash_id.c_str()); + Log_WarningPrintf("No entry found for disc '%s'", id.c_str()); return nullptr; } @@ -498,7 +493,7 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes "Controller in port %u (%s) is not supported for %s.\nSupported controllers: " "%s\nPlease configure a supported controller from the list above."), i + 1u, Host::TranslateString("ControllerType", Settings::GetControllerTypeDisplayName(ctype)).GetCharArray(), - System::GetRunningTitle().c_str(), supported_controller_string.GetCharArray()); + System::GetGameTitle().c_str(), supported_controller_string.GetCharArray()); } } } diff --git a/src/core/game_database.h b/src/core/game_database.h index ee9d0ad778..a4dfe6e02c 100644 --- a/src/core/game_database.h +++ b/src/core/game_database.h @@ -88,6 +88,7 @@ void EnsureLoaded(); void Unload(); const Entry* GetEntryForDisc(CDImage* image); +const Entry* GetEntryForId(const std::string_view& code); const Entry* GetEntryForSerial(const std::string_view& serial); std::string GetSerialForDisc(CDImage* image); std::string GetSerialForPath(const char* path); diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 9781eb35e5..d099a2be4b 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -313,7 +313,7 @@ void GPU::UpdateDMARequest() case BlitterState::ReadingVRAM: m_GPUSTAT.ready_to_send_vram = true; - m_GPUSTAT.ready_to_recieve_dma = false; + m_GPUSTAT.ready_to_recieve_dma = m_fifo.IsEmpty(); break; } diff --git a/src/core/host_display.cpp b/src/core/host_display.cpp index bb6f189b0f..18cd5785d7 100644 --- a/src/core/host_display.cpp +++ b/src/core/host_display.cpp @@ -232,18 +232,20 @@ void HostDisplay::CalculateDrawRect(s32 window_width, s32 window_height, float* apply_aspect_ratio ? (display_aspect_ratio / (static_cast(m_display_width) / static_cast(m_display_height))) : 1.0f; - const float display_width = g_settings.display_stretch_vertically ? - static_cast(m_display_width) : static_cast(m_display_width) * x_scale; - const float display_height = g_settings.display_stretch_vertically ? - static_cast(m_display_height) / x_scale : static_cast(m_display_height); - const float active_left = g_settings.display_stretch_vertically ? - static_cast(m_display_active_left) : static_cast(m_display_active_left) * x_scale; - const float active_top = g_settings.display_stretch_vertically ? - static_cast(m_display_active_top) / x_scale : static_cast(m_display_active_top); + const float display_width = g_settings.display_stretch_vertically ? static_cast(m_display_width) : + static_cast(m_display_width) * x_scale; + const float display_height = g_settings.display_stretch_vertically ? static_cast(m_display_height) / x_scale : + static_cast(m_display_height); + const float active_left = g_settings.display_stretch_vertically ? static_cast(m_display_active_left) : + static_cast(m_display_active_left) * x_scale; + const float active_top = g_settings.display_stretch_vertically ? static_cast(m_display_active_top) / x_scale : + static_cast(m_display_active_top); const float active_width = g_settings.display_stretch_vertically ? - static_cast(m_display_active_width) : static_cast(m_display_active_width) * x_scale; + static_cast(m_display_active_width) : + static_cast(m_display_active_width) * x_scale; const float active_height = g_settings.display_stretch_vertically ? - static_cast(m_display_active_height) / x_scale : static_cast(m_display_active_height); + static_cast(m_display_active_height) / x_scale : + static_cast(m_display_active_height); if (out_x_scale) *out_x_scale = x_scale; @@ -500,11 +502,13 @@ bool HostDisplay::WriteDisplayTextureToFile(std::string filename, bool full_reso const float ss_height_scale = static_cast(m_display_active_height) / static_cast(m_display_height); const float ss_aspect_ratio = m_display_aspect_ratio * ss_width_scale / ss_height_scale; resize_width = g_settings.display_stretch_vertically ? - m_display_texture_view_width : static_cast(static_cast(resize_height) * ss_aspect_ratio); + m_display_texture_view_width : + static_cast(static_cast(resize_height) * ss_aspect_ratio); resize_height = g_settings.display_stretch_vertically ? - static_cast(static_cast(resize_height) / - (m_display_aspect_ratio / (static_cast(m_display_width) / static_cast(m_display_height)))) : - resize_height; + static_cast(static_cast(resize_height) / + (m_display_aspect_ratio / + (static_cast(m_display_width) / static_cast(m_display_height)))) : + resize_height; } else { @@ -614,17 +618,65 @@ bool HostDisplay::WriteDisplayTextureToBuffer(std::vector* buffer, u32 resi return true; } -bool HostDisplay::WriteScreenshotToFile(std::string filename, bool compress_on_thread /*= false*/) +bool HostDisplay::WriteScreenshotToFile(std::string filename, bool internal_resolution /* = false */, + bool compress_on_thread /* = false */) { - const u32 width = m_window_info.surface_width; - const u32 height = m_window_info.surface_height; + u32 width = m_window_info.surface_width; + u32 height = m_window_info.surface_height; + auto [draw_left, draw_top, draw_width, draw_height] = CalculateDrawRect(width, height); + + if (internal_resolution && m_display_texture_view_width != 0 && m_display_texture_view_height != 0) + { + // If internal res, scale the computed draw rectangle to the internal res. + // We re-use the draw rect because it's already been AR corrected. + const float sar = + static_cast(m_display_texture_view_width) / static_cast(m_display_texture_view_height); + const float dar = static_cast(draw_width) / static_cast(draw_height); + if (sar >= dar) + { + // stretch height, preserve width + const float scale = static_cast(m_display_texture_view_width) / static_cast(draw_width); + width = m_display_texture_view_width; + height = static_cast(std::round(static_cast(draw_height) * scale)); + } + else + { + // stretch width, preserve height + const float scale = static_cast(m_display_texture_view_height) / static_cast(draw_height); + width = static_cast(std::round(static_cast(draw_width) * scale)); + height = m_display_texture_view_height; + } + + // DX11 won't go past 16K texture size. + constexpr u32 MAX_TEXTURE_SIZE = 16384; + if (width > MAX_TEXTURE_SIZE) + { + height = static_cast(static_cast(height) / + (static_cast(width) / static_cast(MAX_TEXTURE_SIZE))); + width = MAX_TEXTURE_SIZE; + } + if (height > MAX_TEXTURE_SIZE) + { + height = MAX_TEXTURE_SIZE; + width = static_cast(static_cast(width) / + (static_cast(height) / static_cast(MAX_TEXTURE_SIZE))); + } + + // Remove padding, it's not part of the framebuffer. + draw_left = 0; + draw_top = 0; + draw_width = static_cast(width); + draw_height = static_cast(height); + } if (width == 0 || height == 0) return false; std::vector pixels; u32 pixels_stride; GPUTexture::Format pixels_format; - if (!RenderScreenshot(width, height, &pixels, &pixels_stride, &pixels_format)) + if (!RenderScreenshot(width, height, + Common::Rectangle::FromExtents(draw_left, draw_top, draw_width, draw_height), &pixels, + &pixels_stride, &pixels_format)) { Log_ErrorPrintf("Failed to render %ux%u screenshot", width, height); return false; diff --git a/src/core/host_display.h b/src/core/host_display.h index f406ef4de4..cd23b93af4 100644 --- a/src/core/host_display.h +++ b/src/core/host_display.h @@ -104,8 +104,8 @@ class HostDisplay virtual bool Render(bool skip_present) = 0; /// Renders the display with postprocessing to the specified image. - virtual bool RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) = 0; + virtual bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, + std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) = 0; ALWAYS_INLINE bool IsVsyncEnabled() const { return m_vsync_enabled; } virtual void SetVSync(bool enabled) = 0; @@ -206,7 +206,7 @@ class HostDisplay bool clear_alpha = true); /// Helper function to save screenshot to PNG. - bool WriteScreenshotToFile(std::string filename, bool compress_on_thread = false); + bool WriteScreenshotToFile(std::string filename, bool internal_resolution = false, bool compress_on_thread = false); protected: ALWAYS_INLINE bool HasSoftwareCursor() const { return static_cast(m_cursor_texture); } @@ -251,7 +251,7 @@ class HostDisplay bool m_vsync_enabled = false; }; -/// Returns a pointer to the current host display abstraction. Assumes AcquireHostDisplay() has been caled. +/// Returns a pointer to the current host display abstraction. Assumes AcquireHostDisplay() has been called. extern std::unique_ptr g_host_display; namespace Host { diff --git a/src/core/settings.cpp b/src/core/settings.cpp index bd9c676483..68fb136b26 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -788,10 +788,11 @@ const char* Settings::GetConsoleRegionDisplayName(ConsoleRegion region) return s_console_region_display_names[static_cast(region)]; } -static std::array s_disc_region_names = {{"NTSC-J", "NTSC-U", "PAL", "Other"}}; -static std::array s_disc_region_display_names = { +static std::array s_disc_region_names = {{"NTSC-J", "NTSC-U", "PAL", "Other", "Non-PS1"}}; +static std::array s_disc_region_display_names = { {TRANSLATABLE("DiscRegion", "NTSC-J (Japan)"), TRANSLATABLE("DiscRegion", "NTSC-U/C (US, Canada)"), - TRANSLATABLE("DiscRegion", "PAL (Europe, Australia)"), TRANSLATABLE("DiscRegion", "Other")}}; + TRANSLATABLE("DiscRegion", "PAL (Europe, Australia)"), TRANSLATABLE("DiscRegion", "Other"), + TRANSLATABLE("DiscRegion", "Non-PS1")}}; std::optional Settings::ParseDiscRegionName(const char* str) { diff --git a/src/core/system.cpp b/src/core/system.cpp index e2a7141d24..1676835144 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "system.h" @@ -91,7 +91,6 @@ static bool LoadMemoryState(const MemorySaveState& mss); static bool LoadEXE(const char* filename); static std::string GetExecutableNameForImage(ISOReader& iso, bool strip_subdirectories); - static bool ReadExecutableFromImage(ISOReader& iso, std::string* out_executable_name, std::vector* out_executable_data); @@ -128,6 +127,7 @@ static void SetTimerResolutionIncreased(bool enabled); } // namespace System static constexpr const float PERFORMANCE_COUNTER_UPDATE_INTERVAL = 1.0f; +static constexpr const char FALLBACK_EXE_NAME[] = "PSX.EXE"; static std::unique_ptr s_game_settings_interface; static std::unique_ptr s_input_settings_interface; @@ -147,7 +147,8 @@ static BIOS::Hash s_bios_hash = {}; static std::string s_running_game_path; static std::string s_running_game_serial; static std::string s_running_game_title; -static bool s_running_bios; +static System::GameHash s_running_game_hash; +static bool s_running_unknown_game; static float s_throttle_frequency = 60.0f; static float s_target_speed = 1.0f; @@ -315,23 +316,28 @@ void System::IncrementInternalFrameNumber() s_internal_frame_number++; } -const std::string& System::GetRunningPath() +const std::string& System::GetDiscPath() { return s_running_game_path; } -const std::string& System::GetRunningSerial() +const std::string& System::GetGameSerial() { return s_running_game_serial; } -const std::string& System::GetRunningTitle() +const std::string& System::GetGameTitle() { return s_running_game_title; } -bool System::IsRunningBIOS() +System::GameHash System::GetGameHash() +{ + return s_running_game_hash; +} + +bool System::IsRunningUnknownGame() { - return s_running_bios; + return s_running_unknown_game; } const BIOS::ImageInfo* System::GetBIOSImageInfo() @@ -447,6 +453,7 @@ ConsoleRegion System::GetConsoleRegionForDiscRegion(DiscRegion region) case DiscRegion::NTSC_U: case DiscRegion::Other: + case DiscRegion::NonPS1: default: return ConsoleRegion::NTSC_U; @@ -455,70 +462,86 @@ ConsoleRegion System::GetConsoleRegionForDiscRegion(DiscRegion region) } } -std::string System::GetGameSerialForPath(const char* image_path, bool fallback_to_hash) +std::string System::GetGameHashId(GameHash hash) { - std::unique_ptr cdi = CDImage::Open(image_path, false, nullptr); - if (!cdi) - return {}; - - return GetGameIdFromImage(cdi.get(), fallback_to_hash); -} - -std::string System::GetGameIdFromImage(CDImage* cdi, bool fallback_to_hash) -{ - std::string code(GetExecutableNameForImage(cdi)); - if (!code.empty()) - { - // SCES_123.45 -> SCES-12345 - for (std::string::size_type pos = 0; pos < code.size();) - { - if (code[pos] == '.') - { - code.erase(pos, 1); - continue; - } - - if (code[pos] == '_') - code[pos] = '-'; - else - code[pos] = static_cast(std::toupper(code[pos])); - - pos++; - } - - return code; - } - - if (!fallback_to_hash) - return {}; - - return GetGameHashIdFromImage(cdi); + return StringUtil::StdStringFromFormat("HASH-%" PRIX64, hash); } -std::string System::GetGameHashIdFromImage(CDImage* cdi) +bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash* out_hash) { ISOReader iso; if (!iso.Open(cdi, 1)) - return {}; + { + if (out_id) + out_id->clear(); + if (out_hash) + *out_hash = 0; + return false; + } + std::string id; std::string exe_name; std::vector exe_buffer; - if (!ReadExecutableFromImage(cdi, &exe_name, &exe_buffer)) - return {}; + if (!ReadExecutableFromImage(iso, &exe_name, &exe_buffer)) + { + if (out_id) + out_id->clear(); + if (out_hash) + *out_hash = 0; + return false; + } + // Always compute the hash. const u32 track_1_length = cdi->GetTrackLength(1); - XXH64_state_t* state = XXH64_createState(); XXH64_reset(state, 0x4242D00C); XXH64_update(state, exe_name.c_str(), exe_name.size()); XXH64_update(state, exe_buffer.data(), exe_buffer.size()); XXH64_update(state, &iso.GetPVD(), sizeof(ISOReader::ISOPrimaryVolumeDescriptor)); XXH64_update(state, &track_1_length, sizeof(track_1_length)); - const u64 hash = XXH64_digest(state); + const GameHash hash = XXH64_digest(state); XXH64_freeState(state); + Log_DevPrintf("Hash for '%s' - %" PRIX64, exe_name.c_str(), hash); - Log_InfoPrintf("Hash for '%s' - %" PRIX64, exe_name.c_str(), hash); - return StringUtil::StdStringFromFormat("HASH-%" PRIX64, hash); + if (exe_name != FALLBACK_EXE_NAME) + { + // Strip off any subdirectories. + const std::string::size_type slash = exe_name.rfind('\\'); + if (slash != std::string::npos) + id = std::string_view(exe_name).substr(slash + 1); + else + id = exe_name; + + // SCES_123.45 -> SCES-12345 + for (std::string::size_type pos = 0; pos < id.size();) + { + if (id[pos] == '.') + { + id.erase(pos, 1); + continue; + } + + if (id[pos] == '_') + id[pos] = '-'; + else + id[pos] = static_cast(std::toupper(id[pos])); + + pos++; + } + } + + if (out_id) + { + if (id.empty()) + *out_id = GetGameHashId(hash); + else + *out_id = std::move(id); + } + + if (out_hash) + *out_hash = hash; + + return true; } std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdirectories) @@ -526,7 +549,7 @@ std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdire // Read SYSTEM.CNF std::vector system_cnf_data; if (!iso.ReadFile("SYSTEM.CNF", &system_cnf_data)) - return {}; + return FALLBACK_EXE_NAME; // Parse lines std::vector> lines; @@ -568,7 +591,10 @@ std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdire auto iter = std::find_if(lines.begin(), lines.end(), [](const auto& it) { return StringUtil::Strcasecmp(it.first.c_str(), "boot") == 0; }); if (iter == lines.end()) - return {}; + { + // Fallback to PSX.EXE + return FALLBACK_EXE_NAME; + } std::string code = iter->second; std::string::size_type pos; @@ -608,57 +634,44 @@ std::string System::GetExecutableNameForImage(ISOReader& iso, bool strip_subdire return code; } -std::string System::GetExecutableNameForImage(CDImage* cdi) +std::string System::GetExecutableNameForImage(CDImage* cdi, bool strip_subdirectories) { ISOReader iso; if (!iso.Open(cdi, 1)) return {}; - return GetExecutableNameForImage(iso, true); + return GetExecutableNameForImage(iso, strip_subdirectories); } -bool System::ReadExecutableFromImage(ISOReader& iso, std::string* out_executable_name, - std::vector* out_executable_data) +bool System::ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name, + std::vector* out_executable_data) { - bool result = false; + ISOReader iso; + if (!iso.Open(cdi, 1)) + return false; + + return ReadExecutableFromImage(iso, out_executable_name, out_executable_data); +} - std::string executable_path(GetExecutableNameForImage(iso, false)); +bool System::ReadExecutableFromImage(ISOReader& iso, std::string* out_executable_name, std::vector* out_executable_data) +{ + const std::string executable_path = GetExecutableNameForImage(iso, false); Log_DevPrintf("Executable path: '%s'", executable_path.c_str()); - if (!executable_path.empty()) + if (!executable_path.empty() && out_executable_data) { - result = iso.ReadFile(executable_path.c_str(), out_executable_data); - if (!result) + if (!iso.ReadFile(executable_path.c_str(), out_executable_data)) + { Log_ErrorPrintf("Failed to read executable '%s' from disc", executable_path.c_str()); + return false; + } } - if (!result) - { - // fallback to PSX.EXE - executable_path = "PSX.EXE"; - result = iso.ReadFile(executable_path.c_str(), out_executable_data); - if (!result) - Log_ErrorPrint("Failed to read fallback PSX.EXE from disc"); - } - - if (!result) - return false; - if (out_executable_name) *out_executable_name = std::move(executable_path); return true; } -bool System::ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name, - std::vector* out_executable_data) -{ - ISOReader iso; - if (!iso.Open(cdi, 1)) - return false; - - return ReadExecutableFromImage(iso, out_executable_name, out_executable_data); -} - DiscRegion System::GetRegionForSerial(std::string_view serial) { std::string prefix; @@ -705,15 +718,25 @@ DiscRegion System::GetRegionFromSystemArea(CDImage* cdi) DiscRegion System::GetRegionForImage(CDImage* cdi) { - DiscRegion system_area_region = GetRegionFromSystemArea(cdi); + const DiscRegion system_area_region = GetRegionFromSystemArea(cdi); if (system_area_region != DiscRegion::Other) return system_area_region; - std::string serial = GetGameIdFromImage(cdi, false); - if (serial.empty()) - return DiscRegion::Other; + ISOReader iso; + if (!iso.Open(cdi, 1)) + return DiscRegion::NonPS1; - return GetRegionForSerial(serial); + // The executable must exist, because this just returns PSX.EXE if it doesn't. + const std::string exename = GetExecutableNameForImage(iso, false); + if (exename.empty() || !iso.FileExists(exename.c_str())) + return DiscRegion::NonPS1; + + // Strip off any subdirectories. + const std::string::size_type slash = exename.rfind('\\'); + if (slash != std::string::npos) + return GetRegionForSerial(std::string_view(exename).substr(slash + 1)); + else + return GetRegionForSerial(exename); } DiscRegion System::GetRegionForExe(const char* path) @@ -967,9 +990,6 @@ void System::ResetSystem() ResetPerformanceCounters(); ResetThrottler(); Host::AddOSDMessage(Host::TranslateStdString("OSDMessage", "System reset.")); - - // need to clear this here, because of eject disc -> reset. - s_running_bios = !s_running_game_path.empty(); } void System::PauseSystem(bool paused) @@ -1118,7 +1138,8 @@ bool System::BootSystem(SystemBootParameters parameters) // Load CD image up and detect region. Common::Error error; - std::unique_ptr media; + std::unique_ptr disc; + DiscRegion disc_region = DiscRegion::NonPS1; std::string exe_boot; std::string psf_boot; if (!parameters.filename.empty()) @@ -1142,8 +1163,8 @@ bool System::BootSystem(SystemBootParameters parameters) else { Log_InfoPrintf("Loading CD image '%s'...", parameters.filename.c_str()); - media = CDImage::Open(parameters.filename.c_str(), g_settings.cdrom_load_image_patches, &error); - if (!media) + disc = CDImage::Open(parameters.filename.c_str(), g_settings.cdrom_load_image_patches, &error); + if (!disc) { Host::ReportErrorAsync("Error", fmt::format("Failed to load CD image '{}': {}", Path::GetFileName(parameters.filename), error.GetCodeAndMessage())); @@ -1152,9 +1173,9 @@ bool System::BootSystem(SystemBootParameters parameters) return false; } + disc_region = GetRegionForImage(disc.get()); if (s_region == ConsoleRegion::Auto) { - const DiscRegion disc_region = GetRegionForImage(media.get()); if (disc_region != DiscRegion::Other) { s_region = GetConsoleRegionForDiscRegion(disc_region); @@ -1181,7 +1202,7 @@ bool System::BootSystem(SystemBootParameters parameters) Log_InfoPrintf("Console Region: %s", Settings::GetConsoleRegionDisplayName(s_region)); // Switch subimage. - if (media && parameters.media_playlist_index != 0 && !media->SwitchSubImage(parameters.media_playlist_index, &error)) + if (disc && parameters.media_playlist_index != 0 && !disc->SwitchSubImage(parameters.media_playlist_index, &error)) { Host::ReportFormattedErrorAsync("Error", "Failed to switch to subimage %u in '%s': %s", parameters.media_playlist_index, parameters.filename.c_str(), @@ -1192,7 +1213,7 @@ bool System::BootSystem(SystemBootParameters parameters) } // Update running game, this will apply settings as well. - UpdateRunningGame(media ? media->GetFileName().c_str() : parameters.filename.c_str(), media.get(), true); + UpdateRunningGame(disc ? disc->GetFileName().c_str() : parameters.filename.c_str(), disc.get(), true); if (!parameters.override_exe.empty()) { @@ -1210,7 +1231,7 @@ bool System::BootSystem(SystemBootParameters parameters) } // Check for SBI. - if (!CheckForSBIFile(media.get())) + if (!CheckForSBIFile(disc.get())) { s_state = State::Shutdown; ClearRunningGame(); @@ -1248,9 +1269,6 @@ bool System::BootSystem(SystemBootParameters parameters) return false; } - // Allow controller analog mode for EXEs and PSFs. - s_running_bios = s_running_game_path.empty() && exe_boot.empty() && psf_boot.empty(); - UpdateControllers(); UpdateMemoryCardTypes(); UpdateMultitaps(); @@ -1280,8 +1298,8 @@ bool System::BootSystem(SystemBootParameters parameters) } // Insert CD, and apply fastboot patch if enabled. - if (media) - CDROM::InsertMedia(std::move(media)); + if (disc) + CDROM::InsertMedia(std::move(disc), disc_region); if (CDROM::HasMedia() && (parameters.override_fast_boot.has_value() ? parameters.override_fast_boot.value() : g_settings.bios_patch_fast_boot)) { @@ -1519,7 +1537,8 @@ void System::ClearRunningGame() s_running_game_serial.clear(); s_running_game_path.clear(); s_running_game_title.clear(); - s_running_bios = false; + s_running_game_hash = 0; + s_running_unknown_game = false; s_cheat_list.reset(); s_state = State::Shutdown; @@ -1973,7 +1992,8 @@ bool System::DoLoadState(ByteStream* state, bool force_software_renderer, bool u CDROM::Reset(); if (media) { - CDROM::InsertMedia(std::move(media)); + const DiscRegion region = GetRegionForImage(media.get()); + CDROM::InsertMedia(std::move(media), region); if (g_settings.cdrom_load_image_to_ram) CDROM::PrecacheMedia(); } @@ -2069,8 +2089,9 @@ bool System::InternalSaveState(ByteStream* state, u32 screenshot_size /* = 256 * std::vector screenshot_buffer; u32 screenshot_stride; GPUTexture::Format screenshot_format; - if (g_host_display->RenderScreenshot(screenshot_width, screenshot_height, &screenshot_buffer, &screenshot_stride, - &screenshot_format) && + if (g_host_display->RenderScreenshot(screenshot_width, screenshot_height, + Common::Rectangle::FromExtents(0, 0, screenshot_width, screenshot_height), + &screenshot_buffer, &screenshot_stride, &screenshot_format) && GPUTexture::ConvertTextureDataToRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride, screenshot_format)) { @@ -3002,8 +3023,9 @@ bool System::InsertMedia(const char* path) return false; } + const DiscRegion region = GetRegionForImage(image.get()); UpdateRunningGame(path, image.get(), false); - CDROM::InsertMedia(std::move(image)); + CDROM::InsertMedia(std::move(image), region); Log_InfoPrintf("Inserted media from %s (%s, %s)", s_running_game_path.c_str(), s_running_game_serial.c_str(), s_running_game_title.c_str()); if (g_settings.cdrom_load_image_to_ram) @@ -3036,6 +3058,8 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting) s_running_game_path.clear(); s_running_game_serial.clear(); s_running_game_title.clear(); + s_running_game_hash = 0; + s_running_unknown_game = true; if (path && std::strlen(path) > 0) { @@ -3048,17 +3072,20 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting) } else if (image) { - const GameDatabase::Entry* entry = GameDatabase::GetEntryForDisc(image); + std::string id; + GetGameDetailsFromImage(image, &id, &s_running_game_hash); + + const GameDatabase::Entry* entry = GameDatabase::GetEntryForId(id); if (entry) { s_running_game_serial = entry->serial; s_running_game_title = entry->title; + s_running_unknown_game = false; } else { - const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); - s_running_game_serial = GetGameIdFromImage(image, true); - s_running_game_title = Path::GetFileTitle(display_name); + s_running_game_serial = std::move(id); + s_running_game_title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path)); } if (image->HasSubImages() && g_settings.memory_card_use_playlist_title) @@ -3185,14 +3212,17 @@ bool System::SwitchMediaSubImage(u32 index) Host::AddFormattedOSDMessage(10.0f, Host::TranslateString("OSDMessage", "Failed to switch to subimage %u in '%s': %s."), index + 1u, image->GetFileName().c_str(), error.GetCodeAndMessage().GetCharArray()); - CDROM::InsertMedia(std::move(image)); + + const DiscRegion region = GetRegionForImage(image.get()); + CDROM::InsertMedia(std::move(image), region); return false; } Host::AddFormattedOSDMessage(20.0f, Host::TranslateString("OSDMessage", "Switched to sub-image %s (%u) in '%s'."), image->GetSubImageMetadata(index, "title").c_str(), index + 1u, image->GetMetadata("title").c_str()); - CDROM::InsertMedia(std::move(image)); + const DiscRegion region = GetRegionForImage(image.get()); + CDROM::InsertMedia(std::move(image), region); ClearMemorySaveStates(); return true; @@ -3843,7 +3873,7 @@ bool System::StartDumpingAudio(const char* filename) std::string auto_filename; if (!filename) { - const auto& serial = System::GetRunningSerial(); + const auto& serial = System::GetGameSerial(); if (serial.empty()) { auto_filename = Path::Combine( @@ -3888,7 +3918,7 @@ bool System::SaveScreenshot(const char* filename /* = nullptr */, bool full_reso std::string auto_filename; if (!filename) { - const auto& code = System::GetRunningSerial(); + const auto& code = System::GetGameSerial(); const char* extension = "png"; if (code.empty()) { @@ -3911,10 +3941,8 @@ bool System::SaveScreenshot(const char* filename /* = nullptr */, bool full_reso return false; } - const bool screenshot_saved = - g_settings.display_internal_resolution_screenshots ? - g_host_display->WriteDisplayTextureToFile(filename, full_resolution, apply_aspect_ratio, compress_on_thread) : - g_host_display->WriteScreenshotToFile(filename, compress_on_thread); + const bool screenshot_saved = g_host_display->WriteScreenshotToFile( + filename, g_settings.display_internal_resolution_screenshots, compress_on_thread); if (!screenshot_saved) { @@ -4084,7 +4112,7 @@ std::string System::GetCheatFileName() { std::string ret; - const std::string& title = System::GetRunningTitle(); + const std::string& title = System::GetGameTitle(); if (!title.empty()) ret = Path::Combine(EmuFolders::Cheats, fmt::format("{}.cht", title.c_str())); diff --git a/src/core/system.h b/src/core/system.h index 7dfd229023..8318a48819 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once @@ -88,6 +88,8 @@ enum class State Paused }; +using GameHash = u64; + extern TickCount g_ticks_per_second; /// Returns true if the filename is a PlayStation executable we can inject. @@ -105,12 +107,12 @@ bool IsSaveStateFilename(const std::string_view& path); /// Returns the preferred console type for a disc. ConsoleRegion GetConsoleRegionForDiscRegion(DiscRegion region); -std::string GetExecutableNameForImage(CDImage* cdi); +std::string GetExecutableNameForImage(CDImage* cdi, bool strip_subdirectories); bool ReadExecutableFromImage(CDImage* cdi, std::string* out_executable_name, std::vector* out_executable_data); -std::string GetGameHashIdFromImage(CDImage* cdi); -std::string GetGameIdFromImage(CDImage* cdi, bool fallback_to_hash); -std::string GetGameSerialForPath(const char* image_path, bool fallback_to_hash); +bool IsValidGameImage(CDImage* cdi); +std::string GetGameHashId(GameHash hash); +bool GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash* out_hash); DiscRegion GetRegionForSerial(std::string_view serial); DiscRegion GetRegionFromSystemArea(CDImage* cdi); DiscRegion GetRegionForImage(CDImage* cdi); @@ -177,11 +179,12 @@ u32 GetInternalFrameNumber(); void FrameDone(); void IncrementInternalFrameNumber(); -const std::string& GetRunningPath(); -const std::string& GetRunningSerial(); -const std::string& GetRunningTitle(); +const std::string& GetDiscPath(); +const std::string& GetGameSerial(); +const std::string& GetGameTitle(); +GameHash GetGameHash(); +bool IsRunningUnknownGame(); -bool IsRunningBIOS(); const BIOS::ImageInfo* GetBIOSImageInfo(); const BIOS::Hash& GetBIOSHash(); diff --git a/src/core/types.h b/src/core/types.h index fa61394236..49ede88a55 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -37,6 +37,7 @@ enum class DiscRegion : u8 NTSC_U, // SCEA PAL, // SCEE Other, + NonPS1, Count }; diff --git a/src/duckstation-nogui/nogui_host.cpp b/src/duckstation-nogui/nogui_host.cpp index e0d6564e6e..2d1799ae79 100644 --- a/src/duckstation-nogui/nogui_host.cpp +++ b/src/duckstation-nogui/nogui_host.cpp @@ -657,7 +657,7 @@ bool NoGUIHost::AcquireHostDisplay(RenderAPI api) Assert(!g_host_display); g_nogui_window->ExecuteInMessageLoop([api]() { - if (g_nogui_window->CreatePlatformWindow(GetWindowTitle(System::GetRunningTitle()))) + if (g_nogui_window->CreatePlatformWindow(GetWindowTitle(System::GetGameTitle()))) { const std::optional wi(g_nogui_window->GetPlatformWindowInfo()); if (wi.has_value()) diff --git a/src/duckstation-qt/controllersettingsdialog.cpp b/src/duckstation-qt/controllersettingsdialog.cpp index ac07b3e2cf..6a4ac1b35c 100644 --- a/src/duckstation-qt/controllersettingsdialog.cpp +++ b/src/duckstation-qt/controllersettingsdialog.cpp @@ -392,7 +392,7 @@ void ControllerSettingsDialog::createWidgets() const Controller::ControllerInfo* ci = Controller::GetControllerInfo(m_port_bindings[global_slot]->getControllerType()); - const QString display_name(ci ? QString::fromUtf8(ci->display_name) : QStringLiteral("Unknown")); + const QString display_name(ci ? qApp->translate("ControllerType", ci->display_name) : QStringLiteral("Unknown")); QListWidgetItem* item = new QListWidgetItem(); item->setText(mtap_enabled[port] ? @@ -433,7 +433,7 @@ void ControllerSettingsDialog::updateListDescription(u32 global_slot, Controller const bool mtap_enabled = getBoolValue("Pad", (port == 0) ? "MultitapPort1" : "MultitapPort2", false); const Controller::ControllerInfo* ci = Controller::GetControllerInfo(widget->getControllerType()); - const QString display_name(ci ? QString::fromUtf8(ci->display_name) : QStringLiteral("Unknown")); + const QString display_name(ci ? qApp->translate("ControllerType", ci->display_name) : QStringLiteral("Unknown")); item->setText(mtap_enabled ? (tr("Controller Port %1%2\n%3").arg(port + 1).arg(s_mtap_slot_names[slot]).arg(display_name)) : diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index b7174b3fc9..c37379b825 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -690,7 +690,7 @@ void MainWindow::onApplicationStateChanged(Qt::ApplicationState state) // Clear the state of all keyboard binds. // That way, if we had a key held down, and lost focus, the bind won't be stuck enabled because we never // got the key release message, because it happened in another window which "stole" the event. - InputManager::ClearBindStateFromSource(InputManager::MakeHostKeyboardKey(0)); + g_emu_thread->clearInputBindStateFromSource(InputManager::MakeHostKeyboardKey(0)); } else { @@ -1284,8 +1284,8 @@ void MainWindow::onViewGamePropertiesActionTriggered() if (!s_system_valid) return; - const std::string& path = System::GetRunningPath(); - const std::string& serial = System::GetRunningSerial(); + const std::string& path = System::GetDiscPath(); + const std::string& serial = System::GetGameSerial(); if (path.empty() || serial.empty()) return; @@ -2416,6 +2416,7 @@ void MainWindow::closeEvent(QCloseEvent* event) // If there's no VM, we can just exit as normal. if (!s_system_valid) { + saveGeometryToConfig(); QMainWindow::closeEvent(event); return; } diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 3a7fd50ad5..f02892fefb 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1065,6 +1065,17 @@ void EmuThread::reloadPostProcessingShaders() System::ReloadPostProcessingShaders(); } +void EmuThread::clearInputBindStateFromSource(InputBindingKey key) +{ + if (!isOnThread()) + { + QMetaObject::invokeMethod(this, "clearInputBindStateFromSource", Qt::QueuedConnection, Q_ARG(InputBindingKey, key)); + return; + } + + InputManager::ClearBindStateFromSource(key); +} + void EmuThread::runOnEmuThread(std::function callback) { callback(); @@ -1116,11 +1127,11 @@ void EmuThread::loadState(bool global, qint32 slot) } // shouldn't even get here if we don't have a running game - if (!global && System::GetRunningSerial().empty()) + if (!global && System::GetGameSerial().empty()) return; bootOrLoadState(global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)); + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); } void EmuThread::saveState(const QString& filename, bool block_until_done /* = false */) @@ -1147,11 +1158,11 @@ void EmuThread::saveState(bool global, qint32 slot, bool block_until_done /* = f return; } - if (!global && System::GetRunningSerial().empty()) + if (!global && System::GetGameSerial().empty()) return; System::SaveState((global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)) + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)) .c_str(), g_settings.create_save_state_backups); } diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index d45be24602..c57f89be60 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -187,6 +187,7 @@ public Q_SLOTS: void setCheatEnabled(quint32 index, bool enabled); void applyCheat(quint32 index); void reloadPostProcessingShaders(); + void clearInputBindStateFromSource(InputBindingKey key); private Q_SLOTS: void stopInThread(); diff --git a/src/duckstation-qt/qttranslations.cpp b/src/duckstation-qt/qttranslations.cpp index d50a103609..a1524bd07c 100644 --- a/src/duckstation-qt/qttranslations.cpp +++ b/src/duckstation-qt/qttranslations.cpp @@ -148,7 +148,7 @@ static const ImWchar* QtHost::GetGlyphRangesJapanese() { // clang-format off // auto update by generate_update_glyph_ranges.py with duckstation-qt_ja.ts - static const char16_t chars[] = u"←↑→↓□△○ 、。々「」〜あいうえおかがきぎくぐけげこごさざしじすずせそただちっつづてでとどなにぬねのはばびへべほぼぽまみむめもやゆよらりるれろわをんァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソタダチッツテデトドナニネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロワンー一上下不与両並中主了予事二互交人今介他付代令以件任休伸位低体作使例供依価係保信修個倍値停側傍備像優元先光入全公共具典内再凍処出分切初別利到制削前割力加効動勧化十協単去参及反収取古可右号各合同名向含告周呼命問善回囲固国圧在地垂型埋域基報場境増壊声売変外多大央失奨妥始子字存学安完定宛実密対専射小少岐左差巻帰常幅平年度座延式引弱張強当形影役待後従得御復微心必忘応性恐情意感態成我戻所手扱投択押拡持指振挿排探接推描提換損摩撃撮操改敗数整文料断新方既日早明映昨時景更書替最有望期未本来析枚果枠栄検概構標権機欄次止正歪残毎比水永求汎決況法波注海消深混済減測源準滑演点無照版牲特犠状獲率現理生用申画界番異疑発登的目直相瞬知短破確示禁秒称移程種穴空立端符等算管範簡粋精約純索細終組結統続維緑線編縮績繰置翻者耗背能自致般良色荷行表装補製複要見規視覧観解言計記設許訳証試詳認語説読調識警護象負販費質赤起超跡転軸軽較込近返追送逆通速連進遅遊達遠適遷選部重野量録長閉開間関防降限除隅隠集離電青非面音響頂順領頭頻頼題類飛高鮮黒%?X"; + static const char16_t chars[] = u"←↑→↓□△○ 、。々「」〜あいうえおかがきぎくぐけげこごさざしじすずせそただちっつづてでとどなにぬねのはばびへべほぼぽまみむめもやゆよらりるれろわをんァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソタダチッツテデトドナニネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロワンー一上下不与両並中主了予事二互交人今介他付代令以件任休伸位低体作使例供依価係保信修個倍値停側傍備像優元先光入全公共具典内再凍処出分切初別利到制削前割力加効動勧化十協単去参及反収取古可右号各合同名向含告周呼命問善回囲固国圧在地垂型埋域基報場境増壊声売変外多大央失奨妥始子字存学安完定宛実密対専射小少岐左差巻帰常幅平年度座延式引弱張強当形影役待後従得御復微心必忘応性恐情意感態成我戻所手扱投択押拡持指振挿排探接推描提換損摩撃撮操改敗数整文料断新方既日早明昔映昨時景更書替最有望期未本来析枚果枠栄検概構標権機欄次止正歪残毎比水永求汎決況法波注海消深混済減測源準滑演点無照版牲特犠状獲率現理生用申画界番異疑発登的目直相瞬知短破確示禁秒称移程種穴空立端符等算管範簡粋精約純索細終組結統続維緑線編縮績繰置翻者耗背能自致般良色荷行表装補製複要見規視覧観解言計記設許訳証試詳認語説読調識警護象負販費質赤起超跡転軸軽較込近返追送逆通速連進遅遊達遠適遷選部重野量録長閉開間関防降限除隅隠集離電青非面音響頂順領頭頻頼題類飛高鮮黒%?X"; const int chars_length = sizeof(chars) / sizeof(chars[0]); // clang-format on diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index 2ebdd1a961..d624a10d3c 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -848,6 +848,7 @@ QIcon GetIconForRegion(DiscRegion region) case DiscRegion::NTSC_U: return QIcon(QStringLiteral(":/icons/flag-uc.svg")); case DiscRegion::Other: + case DiscRegion::NonPS1: default: return QIcon::fromTheme(QStringLiteral("file-unknow-line")); } diff --git a/src/duckstation-qt/translations/duckstation-qt_es-es.ts b/src/duckstation-qt/translations/duckstation-qt_es-es.ts index add0475703..193ff528ed 100644 --- a/src/duckstation-qt/translations/duckstation-qt_es-es.ts +++ b/src/duckstation-qt/translations/duckstation-qt_es-es.ts @@ -178,7 +178,7 @@ - + Login... Acceso... @@ -266,29 +266,29 @@ Muestra iconos en la esquina inferior derecha de la pantalla cuando haya activo un logro cercano o un desafío. - + Reset System Reiniciar sistema - + Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now? El modo «hardcore» no se activará hasta que se reinicie el sistema. ¿Deseas hacerlo ahora? - + Username: %1 Login token generated on %2. Nombre de usuario: %1 Token de inicio de sesión generado en %2. - + Logout Cerrar sesión - + Not Logged In. Sesión no iniciada. @@ -296,89 +296,89 @@ Token de inicio de sesión generado en %2. Achievements - + Loading state Cargando estado - + Resuming state Reanudando estado - + Hardcore mode disabled by state switch. El cambio de estados guardados ha desactivado el modo «hardcore». - + Hardcore mode will be enabled on system reset. El modo «hardcore» se activará al reiniciar el sistema. - + Confirm Hardcore Mode Confirmar el modo «hardcore» - + {0} cannot be performed while hardcore mode is active. Do you want to disable hardcore mode? {0} will be cancelled if you select No. No se puede utilizar la función de {0} mientras el modo «hardcore» esté activo. ¿Deseas desactivar el modo «hardcore»? Se cancelará la acción de {0} si seleccionas No. - + Hardcore mode is now enabled. El modo «hardcore» está activado. - + Hardcore mode is now disabled. El modo «hardcore» está desactivado. - + {} (Hardcore Mode) {} (modo «hardcore») - + You have earned {} of {} achievements, and {} of {} points. Has conseguido {} de {} logros y {} de {} puntos. - + This game has no achievements. No hay logros para este juego. - + Your Score: {} (Best: {}) Leaderboard Position: {} of {} Tu puntuación: {} (mejor: {}) Posición en la tabla de puntuaciones: {} de {} - + This game has {} leaderboards. Este juego tiene {} tablas de puntuaciones. - + Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only. No se enviarán puntuaciones porque el modo «hardcore» está desactivado. Las tablas de puntuaciones permanecerán en modo solo lectura. - + Time Tiempo - + Score Puntuación - + Downloading leaderboard data, please wait... Descargando datos de tablas de puntuaciones, espera... @@ -407,31 +407,31 @@ Posición en la tabla de puntuaciones: {} de {} - + Log To System Console Registrar en consola del sistema - + Log To Window Registrar en ventana - + Log To Debug Console Registrar en consola de depuración - + Log To File Registrar en archivo - + Show Debug Menu Mostrar menú de depuración @@ -456,195 +456,230 @@ Posición en la tabla de puntuaciones: {} de {} Restablecer valores predeterminados - + Show Status Indicators Mostrar indicadores de estado - + Multisample Antialiasing - Suavizado de bordes de muestreo múltiple + Suavizado de bordes de muestreo múltiple (MSAA) - + PGXP Vertex Cache Caché de vértices de la PGXP - + PGXP Geometry Tolerance Tolerancia geométrica de la PGXP - + PGXP Depth Clear Threshold Umbral de limpieza de profundidad de la PGXP - + Enable Recompiler Memory Exceptions Excepciones de memoria del recompilador - + Enable Recompiler Block Linking Vinculación de bloques del recompilador - + Enable Recompiler Fast Memory Access Memoria de acceso rápido del recompilador - + Apply Compatibility Settings Aplicar configuración de compatibilidad - + + Select folder for %1 + Seleccionar carpeta de %1 + + + + Show Frame Times + Mostrar duración de fotogramas + + + Display Active Start Offset Compensación del inicio de imagen activa - + Display Active End Offset Compensación del final de imagen activa - + Display Line Start Offset Compensación de la primera línea de imagen - + Display Line End Offset Compensación de la última línea de imagen - + + Use Old MDEC Routines + Utilizar rutinas MDEC antiguas + + + Enable VRAM Write Texture Replacement Escritura de la VRAM a las texturas de reemplazo - + Preload Texture Replacements Precargar reemplazos de texturas - + Dump Replaceable VRAM Writes Volcar escrituras de VRAM reemplazables - + Set Dumped VRAM Write Alpha Channel Establecer canal alfa de los volcados de escritura VRAM - + Minimum Dumped VRAM Write Width Anchura mínima de volcados de escrituras de VRAM - + Minimum Dumped VRAM Write Height Altura mínima de volcados de escrituras de VRAM - + DMA Max Slice Ticks Duración máxima de los cortes de la DMA - + DMA Halt Ticks Duración de las paradas de la DMA - + GPU FIFO Size Tamaño del FIFO de la GPU - + GPU Max Run-Ahead Predicción máxima de la GPU - + + Stretch Display Vertically + Estirar imagen verticalmente + + + Allow Booting Without SBI File Arrancar sin un archivo SBI - + Create Save State Backups Crear copias de seguridad de los estados guardados - + + Enable PCDrv + Habilitar PCDrv + + + + Enable PCDrv Writes + Escrituras de PCDrv + + + + PCDrv Root Directory + Directorio raíz de PCDrv + + + Log Level Nivel de registro - + Information Información - + Sets the verbosity of messages logged. Higher levels will log more messages. Establece el grado de detalle de los mensajes del registro. Los niveles más altos mostrarán más mensajes. - - - - + + + + User Preference Preferencia de usuario - + Logs messages to the console window. Muestra los mensajes en la ventana de la consola. - + Logs messages to the debug console where supported. Muestra los mensajes en la consola de depuración cuando sea posible. - + Logs messages to the window. Muestra los mensajes en la ventana. - + Logs messages to duckstation.log in the user directory. Copia los mensajes al archivo duckstation.log, en el directorio del usuario. - + Unchecked Deshabilitado - + Shows a debug menu bar with additional statistics and quick settings. Muestra un menú de depuración con estadísticas adicionales y configuraciones rápidas. - + Display FPS Limit Mostrar límite de FPS - + Disable All Enhancements Desactivar todas las mejoras - + Increase Timer Resolution Incrementar la resolución del temporizador @@ -654,7 +689,7 @@ Posición en la tabla de puntuaciones: {} de {} Opciones del sistema - + Use Debug Host GPU Device Usar dispositivo gráfico de depuración @@ -662,14 +697,12 @@ Posición en la tabla de puntuaciones: {} de {} AnalogController - Controller %u is locked to analog mode by the game. - Mando %u bloqueado en modo analógico por el juego. + Mando %u bloqueado en modo analógico por el juego. - Controller %u is locked to digital mode by the game. - Mando %u bloqueado en modo digital por el juego. + Mando %u bloqueado en modo digital por el juego. @@ -683,6 +716,16 @@ Posición en la tabla de puntuaciones: {} de {} Controller {} switched to digital mode. Mando {} cambiado a modo digital. + + + Controller {} is locked to analog mode by the game. + Mando {} bloqueado en modo analógico por el juego. + + + + Controller {} is locked to digital mode by the game. + Mando {} bloqueado en modo digital por el juego. + Not Inverted @@ -709,40 +752,54 @@ Posición en la tabla de puntuaciones: {} de {} Forzar el modo analógico al reiniciar - Forces the controller to analog mode when the console is reset/powered on. May cause issues with games, so it is recommended to leave this option off. - Fuerza el modo analógico en el mando cuando la consola se reinicie/encienda. Puede causar problemas con los juegos, así que se recomienda dejar esta opción desactivada. + Fuerza el modo analógico en el mando cuando la consola se reinicie/encienda. Puede causar problemas con los juegos, así que se recomienda dejar esta opción desactivada. - + + Forces the controller to analog mode when the console is reset/powered on. + Fuerza el modo analógico en el mando cuando la consola se reinicie/encienda. + + + Use Analog Sticks for D-Pad in Digital Mode Usar los joysticks analógicos como cruceta en el modo digital - + Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons. Permite usar los joysticks analógicos para controlar la cruceta y los botones en el modo digital. - + Analog Deadzone Zona muerta del joystick analógico - + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. Establece la zona muerta del joystick analógico, es decir, cuánto movimiento del joystick se ignorará. - + Analog Sensitivity Sensibilidad analógica - + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. Establece el factor de escalado para los ejes de los joysticks analógicos. Se recomienda un valor entre 130 % y 140 % cuando se usen mandos modernos, como el DualShock 4 o el mando de Xbox One. + + + Button/Trigger Deadzone + Zona muerta de botón/gatillo + + + + Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored. + Establece la zona de inactividad para activar botones o gatillos, es decir, la distancia de la pulsación que será ignorada. + Vibration Bias @@ -753,6 +810,26 @@ Posición en la tabla de puntuaciones: {} de {} Sets the rumble bias value. If rumble in some games is too weak or not functioning, try increasing this value. Indica la medida de la vibración. Si la vibración en algunos juegos es débil o no funciona, intenta incrementar este valor. + + + Invert Left Stick + Invertir joystick izquierdo + + + + Inverts the direction of the left analog stick. + Invierte la dirección del joystick analógico izquierdo. + + + + Invert Right Stick + Invertir joystick derecho + + + + Inverts the direction of the right analog stick. + Invierte la dirección del joystick analógico derecho. + AnalogJoystick @@ -808,21 +885,41 @@ Posición en la tabla de puntuaciones: {} de {} Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. Establece el factor de escalado para los ejes de los joysticks analógicos. Se recomienda un valor entre 130 % y 140 % cuando se usen mandos modernos, como el DualShock 4 o el mando de Xbox One. + + + Invert Left Stick + Invertir joystick izquierdo + + + + Inverts the direction of the left analog stick. + Invierte la dirección del joystick analógico izquierdo. + + + + Invert Right Stick + Invertir joystick derecho + + + + Inverts the direction of the right analog stick. + Invierte la dirección del joystick analógico derecho. + AudioBackend - + Null (No Output) Nulo (sin salida) - + Cubeb Cubeb - + XAudio2 XAudio2 @@ -840,91 +937,96 @@ Posición en la tabla de puntuaciones: {} de {} Configuración - + Backend: Motor: - + Buffer Size: Tamaño de búfer: - + Maximum latency: 0 frames (0.00ms) Latencia máxima: 0 fotogramas (0.00ms) - + Start Dumping On Boot Volcar audio al arrancar - + Minimal Mínima - + Off (Noisy) Desactivado (con ruidos) - + Resampling (Pitch Shift) Remuestreo (alterará el tono) - + Time Stretch (Tempo Change, Best Sound) Estiramiento temporal (alterará el tempo, mejor calidad) - + Output Latency: Latencia de salida: - + Driver: Controlador: - + Stretch Mode: Modo de estiramiento: - + + Output Device: + Dispositivo de salida: + + + Controls Controles - + Output Volume: Volumen de salida: - + Fast Forward Volume: Volumen de avance rápido: - + Mute All Sound Silenciar todo - + Mute CD Audio Silenciar audio de CD - - + + 100% 100% @@ -984,12 +1086,18 @@ Posición en la tabla de puntuaciones: {} de {} Cuando el emulador se ejecute a una velocidad que no sea el 100 %, ajustará el tempo del audio para no eliminar fotogramas. Producirá un audio más agradable cuando haya avances rápidos o se frene la emulación a cambio de perder un poco de rendimiento. - + + + Default + Predeterminado + + + Maximum Latency: %1 frames / %2 ms (%3ms buffer + %5ms output) Latencia máxima: %1 fotogramas/%2ms (búfer de %3ms + %5ms de salida) - + Maximum Latency: %1 frames / %2 ms Latencia máxima: %1 fotogramas/%2ms @@ -1016,8 +1124,8 @@ Posición en la tabla de puntuaciones: {} de {} Silencia a la fuerza el audio CD-DA y XA del CD-ROM. Puede usarse para deshabilitar la música de fondo en algunos juegos. - - + + %1% %1% @@ -1221,17 +1329,17 @@ Posición en la tabla de puntuaciones: {} de {} CPUExecutionMode - + Interpreter (Slowest) Intérprete (el más lento) - + Cached Interpreter (Faster) Intérprete en caché (más rápido) - + Recompiler (Fastest) Recompilador (el más rápido) @@ -1239,17 +1347,17 @@ Posición en la tabla de puntuaciones: {} de {} CPUFastmemMode - + Disabled (Slowest) Deshabilitado (lo más lento) - + MMap (Hardware, Fastest, 64-Bit Only) MMap (por hardware, el más rápido, solo para 64 bits) - + LUT (Faster) LUT (más rápido) @@ -1791,10 +1899,26 @@ Posición en la tabla de puntuaciones: {} de {} Automática (al acabar el fotograma) + + ColorPickerButton + + + Select LED Color + Seleccionar color del LED + + + + CommonHost + + + Default Output Device + Dispositivo de salida predeterminado + + CommonHostInterface - + Invalid version %u (%s version %u) Versión %u inválida (versión %s %u) @@ -1802,22 +1926,22 @@ Posición en la tabla de puntuaciones: {} de {} ConsoleRegion - + Auto-Detect Detección automática - + NTSC-J (Japan) NTSC-J (Japón) - + NTSC-U/C (US, Canada) NTSC-U/C (EE. UU., Canadá) - + PAL (Europe, Australia) PAL (Europa, Australia) @@ -2837,7 +2961,7 @@ Esta advertencia se mostrará solo una vez. Dirección/Torcer - + %1% %1 % @@ -2845,22 +2969,22 @@ Esta advertencia se mostrará solo una vez. ControllerCustomSettingsWidget - + %1 Settings Configuración de %1 - + Restore Default Settings Restablecer valores predeterminados - + Browse... Buscar... - + Select File Seleccionar archivo @@ -2938,7 +3062,7 @@ Esta advertencia se mostrará solo una vez. Activar origen de entrada SDL - + DualShock 4 / DualSense Enhanced Mode Modo mejorado para DualShock 4/DualSense @@ -2948,72 +3072,105 @@ Esta advertencia se mostrará solo una vez. Dispositivos detectados - + Mouse/Pointer Source Origen para ratón/cursor - - + + 10 10 - + Using raw input improves precision when you bind controller sticks to the mouse pointer. Also enables multiple mice to be used. El uso de entradas sin procesar mejora la precisión a la hora de asignar joysticks de mandos al cursor del ratón. También permite el uso de varios ratones a la vez. - + Vertical Sensitivity: Sensibilidad vertical: - + Horizontal Sensitivity: Sensibilidad horizontal: - + Enable Mouse Mapping Permitir asignar el ratón - + Use Raw Input Utilizar entradas sin procesar - + XInput Source Origen XInput - + + Controller LED Settings + Configuración de LED + + + The XInput source provides support for XBox 360 / XBox One / XBox Series controllers, and third party controllers which implement the XInput protocol. El origen XInput permite el uso de mandos de XBox 360/XBox One/XBox Series y otros mandos de terceros que utilizan el protocolo XInput. - + Enable XInput Input Source Activar origen de entrada XInput - + Profile Settings Configuración del perfil - + When this option is enabled, hotkeys can be set in this input profile, and will be used instead of the global hotkeys. By default, hotkeys are always shared between all profiles. Al activar esta opción se podrán asignar atajos a este perfil de entrada, los cuales se utilizarán en lugar de los atajos globales. Los atajos siempre se comparten entre todos los perfiles de manera predeterminada. - + Use Per-Profile Hotkeys Cambiar atajos según el perfil + + ControllerLEDSettingsDialog + + + Controller LED Settings + Configuración de LED de mandos + + + + SDL-0 LED + LED SDL-0 + + + + SDL-1 LED + LED SDL-1 + + + + SDL-2 LED + LED SDL-2 + + + + SDL-3 LED + LED SDL-3 + + ControllerMacroEditWidget @@ -3062,27 +3219,27 @@ Esta advertencia se mostrará solo una vez. Establecer... - + Not Configured Sin configurar - + Set Frequency Establecer frecuencia - + Frequency: Frecuencia: - + Macro will not repeat. La macro no se repetirá. - + Macro will toggle buttons every %1 frames. La macro alternará pulsaciones cada %1 fotogramas. @@ -3260,50 +3417,55 @@ Esta acción no puede deshacerse. ControllerType - + None Ninguno - + Digital Controller Mando digital - + Analog Controller (DualShock) Mando analógico (DualShock) - - + + Analog Joystick Palancas analógicas - + PlayStation Mouse PlayStation Mouse - + NeGcon NeGcon - + Analog Controller Mando analógico - + GunCon GunCon + + + Not Connected + Sin conectar + CoverDownloadDialog @@ -3392,42 +3554,42 @@ Esta acción no puede deshacerse. DebuggerMessage - + Added breakpoint at 0x%08X. Punto de interrupción añadido en 0x%08X. - + Removed breakpoint at 0x%08X. Punto de interrupción en 0x%08X eliminado. - + 0x%08X is not a call instruction. 0x%08X no es una instrucción de llamada. - + Can't step over double branch at 0x%08X No se puede pasar por encima de la rama doble en 0x%08X - + Stepping over to 0x%08X. Pasando a 0x%08X. - + Instruction read failed at %08X while searching for function end. Error de lectura de instrucción en %08X mientras se buscaba el fin de la función. - + Stepping out to 0x%08X. Saliendo a 0x%08X. - + No return instruction found after %u instructions for step-out at %08X. No se ha encontrado instrucción de retorno después de %u instrucciones para la salida en%08X. @@ -3598,6 +3760,7 @@ Esta acción no puede deshacerse. + Toggle &Breakpoint Activar punto de &interrupción @@ -3633,6 +3796,7 @@ Esta acción no puede deshacerse. + &Run To Cursor &Ejecutar hasta cursor @@ -3771,23 +3935,33 @@ Este archivo puede llegar a ocupar varios gigabytes, así que ten cuidado con el Fallo al añadir el punto de interrupción de salida, ¿estás en una función valida? - - + + View in &Dump + Ver en vo&lcado + + + + Follow Load/Store + Seguir carga/almacenamiento + + + + Invalid search pattern. It should contain hex digits or question marks. El patrón de búsqueda no es válido, debería contener dígitos hexadecimales o signos de interrogación. - + Pattern not found. No se ha encontrado el patrón. - + Pattern found at 0x%1 (passed the end of memory). Patrón encontrado en 0x%1 (pasó el final de la memoria). - + Pattern found at 0x%1. Patrón encontrado en 0x%1. @@ -3813,40 +3987,45 @@ Este archivo puede llegar a ocupar varios gigabytes, así que ten cuidado con el DiscRegion - + NTSC-J (Japan) NTSC-J (Japón) - + NTSC-U/C (US, Canada) NTSC-U/C (EE. UU., Canadá) - + PAL (Europe, Australia) PAL (Europa, Australia) - + Other Otra + + + Non-PS1 + Ajena a PS1 + DisplayAlignment - + Left / Top Superior izquierda - + Center Centrada - + Right / Bottom Inferior derecha @@ -3854,17 +4033,17 @@ Este archivo puede llegar a ocupar varios gigabytes, así que ten cuidado con el DisplayAspectRatio - + Auto (Game Native) Automática (nativa del juego) - + Auto (Match Window) Automática (ajustada a la ventana) - + Custom Personalizada @@ -3872,17 +4051,17 @@ Este archivo puede llegar a ocupar varios gigabytes, así que ten cuidado con el DisplayCropMode - + None No recortar - + Only Overscan Area Solo el área de sobrebarrido - + All Borders Todos los bordes @@ -4234,17 +4413,17 @@ Este archivo puede llegar a ocupar varios gigabytes, así que ten cuidado con el EmuThread - + Error Error - + No resume save state found. No se han encontrado estados guardados para continuar. - + Game ID: %1 Game Title: %2 Achievements: %5 (%6) @@ -4257,7 +4436,7 @@ Logros: %5 (%6) - + %n points %n punto @@ -4265,27 +4444,27 @@ Logros: %5 (%6) - + Rich presence inactive or unsupported. «Rich Presence» inactiva o no compatible. - + Game not loaded or no RetroAchievements available. No se ha cargado un juego o RetroAchievements no está disponible. - + %1x%2 %1 × %2 - + Game: %1 FPS Juego: %1 FPS - + Video: %1 FPS (%2%) Vídeo: %1 FPS (%2 %) @@ -4893,17 +5072,17 @@ Logros: %5 (%6) GPUDownsampleMode - + Disabled Deshabilitado - + Box (Downsample 3D/Smooth All) Cuadro (reducir resolución 3D/suavizar todo) - + Adaptive (Preserve 3D/Smooth 2D) Adaptable (preservar 3D/suavizar 2D) @@ -4911,27 +5090,27 @@ Logros: %5 (%6) GPURenderer - + Hardware (D3D11) Hardware (D3D11) - + Hardware (D3D12) Hardware (D3D12) - + Hardware (Vulkan) Hardware (Vulkan) - + Hardware (OpenGL) Hardware (OpenGL) - + Software Software @@ -5042,37 +5221,37 @@ Logros: %5 (%6) GPUTextureFilter - + Nearest-Neighbor Vecino más cercano - + Bilinear Bilineal - + Bilinear (No Edge Blending) Bilineal (sin unión de bordes) - + JINC2 (Slow) JINC2 (lento) - + JINC2 (Slow, No Edge Blending) JINC2 (lento, sin unión de bordes) - + xBR (Very Slow) xBR (muy lento) - + xBR (Very Slow, No Edge Blending) xBR (muy lento, sin unión de bordes) @@ -5080,72 +5259,72 @@ Logros: %5 (%6) GameList - + Disc Disco - + PS-EXE PS-EXE - + Playlist Lista de reproducción - + PSF PSF - + Never Nunca - + Today Hoy - + Yesterday Ayer - + {}h {}m {} h {} min - + {}h {}m {}s {} h {} min {} s - + {}m {}s {} min {} s - + {}s {} s - + None Ninguno - + {} hours {} horas - + {} minutes {} minutos @@ -5153,32 +5332,32 @@ Logros: %5 (%6) GameListCompatibilityRating - + Unknown Desconocido - + Doesn't Boot No inicia - + Crashes In Intro Cuelga al inicio - + Crashes In-Game Cuelga en juego - + Graphical/Audio Issues Problemas audiovisuales - + No Issues Sin problemas @@ -5193,7 +5372,7 @@ Logros: %5 (%6) Serial - Número de serie + N.º de serie @@ -5380,97 +5559,97 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es GameSettingsTrait - + Force Interpreter Forzar intérprete - + Force Software Renderer Forzar renderizado por software - + Force Software Renderer For Readbacks Forzar el renderizador por software para cotejar - + Force Interlacing Forzar entrelazado - + Disable True Color Deshabilitar color verdadero - + Disable Upscaling Deshabilitar escalado de resolución - + Disable Scaled Dithering Deshabilitar escalado de tramado - + Disallow Forcing NTSC Timings Deshabilitar forzado de velocidad NTSC - + Disable Widescreen Deshabilitar pantalla panorámica - + Disable PGXP Deshabilitar PGXP - + Disable PGXP Culling Deshabilitar «culling» de la PGXP - + Disable PGXP Depth Buffer Deshabilitar búfer de profundidad de la PGXP - + Force PGXP Vertex Cache Forzar caché de vértices de la PGXP - + Force PGXP CPU Mode Forzar modo CPU de la PGXP - + Force Recompiler LUT Fastmem Forzar fastmem LUT del recompilador - + Force Recompiler Memory Exceptions Forzar excepciones de memoria del recompilador - + Disable PGXP Perspective Correct Textures Deshabilitar corrección de perspectiva de texturas de la PGXP - + Disable PGXP Perspective Correct Colors Deshabilitar corrección de perspectiva de colores de la PGXP - + Force Recompiler ICache Forzar ICache del recompilador @@ -5943,12 +6122,12 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es HostInterface - + Failed to load configured BIOS file '%s' Fallo al cargar la BIOS configurada «%s» - + No BIOS image found for %s region No se ha encontrado una imagen de BIOS para la región %s @@ -5956,470 +6135,470 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es Hotkeys - - - - - - - - - - - + + + + + + + + + + + General General - + Fast Forward Avance rápido - + Toggle Fast Forward Alternar avance rápido - + Turbo Turbo - + Toggle Turbo Alternar turbo - + Toggle Fullscreen Alternar pantalla completa - + Toggle Pause Alternar pausa - + Toggle Cheats Alternar trucos - + Power Off System Apagar sistema - + Toggle Patch Codes Alternar códigos de parche - + Reset System Reiniciar sistema - + Save Screenshot Capturar pantalla - + Change Disc Cambiar disco - + Frame Step Avanzar fotograma - + Rewind Rebobinar - + Toggle Clock Speed Control (Overclocking) Alternar control de velocidad de reloj («overclocking») - - - - - - - - - - + + + + + + + + + + Graphics Gráficos - + Toggle Software Rendering Alternar renderizado por software - + Toggle PGXP Alternar PGXP - + Toggle PGXP Depth Buffer Alternar búfer de profundidad de la PGXP - + Increase Resolution Scale Incrementar escala de resolución - + Open Pause Menu Abrir menú de pausa - + Open Achievement List Abrir lista de logros - + Open Leaderboard List Abrir tabla de clasificación - - - - - - - - - - - + + + + + + + + + + + System Sistema - + Swap Memory Card Slots Cambiar ranuras de Memory Card - + Increase Emulation Speed Incrementar velocidad de emulación - + Decrease Emulation Speed Disminuir velocidad de emulación - + Reset Emulation Speed Reiniciar velocidad de emulación - + Decrease Resolution Scale Disminuir escala de resolución - + Toggle Post-Processing Activar posprocesamiento - + Reload Post Processing Shaders Recargar sombreadores de posprocesamiento - + Reload Texture Replacements Recargar reemplazos de textura - + Toggle Widescreen Alternar pantalla panorámica - + Toggle PGXP CPU Mode Alternar modo CPU de la PGXP - - - - - - - + + + + + + + Save States Estados guardados - + Load From Selected Slot Cargar ranura seleccionada - + Save To Selected Slot Guardar en ranura seleccionada - + Select Previous Save Slot Seleccionar ranura de guardado anterior - + Select Next Save Slot Seleccionar ranura de guardado siguiente - + Undo Load State Deshacer carga de estado - + Load Game State 1 Cargar estado de juego 1 - + Load Game State 2 Cargar estado de juego 2 - + Load Game State 3 Cargar estado de juego 3 - + Load Game State 4 Cargar estado de juego 4 - + Load Game State 5 Cargar estado de juego 5 - + Load Game State 6 Cargar estado de juego 6 - + Load Game State 7 Cargar estado de juego 7 - + Load Game State 8 Cargar estado de juego 8 - + Load Game State 9 Cargar estado de juego 9 - + Load Game State 10 Cargar estado de juego 10 - + Save Game State 1 Guardar estado de juego 1 - + Save Game State 2 Guardar estado de juego 2 - + Save Game State 3 Guardar estado de juego 3 - + Save Game State 4 Guardar estado de juego 4 - + Save Game State 5 Guardar estado de juego 5 - + Save Game State 6 Guardar estado de juego 6 - + Save Game State 7 Guardar estado de juego 7 - + Save Game State 8 Guardar estado de juego 8 - + Save Game State 9 Guardar estado de juego 9 - + Save Game State 10 Guardar estado de juego 10 - + Load Global State 1 Cargar estado global 1 - + Load Global State 2 Cargar estado global 2 - + Load Global State 3 Cargar estado global 3 - + Load Global State 4 Cargar estado global 4 - + Load Global State 5 Cargar estado global 5 - + Load Global State 6 Cargar estado global 6 - + Load Global State 7 Cargar estado global 7 - + Load Global State 8 Cargar estado global 8 - + Load Global State 9 Cargar estado global 9 - + Load Global State 10 Cargar estado global 10 - + Save Global State 1 Guardar estado global 1 - + Save Global State 2 Guardar estado global 2 - + Save Global State 3 Guardar estado global 3 - + Save Global State 4 Guardar estado global 4 - + Save Global State 5 Guardar estado global 5 - + Save Global State 6 Guardar estado global 6 - + Save Global State 7 Guardar estado global 7 - + Save Global State 8 Guardar estado global 8 - + Save Global State 9 Guardar estado global 9 - + Save Global State 10 Guardar estado global 10 - - - - + + + + Audio Audio - + Toggle Mute Alternar silenciado de audio - + Toggle CD Audio Mute Alternar silenciado de audio CD - + Volume Up Subir volumen - + Volume Down Bajar volumen @@ -6452,18 +6631,18 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es Borrar asignaciones - + Bindings for %1 %2 Asignaciones para %1 %2 - + Close Cerrar - - + + Push Button/Axis... [%1] Pulsa un botón/eje... [%1] @@ -6471,7 +6650,7 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es InputBindingWidget - + %n bindings %n asignación @@ -6479,8 +6658,8 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es - - + + Push Button/Axis... [%1] Pulsa un botón/eje... [%1] @@ -6488,17 +6667,17 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es InputVibrationBindingWidget - + Error Error - + No devices with vibration motors were detected. No se han detectado dispositivos con motores de vibración. - + Select vibration motor for %1. Selecciona el motor de vibración para %1. @@ -6506,52 +6685,52 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es LogLevel - + None Ninguno - + Error Error - + Warning Alerta - + Performance Rendimiento - + Information Información - + Developer Desarrollador - + Profile Perfil - + Verbose Detalles - + Debug Depuración - + Trace Seguimiento @@ -6565,15 +6744,15 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es - - + + Change Disc Cambiar disco - - + + Load State Cargar estado @@ -6598,107 +6777,107 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es Idioma - + &Help &Ayuda - + &Debug &Depuración - + Switch GPU Renderer Cambiar renderizador de GPU - + Switch CPU Emulation Mode Cambiar modo de emulación de CPU - + toolBar Barra de herramientas - + Start &Disc... Iniciar &disco... - + Start &BIOS Iniciar &BIOS - + &Scan For New Games &Buscar juegos nuevos - + &Rescan All Games &Volver a buscar todos los juegos - + Power &Off &Apagar - + &Reset &Reiniciar - + &Pause &Pausar - + &Load State &Cargar estado - + &Save State &Guardar estado - + E&xit &Salir - + Fullscreen Pantalla completa - + Resolution Scale Escala de resolución - + &GitHub Repository... Repositorio en &GitHub... - + &Issue Tracker... &Gestor de problemas... - + &Discord Server... Servidor de &Discord... - + Check for &Updates... Buscar a&ctualizaciones... @@ -6713,486 +6892,496 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es Trucos - + Switch Crop Mode Cambiar modo de recorte - + &View &Vista - + &Window Size &Tamaño de ventana - + &Tools &Herramientas - + Start &File... Iniciar &archivo... - + About &Qt... Acerca de &Qt... - + &About DuckStation... &Acerca de DuckStation... - + Change Disc... Cambiar disco... - + Cheats... Trucos... - + B&IOS B&IOS - + C&onsole &Consola - + E&mulation &Emulación - + &Controllers Man&dos - + &Hotkeys &Atajos - + &Display - &Pantalla + Ima&gen - + &Enhancements Me&joras - + &Post-Processing &Posprocesado - + Audio Audio - + Achievements Logros - + Folders Carpetas - + Game List Lista de juegos - + General General - + Advanced Avanzada - + Add Game Directory... Añadir directorio de juegos... - &Settings... - &Configuración... + &Configuración... - + From File... Desde archivo... - + From Device... Desde dispositivo... - + From Game List... Desde lista de juegos... - + Remove Disc Quitar disco - + Resume State Reanudar estado - + Global State Estado global - + Show VRAM Mostrar VRAM - + Dump CPU to VRAM Copies Volcar copias de CPU a VRAM - + Dump VRAM to CPU Copies Volcar copias de VRAM a CPU - + Disable All Enhancements Desactivar todas las mejoras - + Disable Interlacing Deshabilitar entrelazado - + Force NTSC Timings Forzar velocidad NTSC - + Dump Audio Volcar audio - + Dump RAM... Volcar RAM... - + Dump VRAM... Volcar VRAM... - + Dump SPU RAM... Volcar RAM del SPU... - + Show GPU State Mostrar estado de la GPU - + Show CD-ROM State Mostrar estado del CD-ROM - + Show SPU State Mostrar estado de la SPU - + Show Timers State Mostrar estado de los temporizadores - + Show MDEC State Mostrar estado del MDEC - + Show DMA State Mostrar estado del DMA - + &Screenshot Cap&tura - + &Memory Cards &Memory Cards - + + Enable GDB server + Habilitar servidor GDB + + + Start Big Picture Mode Iniciar Big Picture - + Big Picture Big Picture - + Cover Downloader Descargador de carátulas - - + + Resume Continuar - + + + &Settings + &Configuración + + + Resumes the last save state created. Continúa desde el último estado guardado. - + &Toolbar Barra de &herramientas - + Lock Toolbar Bloquear barra de herramientas - + &Status Bar Barra de &estado - + Game &List &Lista de juegos - + System &Display &Ventana del sistema - + Game &Properties &Propiedades del juego - + Memory &Card Editor &Editor de Memory Cards - + C&heat Manager &Administrador de trucos - + CPU D&ebugger D&epurador de CPU - + Game &Grid &Cuadrícula de juegos - + Show Titles (Grid View) Mostrar títulos (vista en cuadrícula) - + Zoom &In (Grid View) &Aumentar tamaño (vista en cuadrícula) - + Ctrl++ Ctrl++ - + Zoom &Out (Grid View) &Disminuir tamaño (vista en cuadrícula) - + Ctrl+- Ctrl+- - + Refresh &Covers (Grid View) Actuali&zar carátulas (vista en cuadrícula) - + Open Memory Card Directory... Abrir directorio de Memory Cards... - + Open Data Directory... Abrir directorio de datos... - + Power Off &Without Saving Apagar &sin guardar - + Failed to create host display device context. Fallo al crear el contexto del dispositivo de vídeo del sistema. - - + + Select Disc Image Seleccionar imagen de disco - + Could not find any CD-ROM devices. Please ensure you have a CD-ROM drive connected and sufficient permissions to access it. No se ha encontrado un dispositivo de CD-ROM. Asegúrate de tener una unidad de CD-ROM conectada y los permisos necesarios de acceso. - + %1 (%2) %1 (%2) - + Select disc drive: Selecciona la unidad de disco: - + Start Disc Iniciar disco - - + + Cheat Manager Administrador de trucos - + Properties... Propiedades... - + Open Containing Directory... Abrir directorio contenedor... - + Set Cover Image... Establecer imagen de carátula... - + Default Boot Inicio predeterminado - + Fast Boot Inicio rápido - + Full Boot Inicio completo - + Boot and Debug Iniciar y depurar - + Exclude From List Excluir de la lista - + Add Search Directory... Añadir directorio de búsqueda... - + Select Cover Image Seleccionar imagen de carátula - + All Cover Image Types (*.jpg *.jpeg *.png) Todos los archivos de imágenes (*.jpg *.jpeg *.png) - + Cover Already Exists La carátula ya existe - + A cover image for this game already exists, do you wish to replace it? Ya existe una carátula para este juego, ¿quieres reemplazarla? - - + + Copy Error Error de copia - + Failed to remove existing cover '%1' Fallo al eliminar la carátula existente «%1» - + Failed to copy '%1' to '%2' Fallo al copiar «%1» a «%2» @@ -7202,58 +7391,58 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es Todos los tipos de archivo (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psf *.minipsf *.m3u);;Imágenes RAW de una pista (*.bin *.img *.iso);;Archivo CUE (*.cue);;Imágenes CHD para MAME (*.chd);;Imágenes Error Code Modeler (*.ecm);;Imágenes de Media Descriptor (*.mds);;EBOOTs de PlayStation (*.pbp *.PBP);;Ejecutables de PlayStation (*.exe *.psexe *.ps-exe);;Archivos Portable Sound Format (*.psf *.minipsf);;Listas de reproducción (*.m3u) - - - - - + + + + + Error Error - + Failed to get window info from widget Fallo al obtener la información de la ventana a partir del widget - + Failed to get new window info from widget Fallo al obtener la información de la ventana nueva a partir del widget - + Paused En pausa - + Resume (%1) Continuar (%1) - - - + + + Game Save %1 (%2) Estado de juego %1 (%2) - + Edit Memory Cards... Editar Memory Cards... - + Delete Save States... Borrar estados guardados... - + Confirm Save State Deletion Confirmar borrado de estados guardados - + Are you sure you want to delete all save states for %1? The saves will not be recoverable. @@ -7262,67 +7451,67 @@ The saves will not be recoverable. Una vez sean borrados, no se podrán recuperar. - + Load From File... Cargar archivo... - - + + Select Save State File Seleccionar archivo de estado guardado - - + + Save States (*.sav) Estados guardados (*.sav) - + Undo Load State Deshacer carga de estado - - + + Game Save %1 (Empty) Estado de juego %1 (vacío) - - + + Global Save %1 (%2) Estado global %1 (%2) - - + + Global Save %1 (Empty) Estado global %1 (vacío) - + Save To File... Guardar en archivo... - + &Enabled Cheats &Trucos habilitados - + &Apply Cheats &Aplicar trucos - + Load Resume State Cargar estado para continuar - + A resume save state was found for this game, saved at: %1. @@ -7335,144 +7524,163 @@ Do you want to load this state, or start from a fresh boot? ¿Deseas cargar este estado o ejecutar el juego desde el principio? - + Fresh Boot Empezar de cero - + Delete And Boot Borrar y empezar - + Failed to delete save state file '%1'. Fallo al eliminar el estado guardado «%1». - + Confirm Disc Change Confirmación de cambio de disco - + Do you want to swap discs or boot the new image (via system reset)? ¿Deseas cambiar de disco o ejecutar la imagen nueva (reiniciando el sistema)? - + Swap Disc Cambiar disco - + Reset Reiniciar - + Cancel Cancelar - + You must select a disc to change discs. Para cambiar de disco, debes seleccionar uno. - + + Reset Play Time + Reiniciar tiempo jugado + + + + Confirm Reset + Confirmar reinicio + + + + Are you sure you want to reset the play time for '%1'? + +This action cannot be undone. + ¿Seguro que quieres reiniciar el tiempo jugado de «%1»? + +Esta acción no se puede deshacer. + + + %1x Scale Escala %1x - - - + + + Destination File Archivo de destino - - + + Binary Files (*.bin) Archivos BIN (*.bin) - + Binary Files (*.bin);;PNG Images (*.png) Archivos BIN (*.bin);;Imágenes PNG (*.png) - + Default Predeterminado - + Fusion Fusion - + Dark Fusion (Gray) Dark Fusion (gris) - + Dark Fusion (Blue) Dark Fusion (azul) - + QDarkStyle QDarkStyle - + Confirm Shutdown Confirmar apagado - + Are you sure you want to shut down the virtual machine? ¿Seguro que quieres apagar la máquina virtual? - + Save State For Resume Estado para continuar - - - - + + + + Memory Card Not Found Memory Card no encontrada - + Memory card '%1' does not exist. Do you want to create an empty memory card? La Memory Card «%1» no existe. ¿Quieres crear una Memory Card vacía? - + Failed to create memory card '%1' Fallo al crear la Memory Card «%1» - - + + Memory card '%1' could not be found. Try starting the game and saving to create it. No se ha encontrado la Memory Card «%1». Intenta iniciar el juego y guardar una partida para crearla. - + Do not show again No mostrar otra vez - + Using cheats can have unpredictable effects on games, causing crashes, graphical glitches, and corrupted saves. By using the cheat manager, you agree that it is an unsupported configuration, and we will not provide you with any assistance when games break. Cheats persist through save states even after being disabled, please remember to reset/reboot the game after turning off any codes. @@ -7485,17 +7693,17 @@ Los trucos persistirán a través de los estados de guardado incluso después de ¿Seguro que quieres continuar? - + Updater Error Error de actualización - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please follow the instructions under "Downloading and Running" at the link below:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> <p>Estás intentando actualizar a una versión de DuckStation no oficial de GitHub. Para evitar incompatibilidades, el actualizador automático solo buscará compilaciones oficiales.</p><p>Para obtener una versión oficial, sigue las instrucciones en el apartado «Downloading and Running» en el siguiente enlace:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> - + Automatic updating is not supported on the current platform. Las actualizaciones automáticas no son compatibles con la plataforma actual. @@ -7829,32 +8037,32 @@ Los trucos persistirán a través de los estados de guardado incluso después de MemoryCardType - + No Memory Card No utilizar una Memory Card - + Shared Between All Games Compartida entre todos los juegos - + Separate Card Per Game (Serial) Memory Card individual para cada juego (por número de serie) - + Separate Card Per Game (Title) Memory Card individual para cada juego (por título) - + Separate Card Per Game (File Title) Memory Card individual para cada juego (por nombre de archivo) - + Non-Persistent Card (Do Not Save) Memory Card no persistente (no guardar) @@ -7862,22 +8070,22 @@ Los trucos persistirán a través de los estados de guardado incluso después de MultitapMode - + Disabled Deshabilitado - + Enable on Port 1 Only Habilitar solamente en el puerto 1 - + Enable on Port 2 Only Habilitar solamente en el puerto 2 - + Enable on Ports 1 and 2 Habilitar en los puertos 1 y 2 @@ -7908,17 +8116,17 @@ Los trucos persistirán a través de los estados de guardado incluso después de OSDMessage - + Acquired exclusive fullscreen. Adquirida pantalla completa exclusiva. - + Failed to acquire exclusive fullscreen. Fallo al adquirir la pantalla completa exclusiva. - + Lost exclusive fullscreen. Se ha perdido la pantalla completa exclusiva. @@ -7963,77 +8171,76 @@ Los trucos persistirán a través de los estados de guardado incluso después de La escala de resolución %ux no es compatible con el suavizado adaptativo, se va a usar %ux. - OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.0 is required. - Renderizador de OpenGL no disponible, tu hardware o tus controladores no son lo suficientemente modernos. Se requiere de OpenGL 3.1 o de OpenGL ES 3.0. + Renderizador de OpenGL no disponible, tu hardware o tus controladores no son lo suficientemente modernos. Se requiere de OpenGL 3.1 o de OpenGL ES 3.0. - + System reset. Reiniciando sistema. - + Loading state from '%s' failed. Resetting. Fallo al cargar el estado «%s». Reiniciando. - + Saving state to '%s' failed. Fallo al guardar el estado «%s». - + PGXP is incompatible with the software renderer, disabling PGXP. PGXP es incompatible con el renderizador por software, por lo tanto se deshabilitará. - + Rewind is not supported on 32-bit ARM for Android. La función de rebobinado no es compatible con las versiones ARM de 32 bits para Android. - + Runahead is not supported on 32-bit ARM for Android. La predicción de latencia no es compatible con las versiones ARM de 32 bits para Android. - + Rewind is disabled because runahead is enabled. Se ha desactivado la función de rebobinado porque la predicción de latencia está activada. - + Switching to %s%s GPU renderer. Cambiando al renderizador de GPU %s%s. - + Switching to %s audio backend. Cambiando al motor de audio %s. - + Switching to %s CPU execution mode. Cambiando al modo de ejecución de CPU %s. - + Recompiler options changed, flushing all blocks. Las opciones del recompilador han cambiado, limpiando los bloques. - + PGXP enabled, recompiling all blocks. PGXP habilitado, recompilando todos los bloques. - + PGXP disabled, recompiling all blocks. PGXP deshabilitado, recompilando todos los bloques. - + Switching to %s renderer... Cambiando al renderizador %s... @@ -8053,119 +8260,119 @@ Los trucos persistirán a través de los estados de guardado incluso después de Memory Card guardada en «{}». - + Save state contains controller type %s in port %u, but %s is used. Switching. El estado guardado contiene el tipo de mando %s en el puerto %u, pero se está utilizando %s. Cambiando. - + Ignoring mismatched controller type %s in port %u. Ignorando diferencias en el tipo de mando %s en el puerto %u. - + Memory card %u from save state does match current card data. Simulating replugging. La Memory Card %u del estado guardado no coincide con los datos de la Memory Card actual. Simulando reconexión. - + Memory card %u present in save state but not in system. Ignoring card. La Memory Card %u está presente en el estado guardado, pero no en el sistema. Ignorando Memory Card. - + Memory card %u present in system but not in save state. Replugging card. La Memory Card %u está presente en el sistema, pero no en el estado guardado. Reconectando Memory Card. - + Memory card %u present in save state but not in system. Creating temporary card. La Memory Card %u está presente en el estado guardado, pero no en el sistema. Creando Memory Card temporal. - + Memory card %u present in system but not in save state. Removing card. La Memory Card %u está presente en el sistema, pero no en el estado guardado. Quitando Memory Card. - + CD image preloading not available for multi-disc image '%s' La precarga de imagen de CD no está disponible para la imagen de varios discos «%s» - + Precaching CD image failed, it may be unreliable. Fallo al precachear la imagen de CD, el sistema podría ser inestable. - + CPU clock speed is set to %u%% (%u / %u). This may result in instability. La velocidad de reloj de la CPU está establecida en %u%% (%u / %u). Podría haber inestabilidades. - + CD-ROM read speedup set to %ux (effective speed %ux). This may result in instability. Aceleración de lectura del CDROM establecida en %ux (velocidad efectiva %ux). Podría haber inestabilidades. - + CD-ROM seek speedup set to instant. This may result in instability. Aceleración de búsqueda de CD-ROM establecida como instantánea. Podría haber inestabilidades. - + CD-ROM seek speedup set to %ux. This may result in instability. Aceleración de búsqueda de CD-ROM establecida en %ux. Podría haber inestabilidades. - + Failed to initialize %s renderer, falling back to software renderer. Fallo al inicializar el renderizador %s, cambiando al renderizador por software. - + WARNING: CPU overclock (%u%%) was different in save state (%u%%). AVISO: la velocidad de «overclocking» de la CPU (%u %%) es diferente a la del estado guardado (%u %%). - + Failed to open CD image from save state '%s': %s. Using existing image '%s', this may result in instability. Fallo al abrir la imagen de CD del estado guardado «%s»: %s. Usando la imagen existente «%s», podría haber inestabilidades. - + Failed to open disc image '%s': %s. Fallo al abrir la imagen de disco «%s»: %s. - + Failed to switch to subimage %u in '%s': %s. Error al cambiar la subimagen %u en «%s»: %s. - + Switched to sub-image %s (%u) in '%s'. Cambiado a la subimagen %s (%u) en «%s». - + Inserted disc '%s' (%s). Disco «%s» introducido (%s). - - + + Failed to load post processing shader chain. Error al cargar la cadena de sombreadores de posprocesamiento. - + No cheats are loaded. No hay trucos cargados. - + %n cheats are now active. %n truco está habilitado. @@ -8173,7 +8380,7 @@ Los trucos persistirán a través de los estados de guardado incluso después de - + %n cheats are now inactive. %n truco está deshabilitado. @@ -8181,147 +8388,147 @@ Los trucos persistirán a través de los estados de guardado incluso después de - + Cannot load state for game without serial. No se puede cargar el estado de un juego que no tenga número de serie. - + No save state found in slot {}. No se ha encontrado un estado guardado en la ranura {}. - + Cannot save state for game without serial. No se puede guardar el estado de un juego que no tenga número de serie. - + Achievements are disabled or unavailable for game. Este juego tiene los logros bloqueados o no disponibles. - + Leaderboards are disabled or unavailable for game. Este juego tiene las tablas de puntuaciones bloqueadas o no disponibles. - + CPU clock speed control enabled (%u%% / %.3f MHz). Control de velocidad de reloj del CPU habilitado (%u %% / %.3f MHz). - + CPU clock speed control disabled (%.3f MHz). Control de velocidad de reloj del CPU deshabilitado (%.3f MHz). - + PGXP is now enabled. PGXP habilitada. - + PGXP is now disabled. PGXP deshabilitada. - + PGXP Depth Buffer is now enabled. Búfer de profundidad de la PGXP habilitado. - + PGXP Depth Buffer is now disabled. Búfer de profundidad de la PGXP deshabilitado. - - - + + + Volume: {}% Volumen: {} % - + Texture replacements reloaded. Reemplazos de textura recargados. - + Failed to save undo load state. Fallo al guardar la acción para deshacer la carga del estado. - + Rewinding is not enabled. El rebobinado no está habilitado. - - - + + + Emulation speed set to %u%%. Velocidad de emulación establecida en %u %%. - + PGXP CPU mode is now enabled. Modo CPU de la PGXP habilitado. - + PGXP CPU mode is now disabled. Modo CPU de la PGXP deshabilitado. - + Volume: Muted Volumen: silenciado - + CD Audio Muted. Audio de CD silenciado. - + CD Audio Unmuted. Según la RAE es válido el uso del prefijo 'de-'. Audio de CD activado. - + Started dumping audio to '%s'. Comenzando a volcar audio en «%s». - + Failed to start dumping audio to '%s'. Fallo al iniciar el volcado de audio en «%s». - + Stopped dumping audio. Volcado de audio finalizado. - + Screenshot file '%s' already exists. El archivo de captura «%s» ya existe. - + Failed to save screenshot to '%s' Fallo al guardar la captura «%s» - + Screenshot saved to '%s'. Captura de pantalla guardada en «%s». - + Controller in port %u (%s) is not supported for %s. Supported controllers: %s Please configure a supported controller from the list above. @@ -8330,32 +8537,32 @@ Mandos soportados: %s Selecciona un control de la lista superior. - + Failed to load cheats from '%s'. Fallo al cargar trucos de «%s». - + Swapped memory card ports. Both ports have a memory card. Intercambiados los puertos de Memory Card. Ambos tienen una Memory Card. - + Swapped memory card ports. Port 2 has a memory card, Port 1 is empty. Intercambiados los puertos de Memory Card. El puerto 2 contiene una Memory Card y el puerto 1 está vacío. - + Swapped memory card ports. Port 1 has a memory card, Port 2 is empty. Intercambiados los puertos de Memory Card. El puerto 1 contiene una Memory Card y el puerto 2 está vacío. - + Swapped memory card ports. Neither port has a memory card. Intercambiados los puertos de Memory Card. Ninguno tiene una Memory Card. - + Saved %n cheats to '%s'. Se ha guardado %n truco en «%s». @@ -8363,37 +8570,42 @@ Selecciona un control de la lista superior. - + Widescreen hack is now enabled, and aspect ratio is set to %s. Hack para pantallas panorámicas habilitado, relación de aspecto configurada en %s. - + Widescreen hack is now disabled, and aspect ratio is set to %s. Hack para pantallas panorámicas deshabilitado, relación de aspecto configurada en %s. - + Failed to save cheat list to '%s' Fallo al guardar la lista de trucos en «%s» - + Loading state from '{}'... Cargando estado de «{}»... - + Save State Guardar estado - + State saved to '{}'. Estado guardado en «{}». + + + This save state was created with a different BIOS version or patch options. This may cause stability issues. + Este estado de guardado se creó con una versión de la BIOS o con opciones de parche distintas. Podría haber problemas de estabilidad. + - + %n cheats are enabled. This may result in instability. %n truco habilitado. Podría haber inestabilidades. @@ -8401,122 +8613,127 @@ Selecciona un control de la lista superior. - + Deleted cheat list '%s'. Lista de trucos «%s» borrada. - + Cheat '%s' enabled. Truco «%s» habilitado. - + Cheat '%s' disabled. Truco «%s» deshabilitado. - + Applied cheat '%s'. Aplicado truco «%s». - + Cheat '%s' is already enabled. El truco «%s» ya está activado. - + Post-processing is now enabled. Posprocesamiento habilitado. - + Post-processing is now disabled. Posprocesamiento deshabilitado. - + Failed to load post-processing shader chain. Fallo al cargar la cadena de sombreadores de posprocesamiento. - + Post-processing shaders reloaded. Sombreadores de posprocesamiento recargados. - + CPU interpreter forced by game settings. Intérprete de CPU forzado por la configuración del juego. - + Software renderer forced by game settings. Renderizado por software forzado por la configuración del juego. - + + Using software renderer for readbacks based on game settings. + Utilizando renderizador por software para cotejar por lo indicado en la configuración del juego. + + + Interlacing forced by game settings. Entrelazado forzado por la configuración del juego. - + True color disabled by game settings. Color verdadero deshabilitado por la configuración del juego. - + Upscaling disabled by game settings. Escalado deshabilitado por la configuración del juego. - + Scaled dithering disabled by game settings. Escalado de tramado deshabilitado por la configuración del juego. - + Widescreen disabled by game settings. Pantalla panorámica deshabilitada por la configuración del juego. - + Forcing NTSC Timings disallowed by game settings. Forzado de velocidad NTSC deshabilitado por la configuración del juego. - + PGXP geometry correction disabled by game settings. Corrección de geometría de la PGXP deshabilitada por la configuración del juego. - + PGXP culling disabled by game settings. «Culling» de la PGXP deshabilitado por la configuración del juego. - + PGXP perspective corrected textures disabled by game settings. Corrección de perspectiva de texturas de la PGXP deshabilitada por la configuración del juego. - + PGXP perspective corrected colors disabled by game settings. Corrección de perspectiva de colores de la PGXP deshabilitada por la configuración del juego. - + PGXP vertex cache forced by game settings. Caché de vértices de la PGXP forzada por la configuración del juego. - + PGXP CPU mode forced by game settings. Modo CPU de la PGXP forzado por la configuración del juego. - + PGXP Depth Buffer disabled by game settings. Búfer de profundidad de la PGXP deshabilitado por la configuración del juego. @@ -8525,6 +8742,11 @@ Selecciona un control de la lista superior. Analog mode forcing is disabled by game settings. Controller will start in digital mode. Forzado del modo analógico deshabilitado por la configuración del juego. El mando se iniciará en modo digital. + + + OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required. + Renderizador de OpenGL no disponible: tu hardware o tus controladores no son lo suficientemente modernos. Se requiere de OpenGL 3.1 o de OpenGL ES 3.1. + PlayStationMouse @@ -8709,29 +8931,29 @@ La URL era: %1 QtHost - - - + + + Error Error - + File '%1' does not exist. El archivo «%1» no existe. - + The specified save state does not exist. El estado de guardado indicado no existe. - + Cannot use no-gui mode, because no boot filename was specified. No se puede utilizar el modo «no-gui» porque no se ha especificado un nombre del archivo de arranque. - + Cannot use batch mode, because no boot filename was specified. No se puede utilizar el modo por lotes porque no se ha especificado un nombre del archivo de arranque. @@ -8767,42 +8989,42 @@ La URL era: %1 SaveStateSelectorUI - + Load Cargar - + Save Guardar - + Select Previous Seleccionar anterior - + Select Next Seleccionar siguiente - + No Save State No hay estados guardados - + Global Slot %d Ranura global %d - + Game Slot %d Ranura de juego %d - + %s Slot %d Ranura %s %d @@ -9019,63 +9241,68 @@ La URL era: %1 System - + Failed to load %s BIOS. Fallo al cargar la BIOS %s. - - + + Error Error - + Failed to load save state file '{}' for booting. Fallo al arrancar con el archivo de estado «{}». - + + Incorrect BIOS image size + Tamaño de imagen de la BIOS incorrecto + + + Save state is incompatible: minimum version is %u but state is version %u. El estado guardado es incompatible: la versión mínima soportada es %u, pero el estado es para la versión %u. - + Save state is incompatible: maximum version is %u but state is version %u. El estado guardado es incompatible: la versión máxima soportada es %u, pero el estado es para la versión %u. - + Failed to open CD image '%s' used by save state: %s. Fallo al abrir la imagen de CD «%s» del estado guardado %s. - + Failed to switch to subimage %u in CD image '%s' used by save state: %s. Fallo al cambiar la subimagen %u en el CD «%s» utilizado por el estado guardado %s. - + Per-game memory card cannot be used for slot %u as the running game has no code. Using shared card instead. No puede usarse una Memory Card individual para el juego en la ranura %u ya que el juego no tiene un código. Se usará una Memory Card compartida. - + Per-game memory card cannot be used for slot %u as the running game has no title. Using shared card instead. No puede usarse una Memory Card individual para el juego en la ranura %u ya que el juego no tiene un título. Se usará una Memory Card compartida. - + Per-game memory card cannot be used for slot %u as the running game has no path. Using shared card instead. No puede usarse una Memory Card individual para el juego en la ranura %u ya que el juego no tiene una ruta. Se usará una Memory Card compartida. - + Game changed, reloading memory cards. Juego cambiado, volviendo a cargar las Memory Cards. - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s @@ -9096,7 +9323,7 @@ Revisa las instrucciones del archivo README sobre como agregar un archivo SBI. ¿Quieres continuar? - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s diff --git a/src/duckstation-qt/translations/duckstation-qt_es.ts b/src/duckstation-qt/translations/duckstation-qt_es.ts old mode 100644 new mode 100755 index f8fe8d9fca..da08ce7dd9 --- a/src/duckstation-qt/translations/duckstation-qt_es.ts +++ b/src/duckstation-qt/translations/duckstation-qt_es.ts @@ -1,6 +1,6 @@ - + AboutDialog @@ -14,27 +14,27 @@ DuckStation - + %1 (%2) %1 (%2) - + DuckStation is a free and open-source simulator/emulator of the Sony PlayStation<span style="vertical-align:super;">TM</span> console, focusing on playability, speed, and long-term maintainability. - DuckStation es un emulador/simulador gratuito y de código abierto de la consola Sony PlayStation<span style="vertical-align:super;">TM</span> especializado en jugabilidad, velocidad y mantenimiento a largo plazo. + DuckStation es un emulador/simulador gratuito y de código abierto de la consola Sony PlayStation<span style="vertical-align:super;">TM</span>, enfocándose en jugabilidad, velocidad y mantenimiento a largo plazo. - + Authors Autores - + Icon by Icono por - + License Licencia @@ -45,18 +45,18 @@ RetroAchievements Login Window title - Inicio de sesión en RetroAchievements + Iniciar sesión en RetroAchievements RetroAchievements Login Header text - Inicio de sesión en RetroAchievements + Iniciar sesión en RetroAchievements Please enter user name and password for retroachievements.org below. Your password will not be saved in DuckStation, an access token will be generated and used instead. - Ingrese el nombre de usuario y contraseña de retroachievements.org. Su contraseña no será guardada en DuckStation, en su lugar se generará y se utilizará un token de acceso. + Ingresa tu nombre de usuario y contraseña de retroachievements.org. DuckStation no guardará tu contraseña; en su lugar se generará y se utilizará un token de acceso. @@ -74,31 +74,27 @@ Listo... - + &Login &Iniciar sesión - &Cancel - &Cancelar - - - + Logging in... Iniciando sesión... - + Login Error Error al iniciar sesión - + Login failed. Please check your username and password, and try again. - Fallo al iniciar sesión. Chequee que el nombre de usuario y contraseña estén bien escritos y vuelva a intentarlo. + Fallo al iniciar sesión. Comprueba el nombre de usuario y contraseña e intenta de nuevo. - + Login failed. Fallo al iniciar sesión. @@ -108,7 +104,7 @@ Form - Formulario + Form @@ -117,245 +113,274 @@ - - Enable Achievements - Logros + + Enable Rich Presence + Habilitar Rich Presence - - Enable Rich Presence - «Rich Presence» + + Enable Leaderboards + Habilitar tabla de posiciones - - Enable Test Mode - Modo de prueba + + Enable Achievements + Habilitar logros - - Use First Disc From Playlist - Usar el primer disco de la lista + + Enable Hardcore Mode + Habilitar modo Hardcore - - Enable Hardcore Mode - Modo «hardcore» + + Show Challenge Indicators + Mostrar indicadores de desafíos - + + Use First Disc From Playlist + Utilizar el primer disco de la lista + + + + + Enable Test Mode + Habilitar modo de prueba + + + + Test Unofficial Achievements Probar logros no oficiales - + + + Enable Sound Effects + Habilitar efectos de sonido + + + + + Show Notifications + Mostrar notificaciones + + + Account Cuenta - - + + Login... - Acceso... + Iniciar sesión... - + View Profile... Ver perfil... - + Game Info Información del juego - + <html><head/><body><p align="justify">DuckStation uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/"><span style=" text-decoration: underline; color:#0000ff;">retroachievements.org</span></a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html> - + <html><head/><body><p align="justify">DuckStation usa RetroAchievements como base de datos de logros y hacer seguimiento del progreso. Para utilizar el sistema de logros, primero debes registrar una cuenta en <a href="https://retroachievements.org/"><span style=" text-decoration: underline; color:#0000ff;">retroachievements.org</span></a>.</p><p align="justify">Para ver la lista de logros dentro del juego, presiona el atajo para <span style=" font-weight:600;">Abrir menu rápido</span> y selecciona <span style=" font-weight:600;">Logros</span> desde el menú.</p></body></html> - <html><head/><body><p align="justify">DuckStation uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/"><span style=" text-decoration: underline; color:#0000ff;">retroachievements.org</span></a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Quick Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html> - <html><body><p align="justify">DuckStation utiliza RetroAchievements como base de datos de logros y para realizar un seguimiento de sus progresos. Para utilizar los logros debe registrarse en <a href="https://retroachievements.org/"><span style="text-decoration: underline; color: # 0000ff;">retroachievements.org</span></a>.</p><p align="justify">Para ver la lista de logros dentro del juego, presione la tecla de acceso rápido <span style="font-weight:600;">Abrir menú rápido</span> y seleccione <span style="font-weight:600;">Logros</span> en el menú.</p></body></html> - - - - - - + + - + + + Unchecked Deshabilitado - + When enabled and logged in, DuckStation will scan for achievements on startup. - Si esta opción está habilitada e inició sesión, DuckStation buscará logros al iniciarse. + Cuando esté habilitado y se haya iniciado sesión, DuckStation buscará logros al iniciarse. - + When enabled, DuckStation will assume all achievements are locked and not send any unlock notifications to the server. - Si esta opción está habilitada, DuckStation asumirá que todos los logros están bloqueados y no enviará notificaciones de desbloqueo al servidor. + Cuando esté habilitado, DuckStation asumirá que todos los logros están bloqueados, y no enviará ninguna notificación de desbloqueo al servidor. - + When enabled, DuckStation will list achievements from unofficial sets. Please note that these achievements are not tracked by RetroAchievements, so they unlock every time. - Si esta opción está habilitada, DuckStation listará los logros no oficiales. Tenga en cuenta que éstos no son rastreados por RetroAchievements, por lo cual se desbloquearán cada vez que ocurran. + Cuando esté habilitado, DuckStation listará logros no oficiales. Ten en cuenta que RetroAchievements no rastreará estos logros, por lo que se desbloquearán cada vez que se consigan. - + When enabled, rich presence information will be collected and sent to the server where supported. - Si esta opción está habilitada, se recopilará y enviará información de «Rich Presence» a un servidor compatible. + Cuando esté habilitado, se recopilará y enviará información de Rich Presence a un servidor compatible. - + When enabled, the first disc in a playlist will be used for achievements, regardless of which disc is active. - Si esta opción está habilitada, se utilizará el primer disco de una lista para buscar logros, independientemente del disco que esté activo. + Cuando esté habilitado, se utilizará el primer disco de una lista para los logros, independientemente de que disco esté activo. - - "Challenge" mode for achievements. Disables save state, cheats, and slowdown functions, but you receive double the achievement points. - Un modo más «desafiante» para los logros. Desactiva los estados de guardado, los trucos y las funciones de ralentización, pero a cambio obtendrá el doble de puntos de logros. + + "Challenge" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions. + Modo "desafío" para los logros, incluyendo el seguimiento de tablas de posiciones. Deshabilita los estados guardados, trucos y funciones de ralentización. + + + + + + + Checked + Habilitado + + + + Displays popup messages on events such as achievement unlocks and leaderboard submissions. + Muestra mensajes emergentes durante eventos como desbloqueo de logros y envíos a la tabla de posiciones. + + + + Plays sound effects for events such as achievement unlocks and leaderboard submissions. + Reproduce efectos de sonido durante eventos como desbloqueo de logros y envíos a la tabla de posiciones. + + + + Enables tracking and submission of leaderboards in supported games. If leaderboards are disabled, you will still be able to view the leaderboard and scores, but no scores will be uploaded. + Habilita el seguimiento y envíos a tablas de posiciones en juegos compatibles. Si las tablas de posiciones están deshabilitadas, podrás ver las tablas y puntuaciones, pero no enviar las tuyas. - + + Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active. + Muestra iconos en la esquina inferior derecha de la pantalla cuando un logro "desafío" esté activo. + + + + Reset System + Reiniciar sistema + + + + Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now? + El modo Hardcore no será habilitado hasta que se reinicie el sistema. ¿Quieres reiniciar el sistema ahora? + + + Username: %1 Login token generated on %2. Nombre de usuario: %1 Token de inicio de sesión generado en %2. - + Logout Cerrar sesión - + Not Logged In. Sesión no iniciada. - - Enabling hardcore mode will shut down your current game. - - - Habilitar el modo «hardcore» finalizará su partida actual. - - - - - The current state will be saved, but you will be unable to load it until you disable hardcore mode. - - - El estado actual será guardado, pero no podrá cargarlo hasta que desactive el modo «hardcore». - - - - - Do you want to continue? - ¿Desea continuar? - Achievements - + Loading state - + Cargando estado - + Resuming state - + Resumiendo estado - + Hardcore mode disabled by state switch. - + El cambio de estados guardados deshabilitó el modo Hardcore. - + Hardcore mode will be enabled on system reset. - + El modo Hardcore será habilitado al reiniciar el sistema. - + Confirm Hardcore Mode - + Confirma el modo Hardcore - + {0} cannot be performed while hardcore mode is active. Do you want to disable hardcore mode? {0} will be cancelled if you select No. - + No se puede utilizar la función de {0} mientras el modo Hardcore esté habilitado. ¿Quieres deshabilitar el modo Hardcore? La función {0} será cancelada si seleccionas no. - + Hardcore mode is now enabled. - + El modo Hardcore está habilitado. - + Hardcore mode is now disabled. - + El modo Hardcore está deshabilitado. - + {} (Hardcore Mode) - + {} (modo Hardcore) - + You have earned {} of {} achievements, and {} of {} points. - + Conseguiste {} de {} logros, y {} de {} puntos. - + This game has no achievements. - No hay logros para este juego. - - - - Leaderboards are enabled. - La tabla de clasificación está habilitada. + No hay logros para este juego. - - Leaderboards are disabled because hardcore mode is off. - - - - + Your Score: {} (Best: {}) Leaderboard Position: {} of {} - + Tu puntuación: {} (mejor: {}) +Posición en la tabla: {} de {} - + This game has {} leaderboards. - + Este juego tiene {} tablas de puntuaciones. - + Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only. - + No se enviarán puntuaciones porque el modo Hardcore está deshabilitado. Las tablas de posiciones serán de solo lectura. - + Time - + Tiempo - + Score - + Puntuación - + Downloading leaderboard data, please wait... - + Descargando información de tablas de posiciones, espera... @@ -363,7 +388,7 @@ Leaderboard Position: {} of {} Form - Formulario + Form @@ -382,31 +407,36 @@ Leaderboard Position: {} of {} - + Log To System Console Registrar en consola del sistema - + Log To Window Registrar en ventana - + Log To Debug Console Registrar en consola de depuración - + Log To File Registrar en archivo + + + System Settings + Opciones del sistema + - + Show Debug Menu Mostrar menú de depuración @@ -431,540 +461,463 @@ Leaderboard Position: {} of {} Restablecer valores predeterminados - - Show Status Indicators - Mostrar indicadores de estado + + Select folder for %1 + Seleccionar carpeta para %1 - - Show Enhancement Settings - Mostrar configuración de mejoras + + Log Level + Nivel de registro - Controller Enhanced Mode (PS4/PS5) - Modo de mejora de control (PS4/PS5) + + Information + Información - - Multisample Antialiasing - Antialiasing de muestreo múltiple + + Sets the verbosity of messages logged. Higher levels will log more messages. + Establece el nivel de detalle de los mensajes en el registro. Niveles más altos mostrarán más mensajes. - - PGXP Vertex Cache - Caché de vértices de la PGXP + + + + + User Preference + Preferencia de usuario - - PGXP Geometry Tolerance - Tolerancia geométrica de la PGXP + + Logs messages to the console window. + Registra mensajes en la ventana de la consola. - - PGXP Depth Clear Threshold - Umbral de limpieza de profundidad de la PGXP + + Logs messages to the debug console where supported. + Registra mensajes en la consola de depuración cuando sea posible. - - Enable Recompiler Memory Exceptions - Excepciones de memoria del recompilador + + Logs messages to the window. + Registra mensajes en la ventana. - - Enable Recompiler Block Linking - Vinculación de bloques del recompilador + + Logs messages to duckstation.log in the user directory. + Registra mensajes en el archivo "duckstation.log" dentro del directorio de usuario. - - Enable Recompiler Fast Memory Access - Memoria de acceso rápido del recompilador + + Unchecked + Deshabilitado - Enable Recompiler ICache - ICache del recompilador + + Shows a debug menu bar with additional statistics and quick settings. + Muestra un menú de depuración con estadísticas adicionales y opciones rápidas. + + + + Disable All Enhancements + Deshabilitar todas las mejoras + + + + Show Status Indicators + Mostrar indicadores de estado + + + + Show Frame Times + Mostrar duración de fotogramas - + Apply Compatibility Settings - + Aplicar configuración de compatibilidad - - Enable VRAM Write Texture Replacement - Escritura de la VRAM a las texturas de reemplazo + + Display FPS Limit + Mostrar límite de FPS - - Preload Texture Replacements - Precargar reemplazos de texturas + + Multisample Antialiasing + Suavizado de bordes de muestreo múltiple (MSAA) - - Dump Replaceable VRAM Writes - Volcar escrituras de VRAM reemplazables + + Display Active Start Offset + Desplazamiento del inicio de imagen activa - - Set Dumped VRAM Write Alpha Channel - Establecer canal alfa de los volcados de escritura VRAM + + Display Active End Offset + Desplazamiento del fin de imagen activa - - Minimum Dumped VRAM Write Width - Anchura mínima de volcados de escrituras de VRAM + + Display Line Start Offset + Desplazamiento de la primera línea de imagen - - Minimum Dumped VRAM Write Height - Altura mínima de volcados de escrituras de VRAM + + Display Line End Offset + Desplazamiento de la última línea de imagen - - DMA Max Slice Ticks - Duración máxima de los cortes de la DMA + + PGXP Vertex Cache + Caché de vértices (PGXP) - - DMA Halt Ticks - Duración de las paradas de la DMA + + PGXP Geometry Tolerance + Tolerancia geométrica (PGXP) - - GPU FIFO Size - Tamaño del FIFO de la GPU + + PGXP Depth Clear Threshold + Umbral de limpieza de profundidad (PGXP) - - GPU Max Run-Ahead - Predicción máxima de la GPU + + Enable Recompiler Memory Exceptions + Habilitar excepciones de memoria del recompilador - - Allow Booting Without SBI File - Arrancar sin un archivo SBI + + Enable Recompiler Block Linking + Habilitar vinculación de bloques del recompilador - - Create Save State Backups - Crear copias de seguridad de los estados guardados + + Enable Recompiler Fast Memory Access + Habilitar memoria de acceso rápido del recompilador - - Log Level - Nivel de registro + + Use Old MDEC Routines + Utilizar rutinas MDEC antiguas - - Information - Información + + Enable VRAM Write Texture Replacement + Habilitar reemplazo de textura de escritura de VRAM - - Sets the verbosity of messages logged. Higher levels will log more messages. - Establece el grado de detalle de los mensajes del registro. Los niveles más altos mostrarán más mensajes. + + Preload Texture Replacements + Precargar reemplazos de textura - - - - - User Preference - Preferencia de usuario + + Dump Replaceable VRAM Writes + Volcar escrituras de VRAM reemplazables - - Logs messages to the console window. - Muestra los mensajes en la ventana de la consola. + + Set Dumped VRAM Write Alpha Channel + Establecer el canal alfa de la escritura de VRAM volcada - - Logs messages to the debug console where supported. - Muestra los mensajes en la consola de depuración cuando sea posible. + + Minimum Dumped VRAM Write Width + Anchura mínima del volcado de escritura de VRAM - - Logs messages to the window. - Muestra los mensajes en la ventana. + + Minimum Dumped VRAM Write Height + Altura mínima del volcado de escritura de VRAM - - Logs messages to duckstation.log in the user directory. - Copia los mensajes al archivo duckstation.log, en el directorio del usuario. + + DMA Max Slice Ticks + Duración máxima de los cortes de la DMA - - Unchecked - Deshabilitado + + DMA Halt Ticks + Duración de las paradas de la DMA - - Shows a debug menu bar with additional statistics and quick settings. - Muestra un menú de depuración con estadísticas adicionales y configuraciones rápidas. + + GPU FIFO Size + Tamaño del FIFO de la GPU - - Display FPS Limit - Mostrar límite de FPS + + GPU Max Run-Ahead + Procesamiento anticipado máximo de la GPU - - Disable All Enhancements - Desactivar todas las mejoras + + Use Debug Host GPU Device + Utilizar dispositivo gráfico de depuración - Show Fullscreen Status Indicators - Mostrar indicadores de estado en pantalla completa + + Stretch Display Vertically + Estirar imagen verticalmente - + Increase Timer Resolution Incrementar la resolución del temporizador - - System Settings - Opciones del sistema + + Allow Booting Without SBI File + Permitir iniciar sin un archivo SBI - - Use Debug Host GPU Device - Usar dispositivo gráfico de depuración + + Create Save State Backups + Crear copias de seguridad de los estados guardados + + + + Enable PCDrv + Habilitar PCDrv + + + + Enable PCDrv Writes + Habilitar escrituras de PCDrv + + + + PCDrv Root Directory + Directorio raíz de PCDrv AnalogController - Controller %u switched to analog mode. - Control %u cambiado a modo analógico. - - - Controller %u switched to digital mode. - Control %u cambiado a modo digital. + + + Controller {} switched to analog mode. + Control {} cambiado a modo analógico. - - Controller %u is locked to analog mode by the game. - Control %u bloqueado en modo analógico por el juego. + + + Controller {} switched to digital mode. + Control {} cambiado a modo digital. - - Controller %u is locked to digital mode by the game. - Control %u bloqueado en modo digital por el juego. + + Controller {} is locked to analog mode by the game. + Control {} bloqueado en modo analógico por el juego. - LeftX - Izquierda X + + Controller {} is locked to digital mode by the game. + Control {} bloqueado en modo digital por el juego. - LeftY - Izquierda Y + + Not Inverted + No invertir - RightX - Derecha X + + Invert Left/Right + Invertir izquierda/derecha - RightY - Derecha Y + + Invert Up/Down + Invertir arriba/abajo - Up - Arriba + + Invert Left/Right + Up/Down + Invertir izquierda/derecha + arriba/abajo - Down - Abajo + + Force Analog Mode on Reset + Forzar el modo analógico al reiniciar - Left - Izquierda + + Forces the controller to analog mode when the console is reset/powered on. + Fuerza el control a modo analógico cuando la consola se inicia/reinicia. - Right - Derecha + + Use Analog Sticks for D-Pad in Digital Mode + Utilizar las palancas analógicas como cruceta en el modo digital - Select - Select + + Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons. + Permite utilizar las palancas analógicas para controlar la cruceta y los botones en modo digital. - Start - Start + + Analog Deadzone + Zona muerta de las palancas analógicas - Triangle - Triángulo + + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. + Establece la zona muerta de las palancas analógicas, es decir, el rango de movimiento de la palanca que será ignorado. - Cross - Cruz + + Analog Sensitivity + Sensibilidad analógica - Circle - Círculo + + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. + Establece el factor de escalado para los ejes de las palancas analógicas. Se recomienda un valor entre 130% y 140% cuando se usen controles modernos, como DualShock 4 o el control de Xbox One. - Square - Cuadrado + + Button/Trigger Deadzone + Zona muerta de botón/gatillo - L1 - L1 + + Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored. + Establece la zona muerta para activar botones/gatillos, es decir, la distancia de pulsación que será ignorada. - L2 - L2 + + Vibration Bias + Fuerza de vibración - R1 - R1 + + Sets the rumble bias value. If rumble in some games is too weak or not functioning, try increasing this value. + Indica la medida de la vibración. Si la vibración en algunos juegos es débil o no funciona, intenta incrementar este valor. - R2 - R2 + + Invert Left Stick + Invertir palanca izquierda - L3 - L3 + + Inverts the direction of the left analog stick. + Invierte la dirección de la palanca analógica izquierda. - R3 - R3 + + Invert Right Stick + Invertir palanca derecha - Analog - Analógico + + Inverts the direction of the right analog stick. + Invierte la dirección de la palanca analógica derecha. + + + AnalogJoystick - - - Controller {} switched to analog mode. - + + + Controller %u switched to analog mode. + Control %u cambiado a modo analógico. - - - Controller {} switched to digital mode. - + + + Controller %u switched to digital mode. + Control %u cambiado a modo digital. - - Force Analog Mode on Reset - Forzar el modo analógico al reiniciar + + Not Inverted + No invertir - - Forces the controller to analog mode when the console is reset/powered on. May cause issues with games, so it is recommended to leave this option off. - Fuerza el control al modo analógico cuando la consola se reinicia/enciende. Puede causar problemas con los juegos, por los cual es recomendado dejar esta opción desactivada. + + Invert Left/Right + Invertir izquierda/derecha - - Use Analog Sticks for D-Pad in Digital Mode - Usar las palancas analógicas como cruceta en el modo digital + + Invert Up/Down + Invertir arriba/abajo - - Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons. - Permite usar las palancas analógicas para controlar la cruceta y los botones en el modo digital. + + Invert Left/Right + Up/Down + Invertir izquierda/derecha + arriba/abajo - + Analog Deadzone - + Zona muerta de las palancas analógicas - + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. - + Establece la zona muerta de las palancas analógicas, es decir, el rango de movimiento de la palanca que será ignorado. - + Analog Sensitivity - + Sensibilidad analógica - + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. - + Establece el factor de escalado para los ejes de las palancas analógicas. Se recomienda un valor entre 130% y 140% cuando se usen controles modernos, como DualShock 4 o el control de Xbox One. - Analog Axis Scale - Escala de ejes analógicos + + Invert Left Stick + Invertir palanca izquierda - Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. - Establece el factor de escalado para los ejes de las palancas analógicas. Se recomienda un valor entre 1.30 y 1.40 cuando se usen controles modernos, como DualShock 4 y el control de Xbox One. + + Inverts the direction of the left analog stick. + Invierte la dirección de la palanca analógica izquierda. - - Vibration Bias - Fuerza de vibración + + Invert Right Stick + Invertir palanca derecha - - Sets the rumble bias value. If rumble in some games is too weak or not functioning, try increasing this value. - Indica la medida de la vibración. Si la vibración en algunos juegos es débil o no funciona, intenta incrementar este valor. - - - - AnalogJoystick - - - - Controller %u switched to analog mode. - Control %u cambiado a modo analógico. - - - - - Controller %u switched to digital mode. - Control %u cambiado a modo digital. - - - - Analog Deadzone - - - - - Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. - - - - - Analog Sensitivity - - - - - Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. - - - - LeftX - Izquierda X - - - LeftY - Izquierda Y - - - RightX - Derecha X - - - RightY - Derecha Y - - - Up - Arriba - - - Down - Abajo - - - Left - Izquierda - - - Right - Derecha - - - Select - Select - - - Start - Start - - - Triangle - Triángulo - - - Cross - Cruz - - - Circle - Círculo - - - Square - Cuadrado - - - L1 - L1 - - - L2 - L2 - - - R1 - R1 - - - R2 - R2 - - - L3 - L3 - - - R3 - R3 - - - Analog - Analógico - - - Analog Axis Scale - Escala de ejes analógicos - - - Sets the analog stick axis scaling factor. A value between 1.30 and 1.40 is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. - Establece el factor de escalado para los ejes de las palancas analógicas. Se recomienda un valor entre 1.30 y 1.40 cuando se usen controles modernos, como DualShock 4 y el control de Xbox One. + + Inverts the direction of the right analog stick. + Invierte la dirección de la palanca analógica derecha. AudioBackend - + Null (No Output) Nulo (sin salida) - + Cubeb Cubeb - + XAudio2 XAudio2 - - SDL - SDL - - - OpenSL ES - OpenSL ES - AudioSettingsWidget Form - Formulario + Form @@ -972,215 +925,195 @@ Leaderboard Position: {} of {} Configuración - - Backend: - Motor: + + Stretch Mode: + Modo de estiramiento: - + Buffer Size: - Tamaño de búfer: - - - - Maximum latency: 0 frames (0.00ms) - Latencia máxima: 0 fotogramas (0.00ms) + Tamaño del búfer: - Sync To Output - Sincronizar con la salida + + Off (Noisy) + Desactivado (con ruidos) - Resampling - Remuestreo + + Resampling (Pitch Shift) + Remuestreo (altera el tono) - - - Start Dumping On Boot - Comenzar volcado al arrancar + + Time Stretch (Tempo Change, Best Sound) + Estiramiento temporal (altera el tempo, mejor calidad) - - Minimal - + + Maximum latency: 0 frames (0.00ms) + Latencia máxima: 0 fotogramas (0.00ms) - - Off (Noisy) - + + Backend: + Motor: - - Resampling (Pitch Shift) - + + Output Latency: + Latencia de salida: - - Time Stretch (Tempo Change, Best Sound) - + + Driver: + Controlador: - - Output Latency: - + + + Start Dumping On Boot + Comenzar volcado de audio al iniciar - - Driver: - + + Minimal + Mínima - - Stretch Mode: - + + Output Device: + Dispositivo de salida: - + Controls Controles - + Output Volume: Volumen de salida: - + + + 100% + 100% + + + Fast Forward Volume: - Volumen de avance rápido: + Volumen durante avance rápido: - - + + Mute All Sound Silenciar todo - - + + Mute CD Audio Silenciar audio de CD - - - 100% - 100% - - - + Audio Backend Motor de audio - + The audio backend determines how frames produced by the emulator are submitted to the host. Cubeb provides the lowest latency, if you encounter issues, try the SDL backend. The null backend disables all host audio output. - El motor de audio determina como se envían los fotogramas producidos por el emulador al host. Cubeb ofrece la menor latencia, en caso de tener problemas, prueba con el motor SDL. El motor nulo deshabilita la salida de audio. + El motor de audio determina como se envían los fotogramas producidos por el emulador al sistema. Cubeb ofrece la menor latencia, pero en caso de tener problemas, prueba con el motor SDL. El motor nulo deshabilita la salida de audio. - Buffer Size - Tamaño de búfer + + Output Latency + Latencia de salida - + The buffer size determines the size of the chunks of audio which will be pulled by the host. Smaller values reduce the output latency, but may cause hitches if the emulation speed is inconsistent. Note that the Cubeb backend uses smaller chunks regardless of this value, so using a low value here may not significantly change latency. - El tamaño del búfer determina el tamaño de los fragmentos de audio que capturará el host. Un valor bajo reducirá la latencia de salida, pero puede provocar parones si la velocidad de la emulación no es consistente. Tenga en cuenta que el motor Cubeb utilizará trozos más pequeños sin importar este valor, por lo que, en ese caso, un valor bajo podría no afectar mucho a la latencia. + El tamaño del búfer determina el tamaño de los fragmentos de audio que serán recibidos por el sistema. Valores más pequeños reducirán la latencia de salida, pero puede causar parones si la velocidad de la emulación no es consistente. Ten en cuenta que el motor Cubeb utiliza fragmentos más pequeños a pesar este valor, por lo tanto, utilizar valores más pequeños en este caso podría no cambiar mucho la latencia. - Checked - Habilitado + + + + Unchecked + Deshabilitado - - Output Latency - + + Start dumping audio to file as soon as the emulator is started. Mainly useful as a debug option. + Comenzar a volcar audio en archivos tan pronto se inicie el emulador. Útil para fines de depuración. - + Output Volume Volumen de salida - + Controls the volume of the audio played on the host. - Controla el volumen del audio que se reproduzca en el host. + Controla el volúmen de reproducción de audio en el sistema. - + Fast Forward Volume - Volumen de avance rápido + Volumen durante avance rápido - + Controls the volume of the audio played on the host when fast forwarding. - Controla el volumen del audio que se reproduzca en el host durante un avance rápido. + Controla el volúmen de reproducción de audio durante el avance rápido en el sistema. - - Stretch Mode - + + Prevents the emulator from producing any audible sound. + Impide que el emulador produzca cualquier sonido. - Time Stretching - - - - - When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces much nicer fast forward/slowdown audio at a small cost to performance. - - - - - Maximum Latency: %1 frames / %2 ms (%3ms buffer + %5ms output) - - - - - Maximum Latency: %1 frames / %2 ms - + Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games. + Silecia a la fuerza el audio CD-DA y XA del CD-ROM. Puede utilizarse para deshabilitar la música de fondo en algunos juegos. - When running outside of 100% speed, resamples audio from the target speed instead of dropping frames. Produces much nicer fast forward/slowdown audio at a small cost to performance. - Cuando el emulador se ejecute a una velocidad que no sea el 100%, remuestreará el audio respecto a la velocidad objetivo en lugar de eliminar fotogramas. Producirá un audio más agradable cuando haya avances rápidos o frenadas en la emulación a cambio de perder un poco de rendimiento. + + Stretch Mode + Modo de estiramiento - Throttles the emulation speed based on the audio backend pulling audio frames. This helps to remove noises or crackling if emulation is too fast. Sync will automatically be disabled if not running at 100% speed. - Acelera la velocidad de la emulación en función de los fotogramas de audio que envíe el motor de audio. Ayuda a eliminar ruidos o distorsiones si la emulación va demasiado rápida. La sincronización se deshabilitará automáticamente si la velocidad no es del 100 %. + + Time Stretching + Estiramiento temporal - - - - Unchecked - Deshabilitado + + When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces much nicer fast forward/slowdown audio at a small cost to performance. + Cuando la velocidad de ejecución sea distinta al 100%, se ajustará el tempo del audio en lugar de eliminar fotogramas. Produce un audio mucho más agradable durante el avance rápido/ralentizaciones a un pequeño costo de rendimiento. - - Start dumping audio to file as soon as the emulator is started. Mainly useful as a debug option. - Comienza a volcar audio en un archivo en cuanto se inicie el emulador. Suele utilizarse con fines de depuración. + + + Default + Predeterminado - - Prevents the emulator from producing any audible sound. - Impide que el emulador produzca cualquier sonido. + + Maximum Latency: %1 frames / %2 ms (%3ms buffer + %5ms output) + Latencia máxima: %1 fotogramas / %2 ms (búfer de %3ms + %5ms de salida) - - Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games. - Silencia a la fuerza el audio CD-DA y XA del CD-ROM. Puede usarse para deshabilitar la música de fondo en algunos juegos. - - - Maximum Latency: %n frames (%1ms) - - Latencia máxima: %n fotograma (%1 ms) - Latencia máxima: %n fotogramas (%1 ms) - + + Maximum Latency: %1 frames / %2 ms + Latencia máxima: %1 fotogramas / %2 ms - - + + %1% %1% @@ -1189,8 +1122,8 @@ Leaderboard Position: {} of {} AutoUpdaterDialog - - + + Automatic Updater Actualizador automático @@ -1207,7 +1140,7 @@ Leaderboard Position: {} of {} New Version: - Versión nueva: + Nueva versión: @@ -1217,7 +1150,7 @@ Leaderboard Position: {} of {} Skip This Update - Saltar actualización + Omitir actualización @@ -1225,57 +1158,57 @@ Leaderboard Position: {} of {} Recordar más tarde - + Updater Error Error de actualización - + No updates are currently available. Please try again later. - Actualmente no hay actualizaciones disponibles. Inténtelo de nuevo más tarde. + No hay actualizaciones disponibles. Intenta más tarde. - + Current Version: %1 (%2) Versión actual: %1 (%2) - + New Version: %1 (%2) - Versión nueva: %1 (%2) + Nueva versión: %1 (%2) - + Loading... Cargando... - + <h2>Changes:</h2> <h2>Cambios:</h2> - + <h2>Save State Warning</h2><p>Installing this update will make your save states <b>incompatible</b>. Please ensure you have saved your games to memory card before installing this update or you will lose progress.</p> - <h2>Alerta sobre estados guardados</h2><p>Esta actualización hará que tus estados guardados <b>dejen de ser compatibles</b>. Asegúrse de que guardó sus partidas salvadas en una tarjeta de memoria antes de instalar la actualización o perderá sus progresos.</p> + <h2>Alerta sobre estados guardados</h2><p>Instalar esta actualización hará que tus estados guardados <b>dejen de ser compatibles</b>. Asegúrate de haber guardado tus partidas en una tarjeta de memoria antes de instalar la actualización o perderás tu progreso.</p> - + <h2>Settings Warning</h2><p>Installing this update will reset your program configuration. Please note that you will have to reconfigure your settings after this update.</p> - <h2>Alerta sobre configuración</h2><p>Esta actualización reiniciará la configuración del programa. Tenga en cuenta que va a tener que cambiar toda la configuración de nuevo cuando haya terminado la actualización.</p> + <h2>Alerta sobre configuración</h2><p>Instalar esta actualización reiniciará tu configuración. Ten en cuenta que deberás ajustar tu configuración nuevamente después de instalar la actualización.</p> - + <h4>Installing this update will download %1 MB through your internet connection.</h4> - <h4>La instalación de esta actualización necesita descargar %1 MB a través de su conexión de internet.</h4> + <h4>Instalar esta actualización descargará %1 MB a través de tu conexión de internet.</h4> - + Downloading %1... Descargando %1... - + Cancel Cancelar @@ -1285,7 +1218,7 @@ Leaderboard Position: {} of {} Form - Formulario + Form @@ -1305,7 +1238,7 @@ Leaderboard Position: {} of {} NTSC-U/C (US/Canada): - NTSC-U/C (EE. UU., Canadá): + NTSC-U/C (Estados Unidos, Canadá): @@ -1339,66 +1272,62 @@ Leaderboard Position: {} of {} - + Fast Boot Inicio rápido - + Enable TTY Output Habilitar salida por terminal - - Auto-Detect - Detección automática - - - - Unknown - Desconocido - - - - + + Unchecked Deshabilitado - + Patches the BIOS to skip the console's boot animation. Does not work with all games, but usually safe to enable. - Parchea el BIOS para saltar la animación de inicio de la consola. No funciona con todos los juegos, pero suele ser seguro. + Parchea el BIOS para saltar la animación de inicio de la consola. No funciona con todos los juegos, pero es generalmente seguro de activar. - + Patches the BIOS to log calls to printf(). Only use when debugging, can break games. - Parchea el BIOS para mostrar los registros de consola. Sólo usar con fines de depuración, puede romper juegos. + Parchea el BIOS para registrar mensajes en la consola. Utilizar solamente para depuración, puede no funcionar con algunos juegos. - + Use Global Setting - + Utilizar configuración global + + + + Auto-Detect + Detectar automáticamente - Select Directory - Seleccionar directorio + + Unknown + Desconocido CPUExecutionMode - + Interpreter (Slowest) Intérprete (el más lento) - + Cached Interpreter (Faster) Intérprete en caché (más rápido) - + Recompiler (Fastest) Recompilador (el más rápido) @@ -1406,17 +1335,17 @@ Leaderboard Position: {} of {} CPUFastmemMode - + Disabled (Slowest) Deshabilitado (el más lento) - + MMap (Hardware, Fastest, 64-Bit Only) - MMap (por hardware, el más rápido, sólo para 64 bits) + MMap (por hardware, el más rápido, solo para x64) - + LUT (Faster) LUT (más rápido) @@ -1426,7 +1355,7 @@ Leaderboard Position: {} of {} Cheat Code Editor - Editor de códigos de trucos + Editor de trucos @@ -1449,28 +1378,20 @@ Leaderboard Position: {} of {} Activación: - Save - Guardar - - - Cancel - Cancelar - - - - + + Error Error - + Description cannot be empty. - La descripción no puede estar en blanco. + La descripcción no puede estar vacía. - + Instructions are invalid. - Las instrucciones no son válidas. + Las instrucciones son inválidas. @@ -1488,12 +1409,12 @@ Leaderboard Position: {} of {} &Add Group... - Agregar &grupo... + Añadir &grupo... &Add Code... - Agregar &código... + Añadir &código... @@ -1507,9 +1428,9 @@ Leaderboard Position: {} of {} - - - + + + Activate Activar @@ -1526,7 +1447,7 @@ Leaderboard Position: {} of {} Clear - Borrar + Limpiar @@ -1604,7 +1525,7 @@ Leaderboard Position: {} of {} Hex - Hexad. + Hexadecimal @@ -1619,12 +1540,12 @@ Leaderboard Position: {} of {} Halfword (2 bytes) - «Halfword» (2 bytes) + Halfword (2 bytes) Word (4 bytes) - «Word» (4 bytes) + Word (4 bytes) @@ -1649,7 +1570,7 @@ Leaderboard Position: {} of {} Greater or Equal... - Mayor o igual que... + Mayor o igual... @@ -1659,7 +1580,7 @@ Leaderboard Position: {} of {} Less or Equal... - Menor o igual que... + Menor o igual... @@ -1669,7 +1590,7 @@ Leaderboard Position: {} of {} Decreased By... - Disminuido por... + Decrementado por... @@ -1679,32 +1600,32 @@ Leaderboard Position: {} of {} Equal to Previous (Unchanged Value) - Igual a valor anterior (sin cambios) + Igual a valor previo (sin cambios) Not Equal to Previous (Changed Value) - Distinto a valor anterior (con cambios) + Distinto a valor previo (con cambios) Greater Than Previous - Mayor que valor anterior + Mayor que valor previo Greater or Equal to Previous - Mayor o igual a valor anterior + Mayor o igual a valor previo Less Than Previous - Menor que valor anterior + Menor a valor previo Less or Equal to Previous - Menor o igual a valor anterior + Menor o igual a valor previo @@ -1734,7 +1655,7 @@ Leaderboard Position: {} of {} Scratchpad - Scratchpad + Scratchpad (SPM) @@ -1754,17 +1675,17 @@ Leaderboard Position: {} of {} Clear Results - Borrar resultados + Limpiar resultados Add Selected Results To Watch List - Agregar resultados seleccionados al análisis + Añadir resultados seleccionados a la lista Number of Results (Display limited to first 5000) : - Número de resultados (limitado a los primeros 5 000): + Número de resultados (mostrando los primeros 5000): @@ -1774,34 +1695,22 @@ Leaderboard Position: {} of {} Simple Cheat Code or Description - Código sencillo o descripción + Código simple o descripción Freeze - Bloq. - - - - Remove Selected Entries from Watch List - Eliminar resultados seleccionados del análisis - - - Description - Descripción - - - Add To Watch - Agregar al análisis + Congelar Add Manual Address - Agregar dirección manual + Añadir dirección manual - Remove Watch - Eliminar del análisis + + Remove Selected Entries from Watch List + Eliminar elementos seleccionados de la lista @@ -1814,243 +1723,212 @@ Leaderboard Position: {} of {} Guardar análisis - + + Byte + Byte + + + + Halfword + Halfword + + + + Word + Word + + + + Signed Byte + Byte con signo + + + + Signed Halfword + Halfword con signo + + + + Signed Word + Word con signo + + + Toggle - Alternar + Activar - + Add Group - Agregar grupo + Añadir grupo - + Group Name: Nombre del grupo: - - - - + + + + Error Error - + This group name already exists. Este nombre de grupo ya existe. - + Delete Code Eliminar código - + Are you sure you wish to delete the selected code? This action is not reversible. - ¿Seguro que desea eliminar el código seleccionado? Esta acción es irreversible. + ¿Seguro que quieres eliminar el código seleccionado? Esta acción es irreversible. - + From File... Desde archivo... - + From Text... Desde texto... - + PCSXR/Libretro Cheat Files (*.cht *.txt);;All Files (*.*) Archivos de trucos de PCSXR/Libretro (*.cht *.txt);;Todos los archivos (*.*) - - + + Import Cheats Importar trucos - - + + Failed to parse cheat file. The log may contain more information. - Fallo al analizar el archivo de trucos. El registro podría contener más información. + Fallo al analizar el archivo de trucos. El registro puede contener más información. - + Cheat File Text: Texto de archivo de trucos: - + PCSXR Cheat Files (*.cht);;All Files (*.*) Archivos de trucos de PCSXR (*.cht);;Todos los archivos (*.*) - + Export Cheats Exportar trucos - + Failed to save cheat file. The log may contain more information. - Fallo al guardar el archivo de trucos. El registro podría contener más información. + Fallo al guardar el archivo de trucos. El registro puede contener más información. - + Confirm Clear - Confirmar borrado + Confirmar limpieza - + Are you sure you want to remove all cheats? This is not reversible. - ¿Seguro que desea borrar todos los trucos? Esta acción es irreversible. + ¿Seguro que quieres eliminar todos los trucos? Esta acción es irreversible. - + Confirm Reset Confirmar reinicio - + Are you sure you want to reset the cheat list? Any cheats not in the DuckStation database WILL BE LOST. - ¿Seguro que desea reiniciar la lista de trucos? Cualquier truco que no esté en la base de datos de DuckStation se descartará. + ¿Seguro que quieres reiniciar la lista de trucos? Cualquier truco que no esté en la base de datos de DuckStation se perderá. - + Enter manual address: - Ingrese la dirección manual: + Ingresar dirección manual: - + Select data size: - Seleccione el tamaño de los datos: + Seleccionar tamaño de datos: + + + Cheats - Memory Scan - Búsqueda en memoria + + Gameshark + GameShark - Memory scan found %1 addresses, but only the first %2 are displayed. - La búsqueda encontró %1 direcciones, pero se muestran sólo las primeras %2. + + Manual + Manual - - Byte - Byte + + Automatic (Frame End) + Automática (final del fotograma) + + + ColorPickerButton - - Halfword - «Halfword» + + Select LED Color + Seleccionar color del LED + + + CommonHost - - Word - «Word» + + Default Output Device + Dispositivo de salida predeterminado + + + CommonHostInterface - - Signed Byte - Byte con signo - - - - Signed Halfword - «Halfword» con signo - - - - Signed Word - «Word» con signo - - - - Cheats - - - Gameshark - GameShark - - - - Manual - Manual - - - - Automatic (Frame End) - Automático (al acabar el fotograma) - - - - Cheevos - - Logging in to RetroAchivements... - Iniciando sesión en RetroAchivements... - - - Downloading achievement resources... - Descargando recursos de logros... - - - (Hardcore Mode) - (Modo «hardcore») - - - You have earned %u of %u achievements, and %u of %u points. - Ha obtenido %u de %u logros y %u de %u puntos. - - - This game has no achievements. - No hay logros para este juego. - - - Leaderboards are enabled. - La tabla de clasificación está habilitada. - - - Leaderboards are DISABLED because Hardcore Mode is off. - La tabla de clasificación está deshabilitada porque el modo «hardcore» también lo está. - - - - CommonHostInterface - - Are you sure you want to stop emulation? - ¿Seguro que desea detener la emulación? - - - The current state will be saved. - Se guardará el estado actual. - - - - Invalid version %u (%s version %u) - Versión %u inválida (versión %s %u) + + Invalid version %u (%s version %u) + Versión %u inválida (%s versión %u) ConsoleRegion - + Auto-Detect - Detección automática + Detectar automáticamente - + NTSC-J (Japan) NTSC-J (Japón) - + NTSC-U/C (US, Canada) - NTSC-U/C (EE. UU., Canadá) + NTSC-U/C (Estados Unidos, Canadá) - + PAL (Europe, Australia) PAL (Europa, Australia) @@ -2060,7 +1938,7 @@ Leaderboard Position: {} of {} Form - Formulario + Form @@ -2074,31 +1952,41 @@ Leaderboard Position: {} of {} - + Enable 8MB RAM (Dev Console) - Habilitar 8 MB de RAM (consola de desarrollo) + Habilitar 8MB de RAM (consola de desarrollo) + + + + CPU Emulation + Emulación de CPU + + + + Execution Mode: + Modo de ejecución: - + Enable Clock Speed Control (Overclocking/Underclocking) - Control de velocidad de reloj («overclocking»/«underclocking») + Habilitar control de velocidad de reloj (overclocking/underclocking) 100% (effective 33.3mhz) - 100 % (33.3 MHz reales) + 100% (33.3 MHz efectivos) - + Enable Recompiler ICache - ICache del recompilador + Habilitar ICache del recompilador CD-ROM Emulation - Emulación de CDROM + Emulación de CD-ROM @@ -2107,14 +1995,14 @@ Leaderboard Position: {} of {} - + None (Double Speed) - Nula (velocidad doble) + Ninguna (velocidad doble) 2x (Quad Speed) - 2x (velocidad x4) + 2x (velocidad cuádruple) @@ -2168,9 +2056,9 @@ Leaderboard Position: {} of {} - + None (Normal Speed) - Nula (velocidad normal) + Ninguna (velocidad normal) @@ -2217,40 +2105,11 @@ Leaderboard Position: {} of {} 10x 10x - - - - Apply Image Patches - Aplicar parches de imagen - - - - Async Readahead: - Lectura asíncrona: - - - Controller Ports - Puertos para controles - - - Multitap: - Multitap: - - - - CPU Emulation - Emulación de CPU - - - - Execution Mode: - Modo de ejecución: - - + Enable Region Check - Habilitar chequeo de región + Habilitar chequeo regional @@ -2258,191 +2117,190 @@ Leaderboard Position: {} of {} Precargar imagen a RAM - - - - - - - Unchecked - Deshabilitado - - - - Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles. Games have to use a larger heap size for this additional RAM to be usable. Titles which rely on memory mirrors may break, so it should only be used with compatible mods. - Activa 6 MB adicionales de RAM adicionales para obtener un total de 8 MB, generalmente presente en las consolas de desarrollo. Para que esta RAM adicional llegue a utilizarse, los juegos deben estar programados para necesitarla. Aquellos juegos que dependan del duplicado de memoria podrían romperse, por lo cual sólo debe usarse con mods compatibles. + + + Apply Image Patches + Aplicar parches de imagen - - - Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some cases also eliminates stutter when games initiate audio track playback. - Carga la imagen del juego en la memoria RAM. Útil para cuando se usan directorios a través de una red. En ocasiones también podría eliminar las pausas que se generan cuando el juego inicia la reproducción de una pista de audio. + + Async Readahead: + Lectura asincrónica: - + Disabled (Synchronous) - Deshabilitar (síncrona) + Deshabilitado (sincrónico) - + %1 sectors (%2 KB / %3 ms) - %1 sectores (%2 KB/%3 ms) + %1 sectores (%2 KB / %3 ms) - + Region Región - + Auto-Detect - Detección automática + Detectar automáticamente - + Determines the emulated hardware type. Determina el tipo de hardware emulado. - + Execution Mode Modo de ejecución - + Recompiler (Fastest) Recompilador (el más rápido) - + Determines how the emulated CPU executes instructions. - Determina cómo ejecutará las instrucciones el CPU emulado. + Determina cómo la CPU emulada ejecuta instrucciones. - + + + + + + + Unchecked + Deshabilitado + + + When this option is chosen, the clock speed set below will be used. - Al habilitar esta opción se utilizará la velocidad de reloj seleccionada. + Cuando se habilite esta opción, se utilizará la velocidad de reloj seleccionada. - + Overclocking Percentage Porcentaje de overclocking - + 100% - 100 % + 100% - + Selects the percentage of the normal clock speed the emulated hardware will run at. - Selecciona el porcentaje de velocidad del reloj normal al que se ejecutará el hardware emulado. + Selecciona el porcentaje de velocidad de reloj normal en el cual se ejecutará el hardware emulado. - + Simulates stalls in the recompilers when the emulated CPU would have to fetch instructions into its cache. Makes games run closer to their console framerate, at a small cost to performance. Interpreter mode always simulates the instruction cache. - + Simula paradas en los recompiladores cuando la CPU emulada necesite buscar instrucciones en su caché. Hace que los juegos tengan una fluidez más fiel a la consola, a un pequeño costo de rendimiento. El modo intérprete siempre simula el caché de instrucciones. + + + + Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles. Games have to use a larger heap size for this additional RAM to be usable. Titles which rely on memory mirrors may break, so it should only be used with compatible mods. + Habilita 6MB de RAM adicionales para obtener un total de 8MB (2MB+6MB), generalmente presente en consolas de desarrollo. Los juegos deben permitir el uso de un tamaño extendido de memoria para que esta RAM adicional sea utilizable. Juegos que dependan de duplicados de memoria podrían romperse, por lo que solo debe utilizarse con modificaciones compatibles. + + + + + Preload Image to RAM + Precargar imagen a RAM + + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some cases also eliminates stutter when games initiate audio track playback. + Carga la imagen del juego en la memoria RAM. Útil para cuando se usan directorios a través de una red. En algunas ocasiones puede eliminar las pausas que se generan cuando el juego inicia la reproducción de una pista de audio. + + + CD-ROM Read Speedup Aceleración de lectura de CD-ROM - + Speeds up CD-ROM reads by the specified factor. Only applies to double-speed reads, and is ignored when audio is playing. May improve loading speeds in some games, at the cost of breaking others. - Acelera la lectura del CD-ROM según el valor especificado. Esta opción sólo se aplica a lecturas de doble velocidad y se ignorará cuando se esté reproduciendo audio. Podría mejorar las velocidades de carga en algunos juegos, mientras que puede romper otros. + Acelera la lectura del CD-ROM por el valor especificado. Solo se aplica a lecturas de doble velocidad, y se ignora cuando se esté reproduciendo audio. Puede mejorar los tiempos de carga en algunos juegos, mientras que puede romper otros. - + CD-ROM Seek Speedup Aceleración de búsqueda de CD-ROM - + Reduces the simulated time for the CD-ROM sled to move to different areas of the disc. Can improve loading times, but crash games which do not expect the CD-ROM to operate faster. - Reduce el tiempo simulado en el que el motor del CD-ROM se desplazaría hacia diferentes partes del disco. Puede mejorar los tiempos de carga, pero bloqueará aquellos juegos que no esperen que el lector de CD-ROM funcione más rápido. + Reduce el tiempo simulado para que el motor del CD-ROM se mueva a distintas partes del disco. Puede mejorar los tiempos de carga, pero también puede romper juegos que no esperen que el CD-ROM funcione más rápido de lo normal. - + Asynchronous Readahead - Lectura asíncrona + Lectura asincrónica - + 8 Sectors 8 sectores - + Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread. Higher sector numbers can reduce spikes when streaming FMVs or audio on slower storage or when using compression formats such as CHD. - Reduce los problemas de emulación leyendo/descomprimiendo los datos de CD de forma asincrónica en un hilo de trabajo. Un valor alto podría reducir los picos al reproducir FMVs o audio en un soporte de almacenamiento más lento, o al utilizar formatos comprimidos, como CHD. + Reduce tirones durante la emulación al leer/descomprimir datos del CD de forma asincrónica en un hilo separado. Números de sectores más altos pueden reducir saltos/picos durante la reproducción de FMVs o audio en dispositivos de almacenamiento lentos, o cuando se usan formatos de compresión como CHD. - + Checked Habilitado - + Simulates the region check present in original, unmodified consoles. - Simula el chequeo de región presente en las consolas originales sin modificar. + Simula el chequeo regional presente en consolas originales sin modificar. - + Automatically applies patches to disc images when they are present in the same directory. Currently only PPF patches are supported with this option. - Aplica parches automáticamente a las imágenes del disco cuando estén presentes en el mismo directorio. Actualmente sólo los parches PPF son compatibles con esta opción. - - - Multitap - Multitap - - - Disabled - Deshabilitado - - - Enables multitap support on specified controller ports. Leave disabled for games that do not support multitap input. - Habilita el soporte de multitap en puertos especificos de los controles. Deje esta opción deshabilitada para aquellos juegos que no sean compatibles con multitaps. + Aplica parches a las imagenes de disco automáticamente si ambos están presentes en el mismo directorio. Actualmente solo los parches PPF son compatibles con esta opción. - + Enabling CPU overclocking will break games, cause bugs, reduce performance and can significantly increase system requirements. By enabling this option you are agreeing to not create any bug reports unless you have confirmed the bug also occurs with overclocking disabled. This warning will only be shown once. - Habilitar el overclocking de CPU puede romper juegos, causar defectos, reducir el rendimiento e incrementar significativamente los requisitos del sistema. + Habilitar el overclocking de CPU puede romper juegos, causar defectos, reducir el rendimiento e incrementar significativamente los requerimientos del sistema. -Si habilita esta opción, usted acepta no enviar reportes de problemas a menos que haya confirmado que éstos ocurran también sin el overclocking activado. +Al habilitar esta opción estás de acuerdo a no crear reportes de problemas a menos que hayas confirmado que estos también ocurren sin el overclocking activado. -Esta advertencia sólo se mostrará una vez. +Esta alerta solo se mostrará una vez. + + + + CPU Overclocking Warning + Alerta de overclocking de CPU - + Yes, I will confirm bugs without overclocking before reporting. - Sí, confirmaré los problemas sin overclocking antes de reportarlos. + Sí, voy a confirmar los problemas sin overclocking antes de reportarlos. - + No, take me back to safety. No, volver a una configuración segura. - - CPU Overclocking Warning - Alerta de overclocking de CPU - - - + %1% (%2MHz) - %1 % (%2 MHz) - - - - - Preload Image to RAM - Precargar imagen a RAM + %1% (%2MHz) @@ -2450,54 +2308,54 @@ Esta advertencia sólo se mostrará una vez. Form - Formulario + Form Controller Type - + Tipo de control Bindings - + Asignaciones Settings - + Configuración Macros - + Macros - + Automatic Mapping - + Asignación automática - + Clear Mapping - + Limpiar asignaciones - + No devices available - + No hay dispositivos disponibles - + Are you sure you want to clear all mappings for this controller? This action cannot be undone. - + ¿Seguro que quieres borrar todas las asignaciones para este control? Esta acción no se puede revertir. - - No generic bindings were generated for device '%1' - + + No generic bindings were generated for device '%1'. The controller/source may not support automatic mapping. + No se pudieron generar asignaciones para el dispositivo "%1". El control/dispositivo de origen podría ser incompatible con las asignaciones automáticas. @@ -2505,19 +2363,19 @@ Esta advertencia sólo se mostrará una vez. Form - Formulario + Form D-Pad - + Botones de dirección Down - Abajo + Abajo @@ -2548,118 +2406,118 @@ Esta advertencia sólo se mostrará una vez. PushButton - + PushButton Left - + Izquierda Up - Arriba + Arriba Right - + Derecha Left Analog - + Analógico izquierdo Large Motor - + Motor grande Select - Select + Select L1 - L1 + L1 R1 - R1 + R1 R2 - R2 + R2 L2 - L2 + L2 Start - + Start Face Buttons - + Botones de acción Cross - Cruz + Cruz Square - Cuadrado + Cuadrado Triangle - Triángulo + Triángulo Circle - Círculo + Círculo Right Analog - + Analógico derecho Small Motor - + Motor pequeño R3 - R3 + R3 Analog - Analógico + Analog L3 - L3 + L3 @@ -2667,19 +2525,19 @@ Esta advertencia sólo se mostrará una vez. Form - Formulario + Form D-Pad - + Botones de dirección Down - Abajo + Abajo @@ -2708,117 +2566,108 @@ Esta advertencia sólo se mostrará una vez. PushButton - + PushButton Left - + Izquierda Up - Arriba + Arriba Right - + Derecha Left Analog - + Analógico izquierdo L2 - L2 + L2 L1 - L1 + L1 R2 - R2 + R2 Start - + Start R1 - R1 + R1 Select - Select + Select Face Buttons - + Botones de acción Cross - Cruz + Cruz Square - Cuadrado + Cuadrado Triangle - Triángulo + Triángulo Circle - Círculo + Círculo Right Analog - + Analógico derecho R3 - R3 + R3 L3 - L3 + L3 Mode - Modo - - - - ControllerBindingWidget_Base - - - - %1% - %1% + Modo @@ -2826,12 +2675,12 @@ Esta advertencia sólo se mostrará una vez. Form - Formulario + Form L1 - L1 + L1 @@ -2849,82 +2698,82 @@ Esta advertencia sólo se mostrará una vez. PushButton - + PushButton L2 - L2 + L2 R2 - R2 + R2 R1 - R1 + R1 Face Buttons - + Botones de acción Cross - Cruz + Cruz Square - Cuadrado + Cuadrado Triangle - Triángulo + Triángulo Circle - Círculo + Círculo D-Pad - + Botones de dirección Down - Abajo + Abajo Left - + Izquierda Up - Arriba + Arriba Right - + Derecha Select - Select + Select Start - + Start @@ -2932,17 +2781,17 @@ Esta advertencia sólo se mostrará una vez. Form - Formulario + Form Side Buttons - + Botones laterales B - B + B @@ -2950,27 +2799,27 @@ Esta advertencia sólo se mostrará una vez. PushButton - + PushButton A - A + A Trigger - Gatillo + Gatillo Fire Offscreen - + Disparo fuera de pantalla Fire - + Disparo @@ -2978,28 +2827,28 @@ Esta advertencia sólo se mostrará una vez. Form - Formulario + Form Buttons - + Botones Left - + Izquierda PushButton - + PushButton Right - + Derecha @@ -3007,17 +2856,17 @@ Esta advertencia sólo se mostrará una vez. Form - Formulario + Form D-Pad - + Botones de dirección Down - Abajo + Abajo @@ -3034,97 +2883,97 @@ Esta advertencia sólo se mostrará una vez. PushButton - + PushButton Left - + Izquierda Up - Arriba + Arriba Right - + Derecha Start - + Start L - L + L R - R + R Face Buttons - + Botones de acción I - I + I II - II + II B - B + B A - A + A Steering/Twist - + Dirección/Giro - + %1% - %1% + %1% ControllerCustomSettingsWidget - + %1 Settings - + Configuración de %1 - + Restore Default Settings - + Restablecer valores predeterminados - + Browse... - Buscar... + Buscar... - + Select File - Seleccionar archivo + Seleccionar archivo @@ -3132,177 +2981,181 @@ Esta advertencia sólo se mostrará una vez. Form - Formulario + Form - Controller Multitap - + DInput Source + Fuente de entrada DInput - The multitap enables up to 8 controllers to be connected to the console. Each multitap provides 4 ports. Multitap is not supported by all games. - + The DInput source provides support for legacy controllers which do not support XInput. Accessing these controllers via SDL instead is recommended. + La fuente de DInput permite utilizar controles antiguos que no sean compatibles con XInput. Sin embargo, es recomendable acceder a estos controles a través de SDL. - Multitap Mode: - Modo de multitap: + Enable DInput Input Source + Habilitar fuente de entrada DInput - - Disabled - + + Detected Devices + Dispositivos detectados - - Enable on Port 1 Only - Habilitar solamente en el puerto 1 + + Controller Multitap + Adaptador multitap - - Enable on Port 2 Only - Habilitar solamente en el puerto 2 + + The multitap enables up to 8 controllers to be connected to the console. Each multitap provides 4 ports. Multitap is not supported by all games. + El multitap permite conectar hasta 8 controles en la consola. Cada multitap tiene 4 puertos. No todos los juegos son compatibles con el multitap. - - Enable on Ports 1 and 2 - Habilitar en los puertos 1 y 2 + + Multitap Mode: + Modo multitap: - - DInput Source - + + Disabled + Deshabilitado - - The DInput source provides support for legacy controllers which do not support XInput. Accessing these controllers via SDL instead is recommended. - + + Enable on Port 1 Only + Habilitar solamente en puerto 1 - - Enable DInput Input Source - + + Enable on Port 2 Only + Habilitar solamente en puerto 2 + + + + Enable on Ports 1 and 2 + Habilitar en puertos 1 y 2 - + SDL Input Source - + Fuente de entrada SDL - + The SDL input source supports most controllers, and provides advanced functionality for DualShock 4 / DualSense pads in Bluetooth mode (Vibration / LED Control). - + La fuente de SDL es compatible con la mayoría de controles, y ofrece funcionalidades adicionales para controles DualShock 4/DualSense en modo Bluetooth (vibración/control de LED). - + Enable SDL Input Source - + Habilitar fuente de entrada SDL - + DualShock 4 / DualSense Enhanced Mode - + Modo mejorado para DualShock 4/DualSense - - Detected Devices - - - - - Mouse/Pointer Source - + + Controller LED Settings + Configuración de LED - - - 10 - 10x {10?} + + XInput Source + Fuente de entrada XInput - - - Invert - + + The XInput source provides support for XBox 360 / XBox One / XBox Series controllers, and third party controllers which implement the XInput protocol. + La fuente de XInput permite el uso de controles de Xbox 360/Xbox One/Xbox Series y otros controles de terceros que implementen el protocolo XInput. - - Using raw input improves precision when you bind controller sticks to the mouse pointer. Also enables multiple mice to be used. - + + Enable XInput Input Source + Habilitar fuente de entrada XInput - - Vertical Sensitivity: - + + Profile Settings + Configuración del perfil - - Horizontal Sensitivity: - + + When this option is enabled, hotkeys can be set in this input profile, and will be used instead of the global hotkeys. By default, hotkeys are always shared between all profiles. + Cuando esté habilitado, se podrá asignar atajos a este perfil de entrada, y se utilizará en lugar de los atajos globales. Por defecto, los atajos se comparten entre todos los perfiles. - - Enable Mouse Mapping - + + Use Per-Profile Hotkeys + Utilizar atajos por perfil - - Use Raw Input - + + Mouse/Pointer Source + Fuente de mouse/cursor - - XInput Source - + + Using raw input improves precision when you bind controller sticks to the mouse pointer. Also enables multiple mice to be used. + Utilizar entrada sin procesar mejora la precisión cuando se asignan las palancas de controles al cursor del mouse. También permite utilizar varios mouses a la vez. - - The XInput source provides support for XBox 360 / XBox One / XBox Series controllers, and third party controllers which implement the XInput protocol. - + + Horizontal Sensitivity: + Sensibilidad horizontal: - - Enable XInput Input Source - + + + 10 + 10 - - Profile Settings - + + Vertical Sensitivity: + Sensibilidad vertical: - - When this option is enabled, hotkeys can be set in this input profile, and will be used instead of the global hotkeys. By default, hotkeys are always shared between all profiles. - + + Enable Mouse Mapping + Habilitar asignación de mouse - - Use Per-Profile Hotkeys - + + Use Raw Input + Utilizar entrada sin procesar - ControllerInterface + ControllerLEDSettingsDialog - None - Ninguno + + Controller LED Settings + Configuración de LED - SDL - SDL + + SDL-0 LED + LED SDL-0 - XInput - XInput + + SDL-1 LED + LED SDL-1 - DInput - DInput + + SDL-2 LED + LED SDL-2 - Evdev - Evdev + + SDL-3 LED + LED SDL-3 @@ -3310,86 +3163,87 @@ Esta advertencia sólo se mostrará una vez. Form - Formulario + Form Binds/Buttons - + Asignaciones/Botones Select the buttons which you want to trigger with this macro. All buttons are activated concurrently. - + Selecciona los botones que quieras activar con este macro. Todos los botones son activados al mismo tiempo. Trigger - Gatillo + Disparador Select the trigger to activate this macro. This can be a single button, or combination of buttons (chord). Shift-click for multiple triggers. - + Selecciona el disparador para activar este macro. Puede ser un solo botón o varios combinados (acorde). Presiona Shift+Clic para configurar varios disparadores. PushButton - + PushButton Frequency - + Frecuencia Macro will toggle every N frames. - + El macro se alternará cada X fotogramas. Set... - + Establecer... - + Not Configured - + Sin configurar - + Set Frequency - + Establecer frecuencia - + Frequency: - + Frecuencia: - + Macro will not repeat. - + El macro no se repetirá. - + Macro will toggle buttons every %1 frames. - + El macro alternará botones cada %1 fotogramas. ControllerMacroWidget - + Controller Port %1 Macros - + Macros del puerto de control %1 - + Macro %1 %2 - + Macro %1 +%2 @@ -3397,320 +3251,288 @@ Esta advertencia sólo se mostrará una vez. Controller Settings - Configuración de controles + Configuración de controles Profile: - + Perfil: New Profile - + Nuevo perfil Load Profile - Cargar perfil + Cargar perfil Delete Profile - + Eliminar perfil - + Restore Defaults - Restaurar los valores predeterminados + Restablecer valores predeterminados - - + + Create Input Profile - + Crear perfil de entrada - + Enter the name for the new input profile: - + Ingresa el nombre para el nuevo perfil de entrada: - - - - + + + + Error - Error + Error - + A profile with the name '%1' already exists. - + Ya existe un perfil con el nombre "%1". - + Do you want to copy all bindings from the currently-selected profile to the new profile? Selecting No will create a completely empty profile. - + ¿Quieres copiar todas las asignaciones del perfil actual al nuevo? Seleccionando no creará un perfil vacío. - + Failed to save the new profile to '%1'. - + Fallo al guardar el nuevo perfil "%1". - + Load Input Profile - + Cargar perfil de entrada - + Are you sure you want to load the input profile named '%1'? All current global bindings will be removed, and the profile bindings loaded. You cannot undo this action. - + ¿Seguro que quieres cargar el perfil de entrada "%1"? + +Todas las asignaciones globales actuales serán reemplazadas por las del perfil cargado. + +Esta acción no se puede revertir. - + Delete Input Profile - + Eliminar perfil de entrada - + Are you sure you want to delete the input profile named '%1'? You cannot undo this action. - + ¿Seguro que quieres eliminar el perfil de entrada "%1"? + +Esta acción no se puede revertir. - + Failed to delete '%1'. - + Fallo al eliminar "%1". - + Are you sure you want to restore the default controller configuration? All shared bindings and configuration will be lost, but your input profiles will remain. You cannot undo this action. - + ¿Seguro que quieres restaurar la configuración predeterminada de los controles? + +Se perderán todas las asignaciones y configuraciones compartidas, excepto tus perfiles de entrada. + +Esta acción no se puede revertir. - + Global Settings - Configuración global + Configuración global - - + + Controller Port %1%2 %3 - + Puerto de control %1%2 +%3 - - + + Controller Port %1 %2 - + Puerto de control %1 +%2 - + Hotkeys - + Atajos - + Shared - + Compartido - + The input profile named '%1' cannot be found. - + No se puede encontrar el perfil de entrada "%1". - ControllerSettingsWidget + ControllerType - Controller Type: - Tipo de control: + + Analog Controller + Control analógico - Load Profile - Cargar perfil + + + Analog Joystick + Joystick analógico - Save Profile - Guardar perfil + + Not Connected + Sin conectar - Clear All - Borrar todo + + + Digital Controller + Control digital - Clear Bindings - Borrar asignaciones + + + GunCon + GunCon - Are you sure you want to clear all bound controls? This can not be reversed. - ¿Seguro que desea borrar todos los controles asignados? Esta acción no puede deshacerse. + + + NeGcon + NeGcon - Rebind All - Reasignar todo - - - Are you sure you want to rebind all controls? All currently-bound controls will be irreversibly cleared. Rebinding will begin after confirmation. - ¿Seguro que desea reasignar todos los controles? Todas las asignaciones existentes serán borradas cuando confirmes la acción. - - - Port %1 - Puerto %1 - - - Port %1%2 - Puerto %1%2 - - - Button Bindings: - Asignaciones de botones: - - - Axis Bindings: - Asignaciones de ejes: - - - Rumble - Vibración - - - Browse... - Buscar... - - - Select File - Seleccionar archivo - - - Auto Fire Buttons - Botones de autodisparo - - - Auto Fire %1 - Autodisparo %1 - - - Frames - fotogramas - - - Select path to input profile ini - Seleccionar la ruta del archivo .ini de perfil de entrada - - - New... - Nuevo... - - - Enter Input Profile Name - Ingrese el nombre del perfil de entrada - - - Error - Error + + + PlayStation Mouse + PlayStation Mouse - No name entered, input profile was not saved. - No se indicó un nombre, el perfil de entrada no se guardó. + + None + Ninguno - No path selected, input profile was not saved. - No se indicó un directorio, el perfil de entrada no se guardó. + + Analog Controller (DualShock) + Control analógico (DualShock) - ControllerType + CoverDownloadDialog - - None - Ninguno + + Download Covers + Descargar portadas - - - Digital Controller - Control digital + + DuckStation can automatically download covers for games which do not currently have a cover set. We do not host any cover images, the user must provide their own source for images. + DuckStation puede descargar automáticamente imágenes de portada para juegos que no tengan una ya establecida. Nosotros no alojamos ninguna imagen de portada, por lo que el usuario debe proveer su propia fuente de imágenes. - - Analog Controller (DualShock) - Control analógico (DualShock) + + <html><head/><body><p>In the box below, specify the URLs to download covers from, with one template URL per line. The following variables are available:</p><p><span style=" font-style:italic;">${title}:</span> Title of the game.<br/><span style=" font-style:italic;">${filetitle}:</span> Name component of the game's filename.<br/><span style=" font-style:italic;">${serial}:</span> Serial of the game.</p><p><span style=" font-weight:700;">Example:</span> https://www.example-not-a-real-domain.com/covers/${serial}.jpg</p></body></html> + <html><head/><body><p>En el cuadro debajo, especifica las direcciones URL de las cuales descargar las portadas, una por línea. Las siguientes variables están disponibles:</p><p><span style=" font-style:italic;">${title}:</span> Título del juego.<br/><span style=" font-style:italic;">${filetitle}:</span> Nombre del archivo del juego.<br/><span style=" font-style:italic;">${serial}:</span> Número de serie del juego.</p><p><span style=" font-weight:700;">Ejemplo:</span> https://www.ejemplo-de-un-dominio-ficticio.com/portadas/${serial}.jpg</p></body></html> - - - Analog Joystick - Joystick analógico + + By default, the downloaded covers will be saved with the game's title. If this is not desired, you can check the "Use Serial File Names" box below. Using serials instead of game titles will prevent conflicts when multiple regions of the same game are used. + Por defecto, las portadas descargadas se guardarán con el título del juego. Si quieres cambiarlo, puedes activar la opción "Utilizar número de serie como nombres de archivo" debajo. Utilizar números de serie en lugar de títulos evitará conflictos cuando hayan multiples regiones para el mismo juego. - Namco GunCon - GunCon de Namco + + Use Serial File Names + Utilizar número de serie como nombres de archivo - - - PlayStation Mouse - PlayStation Mouse + + Waiting to start... + Esperando para iniciar... - - - NeGcon - NeGcon + + + Start + Iniciar - - Analog Controller - + + Close + Cerrar - - - GunCon - + + Download complete. + Descarga finalizada. + + + + Stop + Detener DebuggerCodeModel - - - + + + <invalid> <inválido> - + Address Dirección - + Bytes Bytes - + Instruction Instrucción - + Comment Comentario @@ -3718,55 +3540,55 @@ You cannot undo this action. DebuggerMessage - + Added breakpoint at 0x%08X. - Punto de interrupción añadido en 0x%08X. + Punto de interrupcción añadido en 0x%08X. - + Removed breakpoint at 0x%08X. - Punto de interrupción en 0x%08X eliminado. + Punto de interrupcción en 0x%08X eliminado. - + 0x%08X is not a call instruction. 0x%08X no es una instrucción de llamada. - + Can't step over double branch at 0x%08X - No se puede pasar por encima de la rama doble en 0x%08X + No se puede saltar la ramificación doble en 0x%08X - + Stepping over to 0x%08X. - Pasando a 0x%08X. + Saltando a 0x%08X. - + Instruction read failed at %08X while searching for function end. - Error de lectura de instrucción en %08X mientras se buscaba el fin de la función. + Fallo de lectura de instrucción en %08X mientras se buscaba el final de la función. - + Stepping out to 0x%08X. - Saliendo a 0x%08X. + Regresando a 0x%08X. - + No return instruction found after %u instructions for step-out at %08X. - No se ha encontrado instrucción de retorno después de % u instrucciones para la salida en%08X. + No se encontró una instrucción de retorno después de %u instrucciones para regresar en %08X. DebuggerRegistersModel - + Register Registro - + Value Valor @@ -3774,17 +3596,17 @@ You cannot undo this action. DebuggerStackModel - + <invalid> <inválido> - + Address Dirección - + Value Valor @@ -3805,12 +3627,12 @@ You cannot undo this action. Breakpoints - Puntos de interrupción + Puntos de interrupcción toolBar - Barra de herramientas + toolBar @@ -3835,7 +3657,7 @@ You cannot undo this action. Scratchpad - Scratchpad + Scratchpad (SPM) @@ -3865,7 +3687,7 @@ You cannot undo this action. Hit Count - Recuento + Veces alcanzado @@ -3890,12 +3712,12 @@ You cannot undo this action. Step Into - Paso a paso + Siguiente instrucción &Step Into - Pa&so a paso + &Siguiente instrucción @@ -3905,12 +3727,12 @@ You cannot undo this action. Step Over - Paso a paso por + Saltar instrucción Step &Over - Paso a paso p&or + S&altar instrucción @@ -3920,12 +3742,13 @@ You cannot undo this action. Toggle Breakpoint - Activar punto de interrupción + Activar punto de interrupcción + Toggle &Breakpoint - Activar punto de &interrupción + Activar punto de &interrupcción @@ -3940,12 +3763,12 @@ You cannot undo this action. Step Out - Salir + Regresar Step O&ut - Sali&r + &Regresar @@ -3955,12 +3778,13 @@ You cannot undo this action. Run To Cursor - Ejecutar hasta cursor + Ejecutar hasta el cursor + &Run To Cursor - &Ejecutar hasta cursor + Ejecutar hasta el &cursor @@ -3970,12 +3794,12 @@ You cannot undo this action. Clear Breakpoints - Borrar puntos de interrupción + Limpiar puntos de interrupcción &Clear Breakpoints - &Borrar puntos de interrupción + &Limpiar puntos de interrupcción @@ -3985,12 +3809,12 @@ You cannot undo this action. Add Breakpoint - Agregar punto de interrupción + Añadir punto de interrupcción Add &Breakpoint - Agregar punto de &interrupción + &Añadir punto de interrupcción @@ -4000,12 +3824,12 @@ You cannot undo this action. Go To PC - Ir a PC + Ir al PC &Go To PC - Ir a &PC + Ir al &PC @@ -4015,12 +3839,12 @@ You cannot undo this action. Go To Address - Ir a dirección + Ir a dirección en el desensamblado Go To &Address - Ir a &dirección + Ir a dirección en el &desensamblado @@ -4030,7 +3854,7 @@ You cannot undo this action. &Dump Address - &Volcar dirección + Ir a dirección en el &volcado de memoria @@ -4040,12 +3864,12 @@ You cannot undo this action. Trace - Seguimiento + Rastrear &Trace - &Seguimiento + &Rastrear @@ -4053,182 +3877,154 @@ You cannot undo this action. Ctrl+T - + No address selected. No se seleccionó ninguna dirección. - - + + Enter code address: - Ingrese una dirección del código: + Ingresa la dirección de código: - - + + Enter memory address: - Ingrese una dirección de memoria: + Ingresa la dirección de memoria: - + Trace logging started to cpu_log.txt. This file can be several gigabytes, so be aware of SSD wear. - El registro del seguimiento comenzó a escribirse en cpu_log.txt. -Este archivo puede llegar a ocupar varios gigabytes, así que tenga cuidado con el desgaste si utiliza un SSD. + Registro de rastreo iniciado en "cpu_log.txt". +Este archivo puede alcanzar varios gigabytes en tamaño, así que ten cuidado con el desgaste de SSD. - + Trace logging to cpu_log.txt stopped. - Se detuvo el registro del seguimiento a cpu_log.txt. + Registro de rastreo en "cpu_log.txt" finalizado. - + A breakpoint already exists at this address. - Ya existe un punto de interrupción en esta dirección. + Ya existe un punto de interrupcción en esta dirección. - + Debugger Depurador - + Failed to add step-out breakpoint, are you in a valid function? - Fallo al agregar el punto de interrupción de salida, ¿está en una función valida? + Fallo al añadir punto de interrupcción de regreso. ¿Estás en una función válida? + + + + View in &Dump + Ver en vo&lcado de memoria - - + + Follow Load/Store + Seguir carga/almacenamiento + + + + Invalid search pattern. It should contain hex digits or question marks. - El patrón de búsqueda no es válido, debería contener dígitos hexadecimales o signos de interrogación. + Patrón de búsqueda inválido, debe contener dígitos hexadecimales o signos de interrogación. - + Pattern not found. - No se encontró el patrón. + Patrón no encontrado. - + Pattern found at 0x%1 (passed the end of memory). Patrón encontrado en 0x%1 (pasó el final de la memoria). - + Pattern found at 0x%1. Patrón encontrado en 0x%1. - + Invalid address. It should be in hex (0x12345678 or 12345678) - La dirección no es válida, debe estar escrita en formato hexadecimal (0x12345678 o 12345678) + Dirección inválida. Debe estar en formato hexadecimal (0x12345678 o 12345678) DigitalController - Up - Arriba - - - Down - Abajo - - - Left - Izquierda - - - Right - Derecha - - - Select - Select - - - Start - Start - - - Triangle - Triángulo - - - Cross - Cruz - - - Circle - Círculo - - - Square - Cuadrado - - - L1 - L1 - - - L2 - L2 - - - R1 - R1 - - - R2 - R2 - - - + Force Pop'n Controller Mode - Forzar modo de control Pop'n + Forzar modo para el control de Pop'n - + Forces the Digital Controller to act as a Pop'n Controller. - Fuerza al control digital a actuar como un control Pop'n. + Fuerza el control digital para funcionar como un control de Pop'n. DiscRegion - + NTSC-J (Japan) NTSC-J (Japón) - + NTSC-U/C (US, Canada) - NTSC-U/C (EE. UU., Canadá) + NTSC-U/C (Estados Unidos, Canadá) - + PAL (Europe, Australia) PAL (Europa, Australia) - + Other - Otra + Otro + + + + DisplayAlignment + + + Left / Top + Superior izquierda + + + + Center + Centro + + + + Right / Bottom + Inferior derecha DisplayAspectRatio - + Auto (Game Native) Automática (nativa del juego) - + Auto (Match Window) - Automática (ajustada a la ventana) + Automática (tamaño de ventana) - + Custom Personalizada @@ -4236,17 +4032,17 @@ Este archivo puede llegar a ocupar varios gigabytes, así que tenga cuidado con DisplayCropMode - + None - No recortar + Ninguno - + Only Overscan Area - Sólo el área de sobrescaneo + Solo area de sobreescaneo - + All Borders Todos los bordes @@ -4256,7 +4052,7 @@ Este archivo puede llegar a ocupar varios gigabytes, así que tenga cuidado con Form - Formulario + Form @@ -4276,319 +4072,295 @@ Este archivo puede llegar a ocupar varios gigabytes, así que tenga cuidado con Fullscreen Mode: - Modo a pantalla completa: + Modo de pantalla completa: - - Threaded Rendering - Renderizado multihilo - - - - + VSync Sincronización vertical - - Threaded Presentation - Presentación multihilo + + Threaded Rendering + Hilo de renderizado - - - Sync To Host Refresh Rate - Sincronizar con la velocidad de actualización del sistema + + + Threaded Presentation + Hilo de presentación - - - Optimal Frame Pacing - Optimizar duración de fotogramas + + + Use Blit Swap Chain + Utilizar cadena de intercambio de blits - + Screen Display Imagen - + Aspect Ratio: Relación de aspecto: - + : : - + Crop: Recortar: - - Downsampling: - Submuestreo: + + Position: + Posición: - + + Integer Upscaling + Escalado entero + + + + Stretch To Fill - Estirar para rellenar + Estirar imagen - - + + Linear Upscaling Escalado lineal - - Show CPU Usage - - - - Show Game Frame Rate - Mostrar FPS del juego - - - Show Display FPS - Mostrar FPS de la pantalla - - - - - Show Controller Input - Mostrar entradas del control - - - - - Integer Upscaling - Escalado por números enteros - - - - + + Internal Resolution Screenshots - Capturar pantalla a resolución interna + Capturas de pantalla a resolución interna - + On-Screen Display Mensajes en pantalla - - + + Show Emulation Speed Mostrar velocidad de emulación - + + Show CPU Usage + Mostrar uso de la CPU + + + + + Show OSD Messages + Mostrar mensajes en pantalla + + + + Show FPS - + Mostrar FPS + Show Controller Input + Mostrar entrada del control + + + + Show Resolution Mostrar resolución - + + Show GPU Usage + Mostrar uso de la GPU + + + + Show Settings Overlay + Mostrar configuración en pantalla + + + Renderer Renderizador - + Chooses the backend to use for rendering the console/game visuals. <br>Depending on your system and hardware, Direct3D 11 and OpenGL hardware backends may be available. <br>The software renderer offers the best compatibility, but is the slowest and does not offer any enhancements. - Elige el motor con el que se renderizarán los gráficos de la consola y los juegos.<br>Los motores por hardware para Direct3D 11 y OpenGL estarán disponibles dependiendo de su hardware y sistema.<br>El renderizador por software ofrece la mejor compatibilidad, pero es el más lento y no permite seleccionar ninguna mejora. + Elige el motor para renderizar los gráficos de la consola/juegos.<br>Los motores Direct3D 11 y OpenGL estarán disponibles dependiendo de tu hardware y sistema.<br>El renderizador por software ofrece la mejor compatibilidad, pero es el más lento y no permite seleccionar ninguna mejora. - + Adapter Adaptador - - + + (Default) (Predeterminado) - + If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware renderers. <br>This option is only supported in Direct3D and Vulkan. OpenGL will always use the default device. - Si su sistema tiene múltiples GPU o adaptadores, puede elegir cuál quiere usar para los renderizadores de hardware. <br>Esta opción sólo está disponible para Direct3D y Vulkan, OpenGL siempre usa el dispositivo por defecto. + Si tu sistema tiene múltiples GPUs o adaptadores, puedes elegir cual quieres utilizar para los renderizadores.<br>Esta opción solo está disponible en Direct3D y Vulkan, OpenGL siempre utiliza el dispositivo por defecto. - Aspect Ratio - Relación de aspecto + Fullscreen Mode + Modo de pantalla completa - - Crop Mode - Modo de recorte + + + Borderless Fullscreen + Pantalla completa sin bordes - - Changes the aspect ratio used to display the console's output to the screen. The default is Auto (Game Native) which automatically adjusts the aspect ratio to match how a game would be shown on a typical TV of the era. - Cambia la relación de aspecto usada para mostrar la salida de la consola en la pantalla. La opción por defecto es «Automática (nativa del juego)», la cual ajustará automaticámente el aspecto al que utilizaría un juego en un televisor típico de la época. + + Chooses the fullscreen resolution and frequency. + Selecciona la resolución y frecuencia para el modo de pantalla completa. - - Fullscreen Mode - Modo a pantalla completa + + Aspect Ratio + Relación de aspecto - - Chooses the fullscreen resolution and frequency. - Selecciona la frecuencia y resolución del modo a pantalla completa. + + Changes the aspect ratio used to display the console's output to the screen. The default is Auto (Game Native) which automatically adjusts the aspect ratio to match how a game would be shown on a typical TV of the era. + Cambia la relación de aspecto utilizada para mostrar la imagen de salida de la consola en la pantalla. La opción por defecto es "Automática (nativa del juego)", que ajusta automáticamente la relación de aspecto para que coincida con la forma en que se mostraría un juego en un televisor típico de la época. - - Determines how much of the area typically not visible on a consumer TV set to crop/hide. <br>Some games display content in the overscan area, or use it for screen effects. <br>May not display correctly with the "All Borders" setting. "Only Overscan" offers a good compromise between stability and hiding black borders. - Determina qué partes se recortarán/ocultarán del área que, por norma general, no es visible en un televisor.<br>Algunos juegos muestran contenidos en el área de sobrescaneo o la usan para algunos efectos.<br>La imagen puede no mostrarse correctamente si se ocultan todos los bordes. La opción «Sólo el área de sobrescaneo» ofrece un buen equilibrio entre estabilidad y reducción de los bordes negros. + + Crop Mode + Modo de recorte - - Downsampling - Submuestreo + + Determines how much of the area typically not visible on a consumer TV set to crop/hide. <br>Some games display content in the overscan area, or use it for screen effects. <br>May not display correctly with the "All Borders" setting. "Only Overscan" offers a good compromise between stability and hiding black borders. + Determina cuanto del área generalmente no visible en un televisor recortar/ocultar.<br>Algunos juegos muestran contenido en el área de sobreescaneo, o la usan para algunos efectos.<br>"Todos los bordes" puede mostrar la imagen incorrectamente. "Solo area de sobreescaneo" ofrece un buen balance entre estabilidad y reducción de bordes. - - Disabled - Deshabilitado + + Position + Posición - - Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, but should be disabled for pure 3D games. Only applies to the hardware renderers. - Reduce la resolución de la imagen renderizada antes de mostrarla. Puede mejorar la calidad de imagen en juegos mixtos 2D/3D, pero debe desactivarse para juegos en 3D puros. Sólo se aplica a los renderizadores de hardware. + + Determines the position on the screen when black borders must be added. + Determina la posición de la pantalla cuando se añadan bordes negros. - - - - - + + + + + Checked Habilitado - + Uses bilinear texture filtering when displaying the console's framebuffer to the screen. <br>Disabling filtering will producer a sharper, blockier/pixelated image. Enabling will smooth out the image. <br>The option will be less noticable the higher the resolution scale. - Utiliza un filtro bilineal para mostrar el búfer de fotograma de la consola en pantalla.<br>Deshabilitar el filtrado producirá una imagen más nítida y pixelada/cuadriculada. Activarlo va a suavizar la imagen.<br>Este efecto será menos visible si la escala de resolución es elevada. + Utiliza el filtrado de texturas bilineal cuando se muestra el búfer de fotograma en la pantalla.<br>Deshabilitar el filtrado va a producir una imagen más nítida y pixelada/cuadriculada. Activarlo va a suavizar la imagen.<br>Este efecto será menos notable mientras mayor sea la escala de resolución. - - - - + + + + + - - - + Unchecked Deshabilitado - + Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. <br>May result in a sharper image in some 2D games. - Añade relleno al área de visualización para hacer que la relación entre los pixeles de la consola y del sistema sean un número entero.<br>Puede producir una imagen más nítida en algunos juegos 2D. + Añade relleno en la pantalla para asegurarse que la relación entre los pixeles de la consola y del sistema es un número entero.<br>Puede producir una imagen más nítida en algunos juegos 2D. - + Fills the window with the active display area, regardless of the aspect ratio. - Rellena la ventana con el área de visualización activa sin importar la relación de aspecto. + Rellena la ventana con el área de visualización activa, sin importar la relación de aspecto. - + Saves screenshots at internal render resolution and without postprocessing. If this option is disabled, the screenshots will be taken at the window's resolution. Internal resolution screenshots can be very large at high rendering scales. - Guarda las capturas de pantalla con la resolución de renderización interna y sin aplicar posprocesamiento. Si ésta opción está desactivada, las capturas de pantalla se guardarán con la resolución de la ventana. Las capturas de pantalla a resolución interna pueden ser muy grandes a escalas de renderizado altas. - - - - Enable this option to match DuckStation's refresh rate with your current monitor or screen. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). - Habilite esta opción para vincular la tasa de refresco de DuckStation con la de su pantalla. La sincronización vertical se deshabilitará automáticamente cuando no sea posible (por ejemplo, si la emulación no funciona al 100 % de la velocidad). + Guarda capturas de pantalla en la resolución interna de renderizado, sin postprocesamiento. Si esta opción está deshabilitada, las capturas se tomarán en la resolución de la ventana. Las capturas en resolución interna pueden alcanzar grandes tamaños a escalas de renderizado altas. - Enable this option will ensure every frame the console renders is displayed to the screen, for optimal frame pacing. If you are having difficulties maintaining full speed, or are getting audio glitches, try disabling this option. - Habilite esta opción para hacer que cada fotograma renderizado por la consola se muestre en pantalla, garantizando así una duración de fotogramas óptima. Si tiene dificultades para mantener la velocidad máxima o problemas de audio, intente desactivar esta opción. + Enable this option to match DuckStation's refresh rate with your current monitor or screen. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). + Habilita esta opción para coincidir la tasa de refresco de DuckStation con la de tu pantalla. La sincronización vertical se deshabilita automáticamente cuando no sea posible (por ejemplo, no llegar al 100% de la velocidad). - + Presents frames on a background thread when fast forwarding or vsync is disabled. This can measurably improve performance in the Vulkan renderer. - Presenta los fotogramas en un hilo en segundo plano cuando el avance rápido o la sincronización vertical estén desactivados. Podría mejorar considerablemente el rendimiento con el renderizador Vulkan. + Presenta los fotogramas en un hilo en segundo plano durante el avance rápido o si la sincronización vertical está deshabilitada. Esto puede mejorar considerablemente el rendimiento del renderizador de Vulkan. - + Uses a second thread for drawing graphics. Currently only available for the software renderer, but can provide a significant speed improvement, and is safe to use. - Utiliza un segundo hilo para dibujar los gráficos. Actualmente sólo está disponible para el renderizador por software, pero puede dar un aumento importante de velocidad y es seguro de usar. - - - - Adjusts the emulation speed so the console's refresh rate matches the host's refresh rate when both VSync and Audio Resampling settings are enabled. This results in the smoothest animations possible, at the cost of potentially increasing the emulation speed by less than 1%. Sync To Host Refresh Rate will not take effect if the console's refresh rate is too far from the host's refresh rate. Users with variable refresh rate displays should disable this option. - Ajusta la velocidad de emulación para que la frecuencia de actualización de la consola coincida con la frecuencia de actualización del servidor cuando la sincronización vertical y el remuestreo de audio estén habilitados. Esto producirá las animaciones más suaves posibles a costa de aumentar potencialmente la velocidad de emulación en menos de un 1 %. «Sincronizar con la velocidad de actualización del sistema» no hará efecto si la frecuencia de actualización de la consola se distancia demasiado de la del sistema. Aquellos usuarios que tengan pantallas de frecuencia de actualización variable deben desactivar esta opción. - - - - - Show OSD Messages - Mostrar mensajes + Utiliza un segundo hilo para dibujar los gráficos. Actualmente solo está disponible para el renderizador por software, pero puede aumentar significativamente la velocidad, y es seguro de activar. - + Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc. - Muestra mensajes en pantalla cuando ocurran eventos, tales como la creación o carga de estados guardados, la toma de capturas de pantalla, etc. + Muestra mensajes en pantalla cuando ocurren eventos como crear o cargar estados guardados, tomar capturas de pantalla, etc. - + Shows the internal frame rate of the game in the top-right corner of the display. Muestra la velocidad de fotogramas interna del juego en la esquina superior derecha de la pantalla. - Shows the number of frames (or v-syncs) displayed per second by the system in the top-right corner of the display. - Muestra el número de fotogramas (o sincronizaciones verticales) por segundo mostrados por el sistema en la esquina superior derecha de la pantalla. - - - + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. Muestra el porcentaje de velocidad de emulación actual del sistema en la esquina superior derecha de la pantalla. - + Shows the resolution of the game in the top-right corner of the display. Muestra la resolución del juego en la esquina superior derecha de la pantalla. - - Shows the current controller state of the system in the bottom-left corner of the display. - Muestra el estado actual del control del sistema en la esquina inferior izquierda de la pantalla. + + Shows the host's CPU usage based on threads in the top-right corner of the display. This does not display the emulated system CPU's usage. If a value close to 100% is being displayed, this means your host's CPU is likely the bottleneck. In this case, you should reduce enhancement-related settings such as overclocking. + Muestra el uso de la CPU del sistema detallado por hilos en la esquina superior derecha de la pantalla. Esto no muestra el uso de la CPU de la consola. Si se muestra un valor cercano al 100%, es probable que tu CPU sea el cuello de botella. De ser así, deberías reducir la configuración relacionada a mejoras, como overclocking. - - - Use Blit Swap Chain - Usar cadena de intercambio de blits + + Shows the current controller state of the system in the bottom-left corner of the display. + Muestra el estado actual del control del sistema en la esquina inferior izquierda de la pantalla. - + Uses a blit presentation model instead of flipping when using the Direct3D 11 renderer. This usually results in slower performance, but may be required for some streaming applications, or to uncap framerates on some systems. - Utiliza un modelo de presentación blit en lugar de voltear cuando se usa el renderizador Direct3D 11. En general esto da como resultado un rendimiento más lento, pero puede ser necesario para algunas aplicaciones de transmisión o para quitar el límite de fotogramas por segundo en algunos sistemas. - - - - - Borderless Fullscreen - Pantalla completa sin bordes + Utiliza un modelo de presentación blit en lugar de voltear cuando se usa el renderizador de Direct3D 11. Esto generalmente resulta en menor rendimiento, pero puede ser necesario para algunas aplicaciones de transmisión, o para eliminar el límite de fotogramas en algunos sistemas. @@ -4596,82 +4368,86 @@ Este archivo puede llegar a ocupar varios gigabytes, así que tenga cuidado con Form - Formulario + Form <html><head/><body><p><span style=" font-weight:700;">No games in supported formats were found.</span></p><p>Please add a directory with games to begin.</p><p>Game dumps in the following formats will be scanned and listed:</p></body></html> - + <html><head/><body><p><span style=" font-weight:700;">No se encontraron juegos con formatos compatibles.</span></p><p>Añade un directorio que contenga juegos para comenzar.</p><p>Se buscarán y listarán juegos con los siguientes formatos:</p></body></html> TextLabel - TextLabel + TextLabel Add Game Directory... - Agregar directorio de juegos... + Añadir directorio de juegos... Scan For New Games - Buscar juegos nuevos + Buscar juegos nuevos EmuThread - + Error - Error + Error - + No resume save state found. - No se encontraron estados guardados para continuar. + No se encontraron estados guardados para resumir. - + Game ID: %1 Game Title: %2 Achievements: %5 (%6) - + ID: %1 +Título: %2 +Logros: %5 (%6) + + - + %n points - + %n punto %n puntos - + Rich presence inactive or unsupported. - «Rich Presence» inactiva o no compatible. + Rich Presence está inactiva o es incompatible. - + Game not loaded or no RetroAchievements available. - No se cargó un juego o RetroAchievements no está disponible. + Juego no cargado o RetroAchievements no está disponible. - + %1x%2 - + %1x%2 - + Game: %1 FPS - + Juego: %1 FPS - + Video: %1 FPS (%2%) - + Video: %1 FPS (%2%) @@ -4679,7 +4455,7 @@ Achievements: %5 (%6) Form - Formulario + Form @@ -4699,202 +4475,226 @@ Achievements: %5 (%6) Turbo Speed: - Velocidad del turbo: + Velocidad turbo: - - Rewind/Runahead - Rebobinado/Predicción de latencia + + + Sync To Host Refresh Rate + Sincronizar al refresco de pantalla + + Optimal Frame Pacing + Ritmo de fotogramas óptimo + + + + Rewind/Runahead + Rebobinado/Procesamiento anticipado + + + Enable Rewinding - Hablitar rebobinado + Habilitar rebobinado - + Rewind Save Frequency: Frecuencia de guardado del rebobinado: - + Seconds - segundos + Segundos - + Rewind Buffer Size: - Tamaño del búfer de rebobinado: + Tamaño del búfer del rebobinado: - + Frames - fotogramas + Fotogramas - + Runahead: - Predicción de latencia: + Procesamiento anticipado: - - + + Disabled - Deshabilitada + Deshabilitado - + 1 Frame - 1 fotograma + 1 Fotograma - + 2 Frames - 2 fotogramas + 2 Fotogramas - + 3 Frames - 3 fotogramas + 3 Fotogramas - + 4 Frames - 4 fotogramas + 4 Fotogramas - + 5 Frames - 5 fotogramas + 5 Fotogramas - + 6 Frames - 6 fotogramas + 6 Fotogramas - + 7 Frames - 7 fotogramas + 7 Fotogramas - + 8 Frames - 8 fotogramas + 8 Fotogramas - + 9 Frames - 9 fotogramas + 9 Fotogramas - + 10 Frames - 10 fotogramas + 10 Fotogramas - + TextLabel TextLabel - + Emulation Speed Velocidad de emulación - + Sets the target emulation speed. It is not guaranteed that this speed will be reached, and if not, the emulator will run as fast as it can manage. - Establece la velocidad de emulación de destino. No se asegura que esa velocidad se vaya a alcanzar, y de no hacerlo, el emulador correrá a la mayor velocidad posible. + Establece la velocidad de emulación de destino. No se asegura que esa velocidad se vaya a alcanzar, y de no hacerlo, el emulador funcionará lo más rápido que pueda. - + Fast Forward Speed Velocidad de avance rápido - - + + User Preference Preferencia de usuario - + Sets the fast forward speed. This speed will be used when the fast forward hotkey is pressed/toggled. - Establece la velocidad del avance rápido. Se usará cuando se presione o active el atajo de avance rápido. + Determina la velocidad del avance rápido. Se utilizará cuando se presione/active la tecla de avance rápido. - + Turbo Speed - Velocidad del turbo + Velocidad turbo - + Sets the turbo speed. This speed will be used when the turbo hotkey is pressed/toggled. Turboing will take priority over fast forwarding if both hotkeys are pressed/toggled. - Establece la velocidad del modo turbo. Se usará cuando se presione o active el atajo del turbo. En caso de presionarse o activarse el modo turbo y el avance rápido al mismo tiempo, la velocidad del modo turbo tomará prioridad. + Determina la velocidad del modo turbo. Se utilizará cuando se presione/active la tecla de turbo. Esta opción tendrá prioridad por sobre el avance rápido si el atajo de ambas se presionan/activan al mismo tiempo. - + + + Unchecked Deshabilitado - + + Adjusts the emulation speed so the console's refresh rate matches the host's refresh rate when both VSync and Audio Resampling settings are enabled. This results in the smoothest animations possible, at the cost of potentially increasing the emulation speed by less than 1%. Sync To Host Refresh Rate will not take effect if the console's refresh rate is too far from the host's refresh rate. Users with variable refresh rate displays should disable this option. + Ajusta la velocidad de emulación para que la tasa de refresco de la consola coincida con la pantalla de tu sistema cuando la sincronización vertical y el remuestreo de audio estén habilitados. Produce que las animaciones sean lo más fluídas posibles, a costa de aumentar potencialmente la velocidad de emulación en menos del 1%. Esta opción no tendrá efecto si la tasa de refresco de la consola es muy diferente a de la de tu sistema. Usuarios con tasas de refresco variables deberían deshabilitar esta opción. + + + + Enable this option will ensure every frame the console renders is displayed to the screen, for optimal frame pacing. If you are having difficulties maintaining full speed, or are getting audio glitches, try disabling this option. + Habilitar esta opción asegurará que cada fotograma que renderiza la consola se mostrará en la pantalla, para un ritmo de fotogramas óptimo. Si tienes dificultades para mantener máxima velocidad, o problemas de audio, intenta deshabilitar esta opción. + + + Rewinding Rebobinado - + <b>Enable Rewinding:</b> Saves state periodically so you can rewind any mistakes while playing.<br> <b>Rewind Save Frequency:</b> How often a rewind state will be created. Higher frequencies have greater system requirements.<br> <b>Rewind Buffer Size:</b> How many saves will be kept for rewinding. Higher values have greater memory requirements. - <b>Habilitar rebobinado:</b> Salva la partida periódicamente para que puedas volver a antes de cualquier error que cometieras.<br> <b>Frecuencia de guardado del rebobinado:</b> Determina con qué frecuencia se realizarán los guardados. Una frecuencia muy alta tendrá unos requisitos del sistema muy elevados.<br> <b>Tamaño del búfer de rebobinado:</b> Determina la cantidad de guardados que se conservarán para el rebobinado. Un valor muy alto tendrá unos requisitos de memoria elevados. + <b>Habilitar rebobinado:</b> Guarda estados periódicamente para poder rebobinar/retroceder en el tiempo mientras juegas.<br> <b>Frecuencia de guardado:</b> Que tan seguido se guardarán los estados para el rebobinado. Frecuencias más altas aumentan los requerimientos del sistema.<br> <b>Tamaño del búfer:</b> Cuántos estados se guardarán para rebobinar. Valores más altos aumentan los requerimientos de memoria. - + Runahead - Predicción de latencia + Procesamiento anticipado - + Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements. - Simula el sistema por adelantado y retrocede o repite las acciones para reducir el retraso de entrada. Tiene unos requisitos de sistema muy altos. + Simula el sistema de forma anticipada y regresa/repite el estado para reducir la latencia de entrada. Tiene requerimientos de sistema muy altos. - + Use Global Setting [Unlimited] - + Utilizar configuración global [sin límite] - + Use Global Setting [%1%] - + Utilizar configuración global [%1%] - + Unlimited - Sin límite + Sin límite - + %1% [%2 FPS (NTSC) / %3 FPS (PAL)] - %1 % [%2 FPS (NTSC) / %3 FPS (PAL)] + %1% [%2 FPS (NTSC) / %3 FPS (PAL)] - + Rewind for %n frame(s), lasting %1 second(s) will require up to %2MB of RAM and %3MB of VRAM. - Retroceder durante %n fotogramas, con duración de %1 segundos requerirá hasta %2MB de RAM y %3MB de VRAM. - Rebobinar %n fotograma(s) con una duración de %1 segundo(s) requerirá hasta %2 MB de RAM y %3 MB de VRAM. + Rebobinar por %n fotograma, durante %1 segundo(s) requerirá hasta %2MB de RAM y %3MB de VRAM. + Rebobinar por %n fotograma(s), durante %1 segundo(s) requerirá hasta %2MB de RAM y %3MB de VRAM. - + Rewind is disabled because runahead is enabled. Runahead will significantly increase system requirements. - La función de rebobinado está deshabilitada porque la predicción de latencia rápido está habilitada. La predicción de latencia aumentará significativamente los requisitos del sistema. + El rebobinado está deshabilitado porque el procesamiento anticipado está habilitado. El procesamiento anticipado aumentará significativamente los requerimientos del sistema. - + Rewind is not enabled. Please note that enabling rewind may significantly increase system requirements. - La función de rebobinado no está habilitada. Tenga en cuenta que habilitarla aumentará significativamente los requisitos del sistema. + El rebobinado no esta habilitado. Ten en cuenta que habilitarlo puede aumentar significativamente los requerimientos del sistema. @@ -4902,7 +4702,7 @@ Achievements: %5 (%6) Form - Formulario + Form @@ -4917,245 +4717,261 @@ Achievements: %5 (%6) Texture Filtering: - Filtrado de texturas: + Filtrado de textura: - + True Color Rendering (24-bit, disables dithering) - Renderizado a color verdadero (24 bits, deshabilita el tramado) + Renderizado de color verdadero (24 bits, deshabilita el tramado) - + Scaled Dithering (scale dither pattern to resolution) Escalado de tramado (escalar patrón de tramado a resolución) Widescreen Hack (render 3D in display aspect ratio) - Hack para pantallas panorámicas (renderiza el 3D a la relación de la pantalla) + Hack de pantalla panorámica (renderizar 3D en el aspecto de pantalla) Software Renderer Readbacks (run in parallel for VRAM->CPU transfers) - Relecturas en renderizado por software (ejecución en paralelo para VRAM-> CPU) + Utilizar renderizador por software para lecturas (ejecutar en paralelo para transferencias VRAM->CPU) - Multisample Antialiasing: - Antialiasing de muestreo múltiple: + + Downsampling: + Submuestreo: - + Display Enhancements Mejoras de imagen - - + + Disable Interlacing (force progressive render/scan) Deshabilitar entrelazado (forzar escaneo progresivo) - - + + Force NTSC Timings (60hz-on-PAL) - Forzar velocidad NTSC (60 Hz en PAL) + Forzar tiempos NTSC (60Hz en PAL) - + Force 4:3 For 24-Bit Display (disable widescreen for FMVs) - Forzar 4:3 para imágenes a 24 bits (deshabilitar imagen panorámica en FMVs) + Forzar 4:3 para escenas de 24 bits (deshabilita pantalla panorámica en FMVs) - + Chroma Smoothing For 24-Bit Display (reduce FMV color blockyness) - Suavizar croma en imágenes a 24 bits (reduce el efecto pixelado en FMVs) + Suavizado de croma para escenas de 24 bits (reduce el efecto pixelado en FMVs) - + PGXP (Precision Geometry Transform Pipeline) - PGXP (transformación precisa de geometría) + PGXP (corrección de presición geométrica) + + + + + Culling Correction + Corrección de culling + + + + + Perspective Correct Textures + Correción de perspectiva de texturas - - + + Geometry Correction Correción de geometría - - - Culling Correction - Corrección de «culling» + + + CPU Mode (Very Slow) + Modo CPU (muy lento) - - - Texture Correction - Corrección de texturas + + + Depth Buffer (Low Compatibility) + Búfer de profundidad (baja compatibilidad) - - + + Preserve Projection Precision - Conservar precisión de proyección + Preservar la precisión de proyección - - - Depth Buffer (Low Compatibility) - Búfer de profundidad (baja compatibilidad) + + + Perspective Correct Colors + Correción de perspectiva de colores - - - CPU Mode (Very Slow) - Modo CPU (muy lento) + + Downsampling + Submuestreo - - - + + Disabled + Deshabilitado + + + + Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, but should be disabled for pure 3D games. Only applies to the hardware renderers. + Reduce el tamaño de la imagen renderizada antes de mostrarla. Puede mejorar la calidad general de la imagen en juegos 2D/3D mixtos, pero debería deshabilitarse para juegos exclusivamente 3D. Solo se aplica a los renderizadores por hardware. + + + + - - - - - - - + + + + + + + + + Unchecked Deshabilitado - + Forces the rendering and display of frames to progressive mode. <br>This removes the "combing" effect seen in 480i games by rendering them in 480p. Usually safe to enable.<br> <b><u>May not be compatible with all games.</u></b> - Fuerza el renderizado y visualización de fotogramas en modo progresivo.<br>Esto elimina el «efecto peine» producido por el entrelazado en juegos que usan el modo 480i renderizándolos en 480p. Normalmente es seguro habilitarlo.<b><u>Podría no ser compatible con todos los juegos.</u></b> + Fuerza el renderizado y visualización de fotogramas en modo progresivo.<br>Esto elimina el "efecto peine" producido por el entrelazado en juegos que usan el modo 480i renderizándolos en 480p. Normalmente es seguro habilitarlo.<b><u>Puede que no sea compatible con todos los juegos.</u></b> - + Resolution Scale Escala de resolución - + Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies to the hardware backends. <br>This option is usually safe, with most games looking fine at higher resolutions. Higher resolutions require a more powerful GPU. - Seleccionar un valor superior a 1x mejorará la resolución del renderizado de polígonos y líneas 3D. Sólo se aplica a los renderizadores por hardware.<br>Usualmente activar esta opción es seguro. Las resoluciones más altas requieren una GPU más potente. - - - Multisample Antialiasing - Antialiasing de muestreo múltiple - - - Disabled - Deshabilitado - - - Uses multisample antialiasing for rendering 3D objects. Can smooth out jagged edges on polygons at a lower cost to performance compared to increasing the resolution scale, but may be more likely to cause rendering errors in some games. Only applies to the hardware backends. - Utiliza antialiasing de muestreo múltiple (MSAA) para renderizar objetos 3D. Puede suavizar los bordes con dientes de sierra de los polígonos con menor impacto en el rendimiento en comparación a incrementar la resolución interna, pero es mas susceptible a causar errores en algunos juegos. Sólo se aplica a los renderizadores por hardware. + Seleccionar un valor mayor a 1x mejorará la resolución del renderizado de polígonos y líneas 3D. Solo se aplica a los renderizadores por hardware.<br>Generalmente es seguro activar esta opción. Resoluciones más altas requieren una GPU más potente. - + Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per channel. This produces nicer looking gradients at the cost of making some colours look slightly different. Disabling the option also enables dithering, which makes the transition between colours less sharp by applying a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't and will have broken effects with it enabled. Only applies to the hardware renderers. - Fuerza la precisión de la salida de colores al búfer de fotogramas para que use la totalidad de los 8 bits de precisión por canal. Esto produce mejores gradientes a cambio de alterar algunos colores. Deshabilitar esta opción también activa el tramado, que aplicará un patrón a los píxeles para que la transición entre colores sea menos definida. La mayoría de los juegos son compatibles con esta opción, aunque hay algunos que no y pueden presentar efectos incorrectamente. Sólo se aplica a los renderizadores de hardware. + Fuerza la precisión de la salida de colores al búfer de fotogramas para que use la totalidad de los 8 bits de precisión por canal. Esto produce mejores degradados al costo de alterar algunos colores. Deshabilitar esta opción también activa el tramado, que hace la transición entre colores menos definida al aplicar un patrón sobre los pixeles. La mayoría de los juegos son compatibles con esta opción, pero hay algunos que no y pueden presentar efectos incorrectamente. Solo se aplica a los renderizadores por hardware. - - - + + + Checked Habilitado - + Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less obvious at higher resolutions. <br>Usually safe to enable, and only supported by the hardware renderers. - Escala el patrón del tramado a la escala de resolución de la GPU emulada. Así se disimulará el tramado en las resoluciones más altas.<br>Usualmente es seguro habilitarlo, y sólo está soportado por los renderizadores de hardware. + Escala el patrón del tramado a la escala de resolución de la GPU emulada. Esto disimula el tramado en altas resoluciones.<br>Generalmente es seguro habilitarlo, y solo está soportado por los renderizadores por hardware. - + Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. <br>For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. <br>For variable frame rate games, it may not affect the speed. - Utiliza la velocidad de fotogramas NTSC cuando la consola esté en modo PAL, forzando a los juegos PAL a correr a 60 Hz.<br>En el caso de la mayoría de los juegos, que tienen su velocidad vinculada a los FPS, esto resultará en el juego corriendo aproximadamente un 17 % más rápido.<br>Los juegos que tengan una velocidad de fotogramas variable podrían no verse afectados. + Usa los tiempos de fotograma del modo NTSC cuando la consola está en modo PAL, forzando los juegos PAL a ejecutarse a 60Hz.<br>La mayoría de los juegos, que tienen su velocidad relacionada a los FPS, se ejecutarán aproximadamente un 17% más rápido.<br>Para juegos con velocidad de fotogramas variable, este problema no debería afectarles. - + Force 4:3 For 24-bit Display - Forzar 4:3 para imágenes a 24 bits + Forzar 4:3 para escenas de 24 bits - + Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs. - Cambia a la relación de aspecto 4:3 cuando se muestren contenidos a 24 bits, que suelen ser FMVs. + Cambia al modo de pantalla 4:3 cuando se muestra contenido de 24 bits, generalmente FMVs. - + Chroma Smoothing For 24-Bit Display - Suavizar croma en imágenes a 24 bits + Suavizado de croma para escenas de 24 bits - + Smooths out blockyness between colour transitions in 24-bit content, usually FMVs. Only applies to the hardware renderers. - Suaviza el efecto pixelado que se produce entre las transiciones de colores en contenidos a 24 bits, que suelen ser FMVs. Sólo se aplica a los renderizadores de hardware. + Suaviza el aspecto pixelado que se produce entre las transiciones de colores en contenido de 24 bits, generalmente en FMVs. Solo se aplica a los renderizadores por hardware. - + Texture Filtering - Filtrado de texturas + Filtrado de textura - - Smooths out the blockyness of magnified textures on 3D object by using filtering. <br>Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. - Suaviza mediante el uso de filtros el efecto pixelado de las texturas magnificadas en objetos 3D.<br>Su efecto será más visible al usar escalas de resolución altas. Sólo se aplica a los renderizadores de hardware. + + Smooths out the blockiness of magnified textures on 3D object by using filtering. <br>Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. <br>The JINC2 and especially xBR filtering modes are very demanding, and may not be worth the speed penalty. + Suaviza el aspecto pixelado de las texturas magnificadas en objetos 3D utilizando filtros.<br>Tendrá mayor efecto a mayor escala de resolución. Sólo se aplica a los renderizadores por hardware.<br>Los filtros JINC2 y especialmente xBR son muy demandantes, y tal vez no valgan la pérdida en rendimiento. - + Widescreen Hack - Hack para pantallas panorámicas + Hack de pantalla panorámica - + Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to the chosen display aspect ratio in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <br><b><u>May not be compatible with all games.</u></b> - Escala las posiciones de los vértices en pantalla a una relación de aspecto panorámica, incrementando en los juegos 3D el campo de visión de 4:3 a la relación de aspecto seleccionada.<br>Esta mejora no funcionará correctamente en juegos 2D o con fondos prerenderizados.<br><b><u>Podría no ser compatible con todos los juegos.</u></b> + Escala las posiciones de vértices en pantalla a una relación de aspecto panorámica, incrementando el campo de visión de 4:3 al aspecto seleccionado en juegos 3D.<br>En juegos 2D o con fondos prerenderizados, esta mejora no va a funcionar como se espera.<br><b><u>Tal vez no sea compatible con todos los juegos.</u></b> - + Use Software Renderer For Readbacks - Utilizar el renderizado por software para las relecturas + Utilizar renderizador por software para lecturas - + Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in greater performance when using graphical enhancements with the hardware renderer. - Ejecuta el renderizador por software en paralelo para controlar las lecturas de VRAM. En algunos sistemas esto puede aumentar el rendimiento cuando se utilicen mejoras gráficas con el renderizador por hardware. + Ejecuta el renderizador de software en paralelo para lecturas de VRAM. En algunos sistemas, esto puede resultar en un mayor rendimiento cuando se usan mejoras gráficas con el renderizador por hardware. - + Reduces "wobbly" polygons and "warping" textures that are common in PS1 games. <br>Only works with the hardware renderers. <b><u>May not be compatible with all games.</u></b> - Reduce el efecto tembloroso de los polígonos y la deformación de texturas que son comunes en los juegos de PlayStation.<br>Sólo funciona con los renderizadores por hardware. <b><u>Podría no ser compatible con todos los juegos.</u></b> + Reduce el efecto tembloroso de los polígonos y la deformación de texturas que son comunes en los juegos de PlayStation. <br>Solo funciona con los renderizadores por hardware. <b><u>Puede no ser compatible con todos los juegos.</u></b> - + Increases the precision of polygon culling, reducing the number of holes in geometry. Requires geometry correction enabled. - Incrementa la precisión de la selección (o «culling») de polígonos para reducir el número de huecos en la geometría. Requiere la correción de geometría. + Incrementa la precisión del culling de polígonos, reduciendo el número de huecos en la geometría. Requiere tener la correción de geometría activada. - - Uses perspective-correct interpolation for texture coordinates and colors, straightening out warped textures. Requires geometry correction enabled. - Utiliza una interpolación con perspectiva correcta para las coordenadas de texturas y los colores, enderezando las texturas deformadas. Requiere la correción de geometría. + + Uses perspective-correct interpolation for texture coordinates, straightening out warped textures. Requires geometry correction enabled. + Utiliza interpolación con perspectiva correcta para las coordenadas de texturas, enderezando texturas deformadas. Requiere la correción de geometría activada. - + + Uses perspective-correct interpolation for vertex colors, which can improve visuals in some games, but cause rendering errors in others. Requires geometry correction enabled. + Utiliza interpolación con perspectiva correcta para los colores de los vértices, lo cual puede mejorar la calidad visual en algunos juegos, pero provocar errores de renderizado en otros. Requiere la correción de geometría activada. + + + Attempts to reduce polygon Z-fighting by testing pixels against the depth values from PGXP. Low compatibility, but can work well in some games. Other games may need a threshold adjustment. - Intenta reducir las interferencias entre polígonos (o «Z-fighting») probando píxeles con los valores de profundidad de PGXP. Tiene baja compatibilidad, pero puede funcionar bien en algunos juegos, mientras que otros necesitarán un cambio en el umbral. + Intenta reducir conflictos de planos entre polígonos en el búfer Z al comparar los pixeles contra los valores de profundidad de PGXP. Tiene baja compatibilidad, pero puede funcionar bien en algunos juegos. Otros pueden necesitar un ajuste de umbral. - + Adds additional precision to PGXP data post-projection. May improve visuals in some games. - Agrega una precisión adicional a la posproyección de datos de la PGXP. Puede mejorar los gráficos de algunos juegos. + Añade precisión adicional a los datos de PGXP posproyección. Puede mejorar la visualización en algunos juegos. - + Uses PGXP for all instructions, not just memory operations. Required for PGXP to correct wobble in some games, but has a very high performance cost. - Utiliza PGXP para todas las instrucciones, además de las operaciones de memoria. Es necesario para que PGXP corrija el temblor visual de algunos juegos, pero su costo de rendimiento es muy alto. + Utiliza PGXP para todas las instrucciones, no solo para las operaciones de memoria. Es necesario para que PGXP corrija el temblor de polígonos en algunos juegos, pero tiene un costo de rendimiento muy alto. @@ -5163,12 +4979,12 @@ Achievements: %5 (%6) Form - Formulario + Form Cache Directory - + Directorio de caché @@ -5176,7 +4992,7 @@ Achievements: %5 (%6) Browse... - Buscar... + Buscar... @@ -5184,7 +5000,7 @@ Achievements: %5 (%6) Open... - Abrir... + Abrir... @@ -5192,58 +5008,58 @@ Achievements: %5 (%6) Reset - Reiniciar + Restablecer Used for storing shaders and game list data. - + Utilizado para almacenar datos de sombreadores y la lista de juegos. Covers Directory - + Directorio de portadas Used for storing covers in the game grid/Big Picture UIs. - + Utilizado para almacenar portadas de la cuadrícula de juegos y el modo Big Picture. Screenshots Directory - + Directorio de capturas de pantalla Used for screenshots. - + Utilizado para las capturas de pantalla. Save States Directory - + Directorio de estados guardados Used for storing save states. - + Utilizado para almacenar los estados guardados. GPUDownsampleMode - + Disabled Deshabilitado - + Box (Downsample 3D/Smooth All) - Cuadro (reducir resolución 3D/suavizar todo) + Cuadro (submuestreo 3D/suavizar todo) - + Adaptive (Preserve 3D/Smooth 2D) Adaptable (preservar 3D/suavizar 2D) @@ -5251,27 +5067,27 @@ Achievements: %5 (%6) GPURenderer - + Hardware (D3D11) Hardware (D3D11) - + Hardware (D3D12) Hardware (D3D12) - + Hardware (Vulkan) Hardware (Vulkan) - + Hardware (OpenGL) Hardware (OpenGL) - + Software Software @@ -5279,196 +5095,246 @@ Achievements: %5 (%6) GPUSettingsWidget - + Automatic based on window size - Automática según el tamaño de la ventana + Automático según el tamaño de la ventana - + 1x 1x - + 2x 2x - + 3x (for 720p) 3x (para 720p) - + 4x 4x - + 5x (for 1080p) 5x (para 1080p) - + 6x (for 1440p) 6x (para 1440p) - + 7x 7x - + 8x 8x - + 9x (for 4K) 9x (para 4K) - + 10x 10x - + 11x 11x - + 12x 12x - + 13x 13x - + 14x 14x - + 15x 15x - + 16x 16x - + Disabled - Deshabilitada + Deshabilitado - + %1x MSAA - MSAA %1x + %1x MSAA - + %1x SSAA - SSAA %1x + %1x SSAA GPUTextureFilter - + Nearest-Neighbor - Vecino más cercano + Punto más cercano - + Bilinear - Bilineal + Bilinear - + Bilinear (No Edge Blending) - Bilineal (sin unión de bordes) + Bilinear (sin unión de bordes) - - JINC2 - JINC2 + + JINC2 (Slow) + JINC2 (lento) - - JINC2 (No Edge Blending) - JINC2 (sin unión de bordes) + + JINC2 (Slow, No Edge Blending) + JINC2 (lento, sin unión de bordes) - - xBR - xBR + + xBR (Very Slow) + xBR (muy lento) - - xBR (No Edge Blending) - xBR (sin unión de bordes) + + xBR (Very Slow, No Edge Blending) + xBR (muy lento, sin unión de bordes) GameList - + Disc - + Disco - + PS-EXE - + PS-EXE - + Playlist - + Lista de reproducción - + PSF - + PSF + + + + Never + Nunca + + + + Today + Hoy + + + + Yesterday + Ayer + + + + {}h {}m + {}h {}m + + + + {}h {}m {}s + {}h {}m {}s + + + + {}m {}s + {}m {}s + + + + {}s + {}s + + + + None + Ninguno + + + + {} hours + {} horas + + + + {} minutes + {} minutos GameListCompatibilityRating - + Unknown Desconocido - + Doesn't Boot No inicia - + Crashes In Intro Cuelga al inicio - + Crashes In-Game Cuelga en juego - + Graphical/Audio Issues Problemas audiovisuales - + No Issues Sin problemas @@ -5476,62 +5342,72 @@ Achievements: %5 (%6) GameListModel - + Type Tipo - - Code - Código + + Serial + Número de serie - + Title Título - + File Title Título de archivo - + Developer Desarrollador - + Publisher - Distribuidor + Editor - + Genre Género - + Year Año - + Players Jugadores - + + Time Played + Tiempo jugado + + + + Last Played + Última partida + + + Size Tamaño - + Region Región - + Compatibility Compatibilidad @@ -5539,12 +5415,12 @@ Achievements: %5 (%6) GameListSearchDirectoriesModel - + Path - Ruta + Directorio - + Recursive Recursivo @@ -5554,30 +5430,30 @@ Achievements: %5 (%6) Form - Formulario + Form Search Directories (will be scanned for games) - Directorios donde buscar (sólo para juegos) + Buscar en directorios (se escanearán los juegos) Add - Agregar + Añadir - + Remove - Borrar + Eliminar Excluded Paths (will not be scanned) - Rutas excluidas (no serán escaneadas) + Excluir directorios (no se escanearán) @@ -5590,33 +5466,33 @@ Achievements: %5 (%6) Volver a buscar todos los juegos - + Open Directory... Abrir directorio... - + Select Search Directory - Seleccionar directorio de búsqueda + Seleccionar directorio para buscar - + Scan Recursively? - ¿Hacer una búsqueda recursiva? + ¿Buscar recursivamente? - + Would you like to scan the directory "%1" recursively? Scanning recursively takes more time, but will identify files in subdirectories. - ¿Desea hacer una búsqueda recursiva en el directorio «%1»? + ¿Quieres buscar en el directorio "%1" recursivamente? -La búsqueda recursiva lleva más tiempo, pero identificará los archivos que estén en subdirectorios. +Buscar recursivamente lleva más tiempo, pero identifica archivos en subdirectorios. - + Select Path - Seleccionar ruta + Seleccionar directorio @@ -5624,1602 +5500,1084 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es Form - Formulario + Form Game List - + Lista de juegos Game Grid - + Cuadrícula de juegos Show Titles - + Mostrar títulos All Types - + Todos los tipos All Regions - + Todas las regiones Search... - + Buscar... - GamePropertiesDialog + GameSettingsTrait - Dialog - Diálogo + + Force Interpreter + Forzar intérprete - Properties - Propiedades + + Force Software Renderer + Forzar renderizador por software - Image Path: - Ruta de imagen: + + Force Software Renderer For Readbacks + Forzar renderizador por software para lecturas - Game Code: - Código de juego: + + Force Interlacing + Forzar entrelazado - Title: - Título: + + Disable True Color + Deshabilitar color verdadero - Region: - Región: + + Disable Upscaling + Deshabilitar escalado de resolución - Compatibility: - Compatibilidad: + + Disable Scaled Dithering + Deshabilitar escalado de tramado - Upscaling Issues: - Problemas de escalado: + + Disallow Forcing NTSC Timings + Deshabilitar forzado de tiempos NTSC - Comments: - Comentarios: + + Disable Widescreen + Deshabilitar pantalla panorámica - Version Tested: - Versión testeada: + + Disable PGXP + Deshabilitar PGXP - Set to Current - Establecer actual + + Disable PGXP Culling + Deshabilitar culling (PGXP) - Tracks: - Pistas: + + Disable PGXP Perspective Correct Textures + Deshabilitar correción de perspectiva de texturas (PGXP) - # - N.º + + Disable PGXP Perspective Correct Colors + Deshabilitar correción de perspectiva de colores (PGXP) - Mode - Modo + + Disable PGXP Depth Buffer + Deshabilitar búfer de profundidad (PGXP) - Start - Inicio + + Force PGXP Vertex Cache + Forzar caché de vértices (PGXP) - Length - Duración + + Force PGXP CPU Mode + Forzar modo CPU (PGXP) - Hash - Hash + + Force Recompiler Memory Exceptions + Forzar excepciones de memoria del recompilador - User Settings (Console) - Configuración de usuario (consola) + + Force Recompiler ICache + Forzar ICache del recompilador - CPU Clock Speed Control - Velocidad de reloj de CPU + + Force Recompiler LUT Fastmem + Forzar memoria de acceso rápido LUT del recompilador + + + GameSummaryWidget - Enable Clock Speed Control (Overclocking/Underclocking) - Control de velocidad de reloj («overclocking»/«underclocking») + + Dialog + Diálogo - 100% (effective 33.3mhz) - 100 % (33.3 MHz reales) + + Image Path: + Dirección de imagen: - GPU Screen Display - Visualización de imagen de la GPU + + Serial: + Número de serie: - Aspect Ratio: - Relación de aspecto: + + # + # - Crop Mode: - Modo de recorte: + + Mode + Modo - Linear Upscaling - Escalado lineal + + Start + Inicio - Integer Upscaling - Escalado por números enteros + + Length + Duración - GPU Enhancements - Mejoras de GPU + + Hash + Suma de verificación - Resolution Scale: - Escala de resolución: + + Status + Estado - Multisample Antialiasing: - Antialiasing de muestreo múltiple: + + Region: + Revisión: - Texture Filtering: - Filtrado de texturas: + + Developer: + Desarrollador: - True Color Rendering (24-bit, disables dithering) - Renderizado a color verdadero (24 bits, deshabilita el tramado) + + Controllers: + Controles: - Scaled Dithering (scale dither pattern to resolution) - Escalado de tramado (escalar patrón de tramado a resolución) + + Tracks: + Pistas: - Widescreen Hack - Hack para pantallas panorámicas + + Release Info: + Información de lanzamiento: - Force NTSC Timings (60hz-on-PAL) - Forzar velocidad NTSC (60 Hz en PAL) + + Input Profile: + Perfil de entrada: - Enable 8MB RAM (Dev Console) - Habilitar 8 MB de RAM (consola de desarrollo) + + Genre: + Género: - : - : + + Compute Hashes... + Calcular sumas de verificación... - Downsampling: - Submuestreo: + + Type: + Tipo: - Renderer: - Renderizador: - + + Title: + Título: + - Force 4:3 For 24-Bit Display (disable widescreen for FMVs) - Forzar 4:3 para imágenes a 24 bits (deshabilitar imagen panorámica en FMVs) + + Compatibility: + Compatibilidad: - PGXP Geometry Correction - Correción de geometría de la PGXP + + Edit... + Editar... - PGXP Preserve Projection Precision - Conservar precisión de proyección de la PGXP + + + + + + + + + + Unknown + Desconocido - PGXP Depth Buffer - Búfer de profundidad de la PGXP + + %1 (Published by %2) + %1 (distribuido por %2) - Other Settings - Otras opciones + + Published by %1 + Distribuido por %1 - Status - Estado + + Released %1 + Publicado el %1 - Revision: - Revisión: + + %1-%2 players + %1-%2 jugadores - CD-ROM Read Speedup: - Aceleración de lectura de CD-ROM: + + %1 players + %1 jugadores - (unchanged) - (sin cambios) + + %1-%2 memory card blocks + %1-%2 bloques de tarjeta de memoria - None (Double Speed) - Nula (velocidad doble) + + %1 memory card blocks + %1 bloques de tarjeta de memoria - 2x (Quad Speed) - 2x (velocidad x4) + + Use Global Settings + Utilizar configuración global - 3x (6x Speed) - 3x (velocidad 6x) + + Track %1 + Pista %1 - 4x (8x Speed) - 4x (velocidad 8x) + + <not computed> + <sin calcular> - 5x (10x Speed) - 5x (velocidad 10x) + + Error + Error - 6x (12x Speed) - 6x (velocidad 12x) + + Failed to open CD image for hashing. + Fallo al abrir la imagen de CD para calcular la suma de verificación. - 7x (14x Speed) - 7x (velocidad 14x) + + Revision: %1 + Revisión: %1 - 8x (16x Speed) - 8x (velocidad 16x) + + N/A + No disponible - 9x (18x Speed) - 9x (velocidad 18x) + + Search on Redump.org + Buscar en Redump.org + + + GeneralSettingsWidget - 10x (20x Speed) - 10x (velocidad 20x) + + Form + Form - CD-ROM Seek Speedup: - Aceleración de búsqueda de CD-ROM: + + Behaviour + Comportamiento - Infinite/Instantaneous - Infinita/Instantánea + + + Automatically Load Cheats + Cargar trucos automáticamente - None (Normal Speed) - Nula (velocidad normal) + + + Confirm Power Off + Confirmar apagado - 2x - 2x + + Save State On Shutdown + Guardar estado al apagar - 3x - 3x + + + Pause On Focus Loss + Pausar al perder el foco - 4x - 4x + + + Apply Per-Game Settings + Configuraciones por juego - 5x - 5x + + Create Save State Backups + Crear copias de seguridad de los estados guardados - 6x - 6x + + + Inhibit Screensaver + Deshabilitar salvapantallas - 7x - 7x + + + Load Devices From Save States + Cargar dispositivos en estados guardados - 8x - 8x + + + Pause On Start + Pausar al inicio - 9x - 9x + + + Enable Discord Presence + Habilitar Discord Presence - 10x - 10x + + Compress Save States + Comprimir estados guardados - Runahead Frames: - Fotogramas para predecir latencia: + + Game Display + Pantalla - Disabled - Deshabilitado + + + Start Fullscreen + Iniciar en pantalla completa - 1 Frame - 1 fotograma + + Double-Click Toggles Fullscreen + Doble clic para pantalla completa - 2 Frames - 2 fotogramas + + + Render To Separate Window + Renderizar en la ventana principal - 3 Frames - 3 fotogramas + + Hide Main Window When Running + Ocultar ventana principal al ejecutar - 4 Frames - 4 fotogramas + + Disable Window Resizing + Deshabilitar cambio de tamaño de la ventana - 5 Frames - 5 fotogramas + + + Hide Cursor In Fullscreen + Ocultar cursor en pantalla completa - 6 Frames - 6 fotogramas + + Automatic Updater + Actualizador automático - 7 Frames - 7 fotogramas + + Update Channel: + Canal de actualización: - 8 Frames - 8 fotogramas + + Current Version: + Versión actual: - 9 Frames - 9 fotogramas + + + Enable Automatic Update Check + Habilitar comprobación automática de actualizaciones - 10 Frames - 10 fotogramas + + Check for Updates... + Buscar actualizaciones... - User Settings (Graphics) - Configuración de usuario (gráficos) + + + + + + + + Checked + Habilitado - User Settings (Input) - Configuración de usuario (entrada) + + Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. + Determina si se mostrará un mensaje de confirmación para cerrar el emulador/juego al presionar un atajo. - Controller Settings - Configuración de controles + + Save State On Exit + Guardar estado al salir - Controller 1 Type: - Tipo del control 1: + + Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. + Guarda automáticamente el estado del emulador al apagar o salir. Después podrás continuar desde donde dejaste la última vez. - Controller 2 Type: - Tipo del control 2: + + + + + + + Unchecked + Deshabilitado - Input Profile For Bindings: - Perfil de control: + + Automatically switches to fullscreen mode when a game is started. + Cambia automáticamente a modo en pantalla completa cuando se inicia un juego. - Multitap Mode: - Modo de multitap: + + Hides the mouse pointer/cursor when the emulator is in fullscreen mode. + Oculta el cursor/puntero del mouse cuando el emulador está en modo de pantalla completa. - Memory Card Settings - Configuración de tarjetas de memoria + + Prevents the screen saver from activating and the host from sleeping while emulation is running. + Evita que se active el salvapantallas y se suspenda el sistema cuando el emulador se esté ejecutando. - Memory Card 1 Type: - Tipo de la ranura 1: + + Renders the display of the simulated console to the main window of the application, over the game list. If checked, the display will render in a separate window. + Renderiza la imagen de la consola emulada en la ventana principal de la aplicación, sobre la lista de juegos. Si se habilita, la imagen se renderizará en una ventana separada. - Memory Card 1 Shared Path: - Directorio de memoria 1: + + Pauses the emulator when a game is started. + Pausa el emulador cuando se inicia un juego. - Browse... - Buscar... + + Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back. + Pausa el emulador cuando se minimiza la ventana o se cambia a otra aplicación, y resume cuando se vuelve al emulador. - Memory Card 2 Type: - Tipo de la ranura 2: + + When enabled, memory cards and controllers will be overwritten when save states are loaded. This can result in lost saves, and controller type mismatches. For deterministic save states, enable this option, otherwise leave disabled. + Cuando se habilita, los controles y tarjetas de memoria se sobrescribirán cuando se carguen estados guardados. Esto puede resultar en pérdida de datos guardados e incoherencias con el tipo de control. Habilitar esta opción para estados guardados consistentes. - Memory Card 2 Shared Path: - Directorio de memoria 2: + + When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should leave this option enabled except when testing enhancements with incompatible games. + Cuando se habilita se aplicarán las configuraciones por juego, y se desactivarán las mejoras incompatibles. Es recomendable dejar esta opción activada, a menos que quieras probar mejoras con juegos incompatibles. - Compatibility Settings - Configuración de compatibilidad + + Automatically loads and applies cheats on game start. + Carga y aplica los trucos automáticamente al iniciar el juego. - Traits - Características + + Shows the game you are currently playing as part of your profile in Discord. + Muestra el juego que estás jugando actualmente en tu perfil de Discord. - Overrides - Reemplazos en la configuración + + Automatically checks for updates to the program on startup. Updates can be deferred until later or skipped entirely. + Busca actualizaciones automáticamente al iniciar el programa. Pueden posponerse para más adelante o directamente omitirse. - Display Active Offset: - Mostrar offset activo: + + %1 (%2) + %1 (%2) + + + GunCon - Display Line Offset: - Mostrar offset de línea: + + Crosshair Image Path + Directorio para la imagen de mira - DMA Max Slice Ticks: - Duración máxima de los cortes de la DMA: + + Path to an image to use as a crosshair/cursor. + Seleccionar una imagen para utilizar como punto de mira/cursor. - DMA Halt Ticks: - Duración de las paradas de la DMA: + + Crosshair Image Scale + Escala para la imagen de mira - GPU FIFO Size: - Tamaño del FIFO de la GPU: + + Scale of crosshair image on screen. + Escala para la imagen de mira en pantalla. - GPU Max Run Ahead: - Predicción máxima de la GPU: + + X Scale + Escala X - PGXP Geometry Tolerance: - Tolerancia geométrica de la PGXP: + + Scales X coordinates relative to the center of the screen. + Escala coordenadas en el eje X de forma relativa al centro de la pantalla. + + + HostInterface - PGXP Depth Threshold: - Umbral de limpieza de profundidad de la PGXP: + + Failed to load configured BIOS file '%s' + Fallo al cargar el BIOS "%s" configurado - Compute Hashes - Calcular Hashes + + No BIOS image found for %s region + No se encontró una imagen BIOS para la región %s + + + Hotkeys - Verify Dump - Verificar volcado + + + + + + + + + + + + General + General - Export Compatibility Info - Exportar información de compatibilidad + + Open Pause Menu + Abrir menú rápido - Close - Cerrar + + Fast Forward + Avance rápido - Game Properties - %1 - Propiedades - %1 + + Toggle Fast Forward + Alternar avance rápido - <not verified> - <no verificado> + + Turbo + Turbo - Compute && Verify Hashes - Calcular y verificar «hashes» + + Toggle Turbo + Alternar turbo - <not computed> - <sin calcular> + + Toggle Fullscreen + Alternar pantalla completa - Select path to memory card image - Seleccionar directorio para imagen de tarjeta de memoria + + Toggle Pause + Alternar pausa - %1% (%2MHz) - %1% (%2 MHz) + + Power Off System + Apagar sistema - Search on Redump.org - Buscar en Redump.org + + Save Screenshot + Capturar pantalla - Not yet implemented - Sin implementar + + Open Achievement List + Abrir lista de logros - Compatibility Info Export - Exportar información de compatibilidad + + Open Leaderboard List + Abrir tabla de posiciones - Press OK to copy to clipboard. - Presiona «Aceptar» para copiar al portapapeles. + + + + + + + + + + + + System + Sistema - - - GameSettingsTrait - - Force Interpreter - Forzar intérprete + + Reset System + Reiniciar sistema - - Force Software Renderer - Forzar renderizado por software + + Change Disc + Cambiar disco - - Force Software Renderer For Readbacks - Forzar renderizado por software para relecturas + + Swap Memory Card Slots + Intercambiar ranuras de memoria - - Force Interlacing - Forzar entrelazado + + Frame Step + Avanzar fotograma - - Disable True Color - Deshabilitar color verdadero + + Rewind + Rebobinar - - Disable Upscaling - Deshabilitar escalado de resolución + + Toggle Cheats + Alternar trucos - - Disable Scaled Dithering - Deshabilitar escalado de tramado + + Toggle Patch Codes + Alternar parches - - Disallow Forcing NTSC Timings - Deshabilitar forzado de velocidad NTSC + + Toggle Clock Speed Control (Overclocking) + Alternar control de velocidad de reloj (overclock) - - Disable Widescreen - Deshabilitar pantalla panorámica - - - - Disable PGXP - Deshabilitar PGXP - - - - Disable PGXP Culling - Deshabilitar «culling» de la PGXP - - - - Disable PGXP Texture Correction - Deshabilitar corrección de texturas de la PGXP - - - - Disable PGXP Depth Buffer - Deshabilitar búfer de profundidad de la PGXP - - - - Force PGXP Vertex Cache - Forzar caché de vértices de la PGXP - - - - Force PGXP CPU Mode - Forzar modo CPU de la PGXP - - - - Force Recompiler LUT Fastmem - Forzar fastmem LUT del recompilador - - - - Force Recompiler Memory Exceptions - Forzar excepciones de memoria del recompilador - - - - Force Recompiler ICache - Forzar ICaché del recompilador - - - - GameSummaryWidget - - - Dialog - Diálogo - - - - Image Path: - Ruta de imagen: - - - - Serial: - - - - - # - - - - - Mode - Modo - - - - Start - - - - - Length - Duración - - - - Hash - Hash - - - - Status - Estado - - - - Region: - Región: - - - - Developer: - - - - - Controllers: - - - - - Tracks: - Pistas: - - - - Release Info: - - - - - Input Profile: - - - - - Genre: - - - - - Compute Hashes... - - - - - Type: - Tipo: - - - - Title: - Título: - - - - Compatibility: - Compatibilidad: - - - - Edit... - - - - - - - - - - - - - Unknown - Desconocido - - - - %1 (Published by %2) - - - - - Published by %1 - - - - - Released %1 - - - - - %1-%2 players - - - - - %1 players - - - - - %1-%2 memory card blocks - - - - - %1 memory card blocks - - - - - Use Global Settings - - - - - Track %1 - - - - - <not computed> - <sin calcular> - - - - Error - Error - - - - Failed to open CD image for hashing. - - - - - Revision: %1 - - - - - N/A - - - - - Search on Redump.org - Buscar en Redump.org - - - - GeneralSettingsWidget - - - Form - Formulario - - - - Behaviour - Comportamiento - - - - Save State On Shutdown - - - - - Create Save State Backups - Crear copias de seguridad de los estados guardados - - - - - Pause On Start - Pausar al inicio - - - - - Confirm Power Off - Confirmar apagado - - - - - Inhibit Screensaver - Inhibir salvapantallas - - - - - Pause On Focus Loss - Pausar al pasar a segundo plano - - - - Save State On Exit - Guardar estado al salir - - - - - Apply Per-Game Settings - Configuraciones por juego - - - - - Automatically Load Cheats - Cargar trucos automáticamente - - - - - Load Devices From Save States - Cargar dispositivos de estados guardados - - - - Compress Save States - - - - - Game Display - - - - - Double-Click Toggles Fullscreen - - - - - - Render To Separate Window - - - - - Hide Main Window When Running - - - - - Disable Window Resizing - - - - - - Hide Cursor In Fullscreen - Ocultar cursor en pantalla completa - - - Enable Fullscreen UI - Interfaz para pantalla completa - - - - Automatic Updater - Actualizador automático - - - - Update Channel: - Canal de actualización: - - - - Current Version: - Versión actual: - - - - Check for Updates... - Buscar actualizaciones... - - - Miscellaneous - Varios - - - Controller Backend: - Motor para controles: - - - - - Start Fullscreen - Iniciar a pantalla completa - - - Render To Main Window - Renderizar en la ventana principal - - - - - - - - - - Checked - Habilitado - - - - Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. - Determina si se mostrará un mensaje de confirmación para cerrar el emulador/juego cuando se presione el atajo. - - - - Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. - Guarda automáticamente el estado del emulador al apagar o salir. Después podrá retomar la partida donde la dejara. - - - - - - - - - Unchecked - Deshabilitado - - - - Automatically switches to fullscreen mode when a game is started. - Cambia automáticamente al modo a pantalla completa cuando se inicia un juego. - - - - Hides the mouse pointer/cursor when the emulator is in fullscreen mode. - Oculta el puntero/cursor del mouse cuando el emulador esté en el modo a pantalla completa. - - - - Prevents the screen saver from activating and the host from sleeping while emulation is running. - Evita que el protector de pantalla se active y que el PC entre en suspensión mientras el emulador se esté ejecutando. - - - - Renders the display of the simulated console to the main window of the application, over the game list. If checked, the display will render in a separate window. - - - - Renders the display of the simulated console to the main window of the application, over the game list. If unchecked, the display will render in a separate window. - Renderiza la imagen de la consola emulada en la ventana principal de la aplicación, sobre la lista de juegos. Al deshabilitar esta opción, la imagen se renderizará en una ventana aparte. - - - - Pauses the emulator when a game is started. - Pausa el emulador cuando se inicie un juego. - - - - Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back. - Pausa el emulador cuando se minimice la ventana o se cambie a otra aplicación y lo reanuda cuando se vuelva al emulador. - - - - When enabled, memory cards and controllers will be overwritten when save states are loaded. This can result in lost saves, and controller type mismatches. For deterministic save states, enable this option, otherwise leave disabled. - Al habilitar esta opción, los controles y tarjetas de memoria se sobrescribirán cuando se carguen estados guardados. Puede resultar en la pérdida de datos guardados e incoherencias con el tipo de control. Habilitar esta opción para producir estados guardados deterministas. - - - - When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should leave this option enabled except when testing enhancements with incompatible games. - Al habilitar esta opción, se aplicarán las configuraciones según cada juego se desactivarán las mejoras que no sean compatibles. Es recomendable dejar esta opción activada, a menos que quieras probar mejoras con juegos incompatibles. - - - - Automatically loads and applies cheats on game start. - Carga y aplica trucos automáticamente al inicio del juego. - - - Enables the fullscreen UI mode, suitable for controller operation which is used in the NoGUI frontend. - Habilita el modo de interfaz a pantalla completa, ideal para controlar la interfaz NoGUI con los controles. - - - - %1 (%2) - %1 (%2) - - - Controller Backend - Motor para controles - - - Determines the backend which is used for controller input. Windows users may prefer to use XInput over SDL2 for compatibility. - Determina el motor que se va a usar para la entrada de los controles. Los usuarios de Windows querrán usar XInput por encima de SDL2 por motivos de compatibilidad. - - - - - Enable Discord Presence - Mostrar estado en Discord - - - - Shows the game you are currently playing as part of your profile in Discord. - Muestra el juego al que estés jugando actualmente en tu perfil de Discord. - - - - - Enable Automatic Update Check - Chequear actualizaciones automáticamente - - - - Automatically checks for updates to the program on startup. Updates can be deferred until later or skipped entirely. - Busca actualizaciones automáticamente al iniciar el programa. Éstas pueden posponerse para más adelante o directamente evitarse. - - - Unlimited - Sin límite - - - %1% [%2 FPS (NTSC) / %3 FPS (PAL)] - %1 % [%2 FPS (NTSC) / %3 FPS (PAL)] - - - - GunCon - - - Crosshair Image Path - Ruta para la imagen de mira - - - - Path to an image to use as a crosshair/cursor. - La ruta de una imagen que servirá como punto de mira/cursor. - - - - Crosshair Image Scale - Escala de la imagen de mira - - - - Scale of crosshair image on screen. - La escala en pantalla de la imagen de mira. - - - - X Scale - Escala X - - - - Scales X coordinates relative to the center of the screen. - Escala las coordenadas X relativas al centro de la pantalla. - - - - HostInterface - - - Failed to load configured BIOS file '%s' - Fallo al cargar la BIOS configurada «%s» - - - - No BIOS image found for %s region - No se encontró una imagen de BIOS para la región %s - - - - Hotkeys - - - - - - - - - - - - - General - General - - - Open Quick Menu - Abrir menú rápido - - - - Fast Forward - Avance rápido - - - - Toggle Fast Forward - Alternar avance rápido - - - - Turbo - Turbo - - - - Toggle Turbo - Alternar turbo - - - - Toggle Fullscreen - Alternar pantalla completa - - - - Toggle Pause - Alternar pausa - - - - Toggle Cheats - Alternar trucos - - - - Power Off System - Apagar sistema - - - - Toggle Patch Codes - Alternar códigos de parche - - - - Reset System - Reiniciar sistema - - - - Save Screenshot - Capturar pantalla - - - - Change Disc - Cambiar disco - - - - Frame Step - Avanzar fotograma + + Increase Emulation Speed + Incrementar velocidad de emulación - - Rewind - Rebobinar + + Decrease Emulation Speed + Decrementar velocidad de emulación - - Toggle Clock Speed Control (Overclocking) - Alternar control de velocidad de reloj («overclocking») + + Reset Emulation Speed + Reiniciar velocidad de emulación - - - - - - - - - - + + + + + + + + + + Graphics Gráficos - + Toggle Software Rendering Alternar renderizado por software - + Toggle PGXP Alternar PGXP - - Toggle PGXP Depth Buffer - Alternar búfer de profundidad de la PGXP - - - + Increase Resolution Scale Incrementar escala de resolución - - Open Pause Menu - - - - - Open Achievement List - Abrir lista de logros + + Decrease Resolution Scale + Decrementar escala de resolución - - Open Leaderboard List - Abrir tabla de clasificación - - - - - - - - - - - - - - System - Sistema + + Toggle Post-Processing + Alternar postprocesamiento - - Swap Memory Card Slots - Cambiar ranuras de la tarjeta de memoria + + Reload Post Processing Shaders + Recargar sombreadores de postprocesamiento - - Increase Emulation Speed - Incrementar velocidad de emulación + + Reload Texture Replacements + Recargar reemplazos de textura - - Decrease Emulation Speed - Disminuir velocidad de emulación + + Toggle Widescreen + Alternar pantalla panorámica - - Reset Emulation Speed - Reiniciar velocidad de emulación + + Toggle PGXP Depth Buffer + Alternar búfer de profundidad (PGXP) - - Decrease Resolution Scale - Disminuir escala de resolución + + Toggle PGXP CPU Mode + Alternar modo CPU (PGXP) - - Toggle Post-Processing - Activar posprocesamiento + + + + + Audio + Audio - - Reload Post Processing Shaders - Recargar sombreadores de posprocesamiento + + Toggle Mute + Alternar silencio - - Reload Texture Replacements - Recargar reemplazos de textura + + Toggle CD Audio Mute + Alternar silencio de audio de CD - - Toggle Widescreen - Alternar pantalla panorámica + + Volume Up + Subir volumen - - Toggle PGXP CPU Mode - Alternar modo CPU de la PGXP + + Volume Down + Bajar volumen - - - - - - - + + + + + + + Save States Estados guardados - + Load From Selected Slot - Cargar ranura seleccionada + Cargar de la ranura seleccionada - + Save To Selected Slot - Guardar en ranura seleccionada + Guardar en la ranura seleccionada - + Select Previous Save Slot Seleccionar ranura de guardado anterior - + Select Next Save Slot Seleccionar ranura de guardado siguiente - + Undo Load State - Deshacer carga de estado + Deshacer estado cargado - + Load Game State 1 Cargar estado de juego 1 - - Load Game State 2 - Cargar estado de juego 2 - - - - Load Game State 3 - Cargar estado de juego 3 - - - - Load Game State 4 - Cargar estado de juego 4 - - - - Load Game State 5 - Cargar estado de juego 5 - - - - Load Game State 6 - Cargar estado de juego 6 - - - - Load Game State 7 - Cargar estado de juego 7 - - - - Load Game State 8 - Cargar estado de juego 8 - - - - Load Game State 9 - Cargar estado de juego 9 - - - - Load Game State 10 - Cargar estado de juego 10 - - - + Save Game State 1 Guardar estado de juego 1 - + + Load Game State 2 + Cargar estado de juego 2 + + + Save Game State 2 Guardar estado de juego 2 - + + Load Game State 3 + Cargar estado de juego 3 + + + Save Game State 3 Guardar estado de juego 3 - + + Load Game State 4 + Cargar estado de juego 4 + + + Save Game State 4 Guardar estado de juego 4 - + + Load Game State 5 + Cargar estado de juego 5 + + + Save Game State 5 Guardar estado de juego 5 - + + Load Game State 6 + Cargar estado de juego 6 + + + Save Game State 6 Guardar estado de juego 6 - + + Load Game State 7 + Cargar estado de juego 7 + + + Save Game State 7 Guardar estado de juego 7 - + + Load Game State 8 + Cargar estado de juego 8 + + + Save Game State 8 Guardar estado de juego 8 - + + Load Game State 9 + Cargar estado de juego 9 + + + Save Game State 9 Guardar estado de juego 9 - + + Load Game State 10 + Cargar estado de juego 10 + + + Save Game State 10 Guardar estado de juego 10 - + Load Global State 1 Cargar estado global 1 - - Load Global State 2 - Cargar estado global 2 - - - - Load Global State 3 - Cargar estado global 3 - - - - Load Global State 4 - Cargar estado global 4 - - - - Load Global State 5 - Cargar estado global 5 - - - - Load Global State 6 - Cargar estado global 6 - - - - Load Global State 7 - Cargar estado global 7 - - - - Load Global State 8 - Cargar estado global 8 - - - - Load Global State 9 - Cargar estado global 9 - - - - Load Global State 10 - Cargar estado global 10 - - - + Save Global State 1 Guardar estado global 1 - + + Load Global State 2 + Cargar estado global 2 + + + Save Global State 2 Guardar estado global 2 - + + Load Global State 3 + Cargar estado global 3 + + + Save Global State 3 Guardar estado global 3 - + + Load Global State 4 + Cargar estado global 4 + + + Save Global State 4 Guardar estado global 4 - + + Load Global State 5 + Cargar estado global 5 + + + Save Global State 5 Guardar estado global 5 - - Save Global State 6 - Guardar estado global 6 + + Load Global State 6 + Cargar estado global 6 - - Save Global State 7 - Guardar estado global 7 + + Save Global State 6 + Guardar estado global 6 - - Save Global State 8 - Guardar estado global 8 + + Load Global State 7 + Cargar estado global 7 - - Save Global State 9 - Guardar estado global 9 + + Save Global State 7 + Guardar estado global 7 - - Save Global State 10 - Guardar estado global 10 + + Load Global State 8 + Cargar estado global 8 - - - - - Audio - Audio + + Save Global State 8 + Guardar estado global 8 - - Toggle Mute - Alternar silenciado de audio + + Load Global State 9 + Cargar estado global 9 - - Toggle CD Audio Mute - Alternar silenciado de audio CD + + Save Global State 9 + Guardar estado global 9 - - Volume Up - Subir volumen + + Load Global State 10 + Cargar estado global 10 - - Volume Down - Bajar volumen + + Save Global State 10 + Guardar estado global 10 @@ -7232,36 +6590,36 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es Bindings for Controller0/ButtonCircle - Asignaciones para control 0/botón Círculo + Asignaciones para Controller0/ButtonCircle Add Binding - Agregar asignación + Añadir asignación Remove Binding - Quitar asignación + Eliminar asignación Clear Bindings - Borrar asignaciones + Limpiar asignaciones - + Bindings for %1 %2 Asignaciones para %1 %2 - + Close Cerrar - - + + Push Button/Axis... [%1] Presiona un botón/eje... [%1] @@ -7269,7 +6627,7 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es InputBindingWidget - + %n bindings %n asignación @@ -7277,8 +6635,8 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es - - + + Push Button/Axis... [%1] Presiona un botón/eje... [%1] @@ -7286,72 +6644,72 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es InputVibrationBindingWidget - + Error - Error + Error - + No devices with vibration motors were detected. - + No se detectaron dispositivos con motores de vibración. - + Select vibration motor for %1. - + Selecciona el motor de vibración para %1. LogLevel - + None Ninguno - + Error Error - + Warning Alerta - + Performance Rendimiento - + Information Información - + + Verbose + Detalle + + + Developer Desarrollador - + Profile Perfil - - Verbose - Detalles - - - + Debug Depuración - + Trace - Seguimiento + Rastreo @@ -7361,17 +6719,27 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es DuckStation DuckStation + + + &System + &Sistema + - - + + Change Disc Cambiar disco + + + Cheats + Trucos + - - + + Load State Cargar estado @@ -7391,967 +6759,924 @@ La búsqueda recursiva lleva más tiempo, pero identificará los archivos que es Tema - + Language Idioma - + &Help &Ayuda - + &Debug &Depuración - + Switch GPU Renderer Cambiar renderizador de GPU - + Switch CPU Emulation Mode - Cambiar modo de emulación de CPU + Cambiar modo de emulación de GPU + + + + Switch Crop Mode + Cambiar modo de recorte + + + + &View + &Vista + + + + &Window Size + &Tamaño de ventana + + + + &Tools + &Herramientas - + toolBar - Barra de herramientas + toolBar + + + + Start &File... + Iniciar &archivo... - + Start &Disc... Iniciar &disco... - + Start &BIOS Iniciar &BIOS - + &Scan For New Games &Buscar juegos nuevos - + &Rescan All Games &Volver a buscar todos los juegos - + Power &Off &Apagar - + &Reset &Reiniciar - + &Pause &Pausar - + &Load State &Cargar estado - + &Save State &Guardar estado - + E&xit &Salir - C&onsole Settings... - Configuración de c&onsola... - - - &Controller Settings... - &Configuración de controles... - - - &Hotkey Settings... - Configuración de &atajos... - - - - Fullscreen - Pantalla completa - - - - Resolution Scale - Escala de resolución - - - - &GitHub Repository... - Repositorio en &GitHub... - - - - &Issue Tracker... - &Gestor de problemas... - - - - &Discord Server... - Servidor de &Discord... + + B&IOS + &BIOS - - Check for &Updates... - Buscar a&ctualizaciones... + + C&onsole + &Consola - - &System - &Sistema + + E&mulation + &Emulación - - Cheats - Trucos + + &Controllers + C&ontroles - - Switch Crop Mode - Cambiar modo de recorte + + &Hotkeys + &Atajos - - &View - &Vista + + &Display + &Pantalla - - &Window Size - &Tamaño de ventana + + &Enhancements + &Mejoras - - &Tools - &Herramientas + + &Post-Processing + &Postprocesamiento - - Start &File... - Iniciar &archivo... + + Fullscreen + Pantalla completa - B&IOS Settings... - Configuración de B&IOS... + + Resolution Scale + Escala de resolución - E&mulation Settings... - Configuración de e&mulación... + + &GitHub Repository... + Repositorio en &GitHub... - &Display Settings... - Configuración &de pantalla... + + &Issue Tracker... + &Rastreador de problemas... - &Enhancement Settings... - Configuración de me&joras... + + &Discord Server... + Servidor de &Discord... - &Post-Processing Settings... - Configuración de &posprocesamiento... + + Check for &Updates... + Buscar &actualizaciones... - + About &Qt... Acerca de &Qt... - + &About DuckStation... - &Acerca de DuckStation... + Acerca de &DuckStation... - + Change Disc... Cambiar disco... - + Cheats... Trucos... - Audio Settings... - Configuración de audio... - - - Achievement Settings... - Configuración de logros... - - - Game List Settings... - Configuración de lista de juegos... - - - General Settings... - Configuración general... - - - Advanced Settings... - Configuración avanzada... - - - - B&IOS - - - - - C&onsole - - - - - E&mulation - - - - - &Controllers - - - - - &Hotkeys - - - - - &Display - - - - - &Enhancements - - - - - &Post-Processing - - - - + Audio - Audio + Audio - + Achievements - + Logros - + Folders - + Carpetas - + Game List - + Lista de juegos - + General - General + General - + Advanced - + Avanzada - + Add Game Directory... - Agregar directorio de juegos... + Añadir directorio de juegos... - - &Settings... - &Configuración... + + + &Settings + &Configuración - + From File... Desde archivo... - + From Device... Desde dispositivo... - + From Game List... Desde lista de juegos... - + Remove Disc Quitar disco - + Resume State - Reanudar estado + Resumir estado - + Global State Estado global - + Show VRAM Mostrar VRAM - + Dump CPU to VRAM Copies Volcar copias de CPU a VRAM - + Dump VRAM to CPU Copies Volcar copias de VRAM a CPU - + Disable All Enhancements - Desactivar todas las mejoras + Deshabilitar todas las mejoras - + Disable Interlacing Deshabilitar entrelazado - + Force NTSC Timings - Forzar velocidad NTSC + Forzar tiempos NTSC - + Dump Audio Volcar audio - + Dump RAM... Volcar RAM... - + Dump VRAM... Volcar VRAM... - + Dump SPU RAM... - Volcar RAM de la SPU... + Volcar RAM de SPU... - + Show GPU State - Mostrar estado de GPU + Mostrar estado de la GPU - + Show CD-ROM State Mostrar estado del CD-ROM - + Show SPU State - Mostrar estado de SPU + Mostrar estado de la SPU - + Show Timers State Mostrar estado de los temporizadores - + Show MDEC State Mostrar estado del MDEC - + Show DMA State - Mostrar estado del DMA + Mostrar estado de la DMA - + &Screenshot - Cap&tura + &Captura - + &Memory Cards - - - - - Start Big Picture Mode - - - - - Big Picture - - - - &Memory Card Settings... - Configuración de tar&jetas de memoria... + &Tarjetas de memoria - - + + Resume - Continuar + Resumir - + Resumes the last save state created. - Continúa desde el último estado guardado. + Continúa desde el último estado guardado creado. - + &Toolbar Barra de &herramientas - + Lock Toolbar Bloquear barra de herramientas - + &Status Bar Barra de &estado - + Game &List &Lista de juegos - + System &Display - &Ventana del sistema + &Salida del sistema - + Game &Properties &Propiedades del juego - + Memory &Card Editor &Editor de tarjetas de memoria - + C&heat Manager &Administrador de trucos - + CPU D&ebugger - D&epurador de CPU + Depurador de &CPU + + + + Enable GDB server + Habilitar servidor GDB - + Game &Grid &Cuadrícula de juegos - + Show Titles (Grid View) Mostrar títulos (vista en cuadrícula) - + Zoom &In (Grid View) - &Aumentar zoom (vista en cuadrícula) + &Aumentar tamaño (vista en cuadrícula) - + Ctrl++ Ctrl++ - + Zoom &Out (Grid View) - &Disminuir zoom (vista en cuadrícula) + &Disminuir tamaño (vista en cuadrícula) - + Ctrl+- Ctrl+- - + Refresh &Covers (Grid View) - Actuali&zar carátulas (vista en cuadrícula) + Actualizar &portadas (vista en cuadrícula) - + Open Memory Card Directory... Abrir directorio de tarjetas de memoria... - + Open Data Directory... Abrir directorio de datos... - + Power Off &Without Saving - Apagar &sin guardar + &Apagar sin guardar - All File Types (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.psf *.minipsf *.m3u);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp);;PlayStation Executables (*.exe *.psexe);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u) - Todos los archivos (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.exe *.psexe *.psf *.minipsf *.m3u);;Imágenes de pista única (*.bin *.img *.iso);;Archivos CUE (*.cue);;Imágenes CHD de MAME(*.chd));;Archivo ECM (*.ecm);;Archivo MDS (*.mds);;EBOOT de PlayStation (*.pbp);;Ejecutables de PlayStation (*.exe *.psexe);;Archivos PSF (*.psf *.minipsf);;Listas de reproducción (*.m3u) + + Start Big Picture Mode + Iniciar modo Big Picture - Failed to create host display. - Fallo al crear la visualización del sistema. + + Big Picture + Big Picture - - Failed to create host display device context. - Fallo al crear el contexto del dispositivo de vídeo del sistema. + + Cover Downloader + Descargador de portadas - - - Select Disc Image - Seleccionar imagen de disco + + All File Types (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psf *.minipsf *.m3u);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.exe *.psexe *.ps-exe);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u) + Todos los archivos (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.psf *.minipsf *.m3u);;Imágenes de pista única (*.bin *.img *.iso);;Archivos CUE (*.cue);;Imágenes CHD de MAME(*.chd);;Imágenes Error Code Modeler (*.ecm);;Imágenes Media Descriptor Sidecar (*.mds);;PlayStation EBOOTs (*.pbp);;PlayStation Executables (*.exe *.psexe *.ps-exe);;Portable Sound Format (*.psf *.minipsf);;Listas de reproducción (*.m3u) - - Could not find any CD-ROM devices. Please ensure you have a CD-ROM drive connected and sufficient permissions to access it. - No se encontró ningún dispositivo de CD-ROM. Asegúrese de tener una unidad de CD-ROM conectada y los permisos necesarios de acceso. + + + + + + Error + Error - - %1 (%2) - %1 (%2) + + Failed to get window info from widget + Fallo al obtener información de la ventana del widget - - Select disc drive: - Seleccione la unidad de disco: + + Failed to create host display device context. + Fallo al crear el contexto del dispositivo de visualización del sistema. - - Start Disc - Iniciar disco + + Failed to get new window info from widget + Fallo al obtener información de la nueva ventana del widget - - - Cheat Manager - Administrador de trucos + + Paused + En pausa - Could not find a game list entry for the currently running file. Please make sure this file is in a location scanned by the game list. - No se encontró una entrada en la lista de juegos para el archivo que se está ejecutando. Asegúrese de que este archivo se encuentre en una ubicación reconocida por la lista de juegos. + + + Select Disc Image + Seleccionar imagen de disco - - Properties... - Propiedades... + + Could not find any CD-ROM devices. Please ensure you have a CD-ROM drive connected and sufficient permissions to access it. + No se pudo encontrar ningún dispositivo de CD-ROM. Asegúrate de que tienes una unidad de CD-ROM conectada y los permisos de acceso necesarios. - - Open Containing Directory... - Abrir directorio contenedor... + + %1 (%2) + %1 (%2) - - Set Cover Image... - Establecer imágen de carátula... + + Select disc drive: + Seleccionar unidad de disco: - - Default Boot - Inicio predeterminado + + Resume (%1) + Continuar (%1) - - Fast Boot - Inicio rápido + + + + Game Save %1 (%2) + Estado de juego %1 (%2) - - Full Boot - Inicio completo + + Edit Memory Cards... + Editar tarjetas de memoria... - - Boot and Debug - Iniciar y depurar + + Delete Save States... + Borrar estados guardados... - - Exclude From List - Excluir de la lista + + Confirm Save State Deletion + Confirmar borrado de estados guardados - - Add Search Directory... - Agregar directorio de búsqueda... + + Are you sure you want to delete all save states for %1? + +The saves will not be recoverable. + ¿Seguro que quieres borrar todos los estados guardados de %1? + +Una vez eliminados no se podrán recuperar. - - Select Cover Image - Seleccionar imagen de carátula + + Load From File... + Cargar desde archivo... - - All Cover Image Types (*.jpg *.jpeg *.png) - Todos los archivos de imágenes (*.jpg *.jpeg *.png) + + + Select Save State File + Seleccionar archivo de estado guardado - - Cover Already Exists - La carátula ya existe + + + Save States (*.sav) + Estados guardados (*.sav) - - A cover image for this game already exists, do you wish to replace it? - Ya existe una carátula para este juego, ¿desea reemplazarla? + + Undo Load State + Deshacer estado cargado - - - Copy Error - Error de copia + + + Game Save %1 (Empty) + Estado de juego %1 (vacío) - - Failed to remove existing cover '%1' - Fallo al eliminar la carátula existente «%1» + + + Global Save %1 (%2) + Estado global %1 (%2) - - Failed to copy '%1' to '%2' - Fallo al copiar «%1» a «%2» + + + Global Save %1 (Empty) + Estado global %1 (vacío) - Language changed. Please restart the application to apply. - Se ha cambiado el idioma. Reinicia la aplicación para aplicar los cambios. + + Save To File... + Guardar en archivo... - - All File Types (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psf *.minipsf *.m3u);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.exe *.psexe *.ps-exe);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u) - + + &Enabled Cheats + &Trucos habilitados - - - - - - Error - Error + + &Apply Cheats + &Aplicar trucos - - Failed to get window info from widget - + + Load Resume State + Cargar estado para resumir - - Failed to get new window info from widget - + + A resume save state was found for this game, saved at: + +%1. + +Do you want to load this state, or start from a fresh boot? + Se encontró un estado guardado para resumir este juego en: + +%1 + +¿Quieres cargar este estado, o iniciar desde el principio? - - Paused - + + Fresh Boot + Inicio de cero - - Resume (%1) - Continuar (%1) + + Delete And Boot + Eliminar e iniciar - - - - Game Save %1 (%2) - Estado de juego %1 (%2) + + Failed to delete save state file '%1'. + Fallo al eliminar el archivo de estado guardado "%1". - - Edit Memory Cards... - Editar tarjetas de memoria... + + Confirm Disc Change + Confirmar cambio de disco + + + + Do you want to swap discs or boot the new image (via system reset)? + ¿Quieres cambiar discos o iniciar la nueva imagen (reiniciando el sistema)? + + + + Swap Disc + Cambiar disco - - Delete Save States... - Borrar estados guardados... + + Reset + Reiniciar - - Confirm Save State Deletion - Confirmar borrado de estados guardados + + Cancel + Cancelar - - Are you sure you want to delete all save states for %1? - -The saves will not be recoverable. - ¿Seguro que desea borrar todos los estados guardados de %1? - -Una vez sean borrados, no se podrán recuperar. + + Start Disc + Iniciar disco - - Load From File... - Cargar archivo... + + + Cheat Manager + Administrador de trucos - - - Select Save State File - Seleccionar archivo de estado guardado + + You must select a disc to change discs. + Para cambiar discos, primero debes seleccionar uno. - - - Save States (*.sav) - Estados guardados (*.sav) + + Properties... + Propiedades... - - Undo Load State - Deshacer carga de estado + + Open Containing Directory... + Abrir directorio contenedor... - - - Game Save %1 (Empty) - Estado de juego %1 (vacío) + + Set Cover Image... + Establecer imágen de portada... - - - Global Save %1 (%2) - Estado global %1 (%2) + + Default Boot + Inicio predeterminado - - - Global Save %1 (Empty) - Estado global %1 (vacío) + + Fast Boot + Inicio rápido - - Save To File... - Guardar en archivo... + + Full Boot + Inicio completo - - &Enabled Cheats - &Trucos habilitados + + Boot and Debug + Iniciar y depurar - - &Apply Cheats - &Aplicar trucos + + Exclude From List + Excluir de la lista - - Load Resume State - + + Reset Play Time + Reiniciar tiempo de juego - - A resume save state was found for this game, saved at: - -%1. - -Do you want to load this state, or start from a fresh boot? - + + Add Search Directory... + Añadir directorio de búsqueda... - - Fresh Boot - + + Select Cover Image + Seleccionar imagen de portada - - Delete And Boot - + + All Cover Image Types (*.jpg *.jpeg *.png) + Todos los archivos de imágenes (*.jpg *.jpeg *.png) - - Failed to delete save state file '%1'. - + + Cover Already Exists + La portada ya existe - - Confirm Disc Change - + + A cover image for this game already exists, do you wish to replace it? + Una imágen de portada ya existe para este juego, ¿Quieres reemplazarla? - - Do you want to swap discs or boot the new image (via system reset)? - + + + Copy Error + Error de copia - - Swap Disc - + + Failed to remove existing cover '%1' + Fallo al eliminar la portada existente "%1" - - Reset - Reiniciar + + Failed to copy '%1' to '%2' + Fallo al copiar "%1" a "%2" - - Cancel - Cancelar + + Confirm Reset + Confirmar reinicio - - You must select a disc to change discs. - + + Are you sure you want to reset the play time for '%1'? + +This action cannot be undone. + ¿Seguro que quieres reiniciar el tiempo de juego para "%1"? + +Esta acción no se puede revertir. - + %1x Scale Escala %1x - - - + + + Destination File Archivo de destino - - + + Binary Files (*.bin) - Archivos BIN (*.bin) + Archivos binarios (*.bin) - + Binary Files (*.bin);;PNG Images (*.png) - Archivos BIN (*.bin);;Imágenes PNG (*.png) + Archivos binarios (*.bin);;Imágenes PNG (*.png) - + Default Predeterminado - + Fusion Fusion - + Dark Fusion (Gray) Dark Fusion (gris) - + Dark Fusion (Blue) Dark Fusion (azul) - + QDarkStyle QDarkStyle - + Confirm Shutdown - + Confirmar apagado + + + + Are you sure you want to shut down the virtual machine? + ¿Seguro que quieres apagar la máquina virtual? - + Save State For Resume - + Guardar estado para resumir - - - - + + + + Memory Card Not Found Tarjeta de memoria no encontrada - + Memory card '%1' does not exist. Do you want to create an empty memory card? - La tarjeta de memoria «%1» no existe. ¿Desea crear una tarjeta de memoria vacía? + La tarjeta de memoria "%1" no existe. ¿Quieres crear una nueva tarjeta de memoria? - + Failed to create memory card '%1' - Fallo al crear la tarjeta de memoria «%1» + Fallo al crear la tarjeta de memoria "%1" - - + + Memory card '%1' could not be found. Try starting the game and saving to create it. - No se encontró la tarjeta de memoria «%1». Intente iniciar el juego y salvar una partida para crearla. + No se puede encontrar la tarjeta de memoria "%1". Intenta iniciar y guardar el juego para crearla. - + Do not show again - No mostrar otra vez + No mostrar de nuevo - + Using cheats can have unpredictable effects on games, causing crashes, graphical glitches, and corrupted saves. By using the cheat manager, you agree that it is an unsupported configuration, and we will not provide you with any assistance when games break. Cheats persist through save states even after being disabled, please remember to reset/reboot the game after turning off any codes. Are you sure you want to continue? - El uso de trucos puede tener efectos impredecibles en los juegos, como bloqueos, fallas gráficas y guardados corruptos. Al utilizar el administrador de trucos, acepta que se trata de una configuración sin soporte técnico y que no le proporcionaremos ninguna ayuda cuando los juegos fallen. + Utilizar trucos puede provocar efectos impredecibles en los juegos, causar cuelgues, errores gráficos y guardados corruptos. Al utilizar el administrador de trucos, aceptas que es una opción sin soporte, y no te asistiremos si algo se rompe. -Los trucos persistirán a través de los estados de guardado incluso después de haber sido deshabilitados, así que recuerde reiniciar el juego después de desactivar cualquier código. +Los trucos persisten a través de los estados guardados, incluso después de haber sido deshabilitados, así que recuerda reiniciar el juego después de desactivar algún código. -¿Seguro que desea continuar? +¿Seguro que quieres continuar? - + Updater Error Error de actualización - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please follow the instructions under "Downloading and Running" at the link below:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> - <p>Está intentando actualizar a una versión de DuckStation no oficial de GitHub. Para prevenir incompatibilidades, el actualizador automático sólo buscará compilaciones oficiales.</p><p>Para obtener una versión oficial, siga las instrucciones en el apartado «Downloading and Running» en el siguiente enlace:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> + <p>Estás intentando actualizar una versión de DuckStation no oficial de GitHub. Para prevenir incompatibilidades, el actualizador automático sólo está habilitado en compilaciones oficiales.</p><p>Para obtener una versión oficial, sigue las instrucciones en el apartado "Downloading and Running" (Descargando y Corriendo) en el siguiente enlace:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> - + Automatic updating is not supported on the current platform. Las actualizaciones automáticas no son compatibles con la plataforma actual. @@ -8400,110 +7725,104 @@ Los trucos persistirán a través de los estados de guardado incluso después de Abrir... - - Format Card - Formatear tarjeta - - - - Import File... - Importar archivo... + + All Memory Card Types (*.mcd *.mcr *.mc *.srm *.psm *.ps *.ddf *.mem *.vgs *.psx) + Todos los archivos de tarjeta de memoria (*.mcd *.mcr *.mc *.srm *.psm *.ps *.ddf *.mem *.vgs *.psx) - - Import Card... - Importar tarjeta... + + All Importable Memory Card Types (*.mcd *.mcr *.mc *.gme) + Todos los archivos de tarjeta de memoria importables (*.mcd *.mcr *.mc *.gme) - - Save - Guardar + + Single Save Files (*.mcs);;All Files (*.*) + Archivos de guardado únicos (*.mcs);;Todos los archivos (*.*) - + Delete File - Eliminar archivo + Eliminar - + Undelete File - Recuperar archivo + Recuperar - + Export File - Exportar archivo + Exportar - + << << - + >> >> - - All Memory Card Types (*.mcd *.mcr *.mc) - Todos los archivos de tarjeta de memoria (*.mcd *.mcr *.mc) + + New Card... + Nueva tarjeta... - - All Importable Memory Card Types (*.mcd *.mcr *.mc *.gme) - Todos los archivos de tarjeta de memoria importables (*.mcd *.mcr *.mc *.gme) + + Open Card... + Abrir tarjeta... - - Single Save Files (*.mcs);;All Files (*.*) - Archivos guardados individuales (*.mcs);;Todos los archivos (*.*) + + Format Card + Formatear tarjeta - - - Select Memory Card - Seleccionar tarjeta de memoria + + Import File... + Importar archivo... - - - - - - - - - - - - - Error - Error + + Import Card... + Importar tarjeta... - - New Card... - Nueva tarjeta... + + Save + Guardar - - Open Card... - Abrir tarjeta... + + + + + + + + + + + + + Error + Error - - + + Failed to load memory card image. - Fallo al cargar la imagen de tarjeta de memoria. + Fallo al cargar imagen de tarjeta de memoria. - + (Deleted) - (Borrado) + (Eliminado) - + %n block(s) free%1 %n bloque libre%1 @@ -8511,1042 +7830,903 @@ Los trucos persistirán a través de los estados de guardado incluso después de - + + + Select Memory Card + Seleccionar tarjeta de memoria + + + Failed to write card to '%1' - Fallo al escribir tarjeta a «%1» + Fallo al escribir tarjeta a "%1" - + Save memory card? ¿Guardar tarjeta de memoria? - + Memory card '%1' is not saved, do you want to save before closing? - La tarjeta de memoria «%1» no se ha guardado, ¿desea guardarla antes de cerrar? + La tarjeta de memoria "%1" no se ha guardado, ¿Quieres hacerlo ahora antes de cerrar? - + Destination memory card already contains a save file with the same name (%1) as the one you are attempting to copy. Please delete this file from the destination memory card before copying. - La tarjeta de memoria de destino ya contiene un archivo guardado con el mismo nombre (%1) que el que está intentando copiar. Elimine este archivo de la tarjeta de memoria de destino antes de realizar la copia. + La tarjeta de memoria de destino ya contiene un archivo de guardado con el mismo nombre (%1) al que estás intentando copiar. Elimina este guardado en la tarjeta de memoria de destino antes de realizar la copia. - + Insufficient blocks, this file needs %1 but only %2 are available. - Bloques insuficientes, este archivo necesita %1, pero sólo hay %2 disponibles. + Bloques insuficientes, este archivo necesita %1 pero solo hay %2 disponibles. - + Failed to read file %1 Fallo al leer archivo %1 - + Failed to write file %1 Fallo al escribir archivo %1 - + Failed to delete file %1 Fallo al eliminar archivo %1 - + Failed to undelete file %1. The file may have been partially overwritten by another save. - Fallo al recuperar el archivo %1. Es posible que otro guardado haya sobrescrito el archivo. + Fallo al recuperar el archivo %1. Es posible que el guardado haya sido sobrescrito por otro archivo. - + Select Single Savefile Seleccionar archivo de guardado único - + Failed to export save file %1. Check the log for more details. - Fallo al exportar el archivo de guardado %1. Chequear el registro para más detalles. + Fallo al exportar el guardado %1. Revisa el registro para más detalles. - + Select Import File - Seleccionar archivo a importar + Selecciona archivo a importar - + Failed to import memory card. The log may contain more information. - Fallo al importar tarjeta de memoria. El registro podría contener más información. + Fallo al importar tarjeta de memoria. El registro puede contener más información. - + Format memory card? ¿Formatear tarjeta de memoria? - + Formatting the memory card will destroy all saves, and they will not be recoverable. The memory card which will be formatted is located at '%1'. - Al formatear la tarjeta de memoria, destruirá todos los datos guardados y no podrán ser recuperados. La tarjeta de memoria en cuestión se encuentra en «%1». + Formatear la tarjeta de memoria va a eliminar todos los guardados, y no será posible recuperarlos. La tarjeta a formatear se encuentra en "%1". - + Select Import Save File - Seleccionar el archivo de guardado a importar + Selecciona archivo de guardado a importar - + Failed to import save. Check if there is enough room on the memory card or if an existing save with the same name already exists. - Fallo al importar el guardado. Chequear si hay espacio disponible en la tarjeta de memoria o si ya existe un guardado con el mismo nombre. + Fallo al importar guardado. Comprueba que haya espacio suficiente en la tarjeta de memoria o si un guardado con el mismo nombre ya existe. MemoryCardSettingsWidget - - Shared Settings - Configuración compartida - - - - Checked - Habilitado + + All Memory Card Types (*.mcd *.mcr *.mc) + Todos los archivos de tarjeta de memoria (*.mcd *.mcr *.mc) - - If one of the "separate card per game" memory card types is chosen, these memory cards will be saved to the memory cards directory. - Al seleccionar alguna de las opciones «Separar tarjeta por juego», las tarjetas se guardarán en el directorio de tarjetas de memoria. + + Shared Settings + Configuración compartida - - Memory Card %1 - Tarjeta de memoria %1 + + + Browse... + Buscar... - - Memory Card Type: - Tipo de tarjeta de memoria: + + + Reset + Reiniciar - - - Browse... - Buscar... + + Open Directory... + Abrir directorio... - + Memory Card Directory: - Directorio de tarjetas de memoria: + Directorio de tarjeta de memoria: - - - Reset - Reiniciar + + + Use Single Card For Sub-Images + Utilizar tarjeta única para múltiples discos - - - Use Single Card For Sub-Images - Usar una tarjeta por cada subimagen + + Checked + Habilitado - + When using a multi-disc format (m3u/pbp) and per-game (title) memory cards, a single memory card will be used for all discs. If unchecked, a separate card will be used for each disc. - Cuando se utilice un formato multidisco (m3u/pbp) y tarjetas de memoria para cada juego, se va a usar una sola tarjeta para todos los discos. Si deshabilita esta opción, se usará una tarjeta diferente para cada disco. + Cuando se utilice un formato de discos múltiples (m3u/pbp) y tarjetas de memoria por juego, se utilizará una única tarjeta para todos los discos. Si se deshabilita, se utilizará una tarjeta diferente para cada disco. - - Open Directory... - Abrir directorio... + + If one of the "separate card per game" memory card types is chosen, these memory cards will be saved to the memory cards directory. + Si alguno de los modos "separar tarjeta por juego" es seleccionado, las tarjetas de memoria se guardarán el el directorio de tarjetas de memoria. - + The memory card editor enables you to move saves between cards, as well as import cards of other formats. - El editor de tajeta de memoria le permite mover sus partidas salvadas entre tarjetas, así como también importar tarjetas de otros formatos. + El editor de tarjetas de memoria te permite mover guardados entre tarjetas e importar tarjetas en otros formatos. - + Memory Card Editor... Editor de tarjetas de memoria... - - Shared Memory Card Path: - Ruta de tarjetas de memoria compartidas: + + Memory Card %1 + Tarjeta de memoria %1 - - Select path to memory card image - Seleccionar ruta de imagen de tarjeta de memoria + + Memory Card Type: + Tipo de tarjeta de memoria: - Select path to memory card directory - Seleccionar directorio para tarjetas de memoria + + Shared Memory Card Path: + Directorio de tarjeta de memoria compartida: - - All Memory Card Types (*.mcd *.mcr *.mc) - Todos los archivos de tarjeta de memoria (*.mcd *.mcr *.mc) + + Select path to memory card image + Seleccionar directorio para imagen de tarjeta de memoria MemoryCardType - + No Memory Card Sin tarjeta de memoria - + Shared Between All Games Compartida entre todos los juegos - - Separate Card Per Game (Game Code) - Tarjeta individual para cada juego (por código de juego) + + Separate Card Per Game (Serial) + Tarjeta separada por juego (número de serie) - - Separate Card Per Game (Game Title) - Tarjeta individual para cada juego (por título de juego) + + Separate Card Per Game (Title) + Tarjeta separada por juego (título) - + Separate Card Per Game (File Title) - Tarjeta individual para cada juego (por nombre de archivo) + Tarjeta separada por juego (título de archivo) - + Non-Persistent Card (Do Not Save) - Tarjeta no persistente (no guardar) + Tarjeta no persistente (sin guardar) MultitapMode - + Disabled Deshabilitado - + Enable on Port 1 Only - Habilitar solamente en el puerto 1 + Habilitar solamente en puerto 1 - + Enable on Port 2 Only - Habilitar solamente en el puerto 2 + Habilitar solamente en puerto 2 - + Enable on Ports 1 and 2 - Habilitar en los puertos 1 y 2 - - - - NamcoGunCon - - Trigger - Gatillo - - - ShootOffscreen - Disparo fuera de la pantalla - - - A - A - - - B - B - - - Crosshair Image Path - Ruta para la imagen de mira - - - Path to an image to use as a crosshair/cursor. - La ruta de una imagen que servirá como punto de mira/cursor. - - - Crosshair Image Scale - Escala de la imagen de mira - - - Scale of crosshair image on screen. - La escala en pantalla de la imagen de mira. - - - X Scale - Escala X - - - Scales X coordinates relative to the center of the screen. - Escala las coordenadas X relativas al centro de la pantalla. + Habilitar en puertos 1 y 2 NeGcon - Steering - Dirección - - - I - I - - - II - II - - - L - L - - - Up - Arriba - - - Down - Abajo - - - Left - Izquierda - - - Right - Derecha - - - A - A - - - B - B - - - R - R - - - Start - Start - - - + Steering Axis Deadzone Zona muerta del eje de dirección - + Sets deadzone size for steering axis. Establece el tamaño para la zona muerta del eje de dirección. - - - OSDMessage - - - Acquired exclusive fullscreen. - Adquirida pantalla completa exclusiva. - - - - Failed to acquire exclusive fullscreen. - Fallo al adquirir la pantalla completa exclusiva. - - - - Lost exclusive fullscreen. - Se perdió la pantalla completa exclusiva. - - - - %ux MSAA is not supported, using %ux instead. - El nivel de MSAA %ux no es compatible, se va a usar %ux en su lugar. - - - - SSAA is not supported, using MSAA instead. - SSAA no es compatible, se va a usar MSAA en su lugar. - - - Texture filter '%s' is not supported with the current renderer. - El filtro de texturas «%s» no es compatible con el renderizado actual. + + Steering Axis Sensitivity + Sensibilidad del eje de dirección - - Adaptive downsampling is not supported with the current renderer, using box filter instead. - El submuestreo adaptativo no es compatible con el renderizador actual, se va a usar el filtro «cuadro» en su lugar. + + Sets the steering axis scaling factor. + Establece el factor de escala del eje de dirección. + + + OSDMessage - - Resolution scale set to %ux (display %ux%u, VRAM %ux%u) - Escala de resolución establecida en %ux (pantalla %u x %u, VRAM %u x %u) + + Acquired exclusive fullscreen. + Pantalla completa exclusiva adquirida. - - Multisample anti-aliasing set to %ux (SSAA). - Antialiasing de muestreo múltiple establecido en %ux (SSAA). + + Failed to acquire exclusive fullscreen. + Fallo al adquirir pantalla completa exclusiva. - - Multisample anti-aliasing set to %ux. - Antialiasing de muestreo múltiple establecido en %ux. + + Lost exclusive fullscreen. + Se ha perdido la pantalla completa exclusiva. - - Resolution scale %ux not supported for adaptive smoothing, using %ux. - La escala de resolución %ux no es compatible con el suavizado adaptativo, se va a usar %ux. + + Analog mode forcing is disabled by game settings. Controller will start in digital mode. + Forzado del modo analógico está deshabilitado por la configuración del juego. El control se iniciará en modo digital. - - OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.0 is required. - Renderizador de OpenGL no disponible, tu hardware o tus controladores no son lo suficientemente modernos. Se requiere de OpenGL 3.1 o de OpenGL ES 3.0. + + CD image preloading not available for multi-disc image '%s' + La precarga de imagen de CD no está disponible para la imagen de múltiples discos "%s" - - System reset. - Reiniciando sistema. + + Precaching CD image failed, it may be unreliable. + Fallo al precachear la imagen de CD, podría ser inestable. - Loading state from '%s'... - Cargando estado de «%s»... + + CPU interpreter forced by game settings. + Intérprete de CPU forzado por la configuración del juego. - - Loading state from '%s' failed. Resetting. - Fallo al cargar el estado «%s». Reiniciando. + + Software renderer forced by game settings. + Renderizado por software forzado por la configuración del juego. - - Saving state to '%s' failed. - Fallo al guardar el estado «%s». + + Using software renderer for readbacks based on game settings. + Utilizando renderizador por software para lecturas en base a la configuración del juego. - State saved to '%s'. - Estado guardado en «%s». + + Interlacing forced by game settings. + Entrelazado forzado por la configuración del juego. - - PGXP is incompatible with the software renderer, disabling PGXP. - PGXP es incompatible con el renderizador por software, por lo tanto se deshabilitará. + + True color disabled by game settings. + Color verdadero deshabilitado por la configuración del juego. - - Rewind is not supported on 32-bit ARM for Android. - La función de rebobinado no es compatible con las versiones ARM de 32 bits para Android. + + Upscaling disabled by game settings. + Escalado deshabilitado por la configuración del juego. - - Switching to %s%s GPU renderer. - Cambiando al renderizador de GPU %s%s. + + Scaled dithering disabled by game settings. + Escalado de tramado deshabilitado por la configuración del juego. - - Switching to %s audio backend. - Cambiando al motor de audio %s. + + Widescreen disabled by game settings. + Pantalla panorámica deshabilitada por la configuración del juego. - - Switching to %s CPU execution mode. - Cambiando al modo de ejecución de CPU %s. + + Forcing NTSC Timings disallowed by game settings. + Forzado de tiempos NTSC deshabilitado por la configuración del juego. - - Recompiler options changed, flushing all blocks. - Las opciones del recompilador cambiaron, limpiando los bloques. + + PGXP geometry correction disabled by game settings. + Corrección de geometría (PGXP) deshabilitada por la configuración del juego. - - PGXP enabled, recompiling all blocks. - PGXP habilitado, recompilando todos los bloques. + + PGXP culling disabled by game settings. + Culling (PGXP) deshabilitado por la configuración del juego. - - PGXP disabled, recompiling all blocks. - PGXP deshabilitado, recompilando todos los bloques. + + PGXP perspective corrected textures disabled by game settings. + Corrección de perspectiva de texturas (PGXP) deshabilitada por la configuración del juego. - - Switching to %s renderer... - Cambiando al renderizador %s... + + PGXP perspective corrected colors disabled by game settings. + Corrección de perspectiva de colores (PGXP) deshabilitada por la configuración del juego. - - Memory card at '%s' could not be read, formatting. - No se pudo leer la tarjeta de memoria «%s», procediendo a formatearla. + + PGXP vertex cache forced by game settings. + Caché de vértices (PGXP) forzado por la configuración del juego. - - Failed to save memory card to '{}'. - + + PGXP CPU mode forced by game settings. + Modo CPU (PGXP) forzado por la configuración del juego. - - Saved memory card to '{}'. - + + PGXP Depth Buffer disabled by game settings. + Búfer de profundidad (PGXP) deshabilitado por la configuración del juego. - Failed to save memory card to '%s' - Fallo al guardar la tarjeta de memoria en «%s» + + Controller in port %u (%s) is not supported for %s. +Supported controllers: %s +Please configure a supported controller from the list above. + El control en el puerto %u (%s) no es compatible con %s. +Controles soportados: %s +Configura un control de la lista de arriba. - Saved memory card to '%s' - Tarjeta de memoria guardada en «%s» + + + + Failed to load post processing shader chain. + Fallo al cargar la cadena de sombreadores de postprocesamiento. - - Save state contains controller type %s in port %u, but %s is used. Switching. - El estado guardado contiene el tipo de control %s en el puerto %u, pero se está utilizando %s. Cambiando. + + %ux MSAA is not supported, using %ux instead. + El nivel de MSAA %ux no es compatible, se utilizará %ux en su lugar. - - Ignoring mismatched controller type %s in port %u. - Ignorando diferencias en el tipo de control %s en el puerto %u. + + SSAA is not supported, using MSAA instead. + SSAA no es compatible, se utilizará MSAA en su lugar. - - Memory card %u from save state does match current card data. Simulating replugging. - La tarjeta de memoria %u del estado guardado no coincide con los datos de la tarjeta actual. Simulando reconexión. + + Texture filter '%s' is not supported with the current renderer. + El filtro de textura "%s" no es compatible con el renderizador actual. - - Memory card %u present in save state but not in system. Ignoring card. - La tarjeta de memoria %u está presente en el estado guardado, pero no en el sistema. Ignorando tarjeta. + + Adaptive downsampling is not supported with the current renderer, using box filter instead. + El submuestreo adaptable no es compatible con el renderizador actual, usando el filtro de cuadro en su lugar. - - Memory card %u present in system but not in save state. Replugging card. - La tarjeta de memoria %u está presente en el sistema, pero no en el estado guardado. Reconectando la tarjeta. + + Resolution scale set to %ux (display %ux%u, VRAM %ux%u) + Escala de resolución establecida en %ux (pantalla %ux%u, VRAM %ux%u) - - Memory card %u present in save state but not in system. Creating temporary card. - La tarjeta de memoria %u está presente en el estado guardado, pero no en el sistema. Creando tarjeta temporal. + + Multisample anti-aliasing set to %ux (SSAA). + Suavizado de bordes de muestreo múltiple establecido en %ux (SSAA). - - Memory card %u present in system but not in save state. Removing card. - La tarjeta de memoria %u está presente en el sistema, pero no en el estado guardado. Quitando tarjeta. + + Multisample anti-aliasing set to %ux. + Suavizado de bordes de muestreo múltiple establecido en %ux. - - CD image preloading not available for multi-disc image '%s' - La precarga de imagen de CD no está disponible para la imagen de varios discos «%s» + + Resolution scale %ux not supported for adaptive smoothing, using %ux. + La escala de resolución %ux no es compatible con el suavizado adaptable, utilizando %ux en su lugar. - - Precaching CD image failed, it may be unreliable. - + + OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required. + Renderizador de OpenGL no disponible, tu hardware o controladores no son lo suficientemente modernos. Se requiere de OpenGL 3.1 o de OpenGL ES 3.0. - Failed to apply ppf patch from '%s', using unpatched image. - Fallo al aplicar el parche PPF de «%s», se va a usar una imagen sin parchear. + + Memory card at '%s' could not be read, formatting. + No se pudo leer la tarjeta de memoria en "%s", formateando. - - CPU clock speed is set to %u%% (%u / %u). This may result in instability. - La velocidad de reloj del CPU está establecida en %u%% (%u / %u). Podrían producirse inestabilidades. + + Failed to save memory card to '{}'. + Fallo al guardar la tarjeta de memoria en "{}". - - CD-ROM read speedup set to %ux (effective speed %ux). This may result in instability. - Aceleración de lectura del CDROM establecida en %ux (velocidad efectiva %ux). Podrían producirse inestabilidades. + + Saved memory card to '{}'. + Tarjeta de memoria guardada en "{}". - - CD-ROM seek speedup set to instant. This may result in instability. - Aceleración de búsqueda de CD-ROM establecida como instantánea. Podrían producirse inestabilidades. + + Save state contains controller type %s in port %u, but %s is used. Switching. + El estado guardado contiene el tipo de control %s en el puerto %u, pero se está utilizando %s. Cambiando. - - CD-ROM seek speedup set to %ux. This may result in instability. - Aceleración de búsqueda de CD-ROM establecida en %ux. Podrían producirse inestabilidades. + + Ignoring mismatched controller type %s in port %u. + Ignorando diferencias en el tipo de control %s en el puerto %u. - - Failed to initialize %s renderer, falling back to software renderer. - Fallo al inicializar el renderizador %s, cambiando al renderizador por software. + + Memory card %u present in save state but not in system. Creating temporary card. + La tarjeta de memoria %u está presente en el estado guardado pero no en el sistema. Creando tarjeta temporal. - - WARNING: CPU overclock (%u%%) was different in save state (%u%%). - AVISO: la velocidad de «overclocking» de la CPU (%u %%) es diferente a la del estado guardado (%u %%). + + Memory card %u from save state does match current card data. Simulating replugging. + La tarjeta de memoria %u del estado guardado no coincide con los datos de la tarjeta actual. Simulando reconexión. - - Failed to open CD image from save state '%s': %s. Using existing image '%s', this may result in instability. - Fallo al abrir la imagen de CD del estado guardado «%s»: %s. Usando la imagen existente «%s», podrían producirse inestabilidades. + + Memory card %u present in save state but not in system. Ignoring card. + La tarjeta de memoria %u está presente en el estado guardado pero no en el sistema. Ignorando tarjeta. - - Failed to open disc image '%s': %s. - Fallo al abrir la imagen de disco «%s»: %s. + + Memory card %u present in system but not in save state. Removing card. + La tarjeta de memoria %u está presente en el estado guardado pero no en el sistema. Quitando tarjeta. - - Failed to switch to subimage %u in '%s': %s. - Error al cambiar la subimagen %u en «%s»: %s. + + Memory card %u present in system but not in save state. Replugging card. + La tarjeta de memoria %u está presente en el estado guardado pero no en el sistema. Reconectando tarjeta. - - Switched to sub-image %s (%u) in '%s'. - Cambiado a la subimagen %s (%u) en «%s». + + PGXP is incompatible with the software renderer, disabling PGXP. + PGXP es incompatible con el renderizador por software, se deshabilitará. - - Inserted disc '%s' (%s). - Disco «%s» introducido (%s). + + Rewind is not supported on 32-bit ARM for Android. + El rebobinado no está disponible en dispositivos ARM de 32 bits. - - - - Failed to load post processing shader chain. - Error al cargar la cadena de sombreadores de posprocesamiento. + + Runahead is not supported on 32-bit ARM for Android. + El procesamiento anticipado no está disponible en dispositivos ARM de 32 bits. - - No cheats are loaded. - No hay trucos cargados. - - - - %n cheats are now active. - - %n truco está habilitado. - %n trucos están habilitados. - - - - - %n cheats are now inactive. - - %n truco está deshabilitado. - %n trucos están deshabilitados. - + + Rewind is disabled because runahead is enabled. + El rebobinado está deshabilitado porque el procesamiento anticipado está habilitado. - Fast forwarding... - Avanzando rápido... + + System reset. + Sistema reiniciado. - Stopped fast forwarding. - Se detuvo el avance rápido. + + Loading state from '{}'... + Cargando estado desde "{}"... - Turboing... - Turboing... + + Loading state from '%s' failed. Resetting. + Fallo al cargar estado desde "%s". Reiniciando. - Stopped turboing. - Se detuvo el turboing. + + Save State + Guardar estado - Hotkey unavailable because achievements hardcore mode is active. - El atajo no está disponible porque el modo «hardcore» de logros está activo. + + Saving state to '%s' failed. + Fallo al guardar estado en "%s". - Rewinding... - Rebobinando... + + State saved to '{}'. + Estado guardado en "{}". - Stopped rewinding. - Se detuvo el rebobinado. + + CPU clock speed is set to %u%% (%u / %u). This may result in instability. + La velocidad de reloj del CPU está establecida en %u%% (%u / %u). Esto puede resultar en inestabilidad. - - Cannot load state for game without serial. - + + CD-ROM read speedup set to %ux (effective speed %ux). This may result in instability. + La aceleración de lectura de CD-ROM se estableció a %ux (velocidad efectiva %ux). Esto puede resultar en inestabilidad. - - No save state found in slot {}. - + + CD-ROM seek speedup set to instant. This may result in instability. + La aceleración de búsqueda de CD-ROM se estableció a instantánea. Esto puede resultar en inestabilidad. - - Cannot save state for game without serial. - + + CD-ROM seek speedup set to %ux. This may result in instability. + La aceleración de búsqueda de CD-ROM se estableció a %ux. Esto puede resultar en inestabilidad. - - Achievements are disabled or unavailable for game. - + + Failed to initialize %s renderer, falling back to software renderer. + Fallo al inicializar el renderizador %s, volviendo al renderizador por software. - - Leaderboards are disabled or unavailable for game. - + + This save state was created with a different BIOS version or patch options. This may cause stability issues. + Este estado guardado se creó utilizando una versión diferente de BIOS o parches. Pueden producirse inestabilidades. - - CPU clock speed control enabled (%u%% / %.3f MHz). - Control de velocidad de reloj del CPU habilitado (%u %% / %.3f MHz). + + WARNING: CPU overclock (%u%%) was different in save state (%u%%). + AVISO: El overclock de la CPU (%u%%) era diferente en el estado guardado (%u%%). - - CPU clock speed control disabled (%.3f MHz). - Control de velocidad de reloj del CPU deshabilitado (%.3f MHz). + + Failed to open CD image from save state '%s': %s. Using existing image '%s', this may result in instability. + Fallo al abrir la imagen de CD del estado guardado "%s": %s. Utilizando la imagen existente "%s", esto puede resultar inestable. - - PGXP is now enabled. - PGXP habilitada. + + Rewinding is not enabled. + El rebobinado no está habilitado. - - PGXP is now disabled. - PGXP deshabilitada. + + No cheats are loaded. + No hay trucos cargados. - - - PGXP Depth Buffer is now enabled. - Búfer de profundidad de la PGXP habilitado. + + + %n cheats are now active. + + %n truco está activado. + %n trucos están activados. + + + + + %n cheats are now inactive. + + %n truco está desactivado. + %n trucos están desactivados. + - - PGXP Depth Buffer is now disabled. - Búfer de profundidad de la PGXP deshabilitado. + + Swapped memory card ports. Both ports have a memory card. + Ranuras de memoria intercambiadas. Ambas ranuras tienen una tarjeta de memoria. - - - - Volume: {}% - + + Swapped memory card ports. Port 2 has a memory card, Port 1 is empty. + Ranuras de memoria intercambiadas. La ranura 2 tiene una tarjeta de memoria, la ranura 1 está vacía. - - Texture replacements reloaded. - Reemplazos de textura recargados. + + Swapped memory card ports. Port 1 has a memory card, Port 2 is empty. + Ranuras de memoria intercambiadas. La ranura 1 tiene una tarjeta de memoria, la ranura 2 está vacía. - - Failed to save undo load state. - Fallo al guardar la acción para deshacer la carga del estado. + + Swapped memory card ports. Neither port has a memory card. + Ranuras de memoria intercambiadas. Ninguna de las ranuras tienen una tarjeta de memoria. - - Rewinding is not enabled. - El rebobinado no está habilitado. + + Failed to open disc image '%s': %s. + Fallo al abrir la imagen de disco "%s": %s. - Achievements are disabled or unavailable for this game. - No hay logros para este juego o están deshabilitados. + + Inserted disc '%s' (%s). + Insertado disco "%s" (%s). - Leaderboards are disabled or unavailable for this game. - No hay tablas de clasificación para este juego o están deshabilitadas. + + Failed to switch to subimage %u in '%s': %s. + Fallo al cambiar a la subimagen %u en "%s": %s. - - - - Emulation speed set to %u%%. - Velocidad de emulación establecida en %u %%. + + Switched to sub-image %s (%u) in '%s'. + Cambiado a la subimagen %s (%u) en "%s". - - PGXP CPU mode is now enabled. - Modo CPU de la PGXP habilitado. + + Switching to %s%s GPU renderer. + Cambiando al renderizador de GPU %s%s. - - PGXP CPU mode is now disabled. - Modo CPU de la PGXP deshabilitado. + + Switching to %s audio backend. + Cambiando al motor de audio %s. - - Volume: Muted - Volumen: silenciado + + Switching to %s CPU execution mode. + Cambiando al modo de ejecución de CPU %s. - Volume: %d%% - Volumen: %d %% + + Recompiler options changed, flushing all blocks. + Las opciones del recompilador cambiaron, liberando todos los bloques. - - CD Audio Muted. - Audio de CD silenciado. + + PGXP enabled, recompiling all blocks. + PGXP habilitado, recompilando todos los bloques. - - CD Audio Unmuted. - Según la RAE es válido el uso del prefijo 'de-'. - Audio de CD desilenciado. + + PGXP disabled, recompiling all blocks. + PGXP deshabilitado, recompilando todos los bloques. - Loaded input profile from '%s' - Cargado perfil de control «%s» + + Failed to save undo load state. + Fallo al guardar el deshacer del estado cargado. - + Started dumping audio to '%s'. - Comenzando a volcar audio en «%s». + Comenzando a volcar audio en "%s". - + Failed to start dumping audio to '%s'. - Fallo al iniciar el volcado de audio en «%s». + Fallo al comenzar a volcar audio en "%s". - + Stopped dumping audio. Volcado de audio finalizado. - + Screenshot file '%s' already exists. - El archivo de captura «%s» ya existe. + La captura de pantalla "%s" ya existe. - + Failed to save screenshot to '%s' - Fallo al guardar la captura «%s» + Fallo al guardar la captura de pantalla "%s" - + Screenshot saved to '%s'. - Captura de pantalla guardada en «%s». - - - - Controller in port %u (%s) is not supported for %s. -Supported controllers: %s -Please configure a supported controller from the list above. - El control en el puerto %u (%s) no es compatible con %s. -Controles soportados: %s -Seleccione un control de la lista superior. - - - Input profile '%s' cannot be found. - No se encontró el perfil de control «%s». + Captura de pantalla guardada en "%s". - Using input profile '%s'. - Usando perfil de control «%s». - - - + Failed to load cheats from '%s'. - Fallo al cargar trucos de «%s». - - - - Swapped memory card ports. Both ports have a memory card. - Intercambiados los puertos de tarjetas de memoria. Ambos tienen una tarjeta de memoria. - - - - Swapped memory card ports. Port 2 has a memory card, Port 1 is empty. - Intercambiados los puertos de tarjetas de memoria. El puerto 2 contiene una tarjeta de memoria y el puerto 1 está vacio. + Fallo al cargar trucos desde "%s". - - - Swapped memory card ports. Port 1 has a memory card, Port 2 is empty. - Intercambiados los puertos de tarjetas de memoria. El puerto 1 contiene una tarjeta de memoria y el puerto 2 está vacio. + + + %n cheats are enabled. This may result in instability. + + %n truco está activado. Esto puede causar inestabilidad. + %n trucos estan activados. Esto puede causar inestabilidad. + - - Swapped memory card ports. Neither port has a memory card. - Intercambiados los puertos de tarjetas de memoria. Ninguno tiene una tarjeta de memoria. + + Failed to save cheat list to '%s' + Fallo al guardar la lista de trucos en "%s" - + Saved %n cheats to '%s'. - Se guardó %n truco en «%s». - Se guardaron %n trucos en «%s». + Se guardó %n truco en "%s". + Se guardaron %n trucos en "%s". - - Widescreen hack is now enabled, and aspect ratio is set to %s. - Hack para pantallas panorámicas habilitado, relación de aspecto configurada en %s. + + Deleted cheat list '%s'. + Se eliminó la lista de trucos "%s". - - Widescreen hack is now disabled, and aspect ratio is set to %s. - Hack para pantallas panorámicas deshabilitado, relación de aspecto configurada en %s. + + Cheat '%s' enabled. + Truco "%s" activado. - - Failed to save cheat list to '%s' - Fallo al guardar la lista de trucos en «%s» + + Cheat '%s' disabled. + Truco "%s" desactivado. - - Loading state from '{}'... - + + Applied cheat '%s'. + Aplicado truco "%s". - - Save State - Guardar estado + + Cheat '%s' is already enabled. + El truco "%s" ya está activado. - - State saved to '{}'. - - - - - %n cheats are enabled. This may result in instability. - - %n truco habilitado. Podrían producirse inestabilidades. - %n trucos habilitados. Podrían producirse inestabilidades. - + + Post-processing is now enabled. + Postprocesamiento habilitado. - - Deleted cheat list '%s'. - Lista de trucos «%s» borrada. + + Post-processing is now disabled. + Postprocesamiento deshabilitado. - - Cheat '%s' enabled. - Truco «%s» habilitado. + + Failed to load post-processing shader chain. + Fallo al cargar la cadena de sombreadores de postprocesamiento. - - Cheat '%s' disabled. - Truco «%s» deshabilitado. + + Post-processing shaders reloaded. + Sombreadores de postprocesamiento recargados. - - Applied cheat '%s'. - Aplicado truco «%s». + + Widescreen hack is now enabled, and aspect ratio is set to %s. + Pantalla panorámica habilitada, la relación de aspecto se cambió a %s. - - Cheat '%s' is already enabled. - El truco «%s» ya está activado. + + Widescreen hack is now disabled, and aspect ratio is set to %s. + Pantalla panorámica deshabilitada, la relación de aspecto se cambió a %s. - - Post-processing is now enabled. - Posprocesamiento habilitado. + + Switching to %s renderer... + Cambiando al renderizador %s... - - Post-processing is now disabled. - Posprocesamiento deshabilitado. + + Cannot load state for game without serial. + No se puede cargar estado para un juego sin número de serie. - - Failed to load post-processing shader chain. - Fallo al cargar la cadena de sombreadores de posprocesamiento. + + No save state found in slot {}. + No se encontró un estado guardado en la ranura {}. - - Post-processing shaders reloaded. - Sombreadores de posprocesamiento recargados. + + Cannot save state for game without serial. + No se puede guardar estado para un juego sin número de serie. - - CPU interpreter forced by game settings. - Intérprete de CPU forzado por la configuración del juego. + + Achievements are disabled or unavailable for game. + Los logros están desactivados o no disponibles para este juego. - - Software renderer forced by game settings. - Renderizado por software forzado por la configuración del juego. + + Leaderboards are disabled or unavailable for game. + Las tablas de posiciones están deshabilitadas o no disponibles para este juego. - - Interlacing forced by game settings. - Entrelazado forzado por la configuración del juego. + + CPU clock speed control enabled (%u%% / %.3f MHz). + Control de la velocidad de reloj de la CPU habilitado (%u%% / %.3f MHz). - - True color disabled by game settings. - Color verdadero deshabilitado por la configuración del juego. + + CPU clock speed control disabled (%.3f MHz). + Control de la velocidad de reloj de la CPU deshabilitado (%u%% / %.3f MHz). - - Upscaling disabled by game settings. - Escalado deshabilitado por la configuración del juego. + + + + Emulation speed set to %u%%. + Velocidad de emulación establecida a %u%%. - - Scaled dithering disabled by game settings. - Escalado de tramado deshabilitado por la configuración del juego. + + PGXP is now enabled. + PGXP está habilitado. - - Widescreen disabled by game settings. - Pantalla panorámica deshabilitada por la configuración del juego. + + PGXP is now disabled. + PGXP está deshabilitado. - - Forcing NTSC Timings disallowed by game settings. - Forzado de velocidad NTSC deshabilitado por la configuración del juego. + + Texture replacements reloaded. + Reemplazos de textura recargados. - - PGXP geometry correction disabled by game settings. - Corrección de geometría de la PGXP deshabilitada por la configuración del juego. + + PGXP Depth Buffer is now enabled. + Búfer de profundidad (PGXP) habilitado. - - PGXP culling disabled by game settings. - «Culling» de la PGXP deshabilitado por la configuración del juego. + + PGXP Depth Buffer is now disabled. + Búfer de profundidad (PGXP) deshabilitado. - - PGXP texture correction disabled by game settings. - Corrección de texturas de la PGXP deshabilitada por la configuración del juego. + + PGXP CPU mode is now enabled. + Modo CPU (PGXP) habilitado. - - PGXP vertex cache forced by game settings. - Caché de vértices de la PGXP forzada por la configuración del juego. + + PGXP CPU mode is now disabled. + Modo CPU (PGXP) deshabilitado. - - PGXP CPU mode forced by game settings. - Modo CPU de la PGXP forzado por la configuración del juego. + + Volume: Muted + Volumen: silenciado - - PGXP Depth Buffer disabled by game settings. - Búfer de profundidad de la PGXP deshabilitado por la configuración del juego. + + + + Volume: {}% + Volumen: {}% - - Analog mode forcing is disabled by game settings. Controller will start in digital mode. - Forzado del modo analógico deshabilitado por la configuración del juego. El control se iniciará en modo digital. + + CD Audio Muted. + Audio de CD silenciado. - Failed to read executable from disc. Achievements disabled. - No se pudo leer el ejecutable del disco. Logros desactivados. + + CD Audio Unmuted. + Audio de CD desilenciado. PlayStationMouse - Left - Izquierdo - - - Right - Derecho - - - + Relative Mouse Mode Modo de mouse relativo - + Locks the mouse cursor to the window, use for FPS games. - Bloquea el cursor del mouse en la ventana, ideal para juegos FPS. + Bloquea el cursor del mouse a la ventana, útil para juegos FPS. @@ -9554,32 +8734,32 @@ Seleccione un control de la lista superior. Form - Formulario + Form Add - Agregar + Añadir Remove - Quitar + Eliminar Clear - Borrar + Limpiar Move Up - Subir + Mover arriba Move Down - Bajar + Mover abajo @@ -9587,29 +8767,29 @@ Seleccione un control de la lista superior. Opciones... - + No Shaders Available No hay sombreadores disponibles - + Error Error - + Failed to add shader. The log may contain more information. - Fallo al añadir el sombreador. El registro podría contener más información. + Error al añadir sombreador. El registro puede contener más información. - + Question Pregunta - + Are you sure you want to clear all shader stages? - ¿Seguro que desea borrar la configuración de sombreadores? + ¿Seguro que quieres limpiar la configuración de sombreadores? @@ -9617,107 +8797,79 @@ Seleccione un control de la lista superior. Form - Formulario + Form Enable Post Processing - Habilitar posprocesamiento + Habilitar postprocesamiento &Reload Shaders &Recargar sombreadores - - Load Preset - Cargar perfil - - - Save Preset - Guardar perfil - Post Processing Chain - Cadena de posprocesamiento + Cadena de postprocesamiento - + Error Error - + The current post-processing chain is invalid, it has been reset. - - - - The current post-processing chain is invalid, it has been reset. Any changes made will overwrite the existing config. - La cadena de posprocesamiento actual no es válida y fue reiniciada. Cualquier cambio va a reemplazar la configuración existente. + La cadena de postprocesamiento actual es inválida y se ha reiniciado. PostProcessingShaderConfigDialog - + %1 Shader Options Opciones del sombreador %1 - - Close - Cerrar - PostProcessingShaderConfigWidget - + Red Rojo - + Green Verde - + Blue Azul - + Alpha Alfa - + %1 (%2) %1 (%2) - - Reset to Defaults - Restablecer a predeterminado - QObject - DuckStation Error - Error de DuckStation - - - Failed to initialize host interface. Cannot continue. - Fallo al inicializar la interfaz del sistema. No se puede continuar. - - - + Failed to open URL Fallo al abrir URL - + Failed to open URL. The URL was: %1 @@ -9727,173 +8879,77 @@ La URL era: %1 - QtHost + QtAsyncProgressThread - - - + Error - Error - - - - File '%1' does not exist. - - - - - The specified save state does not exist. - + Error - - Cannot use no-gui mode, because no boot filename was specified. - + + Question + Pregunta - - Cannot use batch mode, because no boot filename was specified. - + + Information + Información - QtHostInterface - - No resume save state found. - No se encontraron estados guardados para continuar. - - - Load From File... - Cargar archivo... - - - Select Save State File - Seleccionar archivo de estado guardado - - - Save States (*.sav) - Estados guardados (*.sav) - - - Undo Load State - Deshacer carga de estado - - - Game Save %1 (%2) - Estado de juego %1 (%2) - - - Game Save %1 (Empty) - Estado de juego %1 (vacío) - - - Global Save %1 (%2) - Estado global %1 (%2) - - - Global Save %1 (Empty) - Estado global %1 (vacío) - - - Save To File... - Guardar en archivo... - - - Resume - Continuar - - - Load State - Cargar estado - - - Resume (%1) - Continuar (%1) - - - Edit Memory Cards... - Editar tarjetas de memoria... - - - &Enabled Cheats - &Trucos habilitados - - - &Apply Cheats - &Aplicar trucos - - - Game ID: %1 -Game Title: %2 -Game Developer: %3 -Game Publisher: %4 -Achievements: %5 (%6) - - - ID: %1 -Titulo: %2 -Desarrollador: %3 -Distribuidor: %4 -Logros: %5 (%6) - - - - - %n points - - %n punto - %n puntos - - + QtHost - Rich presence inactive or unsupported. - «Rich Presence» inactiva o no compatible. + + + + Error + Error - Game not loaded or no RetroAchievements available. - No se cargó un juego o RetroAchievements no está disponible. + + File '%1' does not exist. + El archivo "%1" no existe. - Delete Save States... - Borrar estados guardados... + + The specified save state does not exist. + El estado guardado especificado no existe. - Confirm Save State Deletion - Confirmar borrado de estados guardados + + Cannot use no-gui mode, because no boot filename was specified. + No se puede utilizar el modo No-GUI porque no se especificó un archivo de arranque. - Are you sure you want to delete all save states for %1? - -The saves will not be recoverable. - ¿Seguro que desea borrar todos los estados guardados de %1? - -Una vez sean borrados, no se podrán recuperar. + + Cannot use batch mode, because no boot filename was specified. + No se puede utilizar el modo por lotes porque no se especificó un archivo de arranque. - QtProgressCallback + QtModalProgressCallback - + DuckStation DuckStation - + Cancel Cancelar - + Error Error - + Question Pregunta - + Information Información @@ -9901,64 +8957,64 @@ Una vez sean borrados, no se podrán recuperar. SaveStateSelectorUI - + Load Cargar - + Save Guardar - + Select Previous Seleccionar anterior - + Select Next Seleccionar siguiente - + No Save State - No hay estados guardados + Ningún estado guardado - + Global Slot %d Ranura global %d - + Game Slot %d Ranura de juego %d - + %s Slot %d - Ranura %s %d + %s ranura %d SettingWidgetBinder - - + + Default: - + Predeterminado: - - + + Reset - Reiniciar + Reiniciar - + Select folder for %1 - + Seleccionar carpeta para %1 @@ -9969,319 +9025,252 @@ Una vez sean borrados, no se podrán recuperar. Configuración de DuckStation - General Settings - Configuración general - - - BIOS Settings - Configuración de BIOS - - - Console Settings - Configuración de consola - - - Emulation Settings - Configuración de emulación - - - Game List Settings - Configuración de lista de juegos - - - Hotkey Settings - Configuración de atajos - - - Controller Settings - Configuración de controles - - - Memory Card Settings - Configuración de memorias - - - Display Settings - Configuración de pantalla - - - Enhancement Settings - Configuración de mejoras - - - Post-Processing Settings - Configuración de posprocesamiento - - - Achievement Settings - Configuración de logros - - - Restore Defaults - Restaurar los valores predeterminados - - - Close - Cerrar - - - Audio Settings - Configuración de audio + + Summary + Resumen - Advanced Settings - Configuración avanzada + + <strong>Summary</strong><hr>This page shows information about the selected game, and allows you to validate your disc was dumped correctly. + <strong>Resumen</strong><hr>Esta página muestra información sobre el juego seleccionado, y te permite comprobar si tu disco fue volcado correctamente. - - This DuckStation build was not compiled with RetroAchievements support. - Esta versión de DuckStation no se compiló con el soporte de RetroAchievements. + + General + General - + <strong>General Settings</strong><hr>These options control how the emulator looks and behaves.<br><br>Mouse over an option for additional information. - <strong>Configuración general</strong><hr>Estas opciones controlan el aspecto y comportamiento del emulador.<br><br>Pase el puntero sobre alguna opción para que aparezca más información. - - - - Summary - + <strong>Configuración general</strong><hr>Estas opciones controlan como se ve y comporta el emulador.<br><br>Pasa el cursor sobre alguna opción para más información. - - <strong>Summary</strong><hr>This page shows information about the selected game, and allows you to validate your disc was dumped correctly. - - - - - General - General + + Game List + Lista de juegos - - Game List - + + <strong>Game List Settings</strong><hr>The list above shows the directories which will be searched by DuckStation to populate the game list. Search directories can be added, removed, and switched to recursive/non-recursive. + <strong>Configuración de lista de juegos</strong><hr>La lista de arriba indica los directorios en los cuales se buscarán los juegos a aparecer en la lista. Se pueden añadir y quitar directorios, y buscar tanto de forma recursiva como no. - + BIOS - BIOS + BIOS - + <strong>BIOS Settings</strong><hr>These options control which BIOS is used and how it will be patched.<br><br>Mouse over an option for additional information. - + <strong>Configuración de BIOS</strong><hr>Estas opciones controlan que BIOS utilizar y como será parcheado.<br><br>Pasa el cursor sobre alguna opción para más información. - + Console - Consola + Consola - + <strong>Console Settings</strong><hr>These options determine the configuration of the simulated console.<br><br>Mouse over an option for additional information. - <strong>Configuración de consola</strong><hr>Estas opciones determinan la configuración de la consola emulada.<br><br>Pase el puntero sobre alguna opción para que aparezca más información. + <strong>Configuración de consola</strong><hr>Estas opciones determinan la configuración de la consola emulada.<br><br>Pasa el cursor sobre alguna opción para más información. - + Emulation - + Emulación - + <strong>Emulation Settings</strong><hr>These options determine the speed and runahead behavior of the system.<br><br>Mouse over an option for additional information. - + <strong>Configuración de emulación</strong><hr>Estas opciones determinan la velocidad y el comportamiento del procesamiento anticipado del sistema.<br><br>Pasa el cursor sobre alguna opción para más información. - + Memory Cards - - - - - <strong>Game List Settings</strong><hr>The list above shows the directories which will be searched by DuckStation to populate the game list. Search directories can be added, removed, and switched to recursive/non-recursive. - <strong>Configuración de lista de juegos</strong><hr>La lista de arriba indica los directorios en los cuales se buscarán los juegos que aparecerán en la lista. Se pueden añadir y quitar directorios, y buscar tanto de forma recursiva como no. - - - <strong>Hotkey Settings</strong><hr>Binding a hotkey allows you to trigger events such as a resetting or taking screenshots at the press of a key/controller button. Hotkey titles are self-explanatory. Clicking a binding will start a countdown, in which case you should press the key or controller button/axis you wish to bind. If no button is pressed and the timer lapses, the binding will be unchanged. To clear a binding, right-click the button. To bind multiple buttons, hold Shift and click the button. - <strong>Configuración de atajos</strong><hr>Un atajo permite ejecutar eventos tales como reiniciar la consola o tomar capturas de la pantalla con sólo presionar una tecla o botón. Al hacer clic sobre un botón de asignación comenzará una cuenta regresiva, dentro de la cual debe presionar la tecla o botón que quiera asignar. Si no presiona ninguna tecla para cuando se termine el tiempo, se mantendrá la configuración existente. Para borrar la configuración, haga clic derecho sobre el botón. Para asignar más de un botón o tecla, mantenga la tecla Shift y haga clic en el botón. + Tarjetas de memoria - <strong>Controller Settings</strong><hr>This page lets you choose the type of controller you wish to simulate for the console, and rebind the keys or host game controller buttons to your choosing. Clicking a binding will start a countdown, in which case you should press the key or controller button/axis you wish to bind. (For rumble, press any button/axis on the controller you wish to send rumble to.) If no button is pressed and the timer lapses, the binding will be unchanged. To clear a binding, right-click the button. To bind multiple buttons, hold Shift and click the button. - <strong>Configuración de controles</strong><hr>Esta página permite elegir el tipo de control a emular y configurar los botones a su gusto. Al hacer clic sobre un botón de asignación comenzará una cuenta regresiva, dentro de la cual debe presionar la tecla o botón que quiera asignar (en el caso de la vibración, presione cualquier botón o eje del control al que desee enviar las vibraciones). Si no presiona ninguna tecla para cuando se termine el tiempo, se mantendrá la configuración existente. Para borrar la configuración, haga clic derecho sobre el botón. Para asignar más de un botón o tecla, mantenga la tecla Shift y haga clic en el botón. - - - + <strong>Memory Card Settings</strong><hr>This page lets you control what mode the memory card emulation will function in, and where the images for these cards will be stored on disk. - <strong>Configuración de memorias</strong><hr>Esta página permite controlar el funcionamiento de la emulación de las tarjetas de memoria y dónde se guardarán los archivos en disco. + <strong>Configuración de memorias</strong><hr>Esta página te permite controlar en que modo funcionará la emulación de tarjetas de memoria, y dónde se guardarán los archivos en disco. - + Display - + Pantalla - + <strong>Display Settings</strong><hr>These options control the how the frames generated by the console are displayed on the screen. - <strong>Configuración de pantalla</strong><hr>Estas opciones controlan como se mostrarán las imágenes generadas por la consola en pantalla. + <strong>Configuración de pantalla</strong><hr>Estas opciones controlan como las imágenes generadas por la consola se muestran en pantalla. - + Enhancements - + Mejoras - + <strong>Enhancement Settings</strong><hr>These options control enhancements which can improve visuals compared to the original console. Mouse over each option for additional information. - <strong>Configuración de mejoras</strong><hr>Estas opciones controlan aquellos elementos que pueden mejorar los gráficos respecto a los que mostraría la consola original.<br><br>Pase el puntero sobre alguna opción para que aparezca más información. + <strong>Configuración de mejoras</strong><hr>Esta configuración controla las opciones que pueden mejorar los gráficos comparados a la consola original.<br><br>Pasa el cursor sobre alguna opción para más información. - + Post-Processing - + Postprocesamiento - + <strong>Post-Processing Settings</strong><hr>Post processing allows you to alter the appearance of the image displayed on the screen with various filters. Shaders will be executed in sequence. - <strong>Configuración de posprocesamiento</strong><hr>El posprocesamiento le permite alterar la apariencia de la imagen mostrada en pantalla con varios filtros. Los sombreadores serán ejecutados de forma secuencial. + <strong>Configuración de postprocesamiento</strong><hr>El postprocesamiento te permite alterar la apariencia de la imagen mostrada en pantalla con varios filtros. Los sombreadores serán ejecutados en secuencia. - + Audio - Audio + Audio - + <strong>Audio Settings</strong><hr>These options control the audio output of the console. Mouse over an option for additional information. - <strong>Configuración de audio</strong><hr>Estas opciones controlan la salida de audio de la consola.<br><br>Pase el puntero sobre alguna opción para que aparezca más información. + <strong>Configuración de audio</strong><hr>Estas opciones controlan la salida de audio de la consola.<br><br>Pasa el cursor sobre alguna opción para más información. - + Achievements - + Logros - + <strong>Achievement Settings</strong><hr>These options control RetroAchievements. Mouse over an option for additional information. - + <strong>Configuración de logros</strong><hr>Estas opciones controlan los logros de RetroAchievements. Pasa el cursor sobre alguna opción para más información. + + + + This DuckStation build was not compiled with RetroAchievements support. + Esta versión de DuckStation no fue compilada con soporte para RetroAchievements. - + Folders - + Directorios - + <strong>Folder Settings</strong><hr>These options control where DuckStation will save runtime data files. - + <strong>Configuración de directorios</strong><hr>Estas opciones controlan dónde se guardaran las archivos de datos de DuckStation. - + Advanced - + Avanzada - + <strong>Advanced Settings</strong><hr>These options control logging and internal behavior of the emulator. Mouse over an option for additional information. - <strong>Configuración avanzada</strong><hr>Estas opciones controlan el registro de mensajes y el comportamiento interno del emulador.<br><br>Pase el puntero sobre alguna opción para que aparezca más información. + <strong>Configuración avanzada</strong><hr>Estas opciones controlan el registro de mensajes y el comportamiento interno del emulador.<br><br>Pasa el cursor sobre alguna opción para más información. - + Confirm Restore Defaults - Confirmar la restauración a valores predeterminados + Confirmar el restablecimiento de valores predeterminados - + Are you sure you want to restore the default settings? Any preferences will be lost. - ¿Seguro que desea restaurar la configuración predeterminada? Se perderán todas sus preferencias. + ¿Seguro que quieres restablecer los valores predeterminados? Se perderán todas tus preferencias. - + Recommended Value Valor recomendado - + %1 [%2] - + %1 [%2] - + Use Global Setting [Enabled] - + Utilizar configuración global [Habilitado] - + Use Global Setting [Disabled] - + Utilizar configuración global [Deshabilitado] - - + + Use Global Setting [%1] - + Utilizar configuración global [%1] System - - Failed to load %s BIOS. - Fallo al cargar el BIOS %s. - - - - + + Error - Error + Error - + Failed to load save state file '{}' for booting. - + Fallo al cargar el archivo de estado guardado "{}" para iniciar. + + + + Failed to load %s BIOS. + Fallo al cargar el BIOS %s. - + + Incorrect BIOS image size + Tamaño de imagen del BIOS incorrecto + + + Save state is incompatible: minimum version is %u but state is version %u. - El estado guardado es incompatible: la versión mínima soportada es %u, pero el estado es para la versión %u. + El estado guardado es incompatible: la versión mínima soportada es %u, pero el estado es versión %u. - + Save state is incompatible: maximum version is %u but state is version %u. - El estado guardado es incompatible: la versión máxima soportada es %u, pero el estado es para la versión %u. + El estado guardado es incompatible: la versión máxima soportada es %u, pero el estado es versión %u. - + Failed to open CD image '%s' used by save state: %s. - Fallo al abrir la imagen de CD «%s» del estado guardado %s. + Fallo al abrir la imagen de CD "%s" utilizada por el estado guardado: "%s". - + Failed to switch to subimage %u in CD image '%s' used by save state: %s. - Fallo al cambiar la subimagen %u en el CD «%s» utilizado por el estado guardado %s. + Fallo al cambiar a la subimagen %u en la imagen de CD "%s" utilizada por el estado guardado %s. - + Per-game memory card cannot be used for slot %u as the running game has no code. Using shared card instead. - No puede usarse una tarjeta de memoria individual para el juego en la ranura %u ya que el juego no tiene un código. Se usará una tarjeta compartida. + La tarjeta de memoria por juego no puede utilizarse para la ranura %u ya que el juego no tiene un código. Se utilizará una tarjeta compartida en su lugar. - + Per-game memory card cannot be used for slot %u as the running game has no title. Using shared card instead. - No puede usarse una tarjeta de memoria individual para el juego en la ranura %u ya que el juego no tiene un título. Se usará una tarjeta compartida. + La tarjeta de memoria por juego no puede utilizarse para la ranura %u ya que el juego no tiene un título. Se utilizará una tarjeta compartida en su lugar. - + Per-game memory card cannot be used for slot %u as the running game has no path. Using shared card instead. - No puede usarse una tarjeta de memoria individual para el juego en la ranura %u ya que el juego no tiene una ruta. Se usará una tarjeta compartida. + La tarjeta de memoria por juego no puede utilizarse para la ranura %u ya que el juego no tiene un directorio. Se utilizará una tarjeta compartida en su lugar. - + Game changed, reloading memory cards. - Juego cambiado, volviendo a cargar las tarjetas de memoria. + Juego cambiado, recargando las tarjetas de memoria. - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s @@ -10291,18 +9280,18 @@ The game will likely not run properly. Please check the README for instructions on how to add an SBI file. Do you wish to continue? - Está intentando correr un juego protegido con libcrypt sin un archivo SBI: + Estás intentando ejecutar un juego protegido con LibCrypt sin un archivo SBI %s: %s -Es probable que el juego no corra correctamente. +Probablemente el juego no funcione correctamente. -Chequee las instrucciones del archivo README sobre como agregar un archivo SBI. +Lee las instrucciones en el archivo "README" sobre como añadir un archivo SBI. -¿Desea continuar? +¿Quieres continuar? - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s @@ -10310,21 +9299,13 @@ Chequee las instrucciones del archivo README sobre como agregar un archivo SBI. Your dump is incomplete, you must add the SBI file to run this game. The name of the SBI file must match the name of the disc image. - Está intentando correr un juego protegido con libcrypt sin un archivo SBI: + Estás intentando ejecutar un juego protegido con LibCrypt sin un archivo SBI %s: %s -Este volcado está incompleto. Debe agregar el archivo SBI para poder ejecutar el juego. +Tu copia del juego está incompleta, primero debes añadir el archivo SBI correspondiente para iniciar este juego. -El nombre del archivo SBI debe ser idéntico al nombre de la imagen de disco. - - - Failed to acquire host display. - Fallo al adquirir la pantalla del host. - - - System failed to boot. The log may contain more information. - El sistema no se inició correctamente. El registro podría contener más información. +El nombre del archivo SBI debe coincidir con el nombre de la imagen de disco. diff --git a/src/duckstation-qt/translations/duckstation-qt_fr.ts b/src/duckstation-qt/translations/duckstation-qt_fr.ts index a80945d940..cf767d4715 100644 --- a/src/duckstation-qt/translations/duckstation-qt_fr.ts +++ b/src/duckstation-qt/translations/duckstation-qt_fr.ts @@ -6,7 +6,7 @@ About DuckStation - À propos de Duckstation + À propos de DuckStation @@ -14,27 +14,27 @@ DuckStation - + %1 (%2) %1 (%2) - + DuckStation is a free and open-source simulator/emulator of the Sony PlayStation<span style="vertical-align:super;">TM</span> console, focusing on playability, speed, and long-term maintainability. - DuckStation est un émulateur gratuit et open source simulant la console Sony PlayStation<span style="vertical-align:super;">TM</span>, qui se concentre sur la jouabilité, la vitesse et la maintenanibilité à long terme. + DuckStation est un émulateur gratuit et open source simulant la console Sony PlayStation<span style="vertical-align:super;">TM</span>, qui se concentre sur la jouabilité, la vitesse et la maintenabilité à long terme. - + Authors Auteurs - + Icon by Icône par - + License Licence @@ -45,58 +45,58 @@ RetroAchievements Login Window title - + Connexion à RetroAchievements RetroAchievements Login Header text - + Connexion à RetroAchievements Please enter user name and password for retroachievements.org below. Your password will not be saved in DuckStation, an access token will be generated and used instead. - + Veuillez entrer un nom d'utilisateur et un mot de passe pour retroachievements.org ci-dessous. Votre mot de passe ne sera pas sauvegardé dans DuckStation, un jeton d'accès sera généré et utilisé à la place. User Name: - + Nom d'utilisateur : Password: - + Mot de passe : Ready... - + Prêt... - + &Login - + &Connexion - + Logging in... - + Connexion... - + Login Error - + Erreur de connexion - + Login failed. Please check your username and password, and try again. - + La connexion a échoué. Veuillez vérifier votre nom d'utilisateur et votre mot de passe, et réessayer. - + Login failed. - + Échec de connexion. @@ -104,229 +104,283 @@ Form - Formulaire + Formulaire Global Settings - + Paramètres Généraux - - + + Enable Achievements - + Activer les Succès - - + + Enable Rich Presence - + Activer la Rich Presence - - + + Enable Test Mode - + Activer le Mode Test - - + + Use First Disc From Playlist - + Utiliser le premier disque depuis la liste de lecture - - + + + Enable Leaderboards + Activer les classements + + + + Enable Hardcore Mode - + Activer le Mode Hardcore - - + + + Show Challenge Indicators + Aff. les Indics. de Challenge + + + + Test Unofficial Achievements - + Tester les succès non-officiels + + + + + Enable Sound Effects + Activer les effets sonores - + + + Show Notifications + Afficher les notifications + + + Account - + Compte - - + + Login... - + Connexion... - + View Profile... - + Voir le Profil... - + Game Info - + Informations du jeu - + <html><head/><body><p align="justify">DuckStation uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/"><span style=" text-decoration: underline; color:#0000ff;">retroachievements.org</span></a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html> - + <html><head/><body><p align="justify">DuckStation utilise RetroAchievements en tant que base de données de succès et pour suivre la progression. Pour utiliser les succès, veuillez vous inscrire sur <a href="https://retroachievements.org/"><span style=" text-decoration: underline; color:#0000ff;">retroachievements.org</span></a>.</p><p align="justify">Pour voir la liste des succès en jeu, pressez la touche raccourci pour <span style=" font-weight:600;">Ouvrir menu pause</span> et sélectionnez <span style=" font-weight:600;">Succès</span> depuis le menu.</p></body></html> - - - - + + - + + + Unchecked - Décoché + Décoché - + When enabled and logged in, DuckStation will scan for achievements on startup. - + Quand activé et connecté, DuckStation scannera les succès au démarrage. - + When enabled, DuckStation will assume all achievements are locked and not send any unlock notifications to the server. - + Quand activé, DuckStation supposera que tous les succès sont verrouillés et n'enverra aucune notification de déverrouillage au serveur. - + When enabled, DuckStation will list achievements from unofficial sets. Please note that these achievements are not tracked by RetroAchievements, so they unlock every time. - + Quand activé, DuckStation listera la liste des succès depuis les ensembles non-officiels. Veuillez noter que ces succès ne sont pas suivis par RetroAchievements, et donc qu'ils se débloquent à chaque fois. - + When enabled, rich presence information will be collected and sent to the server where supported. - + Quand activé, les informations rich presence seront collectées et envoyées au serveur quand supporté. - + When enabled, the first disc in a playlist will be used for achievements, regardless of which disc is active. - + Quand activé, le premier disque dans la liste de lecture sera utilisé pour les succès, sans regarder quel disque est actif. - - "Challenge" mode for achievements. Disables save state, cheats, and slowdown functions, but you receive double the achievement points. - + + "Challenge" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions. + Le mode "Challenge" pour les succès, incluant suivi de classement. Désactive les save states, codes de triche, et fonctions de ralentissement. + + + + + + + Checked + Coché + + + + Displays popup messages on events such as achievement unlocks and leaderboard submissions. + Affiche les popups de message lors d'événements tels que le déverrouillage de succès et les soumissions de classement. + + + + Plays sound effects for events such as achievement unlocks and leaderboard submissions. + Joue les effets sonores des événements tels que le déverrouillage des succès et les soumissions de classement. + + + + Enables tracking and submission of leaderboards in supported games. If leaderboards are disabled, you will still be able to view the leaderboard and scores, but no scores will be uploaded. + Active le suivi et la soumission de classement pour les jeux supportés. Si le classement est désactivé, vous pourrez voir les classements et score, mais aucun score ne sera envoyé. + + + + Shows icons in the lower-right corner of the screen when a challenge/primed achievement is active. + Affiche les îcones dans le coin en bas à droite de l'écran quand un challenge/succès primé est actif. + + + + Reset System + Réinitialiser le Système + + + + Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now? + Le mode Hardcore ne sera activé qu'après réinitialisation du système. Voulez-vous réinitialiser le système maintenant ? - + Username: %1 Login token generated on %2. - + Nom d'utilisateur : %1 +Jeton de connexion généré sur %2. - + Logout - + Déconnexion - + Not Logged In. - + Non-connecté. Achievements - + Loading state - + Chargement de l'état - + Resuming state - + Reprise de l'état - + Hardcore mode disabled by state switch. - + Mode Hardcore désactivé par la bascule d'état. - + Hardcore mode will be enabled on system reset. - + Le mode Hardcore sera activé sur réinitialisation du système. - + Confirm Hardcore Mode - + Confirmation du mode Hardcore - + {0} cannot be performed while hardcore mode is active. Do you want to disable hardcore mode? {0} will be cancelled if you select No. - + {0} ne peut être fait quand le mode Hardcore est actif. Voulez-vous désactiver le mode Hardcore ? {0} sera annulé si vous sélectionnez Non. - + Hardcore mode is now enabled. - + Le mode Hardcore est maintenant activé. - + Hardcore mode is now disabled. - + Le mode Hardcore est maintenant désactivé. - + {} (Hardcore Mode) - + {} (mode Hardcore) - + You have earned {} of {} achievements, and {} of {} points. - + Vous avez remporté {} des {} succès, et {} des {} points. - + This game has no achievements. - - - - - Leaderboards are enabled. - - - - - Leaderboards are disabled because hardcore mode is off. - + Ce jeu n'a pas de succès. - + Your Score: {} (Best: {}) Leaderboard Position: {} of {} - + Votre score : {} (Meilleur : {}) +Position dans le classement : {} of {} - + This game has {} leaderboards. - + Ce jeu a {} classements. - + Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only. - + La soumission des scores est désactivée parce que le mode Hardcore est désactivé. Les classements sont en lecture seule. - + Time - + Temps - + Score - + Score - + Downloading leaderboard data, please wait... - + Téléchargement des données de classement, veuillez patienter... @@ -353,25 +407,25 @@ Leaderboard Position: {} of {} - + Log To System Console Enregistrer vers une console système - + Log To Window Enregistrer dans une fenêtre - + Log To Debug Console Enregistrer vers la console de débogage - + Log To File Enregistrer vers un fichier @@ -382,7 +436,7 @@ Leaderboard Position: {} of {} - + Show Debug Menu Menu de débogage @@ -407,12 +461,12 @@ Leaderboard Position: {} of {} Remettre par défaut - + Display FPS Limit - + Afficher la limite d'IPS - + PGXP Vertex Cache Cache Vertex PGXP @@ -425,181 +479,231 @@ Leaderboard Position: {} of {} Préserver la précision de la projection PGXP - + PGXP Geometry Tolerance - + Tolérance de la géométrie PGXP - + PGXP Depth Clear Threshold - + Seuil de profondeur libre PGXP - + Enable Recompiler Memory Exceptions - Activer les exceptions mémoire du recompileur + Activer les exceptions mémoire du recompilateur - + Enable Recompiler Fast Memory Access - + Activer l'accès rapide en mémoire du recompilateur Enable Recompiler ICache Activer le recompileur ICache - + Enable VRAM Write Texture Replacement - + Activer le remplacement des textures d'écriture VRAM - + Preload Texture Replacements - + Précharger les remplacements de texture - + Disable All Enhancements - + Désactiver toutes les améliorations - - Show Enhancement Settings - + + Show Status Indicators + Aficher les indicateurs de statut - - Show Status Indicators - + + Show Frame Times + Afficher les temps d'image - + Apply Compatibility Settings - + Appliquer les paramètres de compatibilité - + Multisample Antialiasing - + Anticrénelage multi-échantillons + + + + Display Active Start Offset + Afficher la position de début active + + + + Display Active End Offset + Afficher la position de fin active + + + + Display Line Start Offset + Afficher la position de la ligne de début + + + + Display Line End Offset + Afficher la position de la ligne de fin - + Enable Recompiler Block Linking - + Activer le bloc d'édition de liens du recompilateur + + + + Use Old MDEC Routines + Utiliser les anciennes routines MDEC - + Dump Replaceable VRAM Writes - + Décharger les écritures VRAM remplaçables - + Set Dumped VRAM Write Alpha Channel - + Affeter le canal d'écriture alpha de la VRAM déchargée - + Minimum Dumped VRAM Write Width - + Longueur minimale de l'écriture de la VRAM déchargée - + Minimum Dumped VRAM Write Height - + Hauteur minimale de l'écriture de la VRAM déchargée - + DMA Max Slice Ticks Taille maximale de l'écart entre deux transferts DMA - + DMA Halt Ticks Fréquence d'arrêt du transfert DMA - + GPU FIFO Size Taille du tampon FIFO du GPU - + GPU Max Run-Ahead Temps d'avance maximale du GPU - + Use Debug Host GPU Device Utiliser le périphérique GPU hôte de débogage - + + Stretch Display Vertically + Étirer l'affichage verticalement + + + Increase Timer Resolution - Augmenter la Résolution du Compteur + Augmenter la Résolution du Compteur - + Allow Booting Without SBI File - + Autorise le démarrage sans fichier SBI - + Create Save State Backups - + Créer des sauvegardes de save state - + + Enable PCDrv + Activer PCDrv + + + + Enable PCDrv Writes + Activer les écritures PCDrv + + + + PCDrv Root Directory + Répertoire racine PCDrv + + + Log Level - + Niveau de log - + + Select folder for %1 + Sélectionner le dossier pour %1 + + + Information - Information + Information - + Sets the verbosity of messages logged. Higher levels will log more messages. - + Définie la verbosité des messages loggés. Les niveaux élevés loggeront plus de messages. - - - - + + + + User Preference - + Préférence utilisateur - + Logs messages to the console window. - + Logge les messages dans la fenêtre console. - + Logs messages to the debug console where supported. - + Logge les messages dans la console de dégogage quand supporté. - + Logs messages to the window. - + Logge les message dans la fenêtre. - + Logs messages to duckstation.log in the user directory. - + Logge les messages dans la log duckstation dans le répertoire utilisateur. - + Unchecked - Décoché + Décoché - + Shows a debug menu bar with additional statistics and quick settings. - + Affiche une barre de menu de débogage avec des statistiques additionnelles et des paramètres rapides. @@ -613,14 +717,12 @@ Leaderboard Position: {} of {} Le Contrôleur %u a basculé sur le mode Digital. - Controller %u is locked to analog mode by the game. - Le contrôleur %u est verrouillé en mode Analogique par le jeu. + Le contrôleur %u est verrouillé en mode Analogique par le jeu. - Controller %u is locked to digital mode by the game. - Le contrôleur %u est verrouillé en mode Digital par le jeu. + Le contrôleur %u est verrouillé en mode Digital par le jeu. LeftX @@ -707,56 +809,116 @@ Leaderboard Position: {} of {} Analogique - - + + Controller {} switched to analog mode. - + Contrôleur {} changée en mode analogique. - - + + Controller {} switched to digital mode. - + Contrôleur {} changée en mode digital. + + + + Controller {} is locked to analog mode by the game. + Contrôleur {} verrouillée en mode analogique par le jeu. + + + + Controller {} is locked to digital mode by the game. + Contrôleur {} verrouillée en mode digital par le jeu. + + + + Not Inverted + Non-inversé + + + + Invert Left/Right + Inverser gauche/droite + + + + Invert Up/Down + Inverser haut/bas - + + Invert Left/Right + Up/Down + Inverser gauche/droite + haut/bas + + + Force Analog Mode on Reset - + Forcer le mode Analogue quand réinitialisation - - Forces the controller to analog mode when the console is reset/powered on. May cause issues with games, so it is recommended to leave this option off. - + + Forces the controller to analog mode when the console is reset/powered on. + Force le contrôleur en mode analogique quand la console est réinitialisée/mise sous tension. + + + + Button/Trigger Deadzone + Zone morte bouton/gachette + + + + Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored. + Définis la zone morte pour activer les boutons/gachettes, càd la fraction de gachette qui sera ignorée. + + + + Invert Left Stick + Inverser le stick gauche + + + + Inverts the direction of the left analog stick. + Inverse la direction du stick analogique gauche. + + + + Invert Right Stick + Inverser le stick droit + + + + Inverts the direction of the right analog stick. + Inverse la direction du stick analogique droit. - + Use Analog Sticks for D-Pad in Digital Mode - + Utiliser les sticks analogiques pour le D-Pad en mode digital - + Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons. - + Permet d'utiliser les sticks analogiques pour contrôler le d-pas en mode digital, tout comme les boutons. - + Analog Deadzone - + Zone morte analogique - + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. - + Définis la zone morte du stick analogique, càd la fraction de mouvement du stick qui sera ignorée. - + Analog Sensitivity - + Sensibilité analogique - + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. - + Définis le facteur d'échelle de l'axe du stick analogique. Une valeur entre 130% et 140% est recommandée pour les manettes récentes, par ex. DualShock 4, manette Xbox One. Enable Analog Mode on Reset @@ -775,51 +937,91 @@ Leaderboard Position: {} of {} Définit le facteur de mise à l'échelle de l'axe du stick analogique. Une valeur comprise entre 1,30 et 1,40 est recommandée lors de l'utilisation de contrôleurs récents, par ex. DualShock 4, manette Xbox One. - + Vibration Bias Le bias est un réglage en général en électronique, et non un alignement. Réglage des vibrations - + Sets the rumble bias value. If rumble in some games is too weak or not functioning, try increasing this value. Le bias est un réglage en général en électronique, et non un alignement. - Définit la valeur de réglage des vibrations. Si la vibration dans certains jeux est trop faible ou ne fonctionne pas, essayez d'augmenter cette valeur. + Définis la valeur de réglage des vibrations. Si la vibration dans certains jeux est trop faible ou ne fonctionne pas, essayez d'augmenter cette valeur. AnalogJoystick - - + + Controller %u switched to analog mode. - Le Contrôleur %u a basculé sur le mode Analogique. + La contrôleur %u a basculé en mode analogique. - - + + Controller %u switched to digital mode. - Le Contrôleur %u a basculé sur le mode Digital. + La contrôleur %u a basculé en mode digital. + + + + Not Inverted + Non-inversé - + + Invert Left/Right + Inverser gauche/droite + + + + Invert Up/Down + Inverser haut/bas + + + + Invert Left/Right + Up/Down + Inverser gauche/droite + haut/bas + + + Analog Deadzone - + Zone morte analogique - + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. - + Définis la zone morte du stick analogique, càd la fraction du mouvement du stick qui sera ignorée. - + Analog Sensitivity - + Sensitivité analogique - + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. - + Définis le facteur d'échelle de l'axe du stick analogique. Une valeur entre 130% et 140% est recommandée pour les manettes récentes, par ex. DualShock 4, manette Xbox One. + + + + Invert Left Stick + Inverser le stick de gauche + + + + Inverts the direction of the left analog stick. + Inverse la direction du stick analogique gauche. + + + + Invert Right Stick + Inverser le stick droit + + + + Inverts the direction of the right analog stick. + Inverse la direction du stick analogique droit. LeftX @@ -917,19 +1119,19 @@ Leaderboard Position: {} of {} AudioBackend - + Null (No Output) Null (Aucune Sortie) - + Cubeb Cubeb - + XAudio2 - + XAudio2 SDL @@ -949,17 +1151,17 @@ Leaderboard Position: {} of {} Configuration - + Backend: Moteur de rendu : - + Buffer Size: Taille du tampon: - + Maximum latency: 0 frames (0.00ms) Latence maximum : 0 image (0.00ms) @@ -968,90 +1170,95 @@ Leaderboard Position: {} of {} Synchroniser avec la sortie - - + + Start Dumping On Boot Commencer l'enregistrement au démarrage - + Minimal - + Minimal - + Off (Noisy) - + Désactivé (bruyant) - + Resampling (Pitch Shift) - + Rééchantillonnage (Pitch Shift) - + Time Stretch (Tempo Change, Best Sound) - + Étirement temporel (changement de tempo, meilleur son) - + Output Latency: - + Latence de sortie : - + Driver: - + Pilote : - + Stretch Mode: - + Mode d'étirement : + + + + Output Device: + Périph. de sortie : - + Controls Contrôles - + Output Volume: - + Volume de sortie : - + Fast Forward Volume: - + Volume d'avance rapide : Volume: Volume: - - + + Mute All Sound Couper le son - - + + Mute CD Audio Couper le son du CD Audio - - + + 100% 100% - + Audio Backend Moteur de rendu audio - + The audio backend determines how frames produced by the emulator are submitted to the host. Cubeb provides the lowest latency, if you encounter issues, try the SDL backend. The null backend disables all host audio output. Le moteur audio détermine comment les images produites par l'émulateur sont soumises à l'hôte. Cubeb fournit une latence basse, si vous rencontrez des problèmes, essayez alors le moteur SDL. Le moteur null désactive toutes les sorties audio de l'hôte. @@ -1060,7 +1267,7 @@ Leaderboard Position: {} of {} Taille du tampon - + The buffer size determines the size of the chunks of audio which will be pulled by the host. Smaller values reduce the output latency, but may cause hitches if the emulation speed is inconsistent. Note that the Cubeb backend uses smaller chunks regardless of this value, so using a low value here may not significantly change latency. La taille du tampon détermine la taille des échantillons audio qui seront récupérés par l'hôte. Les plus petites valeurs réduisent la latence de la sortie, mais peuvent créer des saccades si la vitesse d'émulation est incompatible. Remarque: le moteur Cubeb utilise de plus petits échantillons quelque soit sa valeur, donc utiliser une valeur faible ici peut ne pas faire changer de manière significative la latence. @@ -1073,61 +1280,67 @@ Leaderboard Position: {} of {} Réduit la vitesse d'émulation en fonction du moteur audio appelant les trames audio. Ceci peut permettre de réduire les bruits parasites ou craquements si l'émulation devait être trop rapide La synchronisation sera automatiquement désactivée si l'émulation n'est pas constante à 100% de la vitesse. - + Output Latency - + Latence de sortie - - - + + + Unchecked Décoché - + Start dumping audio to file as soon as the emulator is started. Mainly useful as a debug option. Commencer à enregistrer l'audio vers un fichier dès que l'émulateur démarre. Utilisé principalement en tant qu'option de débogage. - + Output Volume - + Volume de sortie - + Controls the volume of the audio played on the host. - + Contrôle le volume de l'audio joué sur l'hôte. - + Controls the volume of the audio played on the host when fast forwarding. - + Contrôle le volume audio joué sur l'hôte en mode avance rapide. - + Stretch Mode - + Mode étiré - + Time Stretching - + Étirement temporel - + When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces much nicer fast forward/slowdown audio at a small cost to performance. - + Quand au-delà d'une vitesse de 100%, ajuste le tempo sur l'audio plûtot que sauter des images. Produit une avance rapide/ralentissement audio plus joli au prix d'un léger impact sur les performances. + + + + + Default + Défaut - + Maximum Latency: %1 frames / %2 ms (%3ms buffer + %5ms output) - + Latence maximale : %1 images / %2 ms (%3ms tampon + %5ms sortie) - + Maximum Latency: %1 frames / %2 ms - + Latence maximale : %1 images / %2 ms Volume @@ -1138,17 +1351,17 @@ Leaderboard Position: {} of {} Contrôle du volume de l'audio joué par l'hôte. Les valeurs sont en pourcentage. - + Fast Forward Volume - + Volume de l'avance rapide - + Prevents the emulator from producing any audible sound. Empêche l'émulateur de produire le moindre son audible. - + Forcibly mutes both CD-DA and XA audio from the CD-ROM. Can be used to disable background music in some games. Coupure forcée de la lecture audio CD-DA et XA du CD-ROM. Peut être utilisé pour désactiver la musique de fond dans certains jeux. @@ -1157,8 +1370,8 @@ Leaderboard Position: {} of {} Latence maximum : %1 images (%2ms) - - + + %1% %1% @@ -1167,8 +1380,8 @@ Leaderboard Position: {} of {} AutoUpdaterDialog - - + + Automatic Updater Mise à jour automatique @@ -1203,57 +1416,57 @@ Leaderboard Position: {} of {} Me le rappeler plus tard - + Updater Error Erreur de mise à jour - + No updates are currently available. Please try again later. Aucune mise à jour n'est actuellement disponible. Veuillez réessayer plus tard. - + Current Version: %1 (%2) Version installée: %1 (%2) - + New Version: %1 (%2) Dernière version: %1 (%2) - + Loading... Téléchargement... - + <h2>Changes:</h2> <h2>Changements:</h2> - + <h2>Save State Warning</h2><p>Installing this update will make your save states <b>incompatible</b>. Please ensure you have saved your games to memory card before installing this update or you will lose progress.</p> <h2>Avertissement des sauvegardes d'état</h2><p>L'installation de cette mise à jour rendra vos sauvegardes d'états <b>incompatibles</b>. Veuillez vous assurer que vous avez sauvegardé vos parties sur la carte mémoire avant d'installer cette mise à jour ou vous perdrez votre progression.</p> - + <h2>Settings Warning</h2><p>Installing this update will reset your program configuration. Please note that you will have to reconfigure your settings after this update.</p> <h2>Avertissement sur les paramètres</h2><p>L'installation de cette mise à jour réinitialisera la configuration de votre programme. Veuillez noter que vous devrez reconfigurer vos paramètres après cette mise à jour.</p> - + <h4>Installing this update will download %1 MB through your internet connection.</h4> <h4>L'installation de cette mise à jour téléchargera %1 MB via votre connexion internet.</h4> - + Downloading %1... Téléchargement %1... - + Cancel Annuler @@ -1317,44 +1530,44 @@ Leaderboard Position: {} of {} - + Fast Boot Démarrage rapide - + Enable TTY Output Activer la sortie TTY - + Patches the BIOS to log calls to printf(). Only use when debugging, can break games. - + Patche le BIOS pour logger les appels à printf(). Utiliser seulement en débogage, peut casser les jeux. - + Use Global Setting - + Utiliser les paramètres globaux - + Auto-Detect Détection automatique - + Unknown Inconnu - - + + Unchecked Décoché - + Patches the BIOS to skip the console's boot animation. Does not work with all games, but usually safe to enable. Patche le BIOS pour ignorer l'animation de démarrage de la console. Ne fonctionne pas avec tous les jeux, mais généralement sûr. @@ -1370,37 +1583,37 @@ Leaderboard Position: {} of {} Intepréteur (Plus lent) - + Interpreter (Slowest) - + Interpréteur (le + lent) - + Cached Interpreter (Faster) - Intepréteur avec cache (Plus rapide) + Interpréteur avec cache (+ rapide) - + Recompiler (Fastest) - Recompileur '(Le plus rapide) + Recompileur (le + rapide) CPUFastmemMode - + Disabled (Slowest) - + Désactivé (le + lent) - + MMap (Hardware, Fastest, 64-Bit Only) - + MMap (matériel, le + rapide, 64-Bit uniquement) - + LUT (Faster) - + LUT (+ rapide) @@ -1408,27 +1621,27 @@ Leaderboard Position: {} of {} Cheat Code Editor - + Éditeur de code de triche Description: - + Description : Group: - + Groupe : Type: - + Type : Activation: - + Activation : Save @@ -1439,20 +1652,20 @@ Leaderboard Position: {} of {} Annuler - - + + Error - Erreur + Erreur - + Description cannot be empty. - + La description ne peut pas être vide. - + Instructions are invalid. - + Les instructions sont invalides. @@ -1460,491 +1673,507 @@ Leaderboard Position: {} of {} Cheat Manager - + Gestionnaire de triche Cheat List - + Liste de triche &Add Group... - + &Ajouter groupe... &Add Code... - + &Ajouter code... &Edit Code... - + Édit&er code... &Delete Code - + &Supprimer code - - - + + + Activate - + Activer Import... - + Importer... Export... - + Exporter... Clear - Vider + Vider Reset - + Réinitialiser Name - + Nom Type - Type + Type Activation - + Activation Instructions - + Instructions Memory Scanner - + Scanner de mémoire Address - + Adresse Value - Valeur + Valeur Previous Value - + Valeur précédente Search Parameters - + Paramètres de recherche Value: - + Valeur : Signed - + Signé Unsigned - + Non-signé Decimal - + Décimal Hex - + Hex Data Size: - + Taille de donnée : Byte (1 byte) - + Byte (1 byte) Halfword (2 bytes) - + Demi-mot (2 bytes) Word (4 bytes) - + Mot (4 bytes) Operator: - + Opérateur : Equal to... - + Équivaut à... Not Equal to... - + N'équivaut pas à... Greater Than... - + Supérieur à... Greater or Equal... - + Supérieur ou égal... Less Than... - + Inférieur à... Less or Equal... - + Inférieur ou égal... Increased By... - + Augmenté par... Decreased By... - + Diminué par... Changed By... - + Changé par... Equal to Previous (Unchanged Value) - + Équivaut au précédent (valeur inchangée) Not Equal to Previous (Changed Value) - + Non-équivalent au précédent (valeur changée) Greater Than Previous - + Supérieur au précédent Greater or Equal to Previous - + Supérieur ou égal au précédent Less Than Previous - + Inférieur au précédent Less or Equal to Previous - + Inférieur ou égal ou précédent Any Value - + Toute valeur Start Address: - + Adresse de début : End Address: - + Adresse de fin : Preset Range: - + Plage prédéfinie : RAM - + RAM Scratchpad - + Bloc-note BIOS - + BIOS New Search - + Nouvelle recherche Search Again - + Chercher à nouveau Clear Results - + Nettoyer les résultats Add Selected Results To Watch List - + Ajouter les résultats sélectionnés à la liste de surveillance Number of Results (Display limited to first 5000) : - + Nombre de résultats (limite d'affichage au premiers 5000) : 0 - 100% {0?} + 0 Simple Cheat Code or Description - + Code de triche simple ou description Freeze - + Geler Remove Selected Entries from Watch List - + Supprimer les entrées sélectionnées de la liste de surveillance Add Manual Address - + Ajouter une adresse manuelle Load Watch - + Charger une surveillance Save Watch - + Sauvegarder une surveillance - + Byte - + Byte - + Halfword - + Demi-mot - + Word - + Mot - + Signed Byte - + Byte signé - + Signed Halfword - + Demi-mot signé - + Signed Word - + Mot signé - + Toggle - + Basculer - + Add Group - + Ajouter groupe - + Group Name: - + Nom de groupe : - - - - + + + + Error - Erreur + Erreur - + This group name already exists. - + Ce nom de groupe existe déjà. - + Delete Code - + Supprimer le code - + Are you sure you wish to delete the selected code? This action is not reversible. - + Êtes-vous sûr de vouloir supprimer le code sélectionné ? Cette action est irréversible. - + From File... - Depuis le Fichier... + Depuis le fichier... - + From Text... - + Depuis le texte... - + PCSXR/Libretro Cheat Files (*.cht *.txt);;All Files (*.*) - + Fichiers de triche PCSXR/Libretro (*.cht *.txt);;Tous les fichiers (*.*) - - + + Import Cheats - + Importer les codes de triche - - + + Failed to parse cheat file. The log may contain more information. - + Échec d'analyse du fichier de triche. La log peut contenir plus d'informations. - + Cheat File Text: - + Texte du fichier de triche : - + PCSXR Cheat Files (*.cht);;All Files (*.*) - + Fichiers de triche PCSXR/Libretro (*.cht *.txt);;Tous les fichiers (*.*) - + Export Cheats - + Exporter les codes de triche - + Failed to save cheat file. The log may contain more information. - + Échec de la sauvegarde du fichier de triche. La log peut contenir plus d'informations. - + Confirm Clear - + Confirmation de nettoyage - + Are you sure you want to remove all cheats? This is not reversible. - + Étes-vous sûr de vouloir supprimer tous les codes de triche ? Ceci est irréversible. - + Confirm Reset - + Confirmation de réinitialisation - + Are you sure you want to reset the cheat list? Any cheats not in the DuckStation database WILL BE LOST. - + Étes-vous sûr de vouloir réinitialiser la liste de triche ? Tout code de triche absent de la base de données de DuckStation SERA PERDU. - + Enter manual address: - + Entrer une adresse manuelle : - + Select data size: - + Sélectionne la taille de donnée : Cheats - + Gameshark - + Gameshark - + Manual - + Manuel - + Automatic (Frame End) - + Automatique (image de fin) + + + + ColorPickerButton + + + Select LED Color + Sélectionner la couleur de LED + + + + CommonHost + + + Default Output Device + Périphérique de sortie par défaut @@ -1958,30 +2187,30 @@ Leaderboard Position: {} of {} L'état en cours sera sauvegardé. - + Invalid version %u (%s version %u) - + Version invalide %u (%s version %u) ConsoleRegion - + Auto-Detect Détection automatique - + NTSC-J (Japan) NTSC-J (Japon) - + NTSC-U/C (US, Canada) NTSC-U/C (US, Canada) - + PAL (Europe, Australia) PAL (Europe, Australie) @@ -2019,7 +2248,7 @@ Leaderboard Position: {} of {} - + Enable Clock Speed Control (Overclocking/Underclocking) Activer le contrôle de la fréquence de l'horloge du CPU (Overclocking/Underclocking) @@ -2039,7 +2268,7 @@ Leaderboard Position: {} of {} - + Enable Region Check Activer la vérification de la région @@ -2051,23 +2280,23 @@ Leaderboard Position: {} of {} Read Speedup: - Accélérer la lecture: + Accélérer la lecture : - + Enable 8MB RAM (Dev Console) - + Activer la RAM 8MB (console de dév) - + Enable Recompiler ICache - Activer le recompileur ICache + Activer le recompilateur ICache - + None (Double Speed) Aucune (Vitesse 2x) @@ -2119,164 +2348,166 @@ Leaderboard Position: {} of {} Seek Speedup: - + Accélération de recherche : Infinite/Instantaneous - + Infini/Instantané - + None (Normal Speed) - + Aucune (vitesse normale) 2x - 2x + 2x 3x - 100% {3x?} + 3x 4x - 4x + 4x 5x - 100% {5x?} + 5x 6x - 100% {6x?} + 6x 7x - 7x + 7x 8x - 8x + 8x 9x - 9x + 9x 10x - 10x + 10x - + Apply Image Patches - + Appliquer les patchs d'image Async Readahead: - + Lecture anticipée asynchrone : - - + + Preload Image to RAM Précharger l'image disque dans la RAM - - - - - + + + + + Unchecked Décoché - + Disabled (Synchronous) - + Désactivée (synchrone) - + %1 sectors (%2 KB / %3 ms) - + %1 secteurs (%2 KB / %3 ms) - + Region - Région + Région - + Auto-Detect - Détection automatique + Détection automatique - + Determines the emulated hardware type. - + Détermine le type de matériel émulé. - + Execution Mode - + Mode d'exécution - + Recompiler (Fastest) - Recompileur '(Le plus rapide) + Recompilateur (le + rapide) - + Determines how the emulated CPU executes instructions. - + Détermine comment le processeur émulé exécute les instructions. - + When this option is chosen, the clock speed set below will be used. - + Quand cette option est choisie, la vitesse d'horloge ci-dessous sera utilisée. - + Overclocking Percentage - + Pourcentage d'overclocking - + 100% - 100% + 100% - + Selects the percentage of the normal clock speed the emulated hardware will run at. - + Le pourcentage [...] auquel + Sélectionne le pourcentage de la vitesse normale d'horloge auquel le matériel émulé tournera. - + Simulates stalls in the recompilers when the emulated CPU would have to fetch instructions into its cache. Makes games run closer to their console framerate, at a small cost to performance. Interpreter mode always simulates the instruction cache. - + Cf. https://fr.wikipedia.org/wiki/Bulle_(informatique) + Simule des bulles dans le recompilateur quand le processeur émulé a à récupérer les instructions vers son cache. Rapproche les jeux de leur taux d'image console, au coût d'un léger impact des performances. Le mode interpréteur simule toujours le cache d'instruction. - + Enables an additional 6MB of RAM to obtain a total of 2+6 = 8MB, usually present on dev consoles. Games have to use a larger heap size for this additional RAM to be usable. Titles which rely on memory mirrors may break, so it should only be used with compatible mods. - + Active un ajout de 6MB de RAM pour obtenir un total de 2+6 = 8MB, habituellement présent sur les consoles de dév. Les jeux doivent utiliser un tas plus large pour que cette RAM additionnelle soit utilisable. Les titres qui s'appuient sur les miroirs mémoire peuvent casser, cela ne devrait être seulement utilisé qu'avec les mods compatibles. - - + + Loads the game image into RAM. Useful for network paths that may become unreliable during gameplay. In some cases also eliminates stutter when games initiate audio track playback. Chargement de l'image du jeu dans la RAM. Utile pour les chemins réseau qui peuvent devenir peu fiables pendant le jeu. Dans certains cas, élimine également les saccades lorsque les jeux lancent la lecture de la piste audio. @@ -2289,52 +2520,53 @@ Leaderboard Position: {} of {} Aucune (Vitesse 2x) - + Speeds up CD-ROM reads by the specified factor. Only applies to double-speed reads, and is ignored when audio is playing. May improve loading speeds in some games, at the cost of breaking others. Accélère la lecture des CD-ROM selon le facteur spécifié. S'applique uniquement aux lectures à double vitesse, et est ignoré lorsque le son est en cours de lecture. Peut améliorer la vitesse de chargement dans certains jeux, au prix de blocages dans d'autres jeux. - + CD-ROM Seek Speedup - + Accélération de la recherche CD-ROM - + Reduces the simulated time for the CD-ROM sled to move to different areas of the disc. Can improve loading times, but crash games which do not expect the CD-ROM to operate faster. - + Réduit le temps simulé pour la tête de lecture de CD-ROM pour se déplacer entre les différentes zones du disque. Peut améliorer les temps de chargement, mais plante les jeu qui ne s'attendent pas à ce que le CD-ROM aille plus vite. - + Asynchronous Readahead - + Lecture anticipée asynchrone - + 8 Sectors - + 8 secteurs - + Reduces hitches in emulation by reading/decompressing CD data asynchronously on a worker thread. Higher sector numbers can reduce spikes when streaming FMVs or audio on slower storage or when using compression formats such as CHD. - + Cf. https://fr.wikipedia.org/wiki/Thread_(informatique) + Réduit les accrocs de l'émulation en lisant/décompressant les données CD de façon asynchrone sur le fil de travail. Un plus grand nombre de secteurs peut réduire les pics pendant la lecture de FMVs ou de l'audio sur les stockages lents ou les formats de compression comme CHD. - + Checked - Coché + Coché - + Simulates the region check present in original, unmodified consoles. - + Simule la vérification de région présente sur les consoles d'origine, non-modifiées. - + Automatically applies patches to disc images when they are present in the same directory. Currently only PPF patches are supported with this option. - + Applique automatiquement les patchs aux images disque quand ils sont présents dans le même répertoire. Seuls les patchs PPF sont actuellement supportés avec cette option. - + Enabling CPU overclocking will break games, cause bugs, reduce performance and can significantly increase system requirements. By enabling this option you are agreeing to not create any bug reports unless you have confirmed the bug also occurs with overclocking disabled. @@ -2347,27 +2579,27 @@ En activant cette option, vous acceptez de ne créer aucun rapport de bogue à m Cet avertissement ne sera affiché qu'une seule fois. - + Yes, I will confirm bugs without overclocking before reporting. Oui, je confirmerai que les bogues sont sans overclocking avant de les signaler. - + No, take me back to safety. Non, je veux revenir en arrière. - + CPU Overclocking Warning Avertissement sur l'overclocking du CPU - + CD-ROM Read Speedup - + Accélération de lecture CD-ROM - + %1% (%2MHz) %1% (%2MHz) @@ -2377,54 +2609,54 @@ Cet avertissement ne sera affiché qu'une seule fois. Form - Formulaire + Formulaire Controller Type - + Type de contrôleur Bindings - + Mappage des boutons Settings - + Paramètres Macros - + Macros - + Automatic Mapping - + Mappage automatique - + Clear Mapping - + Vider les mappages - + No devices available - + Pas de périphérique disponible - + Are you sure you want to clear all mappings for this controller? This action cannot be undone. - + Êtes-vous sûr de vouloir vider tous les mappages pour ce contrôleur ? Cette action ne peut être annulée. - - No generic bindings were generated for device '%1' - + + No generic bindings were generated for device '%1'. The controller/source may not support automatic mapping. + Aucun mapage générique généré pour le périphérique '%1'. Le contrôleur/source peut ne pas supporter le mappage automatique. @@ -2432,19 +2664,19 @@ Cet avertissement ne sera affiché qu'une seule fois. Form - Formulaire + Formulaire D-Pad - + D-Pad Down - Bas + Bas @@ -2475,118 +2707,122 @@ Cet avertissement ne sera affiché qu'une seule fois. PushButton - + PousserBouton Left - Gauche + Gauche Up - Haut + Haut Right - Droite + Droite Left Analog - + Stick analogique gauche Large Motor - + Par rapport à "petit moteur" + Gros moteur Select - Sélectionner + Nom de la touche + Select L1 - L1 + L1 R1 - R1 + R1 R2 - R2 + R2 L2 - L2 + L2 Start - Start + Nom de la touche + Start Face Buttons - + Boutons de face Cross - Croix + Croix Square - Carré + Carré Triangle - Triangle + Triangle Circle - Rond + Rond Right Analog - + Stick analogique droit Small Motor - + Petit moteur R3 - R3 + R3 Analog - Analogique + Nom du bouton + Analog L3 - L3 + L3 @@ -2594,19 +2830,19 @@ Cet avertissement ne sera affiché qu'une seule fois. Form - Formulaire + Formulaire D-Pad - + D-Pad Down - Bas + Bas @@ -2635,117 +2871,115 @@ Cet avertissement ne sera affiché qu'une seule fois. PushButton - + PousserBouton Left - Gauche + Gauche Up - Haut + Haut Right - Droite + Droite Left Analog - + Stick analogique gauche L2 - L2 + L2 L1 - L1 + L1 R2 - R2 + R2 Start - Start + Start R1 - R1 + R1 Select - Sélectionner + Select Face Buttons - + Boutons de face Cross - Croix + Croix Square - Carré + Carré Triangle - Triangle + Triangle Circle - Rond + Rond Right Analog - + Stick analogique droit R3 - R3 + R3 L3 - L3 + L3 Mode - Mode + Mode ControllerBindingWidget_Base - - %1% - %1% + %1% @@ -2753,12 +2987,12 @@ Cet avertissement ne sera affiché qu'une seule fois. Form - Formulaire + Formulaire L1 - L1 + L1 @@ -2776,82 +3010,82 @@ Cet avertissement ne sera affiché qu'une seule fois. PushButton - + PousserBouton L2 - L2 + L2 R2 - R2 + R2 R1 - R1 + R1 Face Buttons - + Boutons de face Cross - Croix + Croix Square - Carré + Carré Triangle - Triangle + Triangle Circle - Rond + Rond D-Pad - + D-Pad Down - Bas + Bas Left - Gauche + Gauche Up - Haut + Haut Right - Droite + Droite Select - Sélectionner + Select Start - Start + Start @@ -2859,17 +3093,17 @@ Cet avertissement ne sera affiché qu'une seule fois. Form - Formulaire + Formulaire Side Buttons - + Boutons de côté B - B + B @@ -2877,27 +3111,27 @@ Cet avertissement ne sera affiché qu'une seule fois. PushButton - + PousserBouton A - A + A Trigger - Gâchette + Gâchette Fire Offscreen - + Feu hors-écran Fire - + Feu @@ -2905,28 +3139,28 @@ Cet avertissement ne sera affiché qu'une seule fois. Form - Formulaire + Formulaire Buttons - + Boutons Left - Gauche + Gauche PushButton - + PousserBouton Right - Droite + Droite @@ -2934,17 +3168,17 @@ Cet avertissement ne sera affiché qu'une seule fois. Form - Formulaire + Formulaire D-Pad - + D-Pad Down - Bas + Bas @@ -2961,97 +3195,97 @@ Cet avertissement ne sera affiché qu'une seule fois. PushButton - + PousserBouton Left - Gauche + Gauche Up - Haut + Haut Right - Droite + Droite Start - Start + Start L - L + L R - R + R Face Buttons - + Boutons de face I - I + I II - II + II B - B + B A - A + A Steering/Twist - + Direction/Torsion - + %1% - %1% + %1% ControllerCustomSettingsWidget - + %1 Settings - + Paramètres de %1 - + Restore Default Settings - + Restaurer les paramètres par défaut - + Browse... - Parcourir... + Parcourir... - + Select File - Sélectionner un Fichier + Sélectionner un fichier @@ -3059,154 +3293,153 @@ Cet avertissement ne sera affiché qu'une seule fois. Form - Formulaire + Formulaire - + Controller Multitap - + Contrôleur multiprise - + The multitap enables up to 8 controllers to be connected to the console. Each multitap provides 4 ports. Multitap is not supported by all games. - + La multiprise permet de brancher jusqu'à 8 contrôleurs à la console. Chaque multiprise fournit 4 ports. La multiprise n'est pas supportée pas tous les jeux. - + Multitap Mode: - + Mode multiprise : - + Disabled - + Désactivé - + Enable on Port 1 Only - + Actif sur le port 1 uniquement - + Enable on Port 2 Only - + Actif sur le port 2 uniquement - + Enable on Ports 1 and 2 - + Actif sur les ports 1 et 2 - + DInput Source - + Source DInput - + The DInput source provides support for legacy controllers which do not support XInput. Accessing these controllers via SDL instead is recommended. - + La source DInput fournit un support pour les contrôleurs qui ne supportent pas XInput. Passer plutôt par SDL pour ces contrôleurs est recommandé. - + Enable DInput Input Source - + Activer la source d'entrée DInput - + SDL Input Source - + Source d'entrée SDL - + The SDL input source supports most controllers, and provides advanced functionality for DualShock 4 / DualSense pads in Bluetooth mode (Vibration / LED Control). - + La source d'entrée SDL supporte la plupart des contrôleurs, et fournit des fonctionnalités avancées pour DualShock 4 / manettes DualSense en mode Bluetooth (Vibration / contrôle LED). - + Enable SDL Input Source - + Activer la source d'entrée SDL - + DualShock 4 / DualSense Enhanced Mode - + Mode amélioré DualShock 4 / DualSense - + Detected Devices - + Périphériques détectés - + Mouse/Pointer Source - + Souris/Source de pointeur - - + + 10 - 100% {10?} + 10 - - - Invert - - - - + Using raw input improves precision when you bind controller sticks to the mouse pointer. Also enables multiple mice to be used. - + Utiliser l'entrée brute améliore la précision quand vous assignez les sticks du contrôleur au poiteur de souris. Autorise l'usage de multiples souris. - + Vertical Sensitivity: - + Sensitivité verticale : - + Horizontal Sensitivity: - + Sensitivité horizontale : - + Enable Mouse Mapping - + Activer le mappage de la souris - + Use Raw Input - + Utiliser l'entrée brute - + XInput Source - + Source XInput - + + Controller LED Settings + Paramètres des LED de la manette + + + The XInput source provides support for XBox 360 / XBox One / XBox Series controllers, and third party controllers which implement the XInput protocol. - + La source XInput fournit le support pour les manettes XBox 360 / XBox One / XBox Series, et les manettes tierces qui implémentent le protocole XInput. - + Enable XInput Input Source - + Activer la source d'entrée XInput - + Profile Settings - + Paramètres de profil - + When this option is enabled, hotkeys can be set in this input profile, and will be used instead of the global hotkeys. By default, hotkeys are always shared between all profiles. - + Quand cette option est active, des raccourcis peuvent être définis dans ce profil d'entrée, et seront utilisés à la place des raccourcis globaux. Par défaut, les raccourcis sont toujours partagés entre tous les profils. - + Use Per-Profile Hotkeys - + Utiliser des raccourcis par profil @@ -3224,91 +3457,122 @@ Cet avertissement ne sera affiché qu'une seule fois. XInput + + ControllerLEDSettingsDialog + + + Controller LED Settings + Paramètres des LED du contrôleur + + + + SDL-0 LED + SDL-0 LED + + + + SDL-1 LED + SDL-1 LED + + + + SDL-2 LED + SDL-2 LED + + + + SDL-3 LED + SDL-3 LED + + ControllerMacroEditWidget Form - Formulaire + Formulaire Binds/Buttons - + Mappages/Boutons Select the buttons which you want to trigger with this macro. All buttons are activated concurrently. - + Sélectionnez les boutons que vous voulez déclencher avec cette macro. Tous les boutons sont activés en même temps. Trigger - Gâchette + Différent du trigger de la manette + Déclencheur Select the trigger to activate this macro. This can be a single button, or combination of buttons (chord). Shift-click for multiple triggers. - + Chord ? + Sélectionnez le déclencheur qui active la macro. Cela peut être un simple bouton, ou une combinaison de boutons (chord). Shift-clic pour plusieurs déclencheurs. PushButton - + PousserBouton Frequency - + Fréquence Macro will toggle every N frames. - + La macro basculera toutes les N images. Set... - + Définir... - + Not Configured - + Non-configuré - + Set Frequency - + Définir la fréquence - + Frequency: - + Fréquence : - + Macro will not repeat. - + La macro ne se répètera pas. - + Macro will toggle buttons every %1 frames. - + La macro basculera les boutons toutes les %1 images. ControllerMacroWidget - + Controller Port %1 Macros - + Macros du port contrôleur %1 - + Macro %1 %2 - + Macro %1 +%2 @@ -3316,141 +3580,153 @@ Cet avertissement ne sera affiché qu'une seule fois. Controller Settings - + Paramètres du contrôleur Profile: - + Profil : New Profile - + Nouveau profil Load Profile - Charger un Profil + Charger profil Delete Profile - + Supprimer profil - + Restore Defaults - + Restaurer par défaut - - + + Create Input Profile - + Créer un profil d'entrée - + Enter the name for the new input profile: - + Entrez le nom pour le nouveau profil d'entrée : - - - - + + + + Error - Erreur + Erreur - + A profile with the name '%1' already exists. - + Un profil du nom '%1' existe déjà. - + Do you want to copy all bindings from the currently-selected profile to the new profile? Selecting No will create a completely empty profile. - + Voulez-vous copier tous les mappages depuis le profil sélectionné vers le nouveau profil ? Sélectionner Non créera un profil totalement vide. - + Failed to save the new profile to '%1'. - + Échec de sauvegarde du nouveau profil vers '%1'. - + Load Input Profile - + Charger profil d'entrée - + Are you sure you want to load the input profile named '%1'? All current global bindings will be removed, and the profile bindings loaded. You cannot undo this action. - + Êtes-vous sûr de vouloir charger le profil d'entrée nommé '%1' ? + +Tous les mappages globaux en cours seront supprimés, et les mappages du profil par défaut chargés. + +Vous ne pourrez pas annuler cette action. - + Delete Input Profile - + Supprimer profil d'entrée - + Are you sure you want to delete the input profile named '%1'? You cannot undo this action. - + Êtes-vous sûr de vouloir supprimer le profil d'entrée nommé '%1' ? + +Vous ne pourrez pas annuler cette action. - + Failed to delete '%1'. - + Échec de suppression de '%1'. - + Are you sure you want to restore the default controller configuration? All shared bindings and configuration will be lost, but your input profiles will remain. You cannot undo this action. - + Êtes-vous sûr de vouloir restaurer la configuration par défaut du contrôleur ? + +Tous les mappages et configurations partagés seront perdus, mais les profils d'entrée resteront. + +Vous ne pourrez pas annuler cette action. - + Global Settings - + Paramètres globaux - - + + Controller Port %1%2 %3 - + Port contrôleur %1%2 +%3 - - + + Controller Port %1 %2 - + Port contrôleur %1 +%2 - + Hotkeys - + Raccourcis - + Shared - + Partagé - + The input profile named '%1' cannot be found. - + Le profil d'entrée nommé '%1' est introuvable. @@ -3539,159 +3815,219 @@ You cannot undo this action. ControllerType - + None Aucun - - + + Digital Controller Sony Entertainment classe le contrôleur en 'classique' en français. Contrôleur classique - + Analog Controller (DualShock) Contrôleur analogique (DualShock) - - + + Analog Joystick - + Joystick analogique Namco GunCon Namco GunCon - - + + PlayStation Mouse Marque déposée, on ne change pas avec la traduction PlayStation Mouse - - + + NeGcon NeGcon - + Analog Controller - + Contrôleur analogique - - + + GunCon - + GunCon + + + + Not Connected + Non-connecté + + + + CoverDownloadDialog + + + Download Covers + Télécharger des jaquettes + + + + DuckStation can automatically download covers for games which do not currently have a cover set. We do not host any cover images, the user must provide their own source for images. + DuckStation peut automatiquement télécharger des jaquettes pour les jeux qui n'en ont pas actuellement définie. Nous n'hébergeons aucune jaquette, l'utilisateur doit fournir sa propre source d'images. + + + + <html><head/><body><p>In the box below, specify the URLs to download covers from, with one template URL per line. The following variables are available:</p><p><span style=" font-style:italic;">${title}:</span> Title of the game.<br/><span style=" font-style:italic;">${filetitle}:</span> Name component of the game's filename.<br/><span style=" font-style:italic;">${serial}:</span> Serial of the game.</p><p><span style=" font-weight:700;">Example:</span> https://www.example-not-a-real-domain.com/covers/${serial}.jpg</p></body></html> + Attention à ne pas traduire les variables ! + <html><head/><body><p>Dans la boîte ci-dessous, spécifiez les URLs depuis lesquelles télécharger des jaquettes, avec une URL de modèle par ligne. Les variables suivantes sont disponibles :</p><p><span style=" font-style:italic;">${title}:</span> Titre du jeu.<br/><span style=" font-style:italic;">${filetitle}:</span> Nom composant le nom de fichier du jeu.<br/><span style=" font-style:italic;">${serial}:</span> Numéro de série du jeu.</p><p><span style=" font-weight:700;">Exemple :</span> https://www.example-not-a-real-domain.com/covers/${serial}.jpg</p></body></html> + + + + By default, the downloaded covers will be saved with the game's title. If this is not desired, you can check the "Use Serial File Names" box below. Using serials instead of game titles will prevent conflicts when multiple regions of the same game are used. + Par défaut, les jaquettes téléchargées sont sauvegardées avec le titre du jeu. Si cela est indésirable, vous pouvez cocher la case ci-dessous "Utiliser les noms de série fichier". Utiliser les noms de série plûtot que les titres de jeux préviendra les conflits quand plusieurs régions pour un même jeu seront utilisées. + + + + Use Serial File Names + Utiliser les noms de série fichier + + + + Waiting to start... + En attente du démarrage... + + + + + Start + Démarrer + + + + Close + Fermer + + + + Download complete. + Téléchargement fini. + + + + Stop + Stop DebuggerCodeModel - - - + + + <invalid> - + <invalid> - + Address - + Adresse - + Bytes - + Bytes - + Instruction - + Instruction - + Comment - + Commentaire DebuggerMessage - + Added breakpoint at 0x%08X. - + Ajouté un point d'arrêt à 0x%08%X. - + Removed breakpoint at 0x%08X. - + Enlevé le point d'arrêt à 0x%08X. - + 0x%08X is not a call instruction. - + 0x%08X n'est pas une instruction d'appel. - + Can't step over double branch at 0x%08X - + Impossible de franchir la double-branche à 0x%08X - + Stepping over to 0x%08X. - + Franchissement vers 0x%08X. - + Instruction read failed at %08X while searching for function end. - + La lecture d'instruction a échoué à %08X pendant la recherche de la fin de fonction. - + Stepping out to 0x%08X. - + Sortie vers 0x%08X. - + No return instruction found after %u instructions for step-out at %08X. - + Aucune instruction de retour trouvée après les instructions %u pour sortir à %08X. DebuggerRegistersModel - + Register - + Registre - + Value - Valeur + Valeur DebuggerStackModel - + <invalid> - + <invalid> - + Address - + Adresse - + Value - Valeur + Valeur @@ -3699,103 +4035,104 @@ You cannot undo this action. CPU Debugger - + Débogueur CPU &Debug - &Débug + &Débug Breakpoints - + Points d'arrêt toolBar - Barre d'Outils + Vu la tête du texte source, préférence à laisser tel-quel + toolBar Disassembly - + Désassembleur Registers - + Registres Memory - + Mémoire RAM - + RAM Scratchpad - + Bloc-notes EXP1 - + EXP1 BIOS - + BIOS Search - + Rechercher # - # + # Address - + Adresse Hit Count - + Nombre de résultats Stack - + Pile Pause/Continue - + Pause/Continuer &Pause/Continue - + &Pause/Continuer F5 - + F5 Step Into - + Pas-à-pas entrant @@ -3805,12 +4142,12 @@ You cannot undo this action. F11 - + F11 Step Over - + Pas-à-pas principal @@ -3820,22 +4157,23 @@ You cannot undo this action. F10 - + F10 Toggle Breakpoint - + Basculer le point d'arrêt + Toggle &Breakpoint F9 - + F9 @@ -3845,7 +4183,7 @@ You cannot undo this action. Step Out - + Pas-à-pas sortant @@ -3855,27 +4193,28 @@ You cannot undo this action. Ctrl+F11 - + Ctrl+F11 Run To Cursor - + Aller au curseur + &Run To Cursor Ctrl+F10 - + Ctrl+F10 Clear Breakpoints - + Supprimer les points d'arrêt @@ -3885,12 +4224,12 @@ You cannot undo this action. Ctrl+Del - + Ctrl+Del Add Breakpoint - + Ajouter un point d'arrêt @@ -3900,7 +4239,7 @@ You cannot undo this action. Ctrl+F9 - + Ctrl+F9 @@ -3915,116 +4254,127 @@ You cannot undo this action. Ctrl+P - + Ctrl+P Go To Address - + Aller à l'adresse Go To &Address - + Aller à l'&adresse Ctrl+G - + Ctrl+G &Dump Address - + &Décharger l'adresse Ctrl+D - + Ctrl+D Trace - Traceur + Trace &Trace - + &Trace Ctrl+T - + Ctrl+T - + No address selected. - + Aucune adresse sélectionnée. - - + + Enter code address: - + Entrer un code adresse : - - + + Enter memory address: - + Entrer une adresse mémoire : - + Trace logging started to cpu_log.txt. This file can be several gigabytes, so be aware of SSD wear. - + L'enregistrement de la trace a débuté vers cpu_log.txt. +Ce fichier peut faire plusieurs gigaoctets, alors attention à l'usure SSD. - + Trace logging to cpu_log.txt stopped. - + L'enregistrement vers cpu_log.tx s'est arrêté. - + A breakpoint already exists at this address. - + Un point d'arrêt existe déjà à cette adresse. - + Debugger - + Débogueur - + Failed to add step-out breakpoint, are you in a valid function? - + Échec de l'ajout d'un point d'arrêt de pas-à-pas sortant, êtes-vous dans une fonction valide ? - - + + View in &Dump + Voir dans la &décharge + + + + Follow Load/Store + Suivre le chargement/enregistrement + + + + Invalid search pattern. It should contain hex digits or question marks. - + Motif de recherche invalide. Il devrait contenir des chiffres héxa ou des points d'interrogation. - + Pattern not found. - + Motif non-trouvé. - + Pattern found at 0x%1 (passed the end of memory). - + Motif trouvé à l'adresse 0x%1 (passé la fin de mémoire). - + Pattern found at 0x%1. - + Motif trouvé à l'adresse 0x%1. - + Invalid address. It should be in hex (0x12345678 or 12345678) - + Adresse invalide. Elle devrait être en héxa (0x12345678 ou 12345678) @@ -4086,71 +4436,94 @@ This file can be several gigabytes, so be aware of SSD wear. R2 - + Force Pop'n Controller Mode - Forcer le Mode Contrôleur Pop'n + Forcer le mode contrôleur Pop'n - + Forces the Digital Controller to act as a Pop'n Controller. - + Force le contrôleur digital à se comporter comme un contrôleur Pop'n. DiscRegion - + NTSC-J (Japan) NTSC-J (Japon) - + NTSC-U/C (US, Canada) NTSC-U/C (US, Canada) - + PAL (Europe, Australia) PAL (Europe, Australie) - + Other Autre + + + Non-PS1 + Non-PS1 + + + + DisplayAlignment + + + Left / Top + Gauche / Haut + + + + Center + Centre + + + + Right / Bottom + Droit / Bas + DisplayAspectRatio - + Auto (Game Native) - + Auto (natif) - + Auto (Match Window) - + Auto (dimensions fenêtre) - + Custom - + Personnalisé DisplayCropMode - + None Aucun - + Only Overscan Area Zone de surbalayage uniquement - + All Borders Toutes les bordures @@ -4170,112 +4543,111 @@ This file can be several gigabytes, so be aware of SSD wear. Renderer: - Rendu: + Rendu : Adapter: - Adaptateur: + Adaptateur : Fullscreen Mode: - Mode Plein Ecran + Mode plein écran : - - + + Threaded Rendering - + Rendu threadé - - + + Threaded Presentation - + Présentation threadée - - - VSync - Synchro Verticale + + Show GPU Usage + Afficher l'usage GPU - - - Sync To Host Refresh Rate - + + Show Settings Overlay + Afficher les paramètres d'overlay - + - Optimal Frame Pacing - + VSync + Synchro Verticale - + Screen Display - Affichage à l'Ecran + Affichage à l'écran - + Aspect Ratio: - Ratio d'affichage : + Ratio d'affichage : - + : - + : - + Crop: - Rognage: + Rognage : - - Downsampling: - - - - - + + Stretch To Fill - + Étirer pour remplir - - + + Linear Upscaling Upscaling Linéaire - + + Show CPU Usage - + Afficher l'usage CPU - - + + Show Controller Input - + Afficher les entrées contrôleur - - + + Integer Upscaling - Upscaling Complet + Upscaling entier + + + + Position: + Position : - - + + Internal Resolution Screenshots - + Résolution interne des captures d'écran - + On-Screen Display - Affichage à l'Ecran + Affichage à l'écran Show Messages @@ -4283,13 +4655,13 @@ This file can be several gigabytes, so be aware of SSD wear. - + Show FPS - Afficher les FPS + Afficher les IPS - - + + Show Emulation Speed Afficher la vitesse d'émulation @@ -4298,171 +4670,155 @@ This file can be several gigabytes, so be aware of SSD wear. Afficher les VPS - - + + Show Resolution - Afficher la Résolution + Afficher la résolution - + Renderer Rendu - + Chooses the backend to use for rendering the console/game visuals. <br>Depending on your system and hardware, Direct3D 11 and OpenGL hardware backends may be available. <br>The software renderer offers the best compatibility, but is the slowest and does not offer any enhancements. - Choisit le moteur vidéo à utiliser pour le rendu des visuels de la console/du jeu. <br>Selon votre système et votre matériel, des moteurs matériels Direct3D 11 et OpenGL peuvent être disponibles. <br>Le rendu logiciel offre la meilleure compatibilité, mais est le plus lent et n'offre aucune amélioration possible. + Choisis le moteur vidéo à utiliser pour le rendu des visuels de la console/du jeu. <br>Selon votre système et votre matériel, des moteurs matériels Direct3D 11 et OpenGL peuvent être disponibles. <br>Le rendu logiciel offre la meilleure compatibilité, mais est le plus lent et n'offre aucune amélioration possible. - + Adapter Adaptateur - - + + (Default) (Défaut) - + If your system contains multiple GPUs or adapters, you can select which GPU you wish to use for the hardware renderers. <br>This option is only supported in Direct3D and Vulkan. OpenGL will always use the default device. Si votre système contient plusieurs GPU ou adaptateurs, vous pouvez sélectionner le GPU que vous souhaitez utiliser pour les rendus matériels. Cette option n'est prise en charge que dans Direct3D et Vulkan, OpenGL utilisera toujours le périphérique par défaut. - + Fullscreen Mode - + Mode plein écran - + Chooses the fullscreen resolution and frequency. - + Choisis la résolution plein écran et la fréquence. - + Aspect Ratio - Ratio d'affichage + Ratio d'affichage + + + + Position + Position + + + + Determines the position on the screen when black borders must be added. + Détermine la position à l'écran quand des bordures noires doivent être ajoutées. Changes the aspect ratio used to display the console's output to the screen. The default is 4:3 which matches a typical TV of the era. Modifie le format d'image utilisé pour afficher la sortie de la console sur l'écran. Le rapport par défaut est de 4:3, ce qui correspond à un téléviseur typique de l'époque. - + Changes the aspect ratio used to display the console's output to the screen. The default is Auto (Game Native) which automatically adjusts the aspect ratio to match how a game would be shown on a typical TV of the era. - + Change le ratio d'affichage utilisé pour afficher la sortie console à l'écran. Par défaut Auto(natif), qui ajuste automatiquement le ratio d'affichage pour correspondre à la façon un jeu s'affichait sur les TVs de cette période. - + Crop Mode - Mode Rognage + Mode rognage Only Overscan Area Zone de Surbalayage uniquement - + Determines how much of the area typically not visible on a consumer TV set to crop/hide. <br>Some games display content in the overscan area, or use it for screen effects. <br>May not display correctly with the "All Borders" setting. "Only Overscan" offers a good compromise between stability and hiding black borders. Détermine la partie de la zone qui n'est généralement pas visible sur un téléviseur grand public et qui doit être coupée/cachée. Certains jeux affichent le contenu dans la zone de surbalayage, ou l'utilisent pour des effets d'écran et peuvent ne pas s'afficher correctement avec le paramètre "Toutes les bordures". Seul le surbalayage offre un bon compromis entre la stabilité et le masquage des bordures noires. - - Downsampling - - - - - Disabled - - - - - Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, but should be disabled for pure 3D games. Only applies to the hardware renderers. - - - - - - - - + + + + + Checked Coché - + Uses bilinear texture filtering when displaying the console's framebuffer to the screen. <br>Disabling filtering will producer a sharper, blockier/pixelated image. Enabling will smooth out the image. <br>The option will be less noticable the higher the resolution scale. Utilise un filtrage de texture bilinéaire lors de l'affichage du tampon image de la console à l'écran. La désactivation du filtrage produira une image plus nette, plus bloquante et plus pixelisée. L'activation du filtrage rendra l'image plus lisse. L'option sera moins notable plus la résolution sera élevée. - - - - + + + + + - - - + Unchecked Décoché - + Adds padding to the display area to ensure that the ratio between pixels on the host to pixels in the console is an integer number. <br>May result in a sharper image in some 2D games. Ajoute du remplissage à la zone d'affichage pour que le rapport entre les pixels sur l'hôte et les pixels dans la console soit un nombre entier. Peut donner une image plus nette dans certains jeux en 2D. - + Fills the window with the active display area, regardless of the aspect ratio. - + Remplis la fenêtre avec la zone d'affichage active, peu importe le ratio d'affichage. - + Saves screenshots at internal render resolution and without postprocessing. If this option is disabled, the screenshots will be taken at the window's resolution. Internal resolution screenshots can be very large at high rendering scales. - - - - - Enable this option to match DuckStation's refresh rate with your current monitor or screen. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). - Activer cette option pour faire correspondre le taux de rafraîchissement de DuckStation avec votre moniteur ou écran actuel. VSync est automatiquement désactivé lorsque ce n'est pas possible (par exemple, si la vitesse n'est pas de 100 %). + Sauvegarde les captures d'écran à la résolution interne et sans postprocessing. Si cette option est désactivée, les captures d'écran seront prises à la résolution de la fenêtre. Les captures d'écran en résolution interne peuvent être très volumineuses avec les grandes échelles de rendu. - Enable this option will ensure every frame the console renders is displayed to the screen, for optimal frame pacing. If you are having difficulties maintaining full speed, or are getting audio glitches, try disabling this option. - + Enable this option to match DuckStation's refresh rate with your current monitor or screen. VSync is automatically disabled when it is not possible (e.g. running at non-100% speed). + Activer cette option pour faire correspondre le taux de rafraîchissement de DuckStation avec votre moniteur ou écran actuel. La VSync est automatiquement désactivée lorsque ce n'est pas possible (càd si la vitesse n'est pas de 100%). - + Presents frames on a background thread when fast forwarding or vsync is disabled. This can measurably improve performance in the Vulkan renderer. - + Présente les images via un thread d'arrière-plan quand l'avance rapide ou la vsync sont désactivées. Cela peut sensiblement améliorer les performances avec le rendu Vulkan. - + Uses a second thread for drawing graphics. Currently only available for the software renderer, but can provide a significant speed improvement, and is safe to use. - + Utilise un second thread de dessin graphique. Actuellement seulement disponible pour le rendu logiciel, mais peut fournir une amélioration significative de la vitesse, et est sûr à utiliser. - - Adjusts the emulation speed so the console's refresh rate matches the host's refresh rate when both VSync and Audio Resampling settings are enabled. This results in the smoothest animations possible, at the cost of potentially increasing the emulation speed by less than 1%. Sync To Host Refresh Rate will not take effect if the console's refresh rate is too far from the host's refresh rate. Users with variable refresh rate displays should disable this option. - - - - - + + Show OSD Messages Afficher les Messages OSD - + Shows on-screen-display messages when events occur such as save states being created/loaded, screenshots being taken, etc. Affiche des messages à l'écran lorsque des événements se produisent, tels que la création/le chargement des sauvegardes d'états, les captures d'écran, etc. - + Shows the internal frame rate of the game in the top-right corner of the display. Affiche la fréquence d'images interne du jeu dans le coin supérieur droit de l'écran. @@ -4475,36 +4831,41 @@ This file can be several gigabytes, so be aware of SSD wear. Afficher la Vitesse - + Shows the current emulation speed of the system in the top-right corner of the display as a percentage. Indique en pourcentage la vitesse d'émulation actuelle du système dans le coin supérieur droit de l'écran. - + Shows the resolution of the game in the top-right corner of the display. - + Affiche la résolution du jeu dans le coin supérieur-droit de l'affichage. + + + + Shows the host's CPU usage based on threads in the top-right corner of the display. This does not display the emulated system CPU's usage. If a value close to 100% is being displayed, this means your host's CPU is likely the bottleneck. In this case, you should reduce enhancement-related settings such as overclocking. + Affichage l'usage du CPu de l'hôte en se basant sur les threads dans le coin supérieur droit de l'affichage. Cela n'affiche pas l'usage du CPU système émulé. Si une valeur est proche de 100% est affichée, cela signifie que le CPU de l'hôte est probablement le goulôt d'étranglement. Dans ce cas, vous devriez réduire les paramètres liés aux améliorations, comme l'overclocking. - + Shows the current controller state of the system in the bottom-left corner of the display. - + Affiche l'état système du contrôleur courant dans le coin inférieur-gauche de l'affichage. - - + + Use Blit Swap Chain - + Utiliser la chaîne d'échange Blit - + Uses a blit presentation model instead of flipping when using the Direct3D 11 renderer. This usually results in slower performance, but may be required for some streaming applications, or to uncap framerates on some systems. - + Utilise le modèle de présentation blit plutôt que le retournement quand le rendu Direct3D 11 est utilisé. Cela résulte habituellement en des performances plus lentes, mais peut être requis pour certaines applications de streaming, ou pour déplafonner le taux d'image sur certains systèmes. - - + + Borderless Fullscreen - + Plein écran sans bordure @@ -4512,82 +4873,85 @@ This file can be several gigabytes, so be aware of SSD wear. Form - Formulaire + Formulaire <html><head/><body><p><span style=" font-weight:700;">No games in supported formats were found.</span></p><p>Please add a directory with games to begin.</p><p>Game dumps in the following formats will be scanned and listed:</p></body></html> - + <html><head/><body><p><span style=" font-weight:700;">Aucun jeu dans les formats supportés n'a été trouvé.</span></p><p>Veuillez ajouter un répertoire avec des jeux pour commencer.</p><p>Les jeux extraits dans les formats suivants seront scannés et listés :</p></body></html> TextLabel - + TextLabel Add Game Directory... - Ajouter un Répertoire de Jeu... + Ajouter un répertoire de jeu... Scan For New Games - + Scanner pour des nouveaux jeux EmuThread - + Error - Erreur + Erreur - + No resume save state found. - Aucun résumé sur la sauvegarde d'état n'a été trouvé. + Aucun sauvegarde d'état de reprise n'a été trouvée. - + Game ID: %1 Game Title: %2 Achievements: %5 (%6) - + ID du jeu : %1 +Titre du jeu : %2 +Succès : %5 (%6) + - + %n points - - + + %n points - + Rich presence inactive or unsupported. - + Riche presence inactive ou non-supportée. - + Game not loaded or no RetroAchievements available. - + Jeu non-chargé ou RetroAchievements indisponible. - + %1x%2 - + %1x%2 - + Game: %1 FPS - + Jeu : %1 IPS - + Video: %1 FPS (%2%) - + Vidéo : %1 IPS (%2%) @@ -4595,222 +4959,247 @@ Achievements: %5 (%6) Form - Formulaire + Formulaire Speed Control - Contrôle de la Vitesse + Contrôle de la vitesse Emulation Speed: - Vitesse de l’Emulation + Vitesse de l’émulation : Fast Forward Speed: - Vitesse de l'Avance Rapide + Vitesse de l'avance rapide : Turbo Speed: - + Vitesse turbo : - - Rewind/Runahead - + + + Sync To Host Refresh Rate + Sync avec taux rafraîch. hôte + + Optimal Frame Pacing + Rythme optimal d'image + + + + Rewind/Runahead + Laissé tel-quel + Rewind/Runahead + + + Enable Rewinding - + Activer le rewinding - + Rewind Save Frequency: - + Fréquence de sauve : - + Seconds - + secondes - + Rewind Buffer Size: - + Taille du buffer : - + Frames - + images - + Runahead: - + Runahead : - - + + Disabled - + Désactivé - + 1 Frame - + 1 image - + 2 Frames - + 2 images - + 3 Frames - + 3 images - + 4 Frames - + 4 images - + 5 Frames - + 5 images - + 6 Frames - + 6 images - + 7 Frames - + 7 images - + 8 Frames - + 8 images - + 9 Frames - + 9 images - + 10 Frames - + 10 images - + TextLabel - + TextLabel - + Emulation Speed - Vitesse de l'Emulation + Vitesse de l'émulation - + Sets the target emulation speed. It is not guaranteed that this speed will be reached, and if not, the emulator will run as fast as it can manage. - Définit la vitesse d'émulation de la cible. Il n'est pas garanti que cette vitesse sera atteinte, et si ce n'est pas le cas, l'émulateur fonctionnera aussi vite qu'il le pourra. + Définis la vitesse d'émulation de la cible. Il n'est pas garanti que cette vitesse soit atteinte, et si ce n'est pas le cas, l'émulateur fonctionnera aussi vite qu'il le pourra. - + Fast Forward Speed - + Vitesse d'avance rapide - - + + User Preference - + Préférence utilisateur - + Sets the fast forward speed. This speed will be used when the fast forward hotkey is pressed/toggled. - + Définis la vitesse d'avance rapide. Cette vitesse sera utilisée quand le raccourci d'avance rapide sera pressé/basculé. - + Turbo Speed - + Vitesse turbo - + Sets the turbo speed. This speed will be used when the turbo hotkey is pressed/toggled. Turboing will take priority over fast forwarding if both hotkeys are pressed/toggled. - + Définis la vitesse turbo. Cette vitesse sera utilisée quand le raccourci turbo sera pressé/basculé. Le turbo sera prioritaire sur l'avance rapide si les deux raccourcis sont pressés/basculés. - + Rewinding - + Rewinding - + + + Unchecked - Décoché + Décoché - + + Adjusts the emulation speed so the console's refresh rate matches the host's refresh rate when both VSync and Audio Resampling settings are enabled. This results in the smoothest animations possible, at the cost of potentially increasing the emulation speed by less than 1%. Sync To Host Refresh Rate will not take effect if the console's refresh rate is too far from the host's refresh rate. Users with variable refresh rate displays should disable this option. + Ajuste la vitesse d'émulation de façon à ce que le taux de rafraîchissement de la console corresponde à celui de l'hôte quand les options de VSync et rééchantillonnage audio sont activées. Cela résulte en des animations possiblement plus lisses, au coût d'une potentielle augmentation de la vitesse d'émulation inférieure à 1%. Sync. avec le tx de rafraîch. de l'hôte ne prendre effet que si le taux de rafraîchissement de la console est trop éloigné de celui de l'hôte. Les utilisateurs avec un taux de rafraîchissement variable devraient désactiver cette option. + + + + Enable this option will ensure every frame the console renders is displayed to the screen, for optimal frame pacing. If you are having difficulties maintaining full speed, or are getting audio glitches, try disabling this option. + Activer cette option fera en sorte que chaque image que la console rend est affichée à l'écran, pour un frame pacing optimal. Si vous avez des diffultés à maintenant la pleine vitesse, ou que vous avez des glitchs audio, essayez de désactiver cette option. + + + <b>Enable Rewinding:</b> Saves state periodically so you can rewind any mistakes while playing.<br> <b>Rewind Save Frequency:</b> How often a rewind state will be created. Higher frequencies have greater system requirements.<br> <b>Rewind Buffer Size:</b> How many saves will be kept for rewinding. Higher values have greater memory requirements. - + <b>Activer le rewinding :</b> Sauvegarde périodiquement l'état de façon à pouvoir revenir sur ses erreurs en jeu.<br> <b>Fréquence de sauvegarde du rewind :</b> Tous les combien un état sera crée. Les fréquences les plus élevées ont des prérequis systèmes plus grands.<br> <b>Taille du tampon du rewind :</b> Combien de sauvegarde seront conservées pour le rewinding. Les valeurs plus élevées ont des prérequis mémoire plus grands. - + Runahead - + Runahead - + Simulates the system ahead of time and rolls back/replays to reduce input lag. Very high system requirements. - + Simule le système en avance sur le temps et revient en arrière/rejoue pour réduire l'input lag. Très haute configuration système requise. - + Use Global Setting [Unlimited] - + Utiliser le paramètre global [Illimité] - + Use Global Setting [%1%] - + Utiliser le paramètre global [%1%] - + Unlimited - Illimité + Illimité - + %1% [%2 FPS (NTSC) / %3 FPS (PAL)] - + %1% [%2 IPS (NTSC) / %3 IPS (PAL)] - + Rewind for %n frame(s), lasting %1 second(s) will require up to %2MB of RAM and %3MB of VRAM. - - + + Rewind pour %n image(s), durer %1 seconde(s) nécessitera jusqu'à %2MB de RAM et %3MB de VRAM. - + Rewind is disabled because runahead is enabled. Runahead will significantly increase system requirements. - + Le rewind est désactivé parce que le runahead est activé. Runahead augmentera significativement les prérequis système. - + Rewind is not enabled. Please note that enabling rewind may significantly increase system requirements. - + Le rewind est désactivé. Veuillez noter qu'activer le rewind peut augmenter significativement les prérequis système. @@ -4837,13 +5226,13 @@ Achievements: %5 (%6) - + True Color Rendering (24-bit, disables dithering) Rendu en Vrai Couleur (24-bit, désactive le lissage) - + Scaled Dithering (scale dither pattern to resolution) Echelle du Lissage (modèle de lissage à l'échelle pour la résolution) @@ -4855,194 +5244,235 @@ Achievements: %5 (%6) Software Renderer Readbacks (run in parallel for VRAM->CPU transfers) - + Relectures du rendu logiciel (tourne en parallèle pour les transferts VRAM -> CPU) + + + + Downsampling: + Sous-échantillonnage : - + Display Enhancements - Améliorations de l'Affichage + Améliorations de l'affichage - - + + Disable Interlacing (force progressive render/scan) - Désactiver l'Entrelacement (Forcer le Rendu/Scan progressif) + Désactiver l'entrelacement (force le rendu/scan progressif) - - + + Force NTSC Timings (60hz-on-PAL) - Forcer les Timings NTSC (60hz-en-PAL) + Forcer les timings NTSC (60hz-en-PAL) - + Force 4:3 For 24-Bit Display (disable widescreen for FMVs) - Forcer le 4:3 pour l'Affichage en 24-Bit (désactive l'écran large pour les FMVs) + Forcer le 4:3 pour l'affichage en 24-Bit (désactive l'écran large pour les FMVs) - + Chroma Smoothing For 24-Bit Display (reduce FMV color blockyness) - + Lissage chroma. pour l'affichage 24-Bit (réduit l'aspect bloc des coul. des FMV) - + PGXP (Precision Geometry Transform Pipeline) PGXP (Precision Geometry Transform Pipeline) - - + + + Perspective Correct Textures + Textures en perspective correcte + + + + Geometry Correction - Correction de la Géométrie + Correction de la géométrie + + + + + Perspective Correct Colors + Couleurs en perspective correcte - - + + Culling Correction - Correction du Culling + Correction du culling - - Texture Correction - Correction de la Texture + Correction de la Texture - - + + Preserve Projection Precision - + Préserver la précision de la projection - - + + Depth Buffer (Low Compatibility) - + Tampon de profondeur (faible compat.) - - + + CPU Mode (Very Slow) - + Mode CPU (très lent) + + + + Downsampling + Sous-échantillonnage - - - + + Disabled + Désactivé + + + + Downsamples the rendered image prior to displaying it. Can improve overall image quality in mixed 2D/3D games, but should be disabled for pure 3D games. Only applies to the hardware renderers. + Sous-échantillonne l'image rendu avant son affichage. Peut améliorer la qualité globale de l'image dans les jeux mixtes 2D/3D, mais devrait être désactivé pour les jeux pur 3D. S'applique uniquement aux rendus matériels. + + + + - - - - - - - + + + + + + + + + Unchecked Décoché - + Forces the rendering and display of frames to progressive mode. <br>This removes the "combing" effect seen in 480i games by rendering them in 480p. Usually safe to enable.<br> <b><u>May not be compatible with all games.</u></b> Force le rendu et l'affichage des images en mode progressif. <br>Ceci supprime l'effet "combing" vu dans les jeux en 480i en les passant en 480p. Généralement sans danger à activer."<br>b><u>Peut ne pas être compatible avec tous les jeux.</u></b> - + Resolution Scale Echelle de la Résolution - + Setting this beyond 1x will enhance the resolution of rendered 3D polygons and lines. Only applies to the hardware backends. <br>This option is usually safe, with most games looking fine at higher resolutions. Higher resolutions require a more powerful GPU. En paramétrant à plus de 1x, on améliore la résolution du rendu des polygones et des lignes 3D. S'applique uniquement aux moteurs matériels. Cette option est généralement sans danger, la plupart des jeux ayant l'air bien à des résolutions plus élevées. Les résolutions plus élevées nécessitent un GPU plus puissant. - + Forces the precision of colours output to the console's framebuffer to use the full 8 bits of precision per channel. This produces nicer looking gradients at the cost of making some colours look slightly different. Disabling the option also enables dithering, which makes the transition between colours less sharp by applying a pattern around those pixels. Most games are compatible with this option, but there is a number which aren't and will have broken effects with it enabled. Only applies to the hardware renderers. Force la précision de la sortie des couleurs vers le tampon image de la console pour utiliser les 8 bits complets de précision par canal. Cela permet d'obtenir des dégradés plus beaux au prix d'une apparence légèrement différente pour certaines couleurs. La désactivation de l'option permet également d'activer le lissage, qui rend la transition entre les couleurs moins nette en appliquant un motif autour de ces pixels. La plupart des jeux sont compatibles avec cette option, mais il y en a un certain nombre qui ne le seront pas et qui auront des effets non fonctionnels si elle est activée. Ne s'applique qu'aux moteurs de rendu matériels. - - - + + + Checked Coché - + Scales the dither pattern to the resolution scale of the emulated GPU. This makes the dither pattern much less obvious at higher resolutions. <br>Usually safe to enable, and only supported by the hardware renderers. Met à l'échelle du modèle de lissage à l'échelle de résolution du GPU émulé. Cela rend le modèle du lissage beaucoup moins apparent à des résolutions plus élevées. <br>Activer cette option est en général sécurisé, et seulement pris en charge par les moteurs de rendu matériels. - + Uses NTSC frame timings when the console is in PAL mode, forcing PAL games to run at 60hz. <br>For most games which have a speed tied to the framerate, this will result in the game running approximately 17% faster. <br>For variable frame rate games, it may not affect the speed. Utilise la synchronisation des images NTSC lorsque la console est en mode PAL, ce qui oblige les jeux PAL à fonctionner à 60hz. <br>Pour la plupart des jeux dont la vitesse est liée à la fréquence d'images, cela se traduira par un jeu fonctionnant environ 17% plus rapidement. <br>Pour les jeux à fréquence d'images variable, cela peut ne pas affecter la vitesse. - + Force 4:3 For 24-bit Display Forcer en 4:3 pour l'Affichage en 24-bit - + Switches back to 4:3 display aspect ratio when displaying 24-bit content, usually FMVs. Revient au format d'affichage 4:3 pour l'affichage de contenu 24 bits, généralement des FMV. - + Chroma Smoothing For 24-Bit Display - + Lissage chroma. pour les affichages 24-Bit - + Smooths out blockyness between colour transitions in 24-bit content, usually FMVs. Only applies to the hardware renderers. - + Lisse l'aspect bloc entre les transitions de couleur pour les contenus 24-bit, habituellement les FMVs. S'applique uniquement aux rendus matériels. - + Texture Filtering - Filtrage de Texture + Filtrage de texture - - Smooths out the blockyness of magnified textures on 3D object by using filtering. <br>Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. - + + Smooths out the blockiness of magnified textures on 3D object by using filtering. <br>Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. <br>The JINC2 and especially xBR filtering modes are very demanding, and may not be worth the speed penalty. + Lisse l'aspect bloc des textures magnifiées sur les objets 3D en utilisant du filtrage.<br>Aura un plus grand effet sur les grandes échelles de résolution. S'applique uniquement aux rendus matériels.<br>Les modes de filtrage JINC2 et spécifiquement xBR sont très demandeurs, et peuvent ne pas valoir la peine pour la pénalité sur les performances. - + Scales vertex positions in screen-space to a widescreen aspect ratio, essentially increasing the field of view from 4:3 to the chosen display aspect ratio in 3D games. <br>For 2D games, or games which use pre-rendered backgrounds, this enhancement will not work as expected. <br><b><u>May not be compatible with all games.</u></b> - + Mets à l'échelle la position des vertices depuis l'espace-écran vers un ratio d'aspect écran large, essentiellement pour augmenter le champ de vision du 4:3 vers le ratio d'aspect d'affichage choisi pour les jeux 3D. <br>Pour les jeux 2D, ou les jeux qui ont des arrières-plans pré-rendus, cette amélioration ne fonctionne pas comme attendu. <br><b><u>Peut ne pas être compatible avec tous les jeux.</u></b> - + Use Software Renderer For Readbacks - + Utiliser le rendu logiciel pour les relectures - + Runs the software renderer in parallel for VRAM readbacks. On some systems, this may result in greater performance when using graphical enhancements with the hardware renderer. - + Fait tourner le rendu logiciel en parallèle pour les relectures de la VRAM. Sur certains systèmes, cela peut donner de meilleures performances quand des améliorations graphiques sont utilisées avec le rendu matériel. - + + Uses perspective-correct interpolation for texture coordinates, straightening out warped textures. Requires geometry correction enabled. + Utilise l'interpolation fidèle à la perspective pour les coordonnées des textures, redressant les textures déformées. Requiert que la correction de géométrie soit active. + + + + Uses perspective-correct interpolation for vertex colors, which can improve visuals in some games, but cause rendering errors in others. Requires geometry correction enabled. + Utilise l'interpolation fidèle à la perspective pour les couleurs des vertex, ce qui peut améliorer les visuels dans certains jeux, mais causer des erreurs de rendu dans d'autres. Requiert que la correction de géométrie soit active. + + + Attempts to reduce polygon Z-fighting by testing pixels against the depth values from PGXP. Low compatibility, but can work well in some games. Other games may need a threshold adjustment. - + Essaye de réduire le Z-fighting polygonal en testant les pixels par rapport aux valeur de profondeur depuis PGXP. Faible compatibilité, mais peut marcher correctement dans certains jeux. Les autres jeux pourraient nécessiter un ajustement du seuil. - + Adds additional precision to PGXP data post-projection. May improve visuals in some games. - + Ajoute une précision additionnelle à la post-projection des données PGXP. Peut améliorer les visuels dans certains jeux. - + Uses PGXP for all instructions, not just memory operations. Required for PGXP to correct wobble in some games, but has a very high performance cost. - + Utilise PGXP pour toutes les instructions, pas seulement les opérations mémoire. Nécessaire pour que PGXP corrige l'oscillation dans certains jeux, mais à un coût sur les performances très élevé. Smooths out the blockyness of magnified textures on 3D object by using bilinear filtering. <br>Will have a greater effect on higher resolution scales. Only applies to the hardware renderers. Lisse le blocage des textures agrandies sur l'objet 3D en utilisant le filtrage bilinéaire. aura un effet plus important sur les échelles en haute résolution. S'applique uniquement aux rendus matériels. - + Widescreen Hack Hack Ecran Large @@ -5051,19 +5481,18 @@ Achievements: %5 (%6) Met à l'échelle les positions des vertex dans l'espace de l'écran vers une proportion écran large, augmentant essentiellement le champ de vision de 4:3 à 16:9 dans les jeux 3D. <br>Pour les jeux 2D, ou les jeux qui utilisent des fonds pré-rendu, cette amélioration ne fonctionnera pas comme prévu. <br><b><u>Peut ne pas être compatible avec tous les jeux.</u></b> - + Reduces "wobbly" polygons and "warping" textures that are common in PS1 games. <br>Only works with the hardware renderers. <b><u>May not be compatible with all games.</u></b> Réduits les polygones "carrés" et les textures "distordues" qui sont communes dans les jeux PS1. <br>Ne fonctionne qu'avec les rendus matériels. <b><u>Peut ne pas être compatible avec tous les jeux.</u></b> - + Increases the precision of polygon culling, reducing the number of holes in geometry. Requires geometry correction enabled. Augmente la précision des polygones de culling en réduisant le nombre de trous dans la géométrie. Nécessite l'activation de la correction de la géométrie. - Uses perspective-correct interpolation for texture coordinates and colors, straightening out warped textures. Requires geometry correction enabled. - Utilise une interpolation en perspective correcte pour les coordonnées et les couleurs des textures, en redressant les textures déformées. Nécessite l'activation de la correction de la géométrie. + Utilise une interpolation en perspective correcte pour les coordonnées et les couleurs des textures, en redressant les textures déformées. Nécessite l'activation de la correction de la géométrie. @@ -5071,12 +5500,12 @@ Achievements: %5 (%6) Form - Formulaire + Formulaire Cache Directory - + Répertoire de cache @@ -5084,7 +5513,7 @@ Achievements: %5 (%6) Browse... - Parcourir... + Parcourir... @@ -5092,7 +5521,7 @@ Achievements: %5 (%6) Open... - Ouvrir... + Ouvrir... @@ -5100,86 +5529,86 @@ Achievements: %5 (%6) Reset - + Réinitialiser Used for storing shaders and game list data. - + Utilisé pour stocker les shaders et les données de la liste des jeux. Covers Directory - + Répertoire des jaquettes Used for storing covers in the game grid/Big Picture UIs. - + Utiliser pour stocker les jaquettes de la grille des jeux/UIs Big Picture. Screenshots Directory - + Répertoire des captures d'écran Used for screenshots. - + Utilisé pour les captures d'écran. Save States Directory - + Répertoire des save states Used for storing save states. - + Utilisé pour les save states. GPUDownsampleMode - + Disabled - + Désactivé - + Box (Downsample 3D/Smooth All) - + Boîte (Sous-échantillonnage 3D/Tout lisser) - + Adaptive (Preserve 3D/Smooth 2D) - + Adaptif (Préserver 3D/Lisser 2D) GPURenderer - + Hardware (D3D11) Matériel (D3D11) - + Hardware (D3D12) - Matériel (D3D11) {3D?} {12)?} + Matériel (D3D12) - + Hardware (Vulkan) Matériel (Vulkan) - + Hardware (OpenGL) Matériel (OpenGL) - + Software Logiciel @@ -5187,106 +5616,106 @@ Achievements: %5 (%6) GPUSettingsWidget - + Automatic based on window size Automatiquement basé sur la taille de la fenêtre - + 1x 1x - + 2x 2x - + 3x (for 720p) 3x (pour le 720p) - + 4x 4x - + 5x (for 1080p) 5x (pour le 1080p) - + 6x (for 1440p) 6x (pour le 1440p) - + 7x 7x - + 8x 8x - + 9x (for 4K) - 6x (pour le 1440p) {9x?} {4K?} + 9x (pour le 4K) - + Disabled - + Désactivé - + %1x MSAA - + %1x MSAA - + %1x SSAA - + %1x SSAA 9x 9x - + 10x 10x - + 11x 11x - + 12x 12x - + 13x 13x - + 14x 14x - + 15x 15x - + 16x 16x @@ -5294,93 +5723,151 @@ Achievements: %5 (%6) GPUTextureFilter - + Nearest-Neighbor Voisinage le plus proche - + Bilinear Bilinéaire - - JINC2 - JINC2 + + JINC2 (Slow) + JINC2 (lent) - - Bilinear (No Edge Blending) - + + JINC2 (Slow, No Edge Blending) + JINC2 (lent, sans fusion des bords) - - xBR - xBR + + xBR (Very Slow) + xBR (très lent) - - JINC2 (No Edge Blending) - + + xBR (Very Slow, No Edge Blending) + xBR (très lent, sans fusion des bords) - - xBR (No Edge Blending) - + JINC2 + JINC2 + + + + Bilinear (No Edge Blending) + Bilinéaire (sans fusion des bords) + + + xBR + xBR GameList - + Disc - + Disque - + PS-EXE - + PS-EXE - + Playlist - + Liste de lecture - + PSF - + PSF + + + + Never + Jamais + + + + Today + Aujourd'hui + + + + Yesterday + Hier + + + + {}h {}m + {}h {}m + + + + {}h {}m {}s + {}h {}m {}s + + + + {}m {}s + {}m {}s + + + + {}s + {}s + + + + None + Aucun + + + + {} hours + {} heures + + + + {} minutes + {} minutes GameListCompatibilityRating - + Unknown Inconnu - + Doesn't Boot Ne démarre pas - + Crashes In Intro Plante sur l'Intro - + Crashes In-Game Plante dans le Jeu - + Graphical/Audio Issues Problèmes Graphiques/Audio - + No Issues Aucun Problème @@ -5388,62 +5875,76 @@ Achievements: %5 (%6) GameListModel - + Type Type - Code - Code + Code + + + + Serial + Numéro de série - + Title Titre - + File Title Titre du Fichier - + Developer - Développeur + Développeur - + Publisher - + Éditeur - + Genre - + Genre - + Year - + Année - + Players - + Joueurs + + + + Time Played + Temps joué - + + Last Played + Joué dernièrement + + + Size Taille - + Region Région - + Compatibility Comptabilité @@ -5451,12 +5952,12 @@ Achievements: %5 (%6) GameListSearchDirectoriesModel - + Path Chemin - + Recursive Récursif @@ -5475,7 +5976,7 @@ Achievements: %5 (%6) Search Directories (will be scanned for games) - + Répertoires de recherche (seront scannés pour des jeux) @@ -5486,24 +5987,24 @@ Achievements: %5 (%6) - + Remove - Enlever + Enlever Excluded Paths (will not be scanned) - + Chemins à exclure (ne seront pas scannés) Scan For New Games - + Chercher les nouveaux jeux Rescan All Games - + Re-chercher tous les jeux Scan New @@ -5518,22 +6019,22 @@ Achievements: %5 (%6) Mise à Jour de la BDD Redump - + Open Directory... Ouvrir le Répertoire... - + Select Search Directory Sélectionner le Répertoire de Recherche - + Scan Recursively? Scan récursif? - + Would you like to scan the directory "%1" recursively? Scanning recursively takes more time, but will identify files in subdirectories. @@ -5542,9 +6043,9 @@ Scanning recursively takes more time, but will identify files in subdirectories. L'analyse récursive prend plus de temps, mais elle permet d'identifier les fichiers dans les sous-répertoires. - + Select Path - + Sélectionner un chemin Download database from redump.org? @@ -5588,37 +6089,37 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Form - Formulaire + Formulaire Game List - + Liste de jeux Game Grid - + Grille de jeux Show Titles - + Afficher les titres All Types - + Tous les types All Regions - + Toutes les régions Search... - + Rechercher... @@ -5923,96 +6424,105 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter GameSettingsTrait - + Force Interpreter Forcer l'Interpréteur - + Force Software Renderer Forcer le Rendu Logiciel - + Force Software Renderer For Readbacks - + Forcer le rendu logiciel pour les relectures - + Force Interlacing Forcer l'Entrelacement - + Disable True Color Désactiver la Vrai Couleur - + Disable Upscaling Désactiver L'Upscaling - + Disable Scaled Dithering Désactiver l'Echelle du Lissage - + Disallow Forcing NTSC Timings Ne pas autoriser le forcage des Timings NTSC - + Disable Widescreen Désactiver l'Ecran Large - + Disable PGXP Désactiver PGXP - + Disable PGXP Culling Désactiver le Culling PGXP - + + Disable PGXP Perspective Correct Textures + Désactiver les textures fidèles à la perspective PGXP + + + + Disable PGXP Perspective Correct Colors + Désactiver les couleurs fidèles à la perspective PGXP + + Disable PGXP Texture Correction - Désactiver la Correction de Texture PGXP + Désactiver la Correction de Texture PGXP - + Disable PGXP Depth Buffer - + Désactiver le tampon de profondeur PGXP - + Force PGXP Vertex Cache - Forcer le Cache Vertex de PGXP + Forcer le cache vertex PGXP - + Force PGXP CPU Mode - Forcer le Mode CPU PGXP + Forcer le mode CPU PGXP - + Force Recompiler LUT Fastmem - + Forcer le recompilateur LUT Fastmem Force Digital Controller Forcer le Contrôleur Digital - + Force Recompiler Memory Exceptions Forcer les Exceptions Mémoire du Recompileur - + Force Recompiler ICache Forcer l'ICache du Recompileur @@ -6022,195 +6532,196 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Dialog - Dialogue + Dialogue Image Path: - Chemin de l'Image: + Chemin de l'image : Serial: - + Numéro de série : # - # + # Mode - Mode + Mode Start - Start + Début Length - Longueur + Longueur Hash - Hash + Hashage Status - + Statut Region: - + Région : Developer: - + Développeur : Controllers: - + Contrôlleurs : Tracks: - Pistes: + Pistes : Release Info: - + Info. d'édition : Input Profile: - + Profile d'entrée : Genre: - + Genre : Compute Hashes... - + Calcul des hachages... Type: - + Type : Title: - Titre: + Titre : Compatibility: - Compatibilité: + Compatibilité : Edit... - + Éditer... - - - - - - - + + + + + + + Unknown - Inconnu + Inconnu - + %1 (Published by %2) - + %1 (édité par %2) - + Published by %1 - + Édité par %1 - + Released %1 - + Sorti %1 - + %1-%2 players - + %1-%2 joueurs - + %1 players - + %1 joueurs - + %1-%2 memory card blocks - + %1-%2 blocs de carte mémoire - + %1 memory card blocks - + %1 blocs de carte mémoire - + Use Global Settings - + Utiliser les paramètres globaux - + Track %1 - + Piste %1 - + <not computed> - <aucun calcul> + <non-calculé> - + Error - Erreur + Erreur - + Failed to open CD image for hashing. - + Échec de l'ouverture du CD pour hashage. - + Revision: %1 - + Révision : %1 - + N/A - + Sans Objet + S/O - + Search on Redump.org - + Rechercher sur Redump.org @@ -6223,13 +6734,13 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Behaviour - Commportement + Comportement - + Confirm Power Off - Confirmer l'Arrêt + Confirmer l'arrêt Render To Main Window @@ -6237,83 +6748,84 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter - + Pause On Start - Pause au Démarrage + Pause au démarrage - + Pause On Focus Loss - Pause sur la Perte de Focus + Pause sur la perte de focus Save State On Shutdown - + Sauvegarder l'état à l'extinction Create Save State Backups - + Créer des sauvegardes des save states - + Inhibit Screensaver - + Empêcher l'économiseur d'écran Compress Save States - + Compresser les save states Game Display - + Affichage de jeu - + Start Fullscreen - Démarrer en Plein Ecran + Démarrer en plein écran - + Save State On Exit - Sauvegarder l'Etat en Quittant + Sauvegarder l'état en quittant - + Load Devices From Save States - Charger les Périphériques depuis les Sauvegardes d'Etats + Charger les périphériques depuis les save states Double-Click Toggles Fullscreen - + Double-clic bascule le plein écran - + Render To Separate Window - + Rendre dans une fenêtre séparée Hide Main Window When Running - + Abrégé pour que ça rentre dans la fenêtre + Masq. fen. principale qd lancé Disable Window Resizing - + Désactiver redimens. fenêtre - + Hide Cursor In Fullscreen Cacher le Curseur en Plein Ecran @@ -6332,32 +6844,32 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Automatic Updater - Mise à jour automatique + Mise à jour automatique Update Channel: - + Canal de MAJ : Current Version: - + Version courante : Check for Updates... - + Vérifier les MAJs... - + Apply Per-Game Settings Appliquer les Paramètres par Jeu - + Automatically Load Cheats Charger Auto les triches @@ -6386,90 +6898,89 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Moteur du Contrôleur: - - + - - + + + Checked Coché - + Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed. - Détermine si un invité de commande sera affiché pour confirmer l'arrêt de l'émulateur/jeu lorsque la touche de raccourci est enfoncée. - + Détermine si un invite de commande sera affiché pour confirmer l'arrêt de l'émulateur/jeu lorsque la touche de raccourci est enfoncée. - + Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time. Sauvegarde automatique de l'état de l'émulateur lors de la mise hors tension ou de la sortie. Vous pouvez ensuite reprendre directement là où vous vous êtes arrêté la prochaine fois. - - - - - - + + + + + + Unchecked Décoché - + Automatically switches to fullscreen mode when a game is started. Bascule automatiquement vers le mode Plein Ecran quand un jeu est lancé. - + Hides the mouse pointer/cursor when the emulator is in fullscreen mode. - + Masque le pointeur/curseur de souris quand l'émulateur est en mode plein écran. - + Prevents the screen saver from activating and the host from sleeping while emulation is running. - + Empêche l'activation de l'économiseur d'écran et la mise en veille de l'hôte quand l'émulation tourne. - + Renders the display of the simulated console to the main window of the application, over the game list. If checked, the display will render in a separate window. - + Rends l'affichage de la console simulée vers la fenêtre principâle de l'application, par-dessus la liste de jeux. Si coché, l'affichage sera rendu dans une fenêtre séparée. Renders the display of the simulated console to the main window of the application, over the game list. If unchecked, the display will render in a separate window. Rend l'affichage de la console simulée vers la fenêtre principale de l'application, au-dessus de la liste des jeux. Si cette option n'est pas cochée, l'affichage se fera dans une fenêtre séparée. - + Pauses the emulator when a game is started. Met l'émulateur en pause lorsqu'un jeu est lancé. - + Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back. - + Mets en pause l'émulateur quand vous minimisez la fenêtre ou basculez vers une autre application, et rétablit quand vous revenez. - + When enabled, memory cards and controllers will be overwritten when save states are loaded. This can result in lost saves, and controller type mismatches. For deterministic save states, enable this option, otherwise leave disabled. Lorsqu'elles sont activées, les cartes mémoire et les contrôleurs seront écrasés lors du chargement des sauvegardes d'états. Cela peut entraîner la perte des sauvegardes et la non-concordance des types de contrôleurs. Pour des sauvegardes déterminées, activez cette option, sinon désactivez la. - + When enabled, per-game settings will be applied, and incompatible enhancements will be disabled. You should leave this option enabled except when testing enhancements with incompatible games. Lorsque activé, des paramètres de jeu seront appliquées, et les améliorations incompatibles seront désactivées. Vous devriez laisser cette option active, excepté lors des tests des améliorations avec les jeux incompatibles. - + Automatically loads and applies cheats on game start. - + Charge et applique automatiquement les codes de triche au démarrage d'un jeu. - + %1 (%2) - %1 (%2) + %1 (%2) Throttles the emulation speed to the chosen speed above. If unchecked, the emulator will run as fast as possible, which may not be playable. @@ -6493,23 +7004,23 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter - + Enable Discord Presence Activer la Présence Discord - + Shows the game you are currently playing as part of your profile in Discord. Affiche le jeu auquel vous jouez actuellement sur votre profil Discord. - + Enable Automatic Update Check Activer Auto la Vérification de la MaJ Auto - + Automatically checks for updates to the program on startup. Updates can be deferred until later or skipped entirely. Vérifie automatiquement les mises à jour du programme au démarrage. Les mises à jour peuvent être reportées à une date ultérieure ou entièrement ignorées. @@ -6525,45 +7036,45 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter GunCon - + Crosshair Image Path - Chemin de l'Image du Viseur + Chemin de l'image du viseur - + Path to an image to use as a crosshair/cursor. - Chemin d'accès à une image à utiliser comme viseur/curseur. + Chemin d'accès à une image à utiliser comme viseur/curseur. - + Crosshair Image Scale - Echelle de l'Image du Viseur + Échelle de l'image du viseur - + Scale of crosshair image on screen. - Échelle de l'image du viseur à l'écran. + Échelle de l'image du viseur à l'écran. - + X Scale - + Échelle X - + Scales X coordinates relative to the center of the screen. - + Mets à l'échelle les coordonnées X par rapport au centre de l'écran. HostInterface - + Failed to load configured BIOS file '%s' Impossible de charger le fichier BIOS configuré '%s' - + No BIOS image found for %s region Aucune image du BIOS n'a été trouvé pour la région %s @@ -6571,470 +7082,470 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Hotkeys - - - - - - - - - - - + + + + + + + + + + + General Général - + Fast Forward Avance Rapide - + Toggle Fast Forward Basculer sur l'Avance Rapide - + Toggle Fullscreen Basculer en Plein Ecran - + Toggle Pause Basculer sur Pause - + Toggle Cheats - + Basculer les codes de triche - + Power Off System Eteindre le Système - + Reset System Réinitialiser le Système - + Save Screenshot Sauvegarder les Captures d'Ecrans - + Frame Step Pas de la Trame - - - - - - - - - - + + + + + + + + + + Graphics Graphismes - + Toggle Software Rendering Basculer sur le Rendu Logiciel - + Toggle PGXP Basculer sur PGXP - + Toggle PGXP Depth Buffer - + Basculer le tampon de profondeur PGXP - + Increase Resolution Scale Augmenter l'Echelle de Résolution - + Open Pause Menu - + Ouvrir le menu de pause - + Turbo - + Turbo - + Toggle Turbo - + Basculer le turbo - + Open Achievement List - + Ouvrir la liste des succès - + Open Leaderboard List - + Ouvrir la liste du classement - - - - - - - - - - - + + + + + + + + + + + System - + Système - + Change Disc - Changer le Disque + Changer le disque - + Swap Memory Card Slots - + Échanger les emplacements de carte mémoire - + Rewind - + Rewind - + Toggle Patch Codes - + Basculer les codes de patch - + Toggle Clock Speed Control (Overclocking) - + Basculer le contrôle de vitesse de l'horloge (overclocking) - + Increase Emulation Speed - + Augmenter la vitesse d'émulation - + Decrease Emulation Speed - + Diminuer la vitesse d'émulation - + Reset Emulation Speed - + Réinitialiser la vitesse d'émulation - + Decrease Resolution Scale - Diminuer l'Echelle de Résolution + Diminuer l'échelle de résolution - + Toggle Post-Processing - Basculer sur le Post Traitement + Basculer sur le post-traitement - + Reload Post Processing Shaders - Recharger les Shaders du Post Traitement + Recharger les shaders du post-traitement - + Reload Texture Replacements - + Recharger les remplacements de texture - + Toggle Widescreen - + Basculer le plein écran - + Toggle PGXP CPU Mode - + Basculer le mode CPU PGXP - - - - - - - + + + + + + + Save States Sauvegardes d'Etats - + Load From Selected Slot Charger depuis l'Emplacement Sélectionné - + Save To Selected Slot Sauvegarder vers l'Emplacement Sélectionné - + Select Previous Save Slot Choisir l'Emplacement de Sauvegarde Précédent - + Select Next Save Slot Choisir l'Emplacement de Sauvegarde Suivant - + Undo Load State - + Annuler chargement état - + Load Game State 1 Charger l'Etat du Jeu 1 - + Load Game State 2 Charger l'Etat du Jeu 2 - + Load Game State 3 Charger l'Etat du Jeu 3 - + Load Game State 4 Charger l'Etat du Jeu 4 - + Load Game State 5 Charger l'Etat du Jeu 5 - + Load Game State 6 Charger l'Etat du Jeu 6 - + Load Game State 7 Charger l'Etat du Jeu 7 - + Load Game State 8 Charger l'Etat du Jeu 8 - + Load Game State 9 Charger l'Etat du Jeu 9 - + Load Game State 10 Charger l'Etat du Jeu 10 - + Save Game State 1 Sauvegarder l'Etat du Jeu 1 - + Save Game State 2 Sauvegarder l'Etat du Jeu 2 - + Save Game State 3 Sauvegarder l'Etat du Jeu 3 - + Save Game State 4 Sauvegarder l'Etat du Jeu 4 - + Save Game State 5 Sauvegarder l'Etat du Jeu 5 - + Save Game State 6 Sauvegarder l'Etat du Jeu 6 - + Save Game State 7 Sauvegarder l'Etat du Jeu 7 - + Save Game State 8 Sauvegarder l'Etat du Jeu 8 - + Save Game State 9 Sauvegarder l'Etat du Jeu 9 - + Save Game State 10 Sauvegarder l'Etat du Jeu 10 - + Load Global State 1 Charger l'Etat Complet 1 - + Load Global State 2 Charger l'Etat Complet 2 - + Load Global State 3 Charger l'Etat Complet 3 - + Load Global State 4 Charger l'Etat Complet 4 - + Load Global State 5 Charger l'Etat Complet 5 - + Load Global State 6 Charger l'Etat Complet 6 - + Load Global State 7 Charger l'Etat Complet 7 - + Load Global State 8 Charger l'Etat Complet 8 - + Load Global State 9 Charger l'Etat Complet 9 - + Load Global State 10 Charger l'Etat Complet 10 - + Save Global State 1 Sauvegarder l'Etat Complet 1 - + Save Global State 2 Sauvegarder l'Etat Complet 2 - + Save Global State 3 Sauvegarder l'Etat Complet 3 - + Save Global State 4 Sauvegarder l'Etat Complet 4 - + Save Global State 5 Sauvegarder l'Etat Complet 5 - + Save Global State 6 Sauvegarder l'Etat Complet 6 - + Save Global State 7 Sauvegarder l'Etat Complet 7 - + Save Global State 8 Sauvegarder l'Etat Complet 8 - + Save Global State 9 Sauvegarder l'Etat Complet 9 - + Save Global State 10 Sauvegarder l'Etat Complet 10 - - - - + + + + Audio Audio - + Toggle Mute Passer en Muet - + Toggle CD Audio Mute Basculer le CD Audio en Muet - + Volume Up Augmenter le Volume - + Volume Down Diminuer le Volume @@ -7067,20 +7578,20 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Vider les Assignations - + Bindings for %1 %2 Assigne pour %1 %2 - + Close - Fermer + Fermer - - + + Push Button/Axis... [%1] - Pressez le Bouton/Axe... + Pressez le bouton/axe... [%1] @@ -7090,16 +7601,16 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Assigne %1 - + %n bindings - - + + %n assignations - - + + Push Button/Axis... [%1] Pressez le Bouton/Axe... [%1] @@ -7107,40 +7618,40 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter InputVibrationBindingWidget - + Error - Erreur + Erreur - + No devices with vibration motors were detected. - + Aucun périphérique avec des moteurs de vibration déctecté. - + Select vibration motor for %1. - + Sélectionnez le moteur de vibration pour %1. LogLevel - + None Aucun - + Error Erreur - + Warning Avertissement - + Performance Performance @@ -7149,32 +7660,32 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Succès - + Information Information - + Developer Développeur - + Profile Profil - + Verbose - + Verbeux - + Debug Débug - + Trace Traceur @@ -7193,8 +7704,8 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter - - + + Change Disc Changer le Disque @@ -7209,8 +7720,8 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter - - + + Load State Charger un Etat @@ -7230,204 +7741,220 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Thème - + Language Langue - + &Help &Aide - + &Debug &Débug - + Switch GPU Renderer Changer le Rendu du GPU - + Switch CPU Emulation Mode Changer le Mode d'Emulation du CPU - + Switch Crop Mode Changer le Mode de Coupe - + &View &Visualiser - + &Window Size - + &Taille de la fenêtre - + &Tools &Outils - + toolBar Barre d'Outils - + Start &File... - + Démarrer le &fichier... - + Start &Disc... Lancer le &Disque... - + Start &BIOS Démarrer le &BIOS - + &Scan For New Games &Scanner les nouveaux Jeux - + &Rescan All Games &Rescanner tous les Jeux - + Power &Off E&teindre - + &Reset &Réinitialiser - + &Pause &Pause - + &Load State &Charger un Etat - + &Save State &Sauvegarder un Etat - + E&xit &Quitter - + B&IOS - + B&IOS - + C&onsole - + C&onsole - + E&mulation - + É&mulation - + &Controllers - + &Contrôleurs - + &Hotkeys - + Raccourcis - + &Display - + Affichage - + &Enhancements - + Améliorations - + &Post-Processing - + &Post-traitement - + Audio - Audio + Audio - + Achievements - + Succès - + Folders - + Dossiers - + Game List - + Liste de jeux - + General - Général + Général - + Advanced - + Avancé + + + + + &Settings + Paramètres - + Show CD-ROM State - + Afficher état CD-ROM - + &Memory Cards - + Cartes &mémoire + + + + Enable GDB server + Activer le serveur GDB - + Power Off &Without Saving - + Éteindre sans sauvegarder - + Start Big Picture Mode - + Démarrer en mode Big Picture - + Big Picture - + Big Picture + + + + Cover Downloader + Téléchargeur de jaquette B&IOS Settings... @@ -7458,52 +7985,52 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Paramètres du &Post-Traitement... - + Fullscreen Plein Ecran - + Resolution Scale Echelle de la Résolution - + &GitHub Repository... Dépôt &GitHub... - + &Issue Tracker... &Suivi des Problèmes - + &Discord Server... Serveur &Discord... - + Check for &Updates... Vérfication des &Mises à Jour... - + About &Qt... A Propos De &Qt... - + &About DuckStation... &A Propos De DuckStation... - + Change Disc... Changement de Disque... - + Cheats... Triches... @@ -7524,97 +8051,96 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Paramètres Avancés... - + Add Game Directory... - Ajouter un Répertoire de Jeu... + Ajouter un répertoire de jeu... - &Settings... - &Paramètres... + &Paramètres... - + From File... - Depuis le Fichier... + Depuis le fichier... - + From Device... - + Depuis le périphérique... - + From Game List... Depuis la Liste de Jeu... - + Remove Disc - Enlever le Disque + Enlever le disque - + Resume State - Résumer l'Etat + Reprendre l'état - + Global State - Etat Complet + État complet - + Show VRAM Afficher la VRAM - + Dump CPU to VRAM Copies Copier le CPU vers la VRAM - + Dump VRAM to CPU Copies Copier la VRAM vers le CPU - + Disable All Enhancements - + Désactiver toutes les améliorations - + Disable Interlacing Désactiver l'Entrelacement - + Force NTSC Timings Forcer les Timings NTSC - + Dump Audio Copier l'Audio - + Dump RAM... Copier la RAM... - + Dump VRAM... - + Copier la VRAM... - + Dump SPU RAM... - + Copier la RAM SPU... - + Show GPU State Afficher l'Etat du GPU @@ -7623,27 +8149,27 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Afficher l'Etat du CD-ROM - + Show SPU State Afficher l'Etat du SPU - + Show Timers State Afficher l'Etat du Compteur - + Show MDEC State Afficher l'Etat du MDEC - + Show DMA State - + Afficher l'état DMA - + &Screenshot &Capture d'Ecran @@ -7652,103 +8178,103 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Paramètres de la Carte &Mémoire... - - + + Resume Reprendre - + Resumes the last save state created. Reprendre la dernière sauvegarde d'état créée. - + &Toolbar &Barre d'Outils - + Lock Toolbar - + Verrouiller la barre d'outil - + &Status Bar Barre de &Statut - + Game &List &Liste de Jeu - + System &Display &Afficher le Système - + Game &Properties - + &Propriétés de jeu - + Memory &Card Editor Editeur de &Carte Mémoire - + C&heat Manager - + Gestionnaire de triche - + CPU D&ebugger - + Débogu&eur CPU - + Game &Grid &Grille de Jeu - + Show Titles (Grid View) Afficher les Titres (Vue Grille) - + Zoom &In (Grid View) &Zoomer (Vue Grille) - + Ctrl++ Ctrl++ - + Zoom &Out (Grid View) &Dézoomer (Vue Grille) - + Ctrl+- Ctrl+- - + Refresh &Covers (Grid View) Rafraîchir les &Couvertures (Vue Grille) - + Open Memory Card Directory... Ouvrir le Répertoire de la Carte Mémoire... - + Open Data Directory... Ouvrir le Répertoire de Données... @@ -7757,414 +8283,446 @@ Vous pourrez ainsi télécharger environ 4 mégaoctets via votre connexion Inter Tous les Types de Fichiers (*.bin *.img *.cue *.chd *.exe *.psexe *.psf *.m3u);;Images Raw piste seule (*.bin *.img);;Cue Sheets (*.cue);;Images MAME CHD (*.chd);;Exécutables PlayStation (*.exe *.psexe);;Format de fichiers son portables (*.psf);;Listes de lecture (*.m3u) - + All File Types (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psf *.minipsf *.m3u);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.exe *.psexe *.ps-exe);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u) - + Tous les types de fichier (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psf *.minipsf *.m3u);;Single-Track Raw Images (*.bin *.img *.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;Error Code Modeler Images (*.ecm);;Media Descriptor Sidecar Images (*.mds);;PlayStation EBOOTs (*.pbp *.PBP);;PlayStation Executables (*.exe *.psexe *.ps-exe);;Portable Sound Format Files (*.psf *.minipsf);;Playlists (*.m3u) - - - - - + + + + + Error - Erreur + Erreur - + Failed to get window info from widget - + Échec de la récupération des infos fenêtre depuis le widget - + Failed to create host display device context. Impossible de créer un contexte de dispositif d'affichage hôte. - + Failed to get new window info from widget - + Échec de récupération des infos de nouvelle fenêtre depuis le widget - + Paused - + En pause - - + + Select Disc Image Choisir une Image Disque - + Could not find any CD-ROM devices. Please ensure you have a CD-ROM drive connected and sufficient permissions to access it. - + Impossible de trouver un périphérique CD-ROM. Veuillez vérifier que vous avez un lecteur CD-ROM connecté et les droits d'accès. - + %1 (%2) - %1 (%2) + %1 (%2) - + Select disc drive: - + Sélectionnez un lecteur de disque : - + Resume (%1) - Reprendre (%1) + Reprendre (%1) - - - + + + Game Save %1 (%2) - Sauvegarde du Jeu %1 (%2) + Sauvegarde du jeu %1 (%2) - + Edit Memory Cards... - + Éditer les cartes mémoire... - + Delete Save States... - Effacer les Sauvegardes d'Etats... + Effacer les sauvegardes d'état... - + Confirm Save State Deletion - Confirmation la Suppression de la Sauvegarde d'Etat + Confirmation de suppression de la sauvegarde d'état - + Are you sure you want to delete all save states for %1? The saves will not be recoverable. - Êtes-vous sûr de vouloir supprimer tous les États sauf le %1 ? + Êtes-vous sûr de vouloir supprimer toutes les sauvegardes d'état pour %1 ? Les sauvegardes ne seront pas récupérables. - + Load From File... - + Charger depuis le fichier... - - + + Select Save State File - + Sélectionnez un fichier de sauvegarde d'état - - + + Save States (*.sav) - + Sauvegardes d'état (*.sav) - + Undo Load State - + Annuler chargement état - - + + Game Save %1 (Empty) - Sauvegarde du Jeu %1 (Vide) + Sauvegarde du jeu %1 (vide) - - + + Global Save %1 (%2) - Sauvegarde Complète %1 (%2 + Sauvegarde globale %1 (%2) - - + + Global Save %1 (Empty) - Sauvegarde Complète %1 (Vide) + Sauvegarde globale %1 (vide) - + Save To File... - + Sauvegarder vers fichier... - + &Enabled Cheats - &Activation des triches + Activation des cod&es de triche - + &Apply Cheats - &Appliquer les triches + &Appliquer les codes de triche - + Load Resume State - + Charger l'état de reprise - + A resume save state was found for this game, saved at: %1. Do you want to load this state, or start from a fresh boot? - + Une save state de reprise a été trouvée pour ce jeu, sauvegardée dans : + +%1. + +Voulez-vous charger cette save, ou démarrer normalement ? - + Fresh Boot - + Démarrage normal - + Delete And Boot - + Supprimer et démarrer - + Failed to delete save state file '%1'. - + Échec de la suppression du fichier de save state '%1'. - + Confirm Disc Change - + Confirmation de changement de disque - + Do you want to swap discs or boot the new image (via system reset)? - + Voulez-vous changer de disque; ou démarrer la nouvelle image (via une réinitialisation système) ? - + Swap Disc - + Changer de disque - + Reset - + Réinitialiser - + Cancel - Annuler + Annuler - + Start Disc - + Démarrer le disque - - + + Cheat Manager - + Gestionnaire de codes de triche - + You must select a disc to change discs. - + Vous devez sélectionner un disque pour changer de disque. - + Properties... Propriétés... - + Open Containing Directory... Ouvrir le Répertoire Contenant... - + Set Cover Image... Sélectionner l'Image de Couverture... - + Default Boot Démarrage par Défaut - + Fast Boot Démarrage Rapide - + Full Boot Démarrage Complet - + Boot and Debug - + Démarrer et déboguer - + Exclude From List - + Exclure de la liste + + + + Reset Play Time + Réinitialiser le temps de jeu - + Add Search Directory... Ajout d'un Répertoire de Recherche... - + Select Cover Image Choisir l'Image de Couverture - + All Cover Image Types (*.jpg *.jpeg *.png) Tous les Types d'Images de Couverture (*.jpg *.jpeg *.png) - + Cover Already Exists La Couverture existe déjà - + A cover image for this game already exists, do you wish to replace it? Une image de Couverture pour ce jeu existe déjà, souhaitez-vous la remplacer? - - + + Copy Error Copier l'Erreur - + Failed to remove existing cover '%1' N'a pas réussi à supprimer la couverture existante '%1' - + Failed to copy '%1' to '%2' N'a pas réussi à copier '%1' to '%2' + + + Confirm Reset + Confirmation de réinitialisation + + + + Are you sure you want to reset the play time for '%1'? + +This action cannot be undone. + Êtes-vous sûr de vouloir réinitialiser le temps de jeu de '%1' ? + +Cette action ne peut pas être annulée. + Language changed. Please restart the application to apply. Langue changée. Veuillez relancer l'application pour l'appliquer. - + %1x Scale - + Échelle %1x - - - + + + Destination File Fichier de Destination - - + + Binary Files (*.bin) - + Fichiers binaires (*.bin) - + Binary Files (*.bin);;PNG Images (*.png) - + Fichiers binaires (*.bin);;Images PNG (*.png) - + Default Défaut - + Fusion Fusion - + Dark Fusion (Gray) Fusion Sombre (Gris) - + Dark Fusion (Blue) Fusion Sombre (Bleu) - + QDarkStyle QDarkStyle - + Confirm Shutdown - + Confirmation d'arrêt + + + + Are you sure you want to shut down the virtual machine? + Êtes-vous sûr de vouloir arrêter la machine vituelle ? - + Save State For Resume - + Save state pour reprendre - - - - + + + + Memory Card Not Found - + Carte mémoire introuvable - + Memory card '%1' does not exist. Do you want to create an empty memory card? - + La carte mémoire '%1' n'existe pas. Voulez-vous créer une carte mémoire vide ? - + Failed to create memory card '%1' - + Échec de création de la carte mémoire '%1' - - + + Memory card '%1' could not be found. Try starting the game and saving to create it. - + La carte mémoire '%1' n'a pas pu être trouvée. Essayez de démarrer le jeu et de sauvegarder pour la créer. - + Do not show again - + Ne plus montrer à l'avenir - + Using cheats can have unpredictable effects on games, causing crashes, graphical glitches, and corrupted saves. By using the cheat manager, you agree that it is an unsupported configuration, and we will not provide you with any assistance when games break. Cheats persist through save states even after being disabled, please remember to reset/reboot the game after turning off any codes. Are you sure you want to continue? - + Utiliser des codes de triche peut avoir des effets inprévisibles sur les jeux, causant crashs, glitches graphiques, et sauvegardes corrompues. En utiliser le gestionnaire de codes de triche, vous êtes d'accord qu'il s'agit d'une configuration non-supportée, et nous ne vous fournirons pas d'assistance quand les jeux casseront. + +Les codes de triche persistent à travers les save states même après avoir été dédsactivés, veuillez vou rappeler de réinitialiser/redémarrer le jeu après désactivation de tout code de triche. + +Êtes-vous sûr de vouloir continuer ? - + Updater Error Erreur de mise à jour - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please follow the instructions under "Downloading and Running" at the link below:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> <p>Désolé, vous essayez de mettre à jour une version de DuckStation qui n'est pas une version officielle Github. Pour éviter des incompatibilités, la mise à jour automatique n'est activée que sur les builds officielles. </p><p>Pour obtenir une build officielle, veuillez suivre les instructions sous "Téléchargement et Lancement" au lien ci-dessous:</p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> - + Automatic updating is not supported on the current platform. La mise à jour automatique n'est pas prise en charge sur la plateforme actuelle. @@ -8174,7 +8732,7 @@ Are you sure you want to continue? Memory Card Editor - Edition de la Carte Mémoire + Éditeur de carte mémoire @@ -8186,7 +8744,7 @@ Are you sure you want to continue? File Name - Nom du Fichier + Nom du fichier @@ -8198,7 +8756,7 @@ Are you sure you want to continue? Memory Card: - Carte Mémoire: + Carte mémoire : @@ -8210,68 +8768,72 @@ Are you sure you want to continue? Open... - Ouvrir... + Ouvrir... 0 blocks used 0 blocs utilisés - + Import File... - + Importer un fichier... Import file... Importer le fichier... - + Import Card... Importer la Carte... - + Save Sauvegarder - + Delete File Effacer le Fichier - + + All Memory Card Types (*.mcd *.mcr *.mc *.srm *.psm *.ps *.ddf *.mem *.vgs *.psx) + Tous les types de carte mémoire (*.mcd *.mcr *.mc *.srm *.psm *.ps *.ddf *.mem *.vgs *.psx) + + + Single Save Files (*.mcs);;All Files (*.*) - + Fichiers de sauvegarde unique (*.mcs);;Tous les fichiers (*.*) - + Undelete File - + Restaurer le fichier - + Export File Exporter le Fichier - + << << - + >> >> - All Memory Card Types (*.mcd *.mcr *.mc) - Tous les Types de Carte Mémoire (*.mcd *.mcr *.mc) + Tous les Types de Carte Mémoire (*.mcd *.mcr *.mc) - + All Importable Memory Card Types (*.mcd *.mcr *.mc *.gme) Tous les Types de Carte Mémoire Importable (*.mcd *.mcr *.mc *.gme) @@ -8280,45 +8842,45 @@ Are you sure you want to continue? Parcourir... - - + + Select Memory Card Choisir la Carte Mémoire - - - - - - - - - - - - + + + + + + + + + + + + Error Erreur - + New Card... - + Nouvelle carte... - + Open Card... - + Ouvrir une carte... - + Format Card - + Formater la carte - - + + Failed to load memory card image. Impossible de charger l'image de la carte mémoire. @@ -8327,113 +8889,113 @@ Are you sure you want to continue? %1 blocs libre%2 - + (Deleted) - + (supprimé) - + %n block(s) free%1 - - + + %n bloc(s) libérés%1 - + Failed to write card to '%1' N'a pas écrit sur la carte à '%1' - + Save memory card? Sauvegarder la Carte Mémoire ? - + Memory card '%1' is not saved, do you want to save before closing? Carte mémoire '%1' non sauvegardée, voulez-vous l'enregistrer avant la fermeture? - + Destination memory card already contains a save file with the same name (%1) as the one you are attempting to copy. Please delete this file from the destination memory card before copying. - + La carte mémoire de destination contient déjà un fichier de sauvegarde du même nom (%1) que celui que vous essayez de copier. Veuillez supprimer le fichier de carte mémoire de destination avant de copier. - + Insufficient blocks, this file needs %1 but only %2 are available. Blocs insuffisants, ce fichier a besoin de %1 mais seuls %2 sont disponibles. - + Failed to read file %1 N'a pas réussi à lire le fichier %1 - + Failed to write file %1 N'a pas écrit le fichier %1 - + Failed to delete file %1 N'a pas réussi à supprimer le fichier %1 - + Failed to undelete file %1. The file may have been partially overwritten by another save. - + Échec de la restauration du fichier %1. The fichier a peut-être été partiellement écrasé par une autre sauvegarde. - + Select Single Savefile - + Sélection d'un fichier de sauvegarde unique - + Failed to export save file %1. Check the log for more details. - + Échec de l'export du fichier de sauvegarde %1. Vérifiez la log pour plus de détails. - + Select Import File Sélectionnez le Fichier d'Importation - + Failed to import memory card. The log may contain more information. Impossible d'importer la carte mémoire. Le journal peut contenir plus d'informations. - + Format memory card? - + Formater la carte mémoire ? - + Formatting the memory card will destroy all saves, and they will not be recoverable. The memory card which will be formatted is located at '%1'. - + Formater la carte mémoire détruira toutes les sauvegardes, et elle ne seront pas récupérables. La carte mémoire qui va être formatée est située à '%1'. - + Select Import Save File - + Sélection de fichier de sauvegarde d'import - + Failed to import save. Check if there is enough room on the memory card or if an existing save with the same name already exists. - + Échec d'import de la sauvegarde. Vérifiez s'il y a assez de place sur la carte mémoire, ou si une sauvegarde existante avec le même nom n'existe pas déjà. MemoryCardSettingsWidget - + All Memory Card Types (*.mcd *.mcr *.mc) Tous les Types de Carte Mémoire (*.mcd *.mcr *.mc) - + Shared Settings Paramètres Partagés @@ -8442,7 +9004,7 @@ Are you sure you want to continue? Utiliser une seule Carte pour la Liste de lecture - + Checked Coché @@ -8459,70 +9021,70 @@ Are you sure you want to continue? Ouvrir... - + Memory Card %1 Carte Mémoire %1 - + Memory Card Type: Type de Carte Mémoire: - - + + Browse... Parcourir... - - + + Reset - + Réinitialiser - + Open Directory... - Ouvrir le Répertoire... + Ouvrir le répertoire... - + Memory Card Directory: - + Répertoire de carte mémoire : - - + + Use Single Card For Sub-Images - + Utiliser une seule carte pour les sous-images - + When using a multi-disc format (m3u/pbp) and per-game (title) memory cards, a single memory card will be used for all discs. If unchecked, a separate card will be used for each disc. - + En utilisant un format multi-disque (m3u/pbp) et des cartes mémoire par jeu (titre), une carte mémoire unique sera utilisée pour tous les disques. Si décoché, une carte séparée sera utilisé par disque. - + If one of the "separate card per game" memory card types is chosen, these memory cards will be saved to the memory cards directory. - + Si l'un des types de carte mémoire "carte séparée par jeu" est choisi, ces cartes mémoire seront sauvegardées dans le répertoire des cartes mémoire. - + The memory card editor enables you to move saves between cards, as well as import cards of other formats. - + L'éditeur de carte mémoire vous permet de déplacer les sauvegardes entres les cartes, ainsi que d'importer des cartes dans d'autres formats. - + Memory Card Editor... - + Éditeur de carte mémoire... - + Shared Memory Card Path: Chemin de la Carte Mémoire Partagée: - + Select path to memory card image Choisir un chemin pour l'image de la carte mémoire @@ -8530,57 +9092,65 @@ Are you sure you want to continue? MemoryCardType - + No Memory Card Aucune Carte Mémoire - + Shared Between All Games Partagé entre tous les Jeux - + + Separate Card Per Game (Serial) + Carte séparée par jeu (numéro de série) + + + + Separate Card Per Game (Title) + Carte séparée par jeu (titre) + + Separate Card Per Game (Game Code) - Séparation de Carte par Jeu (Code du Jeu) + Séparation de Carte par Jeu (Code du Jeu) - Separate Card Per Game (Game Title) - Séparation de Carte par Jeu (Titre du Jeu) + Séparation de Carte par Jeu (Titre du Jeu) - + Separate Card Per Game (File Title) - + Carte séparée par jeu (titre de fichier) - + Non-Persistent Card (Do Not Save) - + Carte non-persistante (ne pas sauvegarder) MultitapMode - + Disabled - + Désactivé - + Enable on Port 1 Only - + Activer uniquement sur le port 1 - + Enable on Port 2 Only - + Activer uniquement sur le port 2 - + Enable on Ports 1 and 2 - + Activer sur les ports 1 et 2 @@ -8665,20 +9235,30 @@ Are you sure you want to continue? Start - + Steering Axis Deadzone - + Zone morte de l'axe de direction - + Sets deadzone size for steering axis. - + Définis la taille de la zone mort de l'axe de direction. + + + + Steering Axis Sensitivity + Sensitivité de l'axe de direction + + + + Sets the steering axis scaling factor. + Définis le facteur d'échelle de l'axe de direction. OSDMessage - + System reset. Réinitialisation du Système. @@ -8687,12 +9267,12 @@ Are you sure you want to continue? Chargement d'Etat depuis la '%s'... - + Loading state from '%s' failed. Resetting. Le chargement d'Etat depuis la '%s' a échoué. Réinitialisation. - + Saving state to '%s' failed. La sauvegarde vers '%s' a échoué. @@ -8701,31 +9281,41 @@ Are you sure you want to continue? Etat Sauvegardé vers la '%s'. - + PGXP is incompatible with the software renderer, disabling PGXP. PGXP est incompatible avec le logiciel de rendu, ce qui désactive PGXP. - + Rewind is not supported on 32-bit ARM for Android. - + Le rewind n'est pas supporté en 32-bit ARM sous Android. + + + + Runahead is not supported on 32-bit ARM for Android. + Le runahead n'est pas supporté en 32-bit ARM sous Android. + + + + Rewind is disabled because runahead is enabled. + Le rewind est désactivé car le runahead est activé. PGXP CPU mode is incompatible with the recompiler, using Cached Interpreter instead. Le mode PGXP CPU est incompatible avec le recompileur, qui utilise plutôt l'Interpréteur en Cache. - + Switching to %s%s GPU renderer. Passage à %s%s vers le rendu GPU. - + Switching to %s audio backend. Passage à %s%s vers le moteur Audio. - + Switching to %s CPU execution mode. Passage à %s%s vers le Mode d'Execution du CPU. @@ -8746,105 +9336,105 @@ Are you sure you want to continue? CPU ICache désactivé, ce qui permet de vider tous les blocs. - + PGXP enabled, recompiling all blocks. PGXP activé, ce qui permet de recompiler tous les blocs. - + PGXP disabled, recompiling all blocks. PGXP désactivé, ce qui permet de recompiler tous les blocs. - + Failed to save undo load state. - + %n cheats are enabled. This may result in instability. - - + + %n codes de triche sont activés. Cela peut résulter en de l'instabilité. - + Saved %n cheats to '%s'. - - + + Sauvegardé %n codes de triche vers '%s'. - + Deleted cheat list '%s'. - + Supprimé la liste de codes de triche '%s'. - + Widescreen hack is now enabled, and aspect ratio is set to %s. - + Le hack écran large est maintenant activé, et le ratio d'aspect est défini sur %s. - + Widescreen hack is now disabled, and aspect ratio is set to %s. - + Le hack d'écran large est maintenant désactivé, et le ratio d'aspect est défini sur %s. - + Switching to %s renderer... - + Bascule vers le rendu %s... - + Save state contains controller type %s in port %u, but %s is used. Switching. - + La save state contient le type de contrôleur %s dans le port %u, mais %s est utilisé. Bascule. - + Ignoring mismatched controller type %s in port %u. - + Type de contrôleur %s dans le port %u incompatible, ignoré. - + Memory card %u from save state does match current card data. Simulating replugging. Le %u de la carte mémoire de la sauvegarde d'état correspond aux données actuelles de la carte. Simulation du rechargement. - + Memory card %u present in save state but not in system. Ignoring card. La Carte Mémoire %u présente dans la Sauvegarde d'Etat mais pas dans le système. La Carte est ignorée. - + Memory card %u present in system but not in save state. Replugging card. - + La carte mémoire %u est présente dans le système mais pas dans le save state. Rebranchement de la carte. - + Memory card %u present in save state but not in system. Creating temporary card. La Carte mémoire %u présente dans la Sauvegarde d'Etat mais pas dans le système. Création d'une carte temporaire. - + Memory card %u present in system but not in save state. Removing card. La Carte mémoire %u présente dans le système mais pas la Sauvegarde d'Etat. Suppression de la carte. - + CPU clock speed is set to %u%% (%u / %u). This may result in instability. La vitesse de l'horloge du CPU est réglée sur %u%% (%u / %u). Cela peut entraîner une instabilité. - + WARNING: CPU overclock (%u%%) was different in save state (%u%%). ATTENTION : l'overclock du CPU (%u%%) était différent dans la Sauvegarde d'Etat (%u%%). - - - + + + Failed to load post processing shader chain. N'a pas réussi à charger la chaîne de shaders Post-Traitement. @@ -8857,105 +9447,105 @@ Are you sure you want to continue? Limiteur de Vitesse Désactivé. - + PGXP is now enabled. PGXP est maintenant Activé. - + PGXP is now disabled. PGXP est maintenant Désactivé. - + PGXP Depth Buffer is now enabled. - + Le tampon de profondeur PGXP est maintenant activé. - + PGXP Depth Buffer is now disabled. - + Le tampon de profondeur PGXP est maintenant désactivé. - + Texture replacements reloaded. - + Remplacements de texture rechargés. - + Cannot load state for game without serial. - + Impossible de charger l'état poiur le jeu sans numéro de série. - + No save state found in slot {}. - + Aucune save state trouvée dans l'emplacement {}. - + Cannot save state for game without serial. - + Impossible de save state pour le jeu sans numéro de série. - + Achievements are disabled or unavailable for game. - + Les succès sont désactivés ou indisponibles pour le jeu. - + Leaderboards are disabled or unavailable for game. - + Les classements sont désactivés ou indisponibles pour le jeu. - + CPU clock speed control enabled (%u%% / %.3f MHz). - + Contrôle de la vitesse de l'horloge CPU activé (%u%% / %.3f MHz). - + CPU clock speed control disabled (%.3f MHz). - + Contrôle de la vitesse de l'horloge CPU désactivé (%.3f MHz). - - - + + + Emulation speed set to %u%%. - + Vitesse d'émulation définie à %u%%. - + PGXP CPU mode is now enabled. - + Le mode CPU PGXP est maintenant activé. - + PGXP CPU mode is now disabled. - + Le mode CPU PGXP est maintenant désactivé. - + Volume: Muted - Volume : Muet + Volume : muet - - - + + + Volume: {}% - + Volume : {}% Volume: %d%% Volume : %d%% - + CD Audio Muted. - CD Audio Muet + CD Audio muet. - + CD Audio Unmuted. CD Audio audible. @@ -8964,32 +9554,32 @@ Are you sure you want to continue? Profil d'entrée chargé à partir de '%s' - + Started dumping audio to '%s'. - + Début décharge audio vers '%s'. - + Failed to start dumping audio to '%s'. - + Échec du démarrage de la décharge audio vers '%s'. - + Stopped dumping audio. - + Arrêt décharge audio. - + Screenshot file '%s' already exists. - + Le fichier de capture d'écran '%s' existe déjà. - + Failed to save screenshot to '%s' Impossible d'enregistrer la capture d'écran dans '%s' - + Screenshot saved to '%s'. Capture d'Ecran Sauvegardée sur '%s'. @@ -9002,7 +9592,7 @@ Are you sure you want to continue? Utilisation du profil d'entrée '%s'. - + Failed to load cheats from '%s'. N'a pas réussi à charger les triches de '%s'. @@ -9015,237 +9605,258 @@ Are you sure you want to continue? Triches sauvegardées %u vers '%s'. - + Cheat '%s' enabled. Triches '%s' activées. - + Cheat '%s' disabled. Triches '%s' désactivées. - + Failed to save cheat list to '%s' Échec de la sauvegarde de la liste des triches dans '%s' - + Loading state from '{}'... - + Chargement de l'état depuis '{}'... - + Save State - Sauvegarder un Etat + Sauvegarde de l'état - + State saved to '{}'. - + État sauvegardé vers '{}'. - + CD-ROM read speedup set to %ux (effective speed %ux). This may result in instability. - + Accélération de la vitesse de lecture CD-ROM définie à %ux (vitesse effective %ux). Cela peut causer de l'instabilité. - + CD-ROM seek speedup set to instant. This may result in instability. - + Accélération de la recherche CD-ROM définie sur instantanée. Cela peut causer de l'instabilité. - + CD-ROM seek speedup set to %ux. This may result in instability. - + Accélération de la recherche CD-ROM définie sur %ux. Cela peut causer de l'instabilité. - + Failed to initialize %s renderer, falling back to software renderer. - + Échec de l'initialisation du rendu %s, bascule vers le rendu logiciel. + + + + This save state was created with a different BIOS version or patch options. This may cause stability issues. + Cette save state a été créée avec une version différente du BIOS ou des options de patch. Cela peut causer de l'instabilité. - + Failed to open CD image from save state '%s': %s. Using existing image '%s', this may result in instability. - + Éched de l'ouverture de l'image CD à partir de la save state '%s' : %s. Utilisation de l'image existante '%s', cela peut causer de l'instabilité. - + Rewinding is not enabled. - + Le rewinding n'est pas activé. - + No cheats are loaded. - + Aucun code de triche chargé. - + %n cheats are now active. - - + + %n codes de triches sont maintenant actifs. - + %n cheats are now inactive. - - + + %n codes de triche sont maintenant inactifs. - + Swapped memory card ports. Both ports have a memory card. - + Ports de carte mémoire échangés. Les deux ports ont une carte mémoire. - + Swapped memory card ports. Port 2 has a memory card, Port 1 is empty. - + Ports de carte mémoire échangés. Le port 2 a une carte mémoire, le port 1 est vide. - + Swapped memory card ports. Port 1 has a memory card, Port 2 is empty. - + Ports de carte mémoire échangés. Le port 1 a une carte mémoire, le port 2 est vide. - + Swapped memory card ports. Neither port has a memory card. - + Ports de carte mémoire échangés. Aucun des deux ports n'a de carte mémoire. - + Failed to open disc image '%s': %s. - + Échec de l'ouverture de l'image disque '%s' : %s. - + Inserted disc '%s' (%s). - + Disque '%s' inséré (%s). - + Failed to switch to subimage %u in '%s': %s. - + Échec de la bascule vers la sous-image %u dans '%s' : %s. - + Switched to sub-image %s (%u) in '%s'. - + Basculé vers la sous-image %s (%u) dans '%s'. - + Recompiler options changed, flushing all blocks. - + Les options du recompilateur ont changé, vidage de tous les blocs. - + Applied cheat '%s'. Application de la triche '%s'. - + Cheat '%s' is already enabled. La triche '%s'. est déjà activée. - + Post-processing is now enabled. Le Post-Traitement est maintenant activé. - + Post-processing is now disabled. Le Post-Traitement est maintenant désactivé. - + Failed to load post-processing shader chain. N'a pas réussi à charger la chaîne de shaders de Post-Traitement. - + Post-processing shaders reloaded. Les shaders de post-traitement ont été rechargés. - + CPU interpreter forced by game settings. L'Interpréteur CPU a été forcé par les paramètres du jeu. - + Software renderer forced by game settings. Le rendu Logiciel a été forcé par les paramètres du jeu. - + + Using software renderer for readbacks based on game settings. + Utilisation du rendu logiciel pour les relectures en se basant sur les paramètres de jeu. + + + Interlacing forced by game settings. L'Entrelacement a été forcé par les paramètres du jeu. - + True color disabled by game settings. Les Vraies Couleurs a été désactivées par les paramètres du jeu. - + Upscaling disabled by game settings. L'Upscaling a été désactivé par les paramètres du jeu. - + Scaled dithering disabled by game settings. L'Echelle du Lissage a été désactivée par les paramètres du jeu. - + Widescreen disabled by game settings. L'Ecran Large a été désactivé par les paramètres du jeu. - + Forcing NTSC Timings disallowed by game settings. Le Forcage des Timings NTSC a été interdit par les paramètres du jeu. - + PGXP geometry correction disabled by game settings. La Correction de la Géométrie PGXP a été désactivée par les paramètres du jeu. - + PGXP culling disabled by game settings. Le Culling PGXP a été désactivé par les paramètres du jeu. - + + PGXP perspective corrected textures disabled by game settings. + Textures fidèles à la perspective PGXP désactivées par les paramètres du jeu. + + + + PGXP perspective corrected colors disabled by game settings. + Couleurs fidèles à la perspective PGXP désactivées par les paramètres du jeu. + + PGXP texture correction disabled by game settings. - La Correction des Textures PGXP a été désactivée par les paramètres du jeu. + La Correction des Textures PGXP a été désactivée par les paramètres du jeu. - + PGXP vertex cache forced by game settings. Le Cache Vertex PGXP a été forcé par les paramètres du jeu. - + PGXP CPU mode forced by game settings. Le Mode CPU PGXP a été forcé par les paramètres du jeu. - + PGXP Depth Buffer disabled by game settings. - + Tampon de profondeur PGXP désactivé par les paramètres du jeu. - + Controller in port %u (%s) is not supported for %s. Supported controllers: %s Please configure a supported controller from the list above. - + Le contrôleur du port %u (%s) n'est pas supproté pour %s. +Contrôleurs supportés : %s +Veuillez configurer un contrôleur appartenant à la liste ci-dessus. Controller %u changed to digital by game settings. @@ -9260,94 +9871,94 @@ Please configure a supported controller from the list above. Le recompilateur ICache a été forcé par les paramètres du jeu. - + Acquired exclusive fullscreen. - + Obtenu le plein écran exclusif. - + Failed to acquire exclusive fullscreen. - + Échéc de l'obtention du plein écran exclusif. - + Lost exclusive fullscreen. - + Plein écran exclusif perdu. - + Analog mode forcing is disabled by game settings. Controller will start in digital mode. - + Le forçage du mode analogue est désactivé par les paramètre du jeu. Le contrôleur va démarrer en mode digital. - + %ux MSAA is not supported, using %ux instead. - + MSAA %ux n'est pas supporté, utilisation de %ux à la place. - + SSAA is not supported, using MSAA instead. - + SSAA n'est pas supporté, utilisation de MSAA à la place. - + Texture filter '%s' is not supported with the current renderer. - + Le filtre de texture '%s' n'est pas supporté avec le rendu courant. - + Adaptive downsampling is not supported with the current renderer, using box filter instead. - + Le sous-échantillonnage adaptif n'est pas supporté avec le rendu courant, utilisation du filtre boîte à la place. - + Resolution scale set to %ux (display %ux%u, VRAM %ux%u) - + Échelle de résolution définie sur %ux (affichage %ux %u, VRAM %ux %u) - + Multisample anti-aliasing set to %ux (SSAA). - + Anticrénelage multi-échantillonné défini sur %ux (SSAA). - + Multisample anti-aliasing set to %ux. - + Anticrénelage multi-échantillonné défini sur %ux. - + Resolution scale %ux not supported for adaptive smoothing, using %ux. - + L'échelle de résolution %ux n'est pas supportée pour le lissage adaptif, utilisation de %ux. - - OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.0 is required. - - - - + CD image preloading not available for multi-disc image '%s' - + Le pré-chargement d'image CD n'est pas disponible pour l'image multi-disque '%s' - + Precaching CD image failed, it may be unreliable. - + Échec de la mise en pré-cache de l'image CD, cela peut être non-fiable. - + Memory card at '%s' could not be read, formatting. - + La carte mémoire à '%s' ne peut être lue, formatage. - + Failed to save memory card to '{}'. - + Échec de la sauvegarde de la carte mémoire vers '{}'. - + Saved memory card to '{}'. - + Sauvegardé la carte mémoire vers '{}'. + + + + OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required. + Le rendu OpenGL est indisponible, votre pilote ou matériel n'est pas assez récent, OpenGL 3.1 ou OpenGL ES 3.1 est requis. @@ -9361,14 +9972,14 @@ Please configure a supported controller from the list above. Droite - + Relative Mouse Mode - + Mode souris relative - + Locks the mouse cursor to the window, use for FPS games. - + Verrouille le curseur de la souris à la fenêtre, utile pour les jeux FPS. @@ -9409,27 +10020,27 @@ Please configure a supported controller from the list above. Options... - + No Shaders Available Aucuns Shaders de disponible - + Error Erreur - + Failed to add shader. The log may contain more information. N'a pas réussi à ajouter de shader. Le journal peut contenir plus d'informations. - + Question Question - + Are you sure you want to clear all shader stages? Etes-vous sûr de vouloir vider toutes les étapes de shader? @@ -9465,14 +10076,14 @@ Please configure a supported controller from the list above. Chaîne de Post-Traitement - + Error Erreur - + The current post-processing chain is invalid, it has been reset. - + La chaîne de post-traitement courante est invalide, elle a été réinitialisée. The current post-processing chain is invalid, it has been reset. Any changes made will overwrite the existing config. @@ -9482,7 +10093,7 @@ Please configure a supported controller from the list above. PostProcessingShaderConfigDialog - + %1 Shader Options Options du Shader %1 @@ -9494,27 +10105,27 @@ Please configure a supported controller from the list above. PostProcessingShaderConfigWidget - + Red Rouge - + Green Vert - + Blue Bleu - + Alpha Alpha - + %1 (%2) %1 (%2) @@ -9534,12 +10145,12 @@ Please configure a supported controller from the list above. L'interface hôte n'a pas été initialisée. Impossible de continuer. - + Failed to open URL Impossible d'ouvrir l'URL - + Failed to open URL. The URL was: %1 @@ -9548,34 +10159,52 @@ The URL was: %1 L'URL était : %1 + + QtAsyncProgressThread + + + Error + Erreur + + + + Question + Question + + + + Information + Information + + QtHost - - - + + + Error - Erreur + Erreur - + File '%1' does not exist. - + Le fichier '%1' n'existe pas. - + The specified save state does not exist. - + La save state spécifiée n'existe pas. - + Cannot use no-gui mode, because no boot filename was specified. - + Impossible d'utiliser le mode sans-gui, parce qu'aucun nom de fichier de démarrage n'a été spécifié. - + Cannot use batch mode, because no boot filename was specified. - + Impossible d'utiliser le mode batch, parce qu'aucun nom de fichier de démarrage n'a été spécifié. @@ -9654,94 +10283,117 @@ Les sauvegardes ne seront pas récupérables. - QtProgressCallback + QtModalProgressCallback - + DuckStation DuckStation - + Cancel Annuler - + Error Erreur - + Question Question - + Information Information + + QtProgressCallback + + DuckStation + DuckStation + + + Cancel + Annuler + + + Error + Erreur + + + Question + Question + + + Information + Information + + SaveStateSelectorUI - + Load - + Charger - + Save - Sauvegarder + Sauvegarder - + Select Previous - + Sélectionner précédent - + Select Next - + Sélectionner suivant - + No Save State - + Pas de save state - + Global Slot %d - + Emplacement global %d - + Game Slot %d - + Emplacement de jeu %d - + %s Slot %d - + %s Emplacement %d SettingWidgetBinder - - + + Default: - + Défaut : - - + + Reset - + Réinitialiser - + Select folder for %1 - + Sélectionner le dossier pour %1 @@ -9804,12 +10456,12 @@ Les sauvegardes ne seront pas récupérables. Fermer - + <strong>General Settings</strong><hr>These options control how the emulator looks and behaves.<br><br>Mouse over an option for additional information. <strong>Paramètres Généraux</strong><hr>Ces options contrôlent l'apparence et le comportement de l'émulateur.<br><br><hr>Passer la souris sur une option pour obtenir des informations supplémentaires. - + <strong>Console Settings</strong><hr>These options determine the configuration of the simulated console.<br><br>Mouse over an option for additional information. <strong>Paramètres de la Console</strong><hr>Ces options déterminent la configuration de la console simulée.<br><br>Passer la souris sur une option pour obtenir des informations supplémentaires. @@ -9826,222 +10478,227 @@ Les sauvegardes ne seront pas récupérables. <strong>Paramètres des Contrôleurs</strong><hr>Cette page vous permet de choisir le type de contrôleur que vous souhaitez simuler pour la console, et de redéfinir les touches ou les boutons du contrôleur de jeu hôte selon votre choix. En cliquant sur une assignation, un compte à rebours sera lancé, auquel cas vous devrez appuyer sur la touche ou l'axe de la manette que vous souhaitez assigner. (Pour la vibration, appuyez sur n'importe quel bouton/axe de la manette à laquelle vous souhaitez envoyer la vibration). Si vous n'appuyez sur aucun bouton et que le compte à rebours s'écoule, l'assignation ne sera pas modifiée. Pour effacer une assignation, cliquez sur le bouton droit de la souris. Pour assigner plusieurs boutons, maintenez la touche Maj enfoncée et cliquez sur le bouton. - + Summary - + Sommaire - + <strong>Summary</strong><hr>This page shows information about the selected game, and allows you to validate your disc was dumped correctly. - + <strong>Sommaire</strong><hr>Cette page montre les informations à propos du jeu sélectionné, et vous permet de valider que votre disque a été correctement extrait. - + General - Général + Général - + Game List - + Liste de jeux - + <strong>Game List Settings</strong><hr>The list above shows the directories which will be searched by DuckStation to populate the game list. Search directories can be added, removed, and switched to recursive/non-recursive. - + <strong>Paramètres de la liste de jeux</strong><hr>La liste ci-dessus montre les répertoires scannés par DuckStation pour peupler la liste de jeux. Les répertoires de recherche peuvent être ajoutés, supprimés ou basculés en récursif/non-récursif. - + BIOS - + BIOS - + <strong>BIOS Settings</strong><hr>These options control which BIOS is used and how it will be patched.<br><br>Mouse over an option for additional information. - + <strong>Paramètres de BIOS</strong><hr>Ces options contrôles quel BIOS est utilisé et comment il sera patché.<br><br>Survolez une option à la souris pour de plus amples informations. - + Console - Console + Console - + Emulation - + Émulation - + <strong>Emulation Settings</strong><hr>These options determine the speed and runahead behavior of the system.<br><br>Mouse over an option for additional information. - + <strong>Paramètres d'émulation</strong><hr>Ces options déterminent la vitesse et le comportement runahead du système.<br><br>Survolez une option à la souris pour de plus amples informations. - + Memory Cards - + Cartes mémoire - + <strong>Memory Card Settings</strong><hr>This page lets you control what mode the memory card emulation will function in, and where the images for these cards will be stored on disk. <strong>Paramètres de Carte Mémoire</strong><hr>Cette page vous permet de contrôler dans quel mode l'émulation de carte mémoire fonctionnera, et où les images de ces cartes seront stockées sur le disque. - + Display - + Affichage - + <strong>Display Settings</strong><hr>These options control the how the frames generated by the console are displayed on the screen. <strong>Paramètres d'Affichage</strong><hr>Ces options contrôlent la façon dont les images générées par la console sont affichées sur l'écran. - + Enhancements - + Améliorations - + <strong>Enhancement Settings</strong><hr>These options control enhancements which can improve visuals compared to the original console. Mouse over each option for additional information. <strong>Enhancement Settings</strong><hr>Ces options contrôlent les améliorations qui peuvent améliorer les visuels par rapport à la console d'origine. Passez la souris sur une option pour obtenir des informations supplémentaires. - + Post-Processing - + Post-traitement - + <strong>Post-Processing Settings</strong><hr>Post processing allows you to alter the appearance of the image displayed on the screen with various filters. Shaders will be executed in sequence. <strong>Paramètres Post-traitement</strong><hr>Le Post-Traitement permet de modifier l'apparence de l'image affichée à l'écran à l'aide de différents filtres. Les shaders seront exécutés dans la séquence. - + Audio - Audio + Audio - + <strong>Audio Settings</strong><hr>These options control the audio output of the console. Mouse over an option for additional information. <strong>Paramètres Audio</strong><hr>Ces options contrôlent la sortie audio de la console. Passez la souris sur une option pour obtenir des informations supplémentaires. - + Achievements - + Succès - + <strong>Achievement Settings</strong><hr>These options control RetroAchievements. Mouse over an option for additional information. - + <strong>Paramètres des succès</strong><hr>Ces options contrôlent RetroAchievements. Survolez une option à la souris pour de plus amples informations. - + This DuckStation build was not compiled with RetroAchievements support. - + Cette build DuckStation n'a pas été compilée avec le support de RetroAchievements. - + Folders - + Dossiers - + <strong>Folder Settings</strong><hr>These options control where DuckStation will save runtime data files. - + <strong>Paramètres de dossier</strong><hr>Ces options contrôlent où DuckStation sauvegardera ses fichiers de données lors de son exécution. - + Advanced - + Avancé - + <strong>Advanced Settings</strong><hr>These options control logging and internal behavior of the emulator. Mouse over an option for additional information. <strong>Paramètres Avancés</strong><hr>Ces options contrôlent l'enregistrement et le comportement interne de l'émulateur. Passer la souris sur une option pour obtenir des informations supplémentaires. - + Confirm Restore Defaults - + Confirmation de restauration des paramètres par défaut - + Are you sure you want to restore the default settings? Any preferences will be lost. - + Êtes-vous sûr de vouloir restaurer les paramètres par défaut ? Toute préférence sera perdue. - + Recommended Value Valeur Recommandée - + %1 [%2] - + %1 [%2] - + Use Global Setting [Enabled] - + Utiliser le paramétrage global [Activé] - + Use Global Setting [Disabled] - + Utiliser le paramétrage global [Désactivé] - - + + Use Global Setting [%1] - + Utiliser le paramétrage global [%1] System - + Failed to load %s BIOS. - + Échech du chargement du BIOS %s. - - + + Error - Erreur + Erreur - + Failed to load save state file '{}' for booting. - + Échec du chargemement du fichier de save state '{}' pour démarrer. - + + Incorrect BIOS image size + Taille de l'image BIOS incorrecte + + + Save state is incompatible: minimum version is %u but state is version %u. La Sauvegarde d'Etat est incompatible : la version minimale est %u mais l'état est la version %u. - + Save state is incompatible: maximum version is %u but state is version %u. - + La save state est incompatible : la version maximale est %u, mais la version de la state est %u. - + Failed to open CD image '%s' used by save state: %s. - + Échec de l'ouverture de l'image CD '%s' utilisée par la save state %s. - + Failed to switch to subimage %u in CD image '%s' used by save state: %s. - + Échec de la bascule vers la sous-image %u dans l'image CD '%s' utilisée par la save state %s. - + Per-game memory card cannot be used for slot %u as the running game has no path. Using shared card instead. - + La carte mémoire par-jeu ne peut être utilisée pour l'emplacement %u car le jeu en cours n'a pas de chemin. Utilisation de la carte partagée à la place. - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s @@ -10051,10 +10708,18 @@ The game will likely not run properly. Please check the README for instructions on how to add an SBI file. Do you wish to continue? - + Vous essayez de lancer le jeu protégé par libcrypt sans fichier SBI : + +%s : %s + +Le jeu va probablement mal tourner. + +Veuillez lire le README pour les instructions à propos de comment ajouter un fichier SBI. + +Voulez-vous continuer ? - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s @@ -10062,19 +10727,25 @@ Do you wish to continue? Your dump is incomplete, you must add the SBI file to run this game. The name of the SBI file must match the name of the disc image. - + Vous essayez de lancer le jeu protégé par libcrypt sans fichier SBI : + +%s : %s + +Votre vidage est incomplet, vous devez ajouter un fichier SBI pour faire tourner le jeu. + +Le nom du fichier SBI doit correspondre au nom de l'image disque. Failed to open CD image from save state: '%s'. Impossible d'ouvrir l'image CD à partir de l'état de sauvegarde : '%s'. - + Per-game memory card cannot be used for slot %u as the running game has no code. Using shared card instead. La carte mémoire par jeu ne peut pas être utilisée pour l'emplacement %u car le jeu en cours n'a pas de code. Utilisez plutôt une carte partagée. - + Per-game memory card cannot be used for slot %u as the running game has no title. Using shared card instead. La carte mémoire par jeu ne peut pas être utilisée pour l'emplacement %u car le jeu en cours n'a pas de titre. Utilisez plutôt une carte partagée. @@ -10083,7 +10754,7 @@ The name of the SBI file must match the name of the disc image. Le chemin de la carte mémoire pour l'emplacement %u est manquant, utilise celui par défaut. - + Game changed, reloading memory cards. Le Jeu a été changé, rechargement des cartes mémoires. diff --git a/src/duckstation-qt/translations/duckstation-qt_ja.ts b/src/duckstation-qt/translations/duckstation-qt_ja.ts index 52b03ab527..beb72b86f5 100644 --- a/src/duckstation-qt/translations/duckstation-qt_ja.ts +++ b/src/duckstation-qt/translations/duckstation-qt_ja.ts @@ -172,7 +172,7 @@ - + Login... ログイン... @@ -266,29 +266,29 @@ チャレンジ可能な実績がある場合、画面の右下隅にアイコンを表示します。 - + Reset System システムリセット - + Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now? システムがリセットされるまで、ハードコアモードは有効になりません。今すぐシステムをリセットしますか? - + Username: %1 Login token generated on %2. ユーザー名: %1 ログイントークンは %2 に生成されました。 - + Logout ログアウト - + Not Logged In. ログインしていません。 @@ -296,17 +296,17 @@ Login token generated on %2. Achievements - + Loading state ステートロード - + Resuming state ステートセーブからの再開 - + Hardcore mode disabled by state switch. ステートロードによりハードコアモードが無効化されました。 @@ -351,34 +351,34 @@ Login token generated on %2. このゲームには実績がありません。 - + Your Score: {} (Best: {}) Leaderboard Position: {} of {} あなたのスコア: {} (ベスト: {}) リーダーボードでの順位: {} 位 ({} 人中) - + This game has {} leaderboards. このゲームには {} 個のリーダーボードがあります。 - + Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only. ハードコアモードがオフになっているため、スコアの送信は無効になっています。リーダーボードは読み取り専用です。 - + Time タイム - + Score スコア - + Downloading leaderboard data, please wait... リーダーボードのデータをダウンロードしています。お待ちください... @@ -407,25 +407,25 @@ Leaderboard Position: {} of {} - + Log To System Console システムコンソールにログ出力 - + Log To Window ウィンドウにログ表示 - + Log To Debug Console デバッグコンソールにログ出力 - + Log To File ファイルにログ保存 @@ -445,185 +445,220 @@ Leaderboard Position: {} of {} - + PGXP Vertex Cache PGXP 頂点キャッシュ - + Show Status Indicators ステータスインジケータを表示する - + + Select folder for %1 + %1 フォルダを選択してください + + + + Show Frame Times + フレーム時間を表示 + + + Multisample Antialiasing マルチサンプルアンチエイリアス - + Display Active Start Offset 表示開始オフセット (左端/オーバースキャン用) - + Display Active End Offset 表示終了オフセット (右端/オーバースキャン用) - + Display Line Start Offset 表示開始オフセット (上端/オーバースキャン用) - + Display Line End Offset 表示終了オフセット (下端/オーバースキャン用) - + PGXP Geometry Tolerance PGXP ジオメトリトレランス(形状公差) - + PGXP Depth Clear Threshold PGXP 深度 消去しきい値 - + Enable Recompiler Block Linking リコンパイラのブロックリンクを有効にする - + Enable Recompiler Fast Memory Access リコンパイラの高速メモリアクセスを有効にする - + + Use Old MDEC Routines + 昔の MDEC ルーチンを使用する + + + Enable VRAM Write Texture Replacement VRAM 書き込みテクスチャ置換を有効にする - + Preload Texture Replacements テクスチャ置換をプリロードする - + Dump Replaceable VRAM Writes 置換可能な VRAM 書き込みをダンプする - + Set Dumped VRAM Write Alpha Channel ダンプ済み VRAM 書き込みアルファチャネルを設定する - + Minimum Dumped VRAM Write Width 最小ダンプ VRAM 書き込み幅 - + Minimum Dumped VRAM Write Height 最小ダンプ VRAM 書き込み高 - + DMA Max Slice Ticks DMA スライスの最長 Tick 数 - + DMA Halt Ticks DMA 休止 Tick 数 - + GPU FIFO Size GPU FIFO サイズ - + GPU Max Run-Ahead GPU 最大先行実行(Run-Ahead) - + + Stretch Display Vertically + 画面を垂直方向に引き延ばす + + + Allow Booting Without SBI File SBI ファイルなしでの起動を許可する - + Create Save State Backups ステートセーブのバックアップを作成する - + + Enable PCDrv + PCDrv を有効にする + + + + Enable PCDrv Writes + PCDrv の書き込みを有効にする + + + + PCDrv Root Directory + PCDrv のルートディレクトリ + + + Log Level ログレベル - + Information 情報 - + Sets the verbosity of messages logged. Higher levels will log more messages. ログに記録される情報の詳細度を設定します。レベルが高いほど、より多くの情報がログに記録されます。 - - - - + + + + User Preference ユーザー設定 - + Logs messages to the console window. ログをコンソールウィンドウに出力します。 - + Logs messages to the debug console where supported. サポートされている場合は、ログをデバッグコンソールに出力します。 - + Logs messages to the window. ログをウィンドウに表示します。 - + Logs messages to duckstation.log in the user directory. ログをユーザーディレクトリの duckstation.log に記録します。 - + Shows a debug menu bar with additional statistics and quick settings. 追加の統計とクイック設定を含むデバッグメニューバーを表示します。 - + Display FPS Limit FPS 制限を表示 - + Disable All Enhancements すべての拡張機能を無効化 - + Apply Compatibility Settings 互換性設定を適用する - + Increase Timer Resolution タイマー精度を上げる(高精度タイマ) @@ -633,7 +668,7 @@ Leaderboard Position: {} of {} デフォルトにリセット - + Enable Recompiler Memory Exceptions リコンパイラのメモリ例外を有効にする @@ -644,33 +679,23 @@ Leaderboard Position: {} of {} - + Show Debug Menu デバッグメニューを表示 - + Use Debug Host GPU Device デバッグホスト GPU デバイスを使用する - + Unchecked チェックなし AnalogController - - - Controller %u is locked to analog mode by the game. - コントローラー %u は、ゲームによってアナログモードにロックされています。 - - - - Controller %u is locked to digital mode by the game. - コントローラー %u は、ゲームによってデジタルモードにロックされています。 - @@ -683,6 +708,16 @@ Leaderboard Position: {} of {} Controller {} switched to digital mode. コントローラー {} がデジタルモードに切り替わりました。 + + + Controller {} is locked to analog mode by the game. + コントローラー {} は、ゲームによってアナログモードにロックされています。 + + + + Controller {} is locked to digital mode by the game. + コントローラー {} は、ゲームによってデジタルモードにロックされています。 + Not Inverted @@ -862,17 +897,17 @@ Leaderboard Position: {} of {} AudioBackend - + Null (No Output) Null (出力なし) - + Cubeb Cubeb - + XAudio2 XAudio2 @@ -1282,17 +1317,17 @@ Leaderboard Position: {} of {} CPUExecutionMode - + Interpreter (Slowest) インタープリタ (最も遅い) - + Cached Interpreter (Faster) キャッシュされたインタープリタ (高速) - + Recompiler (Fastest) リコンパイラ (最速) @@ -1300,17 +1335,17 @@ Leaderboard Position: {} of {} CPUFastmemMode - + Disabled (Slowest) 無効 (最も遅い) - + MMap (Hardware, Fastest, 64-Bit Only) MMap (ハードウェア, 最速, 64ビットのみ) - + LUT (Faster) LUT (高速) @@ -1851,6 +1886,14 @@ Leaderboard Position: {} of {} 自動 (Frame End) + + ColorPickerButton + + + Select LED Color + LED の色を選択 + + CommonHost @@ -1862,7 +1905,7 @@ Leaderboard Position: {} of {} CommonHostInterface - + Invalid version %u (%s version %u) 無効なバージョン %u (%s バージョン %u) @@ -1870,22 +1913,22 @@ Leaderboard Position: {} of {} ConsoleRegion - + Auto-Detect 自動検出 - + NTSC-J (Japan) NTSC-J (日本) - + NTSC-U/C (US, Canada) NTSC-U/C (US, カナダ) - + PAL (Europe, Australia) PAL (ヨーロッパ, オーストラリア) @@ -2905,7 +2948,7 @@ This warning will only be shown once. ステアリング/ねじり - + %1% %1% @@ -2913,22 +2956,22 @@ This warning will only be shown once. ControllerCustomSettingsWidget - + %1 Settings %1 設定 - + Restore Default Settings デフォルト設定に戻す - + Browse... 参照... - + Select File ファイルを選択 @@ -3006,7 +3049,7 @@ This warning will only be shown once. SDL を有効化 - + DualShock 4 / DualSense Enhanced Mode DualShock 4 / DualSense 拡張モード @@ -3016,72 +3059,105 @@ This warning will only be shown once. 検出されたデバイス - + Mouse/Pointer Source マウス / ポインター - - + + 10 10 - + Using raw input improves precision when you bind controller sticks to the mouse pointer. Also enables multiple mice to be used. Raw Input を使用すると、コントローラーのスティックをマウスポインターに割り当てるときに精度が向上します。複数のマウスを使用することも可能にします。 - + Vertical Sensitivity: 垂直方向の感度: - + Horizontal Sensitivity: 水平方向の感度: - + Enable Mouse Mapping マウスへの割り当てを有効化 - + Use Raw Input Raw Input を使用 - + XInput Source XInput - + + Controller LED Settings + コントローラーの LED 設定 + + + The XInput source provides support for XBox 360 / XBox One / XBox Series controllers, and third party controllers which implement the XInput protocol. XInput は、XBox 360 / XBox One / XBox Series のコントローラ、および XInput プロトコルを実装するサードパーティ製コントローラのサポートを提供します。 - + Enable XInput Input Source XInput を有効化 - + Profile Settings プロファイル設定 - + When this option is enabled, hotkeys can be set in this input profile, and will be used instead of the global hotkeys. By default, hotkeys are always shared between all profiles. このオプションを有効にすると、この入力プロファイル用のホットキーを設定し、グローバルホットキーの代わりに使用することができます。デフォルトでは、ホットキーは常にすべてのプロファイル間で共有されます。 - + Use Per-Profile Hotkeys プロファイルごとのホットキーを使用する + + ControllerLEDSettingsDialog + + + Controller LED Settings + コントローラー LED 設定 + + + + SDL-0 LED + SDL-0 LED + + + + SDL-1 LED + SDL-1 LED + + + + SDL-2 LED + SDL-2 LED + + + + SDL-3 LED + SDL-3 LED + + ControllerMacroEditWidget @@ -3130,27 +3206,27 @@ This warning will only be shown once. 設定... - + Not Configured 設定されていません - + Set Frequency 頻度を設定 - + Frequency: 頻度: - + Macro will not repeat. マクロは繰り返されません。 - + Macro will toggle buttons every %1 frames. マクロは %1 フレームごとにボタンを切り替えます。 @@ -3218,7 +3294,7 @@ This warning will only be shown once. - + Error エラー @@ -3293,33 +3369,33 @@ You cannot undo this action. グローバル設定 - - + + Controller Port %1%2 %3 コントローラーポート %1%2 %3 - - + + Controller Port %1 %2 コントローラーポート %1 %2 - + Hotkeys ホットキー - + Shared 共有 - + The input profile named '%1' cannot be found. '%1' という名前の入力プロファイルが見つかりません。 @@ -3327,36 +3403,36 @@ You cannot undo this action. ControllerType - + None なし - + Digital Controller デジタルコントローラー - + Analog Controller (DualShock) アナログコントローラー (DualShock) - + Analog Joystick アナログジョイスティック - + PlayStation Mouse プレイステーションマウス - + NeGcon ネジコン @@ -3367,10 +3443,15 @@ You cannot undo this action. - + GunCon ガンコン + + + Not Connected + 未接続 + CoverDownloadDialog @@ -3459,42 +3540,42 @@ You cannot undo this action. DebuggerMessage - + Added breakpoint at 0x%08X. 0x%08X にブレークポイントを追加しました。 - + Removed breakpoint at 0x%08X. 0x%08X のブレークポイントを削除しました。 - + 0x%08X is not a call instruction. 0x%08X はコール命令ではありません。 - + Can't step over double branch at 0x%08X 0x%08X で二重分岐をステップオーバーできません - + Stepping over to 0x%08X. 0x%08X にステップオーバーします。 - + Instruction read failed at %08X while searching for function end. 関数の終わりを検索中に、%08X で命令の読み取りに失敗しました。 - + Stepping out to 0x%08X. 0x%08X にステップアウトします。 - + No return instruction found after %u instructions for step-out at %08X. ステップアウトするために必要なリターン命令が以降 %u 個の命令中にありません(%08X)。 @@ -3665,6 +3746,7 @@ You cannot undo this action. + Toggle &Breakpoint ブレークポイント有効/無効の切り替え(&B) @@ -3700,6 +3782,7 @@ You cannot undo this action. + &Run To Cursor カーソルまで実行(&R) @@ -3838,23 +3921,33 @@ This file can be several gigabytes, so be aware of SSD wear. ステップアウトブレークポイントの追加に失敗しました。有効な関数内にいますか? - - + + View in &Dump + ダンプに表示(&D) + + + + Follow Load/Store + ロード/ストア先のメモリーを表示 + + + + Invalid search pattern. It should contain hex digits or question marks. 無効な検索パターン。16進数または疑問符を含める必要があります。 - + Pattern not found. パターンが見つかりません。 - + Pattern found at 0x%1 (passed the end of memory). 0x%1 でパターンが見つかりました (メモリ終端に到達したため先頭から再検索しました)。 - + Pattern found at 0x%1. 0x%1 でパターンが見つかりました。 @@ -3880,22 +3973,22 @@ This file can be several gigabytes, so be aware of SSD wear. DiscRegion - + NTSC-J (Japan) NTSC-J (日本) - + NTSC-U/C (US, Canada) NTSC-U/C (US, カナダ) - + PAL (Europe, Australia) PAL (ヨーロッパ, オーストラリア) - + Other その他 @@ -3903,17 +3996,17 @@ This file can be several gigabytes, so be aware of SSD wear. DisplayAlignment - + Left / Top 左 / 上 - + Center 中央 - + Right / Bottom 右 / 下 @@ -3921,17 +4014,17 @@ This file can be several gigabytes, so be aware of SSD wear. DisplayAspectRatio - + Auto (Game Native) 自動 (ゲーム本来の比率) - + Auto (Match Window) 自動 (ウィンドウに合わせる) - + Custom カスタム @@ -3939,17 +4032,17 @@ This file can be several gigabytes, so be aware of SSD wear. DisplayCropMode - + None なし - + Only Overscan Area オーバースキャンエリアのみ - + All Borders すべてのボーダー @@ -4301,17 +4394,17 @@ This file can be several gigabytes, so be aware of SSD wear. EmuThread - + Error エラー - + No resume save state found. 再開用ステートセーブが見つかりません。 - + Game ID: %1 Game Title: %2 Achievements: %5 (%6) @@ -4324,34 +4417,34 @@ Achievements: %5 (%6) - + %n points %n ポイント - + Rich presence inactive or unsupported. リッチプレゼンスは非アクティブまたはサポートされていません。 - + Game not loaded or no RetroAchievements available. ゲームが読み込まれていないか、RetroAchievements が利用できません。 - + %1x%2 %1x%2 - + Game: %1 FPS ゲーム: %1 FPS - + Video: %1 FPS (%2%) 表示: %1 FPS (%2%) @@ -4956,17 +5049,17 @@ Achievements: %5 (%6) GPUDownsampleMode - + Disabled 無効 - + Box (Downsample 3D/Smooth All) ボックス (ダウンサンプル 3D/すべてスムーズ) - + Adaptive (Preserve 3D/Smooth 2D) アダプティブ (3D を保持/スムーズ 2D) @@ -4974,27 +5067,27 @@ Achievements: %5 (%6) GPURenderer - + Hardware (D3D11) ハードウェア (D3D11) - + Hardware (D3D12) ハードウェア (D3D12) - + Hardware (Vulkan) ハードウェア (Vulkan) - + Hardware (OpenGL) ハードウェア (OpenGL) - + Software ソフトウェア @@ -5105,37 +5198,37 @@ Achievements: %5 (%6) GPUTextureFilter - + Nearest-Neighbor 最近傍 - + Bilinear バイリニア - + JINC2 (Slow) JINC2 (遅い) - + JINC2 (Slow, No Edge Blending) JINC2 (遅い, エッジブレンディングなし) - + xBR (Very Slow) xBR (非常に遅い) - + xBR (Very Slow, No Edge Blending) xBR (非常に遅い, エッジブレンディングなし) - + Bilinear (No Edge Blending) バイリニア (エッジブレンディングなし) @@ -5163,52 +5256,52 @@ Achievements: %5 (%6) PSF ファイル - + Never 未プレイ - + Today 今日 - + Yesterday 昨日 - + {}h {}m {}時間 {}分 - + {}h {}m {}s {}時間 {}分 {}秒 - + {}m {}s {}分 {}秒 - + {}s {}秒 - + None なし - + {} hours {} 時間 - + {} minutes {} 分 @@ -6007,12 +6100,12 @@ Scanning recursively takes more time, but will identify files in subdirectories. HostInterface - + Failed to load configured BIOS file '%s' 構成済みの BIOS ファイル '%s' のロードに失敗しました - + No BIOS image found for %s region リージョン %s の BIOS イメージが見つかりません @@ -6020,470 +6113,470 @@ Scanning recursively takes more time, but will identify files in subdirectories. Hotkeys - - - - - - - - - - - + + + + + + + + + + + General 一般 - + Fast Forward 早送り - + Toggle Fast Forward 早送りオン/オフ切り替え - + Turbo ターボ - + Toggle Turbo ターボオン/オフ切り替え - + Toggle Fullscreen フルスクリーン/ウィンドウ表示切り替え - + Toggle Pause 一時停止/再開 - + Toggle Cheats チート有効/無効の切り替え - + Power Off System システム電源オフ - + Toggle Patch Codes パッチコード有効/無効の切り替え - + Reset System システムリセット - + Save Screenshot スクリーンショットを保存 - + Change Disc ディスク交換 - + Frame Step コマ送り - + Rewind 巻き戻し - + Toggle Clock Speed Control (Overclocking) クロック速度制御 有効/無効の切り替え (オーバークロック) - - - - - - - - - - + + + + + + + + + + Graphics グラフィック - + Toggle Software Rendering ソフトウェアレンダリング有効/無効の切り替え - + Toggle PGXP PGXP 有効/無効の切り替え - + Toggle PGXP Depth Buffer PGXP 深度バッファ有効/無効の切り替え - + Increase Resolution Scale 解像度スケールを上げる - + Open Pause Menu 一時停止メニューを開く - + Open Achievement List 実績リストを開く - + Open Leaderboard List リーダーボードリストを開く - - - - - - - - - - - + + + + + + + + + + + System システム - + Swap Memory Card Slots メモリーカードスロットを交換 - + Increase Emulation Speed エミュレーション速度を上げる - + Decrease Emulation Speed エミュレーション速度を下げる - + Reset Emulation Speed エミュレーション速度をリセット - + Decrease Resolution Scale 解像度スケールを下げる - + Toggle Post-Processing ポストプロセス有効/無効の切り替え - + Reload Post Processing Shaders ポストプロセスシェーダーをリロード - + Reload Texture Replacements テクスチャ置換をリロード - + Toggle Widescreen ワイドスクリーン有効/無効の切り替え - + Toggle PGXP CPU Mode PGXP CPU モード有効/無効の切り替え - - - - - - - + + + + + + + Save States ステートセーブ - + Load From Selected Slot 選択したスロットからロード - + Save To Selected Slot 選択したスロットにセーブ - + Select Previous Save Slot 前のセーブスロットを選択 - + Select Next Save Slot 次のセーブスロットを選択 - + Undo Load State ステートロード前に戻す(Undo) - + Load Game State 1 ゲームステート 1 をロード - + Load Game State 2 ゲームステート 2 をロード - + Load Game State 3 ゲームステート 3 をロード - + Load Game State 4 ゲームステート 4 をロード - + Load Game State 5 ゲームステート 5 をロード - + Load Game State 6 ゲームステート 6 をロード - + Load Game State 7 ゲームステート 7 をロード - + Load Game State 8 ゲームステート 8 をロード - + Load Game State 9 ゲームステート 9 をロード - + Load Game State 10 ゲームステート 10 をロード - + Save Game State 1 ゲームステート 1 にセーブ - + Save Game State 2 ゲームステート 2 にセーブ - + Save Game State 3 ゲームステート 3 にセーブ - + Save Game State 4 ゲームステート 4 にセーブ - + Save Game State 5 ゲームステート 5 にセーブ - + Save Game State 6 ゲームステート 6 にセーブ - + Save Game State 7 ゲームステート 7 にセーブ - + Save Game State 8 ゲームステート 8 にセーブ - + Save Game State 9 ゲームステート 9 にセーブ - + Save Game State 10 ゲームステート 10 にセーブ - + Load Global State 1 グローバルステート 1 をロード - + Load Global State 2 グローバルステート 2 をロード - + Load Global State 3 グローバルステート 3 をロード - + Load Global State 4 グローバルステート 4 をロード - + Load Global State 5 グローバルステート 5 をロード - + Load Global State 6 グローバルステート 6 をロード - + Load Global State 7 グローバルステート 7 をロード - + Load Global State 8 グローバルステート 8 をロード - + Load Global State 9 グローバルステート 9 をロード - + Load Global State 10 グローバルステート 10 をロード - + Save Global State 1 グローバルステート 1 にセーブ - + Save Global State 2 グローバルステート 2 にセーブ - + Save Global State 3 グローバルステート 3 にセーブ - + Save Global State 4 グローバルステート 4 にセーブ - + Save Global State 5 グローバルステート 5 にセーブ - + Save Global State 6 グローバルステート 6 にセーブ - + Save Global State 7 グローバルステート 7 にセーブ - + Save Global State 8 グローバルステート 8 にセーブ - + Save Global State 9 グローバルステート 9 にセーブ - + Save Global State 10 グローバルステート 10 にセーブ - - - - + + + + Audio 音声 - + Toggle Mute 音声有り無しの切り替え - + Toggle CD Audio Mute CD 音声有り無しの切り替え - + Volume Up 音量を上げる - + Volume Down 音量を下げる @@ -6516,18 +6609,18 @@ Scanning recursively takes more time, but will identify files in subdirectories. 割り当てをクリア - + Bindings for %1 %2 %1 %2 の割り当て - + Close 閉じる - - + + Push Button/Axis... [%1] ボタン/軸 を押す... [%1] @@ -6535,15 +6628,15 @@ Scanning recursively takes more time, but will identify files in subdirectories. InputBindingWidget - + %n bindings %n の割り当て - - + + Push Button/Axis... [%1] ボタン/軸 を押す... [%1] @@ -6551,17 +6644,17 @@ Scanning recursively takes more time, but will identify files in subdirectories. InputVibrationBindingWidget - + Error エラー - + No devices with vibration motors were detected. 振動モーターを備えたデバイスは検出されませんでした。 - + Select vibration motor for %1. %1 の振動モーターを選択します。 @@ -6569,52 +6662,52 @@ Scanning recursively takes more time, but will identify files in subdirectories. LogLevel - + None なし - + Error エラー - + Warning 警告 - + Performance パフォーマンス - + Information 情報 - + Developer 開発 - + Profile プロファイル - + Verbose 詳細 - + Debug デバッグ - + Trace トレース @@ -6628,8 +6721,8 @@ Scanning recursively takes more time, but will identify files in subdirectories. - - + + Change Disc ディスク交換 @@ -6640,8 +6733,8 @@ Scanning recursively takes more time, but will identify files in subdirectories. - - + + Load State ステートロード @@ -6666,237 +6759,248 @@ Scanning recursively takes more time, but will identify files in subdirectories. 言語 - + &Help ヘルプ(&H) - + &Debug デバッグ(&D) - + Switch GPU Renderer GPU レンダラーの切り替え - + Switch CPU Emulation Mode CPU エミュレーションモードの切り替え - + &View 表示(&V) - + &Tools ツール(&T) - + toolBar ツールバー - + B&IOS BIOS(&I) - + C&onsole コンソール(&O) - + E&mulation エミュレーション(&M) - + &Controllers コントローラー(&C) - + &Hotkeys ホットキー(&H) - + &Display 表示(&D) - + &Enhancements 拡張(&E) - + &Post-Processing ポストプロセス(&P) - + Audio 音声 - + Achievements 実績 - + Folders フォルダ - + Game List ゲームリスト - + General 一般 - + Advanced 高度な設定 - + + + &Settings + 設定(&S) + + + Show CD-ROM State CDROM の状態を表示 - + &Memory Cards メモリーカード(&M) - + Memory &Card Editor メモリーカードエディタ(&C) - + + Enable GDB server + GDB サーバーを有効化 + + + Ctrl+- Ctrl+- - + Open Memory Card Directory... メモリーカードディレクトリを開く... - + Open Data Directory... データディレクトリを開く... - + Start Big Picture Mode 大画面モードを開始 - + Big Picture 大画面 - + Start &Disc... ディスク起動(&D)... - + Start &BIOS BIOS 起動(&B) - + &Scan For New Games 新規ゲームをスキャン(&S) - + &Rescan All Games すべてのゲームを再スキャン(&R) - + Power &Off 電源オフ(&O) - + &Reset リセット(&R) - + &Pause 一時停止(&P) - + &Load State ステートロード(&L) - + &Save State ステートセーブ(&S) - + E&xit 終了(&X) - + Fullscreen フルスクリーン - + Resolution Scale 解像度スケール - + &GitHub Repository... GitHub リポジトリ(&G)... - + &Issue Tracker... Issue トラッカー(&I)... - + &Discord Server... Discord サーバー(&D)... - + Check for &Updates... 更新の確認(&U)... - + Change Disc... ディスク交換... - + Cheats... チート... @@ -6906,259 +7010,254 @@ Scanning recursively takes more time, but will identify files in subdirectories. システム(&S) - + Switch Crop Mode トリミングモードの切り替え - + &Window Size ウィンドウサイズ(&W) - + Start &File... イメージ起動(&F)... - + About &Qt... Qt について(&Q)... - + &About DuckStation... DuckStation について(&A)... - + Add Game Directory... ゲームディレクトリを追加... - - &Settings... - 設定(&S)... - - - + From File... ファイルから... - + From Device... 光学ドライブから... - + From Game List... ゲームリストから... - + Remove Disc ディスク取り出し - + Resume State 状態を再開 - + Global State グローバルステート - + Show VRAM VRAM を表示 - + Dump CPU to VRAM Copies CPU から VRAM へのコピーをダンプ - + Dump VRAM to CPU Copies VRAM から CPU へのコピーをダンプ - + Disable All Enhancements すべての拡張機能を無効化 - + Disable Interlacing インターレースを無効化 - + Force NTSC Timings NTSC タイミングを強制 - + Dump Audio 音声ダンプ - + Dump RAM... RAM ダンプ... - + Dump VRAM... VRAM ダンプ... - + Dump SPU RAM... SPU RAM ダンプ... - + Show GPU State GPU の状態を表示 - + Show SPU State SPU の状態を表示 - + Show Timers State タイマーの状態を表示 - + Show MDEC State MDEC の状態を表示 - + Show DMA State DMA の状態を表示 - + &Screenshot スクリーンショット(&S) - - + + Resume 再開 - + Resumes the last save state created. 最後に保存されたステートセーブで再開します。 - + &Toolbar ツールバー(&T) - + Lock Toolbar ツールバーを固定 - + &Status Bar ステータスバー(&S) - + Game &List ゲームリスト(&L) - + Game &Properties ゲームプロパティ(&P) - + C&heat Manager チートマネージャ(&H) - + CPU D&ebugger CPU デバッガ(&E) - + Game &Grid ゲームグリッド(&G) - + Show Titles (Grid View) ゲーム名表示 (グリッドビュー) - + Ctrl++ Ctrl++ - + Zoom &Out (Grid View) ズームアウト (グリッドビュー)(&O) - + Power Off &Without Saving 保存せずに電源オフ(&W) - + Cover Downloader カバーダウンローダー - + Zoom &In (Grid View) ズームイン (グリッドビュー)(&I) - + Refresh &Covers (Grid View) カバーの更新 (グリッドビュー)(&C) - + System &Display システム表示(&D) - + Failed to create host display device context. ホストディスプレイデバイスコンテキストの作成に失敗しました。 - - + + Select Disc Image ディスクイメージを選択 - + Start Disc ディスク起動 - + Could not find any CD-ROM devices. Please ensure you have a CD-ROM drive connected and sufficient permissions to access it. CD-ROM デバイスが見つかりませんでした。CD-ROM ドライブが接続されており、<br>それにアクセスするための十分な権限があることを確認してください。 @@ -7168,63 +7267,63 @@ Scanning recursively takes more time, but will identify files in subdirectories. すべてのファイル (*.bin *.img *.iso *.cue *.chd *.ecm *.mds *.pbp *.exe *.psexe *.ps-exe *.psf *.minipsf *.m3u);;シングルトラックイメージ (*.bin *.img *.iso);;Cue シート (*.cue);;MAME CHD イメージ (*.chd);;Error Code Modeler イメージ (*.ecm);;Media Descriptor Sidecar イメージ (*.mds);;PlayStation EBOOT 形式 (*.pbp *.PBP);;PlayStation 実行ファイル (*.exe *.psexe *.ps-exe);;ポータブルサウンド形式 (*.psf *.minipsf);;プレイリスト (*.m3u) - - - - - + + + + + Error エラー - + Failed to get window info from widget ウィジェットからウィンドウ情報を取得できませんでした - + Paused 一時停止中 - + %1 (%2) %1 (%2) - + Select disc drive: ディスクドライブを選択: - + Resume (%1) 再開 (%1) - - - + + + Game Save %1 (%2) ゲームセーブ %1 (%2) - + Edit Memory Cards... メモリーカードを編集... - + Delete Save States... ステートセーブを削除... - + Confirm Save State Deletion ステートセーブ削除の確認 - + Are you sure you want to delete all save states for %1? The saves will not be recoverable. @@ -7233,67 +7332,67 @@ The saves will not be recoverable. 元に戻すことはできません。 - + Load From File... ファイルからロード... - - + + Select Save State File ステートセーブファイルを選択 - - + + Save States (*.sav) ステートセーブ (*.sav) - + Undo Load State ステートロード前に戻す(Undo) - - + + Game Save %1 (Empty) ゲームセーブ %1 (空) - - + + Global Save %1 (%2) グローバルセーブ %1 (%2) - - + + Global Save %1 (Empty) グローバルセーブ %1 (空) - + Save To File... ファイルにセーブ... - + &Enabled Cheats チート有効化(&E) - + &Apply Cheats チート適用(&A) - + Load Resume State ステートセーブからの再開 - + A resume save state was found for this game, saved at: %1. @@ -7306,232 +7405,251 @@ Do you want to load this state, or start from a fresh boot? このステートセーブをロードしますか、それとも新規に起動しますか? - + Fresh Boot 新規に起動 - + Delete And Boot 削除して起動 - + Failed to delete save state file '%1'. ステートセーブファイル '%1' を削除できませんでした。 - + Confirm Disc Change ディスク交換の確認 - + Do you want to swap discs or boot the new image (via system reset)? ディスクを交換しますか、それとも新しいイメージを(システムリセットして)起動しますか? - + Swap Disc ディスク交換 - + Reset リセット - + Cancel キャンセル - - + + Cheat Manager チートマネージャ - + You must select a disc to change discs. ディスクを変更するには、ディスクを選択する必要があります。 - + Properties... プロパティ... - + Open Containing Directory... ファイルがある場所を開く... - + Set Cover Image... カバーイメージ画像を設定... - + Default Boot デフォルト起動 - + Fast Boot 高速ブート (BIOS スキップ) - + Full Boot フルブート - + Boot and Debug 起動とデバッグ - + Exclude From List リストから除外 (除外パスに追加) - + + Reset Play Time + プレイ時間をリセット + + + Add Search Directory... 検索ディレクトリを追加... - + Select Cover Image カバーイメージ画像を選択 - + All Cover Image Types (*.jpg *.jpeg *.png) すべてのカバー画像タイプ (*.jpg *.jpeg *.png) - + Cover Already Exists カバーはすでに存在します - + A cover image for this game already exists, do you wish to replace it? このゲームのカバー画像はすでに存在しますが、置き換えますか? - - + + Copy Error コピーエラー - + Failed to remove existing cover '%1' 既存のカバー '%1'の削除に失敗しました - + Failed to copy '%1' to '%2' '%1' を '%2'にコピーできませんでした - + + Confirm Reset + リセットの確認 + + + + Are you sure you want to reset the play time for '%1'? + +This action cannot be undone. + '%1' のプレイ時間をリセットしてもよろしいですか? + +この操作は元に戻せません。 + + + %1x Scale %1x 倍 - - - + + + Destination File 宛先ファイル - - + + Binary Files (*.bin) バイナリファイル (*.bin) - + Binary Files (*.bin);;PNG Images (*.png) バイナリファイル (*.bin);;PNG 画像 (*.png) - + Default デフォルト - + Fusion フュージョン - + Dark Fusion (Gray) ダークフュージョン (グレイ) - + Dark Fusion (Blue) ダークフュージョン (ブルー) - + QDarkStyle Q ダークスタイル - + Confirm Shutdown シャットダウンの確認 - + Are you sure you want to shut down the virtual machine? コンソールをシャットダウンしてもよろしいですか? - + Save State For Resume 再開用にステートセーブする - - - - + + + + Memory Card Not Found メモリーカードが見つかりません - + Memory card '%1' does not exist. Do you want to create an empty memory card? メモリーカード<br>'%1'<br>は存在しません。 空のメモリーカードを作成しますか? - + Failed to create memory card '%1' メモリーカードの作成に失敗しました '%1' - - + + Memory card '%1' could not be found. Try starting the game and saving to create it. メモリーカード '%1' が見つかりませんでした。ゲームを開始し、保存(作成)してください。 - + Do not show again 再度表示しない - + Using cheats can have unpredictable effects on games, causing crashes, graphical glitches, and corrupted saves. By using the cheat manager, you agree that it is an unsupported configuration, and we will not provide you with any assistance when games break. Cheats persist through save states even after being disabled, please remember to reset/reboot the game after turning off any codes. @@ -7544,22 +7662,22 @@ Are you sure you want to continue? 続行してもよろしいですか? - + Updater Error 更新エラー - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please follow the instructions under "Downloading and Running" at the link below:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> <p>申し訳ありませんが、GitHub の公式リリースではない DuckStation バージョンを更新しようとしています。非互換性を防ぐために、自動更新は公式ビルドでのみ有効になっています。</p><p>公式ビルドを取得するには、以下のリンクの「ダウンロードと実行」の手順に従ってください。</p><p> <a href="https://github.com/stenzek/duckstation/"> https://github.com/stenzek/duckstation/ </a></p> - + Automatic updating is not supported on the current platform. 現在のプラットフォームでは、自動更新はサポートされていません。 - + Failed to get new window info from widget ウィジェットから新しいウィンドウ情報を取得できませんでした @@ -7893,32 +8011,32 @@ Are you sure you want to continue? MemoryCardType - + No Memory Card メモリーカードなし - + Shared Between All Games すべてのゲームでカードを共有する - + Separate Card Per Game (Serial) ゲームごとに別々のカード (シリアル番号) - + Separate Card Per Game (Title) ゲームごとに別々のカード (ゲーム名) - + Separate Card Per Game (File Title) ゲームごとに別々のカード (ファイル名) - + Non-Persistent Card (Do Not Save) 非永続カード (セーブしないでください) @@ -7926,22 +8044,22 @@ Are you sure you want to continue? MultitapMode - + Disabled 使用しない - + Enable on Port 1 Only ポート1のみ有効にする - + Enable on Port 2 Only ポート2のみ有効にする - + Enable on Ports 1 and 2 ポート1および2で有効にする @@ -7972,223 +8090,223 @@ Are you sure you want to continue? OSDMessage - + System reset. システムがリセットされました。 - + Loading state from '%s' failed. Resetting. '%s' からのステートロードに失敗しました。リセットします。 - + Saving state to '%s' failed. '%s' にステートセーブできませんでした。 - + PGXP is incompatible with the software renderer, disabling PGXP. PGXP はソフトウェアレンダラーと互換性がないため、PGXP を無効にします。 - + Rewind is not supported on 32-bit ARM for Android. 巻き戻しは Android 用の32ビット ARM ではサポートされていません。 - + Runahead is not supported on 32-bit ARM for Android. 先行実行は Android 用の32ビット ARM ではサポートされていません。 - + Rewind is disabled because runahead is enabled. 先行実行が有効になっているため、巻き戻しは無効になっています。 - + Recompiler options changed, flushing all blocks. リコンパイラオプションが変更されました。すべてのブロックをフラッシュします。 - + Switching to %s%s GPU renderer. GPU レンダラー %s%s に切り替えます。 - + Switching to %s audio backend. オーディオバックエンド %s に切り替えます。 - + Switching to %s CPU execution mode. CPU 実行モード %s に切り替えます。 - + PGXP enabled, recompiling all blocks. PGXP が有効になりました。すべてのブロックを再コンパイルします。 - + PGXP disabled, recompiling all blocks. PGXP が無効になりました。すべてのブロックを再コンパイルします。 - + Switching to %s renderer... レンダラー %s に切り替えます... - - + + Failed to load post processing shader chain. ポストプロセスシェーダーチェーンの読み込みに失敗しました。 - + Cannot load state for game without serial. シリアルのないゲームはステートセーブを読み込めません。 - + No save state found in slot {}. スロット {} にステートセーブが見つかりません。 - + Cannot save state for game without serial. シリアルのないゲームはステートセーブを保存できません。 - + Achievements are disabled or unavailable for game. このゲームでは、実績が無効になっているか、利用できません。 - + Leaderboards are disabled or unavailable for game. このゲームでは、リーダーボードが無効になっているか、利用できません。 - + CPU clock speed control enabled (%u%% / %.3f MHz). CPU クロック速度制御が有効になりました (%u%% / %.3f MHz)。 - + CPU clock speed control disabled (%.3f MHz). CPU クロック速度制御が無効になりました (%.3f MHz)。 - + PGXP is now enabled. PGXP が有効になりました。 - + PGXP is now disabled. PGXP が無効になりました。 - + PGXP Depth Buffer is now enabled. PGXP 深度バッファが有効になりました。 - + PGXP Depth Buffer is now disabled. PGXP 深度バッファが無効になりました。 - - - + + + Volume: {}% 音量: {}% - + Texture replacements reloaded. テクスチャ置換がリロードされました。 - + Failed to save undo load state. ステートロード取り消し(Undo)用のステートセーブに失敗しました。 - + Rewinding is not enabled. 巻き戻しが有効化されていません。 - - - + + + Emulation speed set to %u%%. エミュレーション速度を %u%% に設定しました。 - + PGXP CPU mode is now enabled. PGXP CPU モードが有効になりました。 - + PGXP CPU mode is now disabled. PGXP CPU モードが無効になりました。 - + Volume: Muted 音量: ミュート - + CD Audio Muted. CD 音声をミュートしました。 - + CD Audio Unmuted. CD 音声のミュートを解除しました。 - + Started dumping audio to '%s'. '%s' への音声ダンプを開始しました。 - + Failed to start dumping audio to '%s'. '%s' への音声ダンプを開始できませんでした。 - + Stopped dumping audio. 音声ダンプを停止しました。 - + Screenshot file '%s' already exists. スクリーンショットファイル '%s' は既に存在します。 - + Failed to save screenshot to '%s' スクリーンショットを '%s' に保存できませんでした - + Screenshot saved to '%s'. スクリーンショットを '%s' に保存しました。 - + Controller in port %u (%s) is not supported for %s. Supported controllers: %s Please configure a supported controller from the list above. @@ -8197,106 +8315,106 @@ Please configure a supported controller from the list above. 上記のリストからサポートされているコントローラーを構成してください。 - + Failed to load cheats from '%s'. '%s' からのチートのロードに失敗しました。 - + %n cheats are enabled. This may result in instability. チート %n が有効になっています。これにより、不安定になる可能性があります。 - + Widescreen hack is now enabled, and aspect ratio is set to %s. ワイドスクリーンハックが有効になりました。アスペクト比は %s に設定されました。 - + Widescreen hack is now disabled, and aspect ratio is set to %s. ワイドスクリーンハックが無効になりました。アスペクト比は %s に設定されました。 - + Swapped memory card ports. Both ports have a memory card. メモリーカードポートを交換しました。両方のポートにメモリーカードがあります。 - + Swapped memory card ports. Port 2 has a memory card, Port 1 is empty. メモリーカードポートを交換しました。ポート2にはメモリーカードがあり、ポート1は空です。 - + Swapped memory card ports. Port 1 has a memory card, Port 2 is empty. メモリーカードポートを交換しました。ポート1にはメモリーカードがあり、ポート2は空です。 - + Swapped memory card ports. Neither port has a memory card. メモリーカードポートを交換しました。どちらのポートにもメモリーカードはありません。 - + Deleted cheat list '%s'. チートリスト '%s' を削除しました。 - + Cheat '%s' enabled. チート '%s' を有効にしました。 - + Cheat '%s' disabled. チート '%s' を無効にしました。 - + Failed to save cheat list to '%s' チートリストを '%s' に保存できませんでした - + No cheats are loaded. チートはロードされていません。 - + Saved %n cheats to '%s'. %n 個のチートを '%s' に保存しました。 - + Applied cheat '%s'. チート '%s' を適用しました。 - + Cheat '%s' is already enabled. チート '%s' は既に有効になっています。 - + Post-processing is now enabled. ポストプロセスが有効になりました。 - + Post-processing is now disabled. ポストプロセスが無効になりました。 - + Failed to load post-processing shader chain. ポストプロセスシェーダーチェーンの読み込みに失敗しました。 - + Post-processing shaders reloaded. ポストプロセスシェーダーがリロードされました。 @@ -8312,182 +8430,192 @@ Please configure a supported controller from the list above. + Using software renderer for readbacks based on game settings. + ゲームごとの設定に基づき、リードバック用にソフトウェアレンダラーを使用します。 + + + Interlacing forced by game settings. ゲームごとの設定に基づき、インターレースを有効化します。 - + True color disabled by game settings. ゲームごとの設定に基づき、トゥルーカラーを無効化します。 - + Upscaling disabled by game settings. ゲームごとの設定に基づき、アップスケーリングを無効化します。 - + Scaled dithering disabled by game settings. ゲームごとの設定に基づき、スケーリングされたディザリングを無効化します。 - + Widescreen disabled by game settings. ゲームごとの設定に基づき、ワイドスクリーンを無効化します。 - + Forcing NTSC Timings disallowed by game settings. ゲームごとの設定に基づき、NTSC タイミングの強制を無効化します。 - + PGXP geometry correction disabled by game settings. ゲームごとの設定に基づき、PGXP ジオメトリ補正を無効化します。 - + PGXP culling disabled by game settings. ゲームごとの設定に基づき、PGXP カリングを無効化します。 - + PGXP perspective corrected textures disabled by game settings. ゲームごとの設定に基づき、PGXP テクスチャ遠近補正を無効化します。 - + PGXP perspective corrected colors disabled by game settings. ゲームごとの設定に基づき、PGXP カラー遠近補正を無効化します。 - + PGXP vertex cache forced by game settings. ゲームごとの設定に基づき、PGXP 頂点キャッシュを有効化します。 - + PGXP CPU mode forced by game settings. ゲームごとの設定に基づき、PGXP CPU モードを有効化します。 - + PGXP Depth Buffer disabled by game settings. ゲームごとの設定に基づき、PGXP 深度バッファを無効化します。 - + Memory card %u from save state does match current card data. Simulating replugging. 元の英語文章は "does not match" の間違いと思われる (コード上でも二つのメモリーカードの内容 (GetData() したもの) を == で比較した else 側なので) ステートセーブ内のメモリーカード %u は、現在のカードデータと一致しません。カードを差し直します。 - + Memory card %u present in save state but not in system. Ignoring card. ステートセーブ内にメモリーカード %u が存在していますが、システムには存在しません。カードを無視します。 - + Memory card %u present in system but not in save state. Replugging card. メモリーカード %u がシステムに存在していますが、ステートセーブ内には存在していません。カードを差し直します。 - + Memory card %u present in save state but not in system. Creating temporary card. ステートセーブ内にメモリーカード %u が存在していますが、システムには存在しません。一時的なカードを作成します。 - + Save state contains controller type %s in port %u, but %s is used. Switching. ステートセーブ内でコントローラータイプ %s (ポート %u)を使用していましたが、現在は %s を使用しています。切り替えます。 - + Ignoring mismatched controller type %s in port %u. ステートセーブ内でコントローラータイプ %s (ポート %u)を使用していましたが、無視します。 - + Memory card %u present in system but not in save state. Removing card. メモリーカード %u がシステムに存在していますが、ステートセーブ内には存在していません。カードを取り外します。 - + CD image preloading not available for multi-disc image '%s' マルチディスクイメージ '%s' では CD イメージのプリロードは使用できません - + Precaching CD image failed, it may be unreliable. CD イメージのプリキャッシングに失敗しました。予期せぬ動作をする恐れがあります。 - + Loading state from '{}'... '{}' からステートロードします... - + Save State ステートセーブ - + State saved to '{}'. '{}' にステートセーブしました。 - + CPU clock speed is set to %u%% (%u / %u). This may result in instability. CPU クロック速度が %u%% (%u / %u) に設定されています。これにより、不安定になる可能性があります。 - + CD-ROM read speedup set to %ux (effective speed %ux). This may result in instability. CD-ROM 読み込み高速化が %ux (実効速度 %ux)に設定されています。これにより、不安定になる可能性があります。 - + CD-ROM seek speedup set to instant. This may result in instability. CD-ROM シーク高速化が "無限/瞬間" に設定されています。これにより、不安定になる可能性があります。 - + CD-ROM seek speedup set to %ux. This may result in instability. CD-ROM シーク高速化が %ux に設定されています。これにより、不安定になる可能性があります。 - + Failed to initialize %s renderer, falling back to software renderer. %s レンダラーの初期化に失敗しました。代わりにソフトウェアレンダラーを使用します。 - + + This save state was created with a different BIOS version or patch options. This may cause stability issues. + このステートセーブは異なるバージョンの BIOS またはパッチオプションで保存されています。このため動作が不安定になる可能性があります。 + + + WARNING: CPU overclock (%u%%) was different in save state (%u%%). 警告: CPU オーバークロック (現在 %u%%) はステートセーブ内では %u%% でした。 - + Failed to open CD image from save state '%s': %s. Using existing image '%s', this may result in instability. ステートセーブ中で使用していた CD イメージ '%s' を開くことができませんでした: %s. 既存の CD イメージ '%s' を使用します。これにより、不安定になる可能性があります。 - + Failed to open disc image '%s': %s. ディスクイメージ '%s' を開くことができませんでした: %s. - + Failed to switch to subimage %u in '%s': %s. サブイメージ %u ('%s' 内)への切り替えに失敗しました: %s. - + Switched to sub-image %s (%u) in '%s'. サブイメージ %s (%u 枚目、'%s' 内)に切り替えました。 - + Inserted disc '%s' (%s). ディスク '%s' (%s) を挿入しました。 @@ -8548,44 +8676,44 @@ Please configure a supported controller from the list above. メモリーカードを '{}' に保存しました。 - + Acquired exclusive fullscreen. 排他的フルスクリーンを取得しました。 - + Failed to acquire exclusive fullscreen. 排他的フルスクリーンの取得に失敗しました。 - + Lost exclusive fullscreen. 排他的フルスクリーンを失いました。 - - - OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.0 is required. - OpenGL レンダラーが利用できません。ドライバーまたはハードウェアが対応していません。OpenGL 3.1 または OpenGL ES 3.0 が必要です。 - Analog mode forcing is disabled by game settings. Controller will start in digital mode. ゲームごとの設定に基づき、アナログモードの強制を無効化します。コントローラーはデジタルモードで動作します。 - + %n cheats are now active. %n 個のチートが有効になりました。 - + %n cheats are now inactive. %n 個のチートが無効になりました。 + + + OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required. + OpenGL レンダラーが利用できません。ドライバーまたはハードウェアが対応していません。OpenGL 3.1 または OpenGL ES 3.1 が必要です。 + PlayStationMouse @@ -8770,29 +8898,29 @@ URL は次のとおりです: %1 QtHost - - - + + + Error エラー - + File '%1' does not exist. ファイル '%1' は存在していません。 - + The specified save state does not exist. 指定されたステートセーブは存在していません。 - + Cannot use no-gui mode, because no boot filename was specified. 起動するファイル名が指定されていないため、GUI なしモードを使用できません。 - + Cannot use batch mode, because no boot filename was specified. 起動するファイル名が指定されていないため、バッチモードを使用できません。 @@ -8828,42 +8956,42 @@ URL は次のとおりです: %1 SaveStateSelectorUI - + Load 読み込み - + Save 保存 - + Select Previous 前を選択 - + Select Next 次を選択 - + No Save State ステートセーブがありません - + Global Slot %d グローバルスロット %d - + Game Slot %d ゲームスロット %d - + %s Slot %d %s スロット %d @@ -9080,58 +9208,63 @@ URL は次のとおりです: %1 System - + Save state is incompatible: minimum version is %u but state is version %u. ステートセーブに互換性がありません。最小バージョンは %u ですが、ステートセーブはバージョン %u です。 - + Failed to load %s BIOS. BIOS %s の読み込みに失敗しました。 - - + + Error エラー - + Failed to load save state file '{}' for booting. 起動用のステートセーブファイル '{}' の読み込みに失敗しました。 - + + Incorrect BIOS image size + BIOS イメージのサイズが正しくありません + + + Save state is incompatible: maximum version is %u but state is version %u. ステートセーブに互換性がありません。最大バージョンは %u ですが、ステートセーブはバージョン %u です。 - + Failed to open CD image '%s' used by save state: %s. ステートセーブで使用していた CD イメージ '%s'を開くことができませんでした: %s. - + Failed to switch to subimage %u in CD image '%s' used by save state: %s. ステートセーブで使用していたサブイメージ %u (CD イメージ '%s' 内)への切り替えに失敗しました: %s. - + Per-game memory card cannot be used for slot %u as the running game has no code. Using shared card instead. 実行中のゲームにはコードがないため、ゲームごとのメモリーカードをスロット %u に使用することはできません。代わりに共有カードを使用します。 - + Per-game memory card cannot be used for slot %u as the running game has no title. Using shared card instead. 実行中のゲームには名称がないため、ゲームごとのメモリーカードをスロット %u に使用することはできません。代わりに共有カードを使用します。 - + Per-game memory card cannot be used for slot %u as the running game has no path. Using shared card instead. 実行中のゲームのパスがないため、ゲームごとのメモリーカードをスロット %u に使用することはできません。代わりに共有カードを使用します。 - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s @@ -9148,12 +9281,12 @@ The name of the SBI file must match the name of the disc image. SBI ファイルの名前は、ディスクイメージの名前と一致する必要があります。 - + Game changed, reloading memory cards. ゲームが変更されました。メモリーカードを再読み込みします。 - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s diff --git a/src/duckstation-qt/translations/duckstation-qt_pt-br.ts b/src/duckstation-qt/translations/duckstation-qt_pt-br.ts index 1e296cfcab..e61b1c1874 100644 --- a/src/duckstation-qt/translations/duckstation-qt_pt-br.ts +++ b/src/duckstation-qt/translations/duckstation-qt_pt-br.ts @@ -215,7 +215,7 @@ função, considere fazer sua conta em:<a href="https://retroachievement Enable Hardcore Mode - Ativar modo dificílimo + Ativar modo hardcore Enable Hardcode Mode @@ -359,57 +359,57 @@ Token gerado %2. Achievements - + Loading state Carregando estado - + Resuming state Retomando estado - + Hardcore mode disabled by state switch. Modo dificílimo desligado pelo estado salvo. - + Hardcore mode will be enabled on system reset. Modo dificílimo será ligado assim que o sistema for reiniciado. - + Confirm Hardcore Mode Confirmar modo dificílimo - + {0} cannot be performed while hardcore mode is active. Do you want to disable hardcore mode? {0} will be cancelled if you select No. {0} não é possível no momento enquanto o modo dificílimo estiver ligado. Deseja desativar o dificílimo? {0} será cancelado se você escolher Não. - + Hardcore mode is now enabled. O modo Hardcore está ativado. - + Hardcore mode is now disabled. O modo Hardcore está desligado. - + {} (Hardcore Mode) {} (Modo dificílimo) - + You have earned {} of {} achievements, and {} of {} points. Você ganhou {} de {} conquistas e {} de {} pontos. - + This game has no achievements. Este jogo não possui conquistas. @@ -422,7 +422,7 @@ Token gerado %2. As tabelas de classificação estão desligadas porque o modo dificílimo está desativado. - + Your Score: {} (Best: {}) Leaderboard Position: {} of {} Sua pontuação: {} (Melhor: : {}) @@ -478,25 +478,25 @@ Posição nos placares de lideres: {} de {} - + Log To System Console Carregar para console - + Log To Window Carregar para janela - + Log To Debug Console Carregar para console de depuração - + Log To File Carregar para arquivo @@ -536,7 +536,7 @@ Posição nos placares de lideres: {} de {} GPU Max Run-Ahead: - + PGXP Vertex Cache PGXP vértice armazenado @@ -549,22 +549,22 @@ Posição nos placares de lideres: {} de {} Preservar Precisão e Projeção do PGXP - + PGXP Geometry Tolerance Tolerância geometrica do PGXP - + PGXP Depth Clear Threshold Limite do PGXP limpo - + Enable Recompiler Block Linking Ativar conexão de bloco recompilador - + Enable Recompiler Fast Memory Access Ativar recompilador de acesso a RAM @@ -573,12 +573,12 @@ Posição nos placares de lideres: {} de {} Ativar recompilador ICache - + Enable VRAM Write Texture Replacement Ativar texturas personalizadas - + Preload Texture Replacements Pré-carregar texturas personalizadas @@ -587,47 +587,47 @@ Posição nos placares de lideres: {} de {} Despejar Texturas Substituíveis - + Set Dumped VRAM Write Alpha Channel Definie o mínimo de despejo (Canal alpha) - + Minimum Dumped VRAM Write Width Mínima do despejo (largura) - + Minimum Dumped VRAM Write Height Mínima do despejo (altura) - + DMA Max Slice Ticks DMA Max Slice Ticks - + DMA Halt Ticks DMA Halt Ticks - + GPU FIFO Size Tamanho de FIFO da GPU - + GPU Max Run-Ahead Execução máxima antecipada da GPU - + Display FPS Limit Mostrar limite de FPS - + Disable All Enhancements Desativar todas as melhorias @@ -636,12 +636,12 @@ Posição nos placares de lideres: {} de {} Mostrar indicadores de tela cheia - + Dump Replaceable VRAM Writes Despejar texturas - + Increase Timer Resolution Aumentar resolução em tempo real @@ -655,7 +655,7 @@ Posição nos placares de lideres: {} de {} Redefinir para o padrão - + Enable Recompiler Memory Exceptions Habilitar exceções de memória @@ -666,12 +666,12 @@ Posição nos placares de lideres: {} de {} - + Show Debug Menu Mostrar menu de depuração - + Show Status Indicators Mostrar indicadores de situação @@ -684,118 +684,138 @@ Posição nos placares de lideres: {} de {} Modo de controle aprimorado (PS4/PS5) - + Apply Compatibility Settings Aplicar configurações de compatibilidade - + Multisample Antialiasing Filtro anti-serrilhado - + Display Active Start Offset Exibir desvio inicial ativo - + Display Active End Offset Exibir desvio final ativo - + Display Line Start Offset Exibir desvio inicial da linha - + Display Line End Offset Exibir desvio final da linha - + Use Old MDEC Routines Usar rotinas MDEC antigas - + Use Debug Host GPU Device Usar GPU para depuração - + Stretch Display Vertically Esticar imagem verticalmente - + Allow Booting Without SBI File Inicializar jogos sem arquivo SBI - + Create Save State Backups Criar cópia de segurança de salvamentos rápidos - + Log Level Nivel do registro - + + Select folder for %1 + Escolha a pasta para %1 + + + Information Informação - + Sets the verbosity of messages logged. Higher levels will log more messages. Configura o nível detalhado de mensagens que serão armazenadas. Níveis mais altos terão mensagens maiores. - - - - + + + + User Preference Preferência do usuário - + Logs messages to the console window. Mostra mensagens na janela de console. - + Logs messages to the debug console where supported. Mostra mensagens no console de depuração quando suportado. - + Logs messages to the window. Mostra o registro em tela. - + Logs messages to duckstation.log in the user directory. Cria um arquivo de registro (duckstation.log) no diretório do usuário. - + Unchecked Desmarcado - + Shows a debug menu bar with additional statistics and quick settings. Mosta a opção depuração com estatísticas adicionais e configurações rápidas. - + Show Frame Times Mostrar tempos por quadro + + + Enable PCDrv + Ativar PCDrv + + + + Enable PCDrv Writes + Ativar escrita PCDrv + + + + PCDrv Root Directory + Diretório raiz PCDrv + Enables the usage of debug devices and shaders for rendering APIs which support them. Should only be used when debugging the emulator. Permite o uso de dispositivos de depuração e shaders para renderizar APIs que os suportam. Só deve ser usado ao depurar o emulador. @@ -866,52 +886,57 @@ Posição nos placares de lideres: {} de {} Inverter Esquerda/Direita + Cima/Baixo - + + Forces the controller to analog mode when the console is reset/powered on. + Força o controle a entrar no modo analógico quando o console é reiniciado/ligado. + + + Analog Deadzone Zona morta do analógico - + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. Define a zona morta do analógico, por exemplo:. a fração do movimento do analógico que será ignorada. - + Analog Sensitivity Sensibilidade do analógico - + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. Define o fator de escala do eixo do analógico. Um valor entre 130% e 140% é recomendável quando estiver usando controles mais recentes, por exemplo: Dualshock 4 e controles de Xbox One. - + Button/Trigger Deadzone Botão/Gatilho zona morta - + Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored. Define a zona morta para acionamento de botões e gatilhos, ou seja, a fração do gatilho que será ignorada. - + Invert Left Stick Inverter analógico esquerdo - + Inverts the direction of the left analog stick. Inverte a direção do controle analógico esquerdo. - + Invert Right Stick Inverter analógico direito - + Inverts the direction of the right analog stick. Inverte a direção do controle analógico direito. @@ -1007,9 +1032,8 @@ Posição nos placares de lideres: {} de {} Forçar modo analógico ao reiniciar - Forces the controller to analog mode when the console is reset/powered on. May cause issues with games, so it is recommended to leave this option off. - Força os controles a ficarem no modo analógico quando o console é reiniciado ou religado. Pode causar problemas em alguns jogos, portanto considere deixar esta opção desligada. + Força os controles a ficarem no modo analógico quando o console é reiniciado ou religado. Pode causar problemas em alguns jogos, portanto considere deixar esta opção desligada. Enable Analog Mode on Reset @@ -1020,12 +1044,12 @@ Posição nos placares de lideres: {} de {} Ativa o modo analógico automaticamente quando o console é reiniciado / desligado. - + Use Analog Sticks for D-Pad in Digital Mode Usar analógicos como D-Pad no modo digital - + Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons. Te permite usar os analógicos como um direcional (D-Pad) no modo digital, assim como os botões. @@ -1038,12 +1062,12 @@ Posição nos placares de lideres: {} de {} Define o fator de escala do eixo do analógico. Um valor entre 1.30 e 1.40 é recomendável quando estiver usando controles mais recentes, por exemplo: Dualshock 4 e controles de Xbox One. - + Vibration Bias Vibração - + Sets the rumble bias value. If rumble in some games is too weak or not functioning, try increasing this value. Define valores de vibração. Se a vibração em alguns jogos estiver fraca ou não funcionar, tente aumentar estes valores. @@ -1222,17 +1246,17 @@ Posição nos placares de lideres: {} de {} AudioBackend - + Null (No Output) Mudo - + Cubeb Cubed - + XAudio2 XAudio2 @@ -1721,17 +1745,17 @@ Posição nos placares de lideres: {} de {} Interpretador (Mais Lento) - + Interpreter (Slowest) Interpretador (mais lento) - + Cached Interpreter (Faster) Interpretador armazenado (rápido) - + Recompiler (Fastest) Recompilador (mais rápido) @@ -1739,17 +1763,17 @@ Posição nos placares de lideres: {} de {} CPUFastmemMode - + Disabled (Slowest) Desativado (Lento) - + MMap (Hardware, Fastest, 64-Bit Only) MMap (hardware, mais rápido) - + LUT (Faster) LUT (rápido) @@ -2396,7 +2420,7 @@ Posição nos placares de lideres: {} de {} O estado atual será salvo. - + Invalid version %u (%s version %u) Versão inválida %u (%s versão %u) @@ -2408,17 +2432,17 @@ Posição nos placares de lideres: {} de {} ConsoleRegion - + Auto-Detect Detectar automaticamente - + NTSC-J (Japan) NTSC-J (Japão) - + NTSC-U/C (US, Canada) NTSC-U/C (US, Canadá) @@ -2427,7 +2451,7 @@ Posição nos placares de lideres: {} de {} NTSC-U (US) - + PAL (Europe, Australia) PAL (Europeu, Austrália) @@ -4218,24 +4242,24 @@ Esta ação não poderá ser desfeita. ControllerType - + None Nenhum - + Digital Controller Controle digital - + Analog Controller (DualShock) Controle analógico (dualshock) - + Analog Joystick Controle analógico @@ -4245,27 +4269,32 @@ Esta ação não poderá ser desfeita. - + PlayStation Mouse Mouse Playstation - + NeGcon NeGcon - + Analog Controller Controle analógico - + GunCon GunCon + + + Not Connected + Não conectado + CoverDownloadDialog @@ -4358,42 +4387,42 @@ Esta ação não poderá ser desfeita. DebuggerMessage - + Added breakpoint at 0x%08X. Interruptor adicionado em 0x%08X. - + Removed breakpoint at 0x%08X. Interruptor removido em 0x%08X. - + 0x%08X is not a call instruction. 0x%08X não é uma chamada de instrução. - + Can't step over double branch at 0x%08X Não é possível duplicar ramificações em 0x%08X - + Stepping over to 0x%08X. Pulando para 0x%8X. - + Instruction read failed at %08X while searching for function end. Leitura de instrução falhou em %08X ao final da busca. - + Stepping out to 0x%08X. Saindo em 0x%08X. - + No return instruction found after %u instructions for step-out at %08X. Sem instrução de retorno encontrada após%u nas instruções de saída em %08X. @@ -4846,7 +4875,7 @@ This file can be several gigabytes, so be aware of SSD wear. DiscRegion - + NTSC-J (Japan) NTSC-J (Japão) @@ -4855,35 +4884,40 @@ This file can be several gigabytes, so be aware of SSD wear. NTSC-U (US) - + NTSC-U/C (US, Canada) NTSC-U/C (US, Canadá) - + PAL (Europe, Australia) PAL (Europeu, Austrália) - + Other Outros + + + Non-PS1 + + DisplayAlignment - + Left / Top Topo superior - + Center Centro - + Right / Bottom Esquerda inferior @@ -4891,17 +4925,17 @@ This file can be several gigabytes, so be aware of SSD wear. DisplayAspectRatio - + Auto (Game Native) Auto (resolução nativa) - + Auto (Match Window) Auto (corresponder a janela) - + Custom Personalizado @@ -4909,17 +4943,17 @@ This file can be several gigabytes, so be aware of SSD wear. DisplayCropMode - + None Nenhuma - + Only Overscan Area Somente área renderizada - + All Borders Todas as bordas @@ -5357,7 +5391,7 @@ This file can be several gigabytes, so be aware of SSD wear. Salvamento rápido não encontrado. - + Game ID: %1 Game Title: %2 Achievements: %5 (%6) @@ -5370,7 +5404,7 @@ Conquistas: %5 (%6) - + %n points %n pontos @@ -5378,27 +5412,27 @@ Conquistas: %5 (%6) - + Rich presence inactive or unsupported. Presença rica do Discord inativa ou não suportada. - + Game not loaded or no RetroAchievements available. Jogo não carregado ou sem conquistas disponíveis. - + %1x%2 %1x%2 - + Game: %1 FPS Jogo: %1 FPS - + Video: %1 FPS (%2%) Vídeo: %1 FPS (%2%) @@ -5647,8 +5681,8 @@ Conquistas: %5 (%6) Rewind for %n frame(s), lasting %1 second(s) will require up to %2MB of RAM and %3MB of VRAM. - Retroceder por % quadros, durará %1 segundo(s) que exigirá pouco mais de %2MB de RAM e %3MB de V-RAM. - + Retroceder por %n quadro, durará %1 segundo o que exigirá pouco mais de %2MB de RAM e %3MB de V-RAM. + Retroceder por %n quadros, durará %1 segundo(s) que exigirá pouco mais de %2MB de RAM e %3MB de V-RAM. @@ -6067,17 +6101,17 @@ Conquistas: %5 (%6) GPUDownsampleMode - + Disabled Desativado - + Box (Downsample 3D/Smooth All) Misto (Reduz 3D / Suaviza tudo) - + Adaptive (Preserve 3D/Smooth 2D) Adaptativo (Preserva o 3D / Suaviza 2D) @@ -6085,27 +6119,27 @@ Conquistas: %5 (%6) GPURenderer - + Hardware (D3D11) Placa de vídeo (D3D11) - + Hardware (D3D12) Placa de vídeo (D3D12) - + Hardware (Vulkan) Placa de vídeo (Vulkan) - + Hardware (OpenGL) Placa de vídeo (OpenGL) - + Software Software @@ -6528,32 +6562,32 @@ Conquistas: %5 (%6) GPUTextureFilter - + Nearest-Neighbor Nearest-Neighbor - + Bilinear Bi-linear - + JINC2 (Slow) JINC2 (Lento) - + JINC2 (Slow, No Edge Blending) JINC2 (Lento, sem AA) - + xBR (Very Slow) xBR (Muito lento) - + xBR (Very Slow, No Edge Blending) xBR (Muito lento sem AA) @@ -6562,7 +6596,7 @@ Conquistas: %5 (%6) JINC2 - + Bilinear (No Edge Blending) Bi-linear (sem AA) @@ -6582,72 +6616,72 @@ Conquistas: %5 (%6) GameList - + Disc Disco - + PS-EXE - + Playlist Lista de reprodução - + PSF - + Never Nunca - + Today Hoje - + Yesterday Ontem - + {}h {}m {}h {}m - + {}h {}m {}s {}h {}m {}s - + {}m {}s {}m {}s - + {}s {}s - + None Nenhum - + {} hours {} horas - + {} minutes {} minutos @@ -6655,32 +6689,32 @@ Conquistas: %5 (%6) GameListCompatibilityRating - + Unknown Desconhecido - + Doesn't Boot Não funciona - + Crashes In Intro Quebra logo no início - + Crashes In-Game Quebra durante o jogo - + Graphical/Audio Issues Problemas de áudio e vídeo - + No Issues Sem problemas @@ -7432,12 +7466,12 @@ This will download approximately 4 megabytes over your current internet connecti GameSettingsTrait - + Force Interpreter Forçar interpretador - + Force Software Renderer Forçar renderização por software @@ -7446,57 +7480,57 @@ This will download approximately 4 megabytes over your current internet connecti Ativar Entrelaçamento - + Force Software Renderer For Readbacks Forçar modo software para releituras - + Force Interlacing Forçar o entrelaçamento - + Disable True Color Desativar cor real - + Disable Upscaling Desativar escalonamento - + Disable Scaled Dithering Desativar dithering escalonado - + Disallow Forcing NTSC Timings Desativa os temporizadores em NTSC - + Disable Widescreen Desativar ajuste de tela panorâmica - + Disable PGXP Desativar PGXP - + Disable PGXP Culling Desativar correção de curvas - + Disable PGXP Perspective Correct Textures Desativar correção de texturas de perspectiva PGXP - + Disable PGXP Perspective Correct Colors Desativar correção de cores de perspectiva PGXP @@ -7505,22 +7539,22 @@ This will download approximately 4 megabytes over your current internet connecti Desligar correção de textura (PGXP) - + Disable PGXP Depth Buffer Desligar PGXP modo eixo Z - + Force PGXP Vertex Cache Forçar armazenamento em modo PGXP - + Force PGXP CPU Mode Força o PGXP em modo CPU - + Force Recompiler LUT Fastmem Forçar recompilador LUT fastmen @@ -7529,12 +7563,12 @@ This will download approximately 4 megabytes over your current internet connecti Desativar Modo Analógico Forçado ao Reiniciar - + Force Recompiler Memory Exceptions Forçar exceções de memória do recompilador - + Force Recompiler ICache Força recompilador em modo armazenado (ICache) @@ -8750,22 +8784,22 @@ This will download approximately 4 megabytes over your current internet connecti LogLevel - + None Nenhum - + Error Erro - + Warning Alerta - + Performance Desempenho @@ -8774,32 +8808,32 @@ This will download approximately 4 megabytes over your current internet connecti Sucesso - + Information Informação - + Developer Desenvolvedor - + Profile Perfil - + Verbose Detalhado - + Debug Depurar - + Trace Rastreio @@ -9657,7 +9691,7 @@ Você deseja que este arquivo seja carregado ou que seja reiniciado novamente? - + Cheat Manager Gerenciador de trapaças @@ -9827,51 +9861,51 @@ Esta ação não poderá ser desfeita. Escuro - + Confirm Shutdown Confirmar desligamento - + Are you sure you want to shut down the virtual machine? Tem certeza de que deseja desligar a máquina virtual? - + Save State For Resume Salvar estado e continuar - - - - + + + + Memory Card Not Found Cartão de memória não encontrado - + Memory card '%1' does not exist. Do you want to create an empty memory card? Cartão de memória '%1' não existe, você deseja criar um cartão de memória vazio? - + Failed to create memory card '%1' Falha ao criar cartão de memória '%1' - - + + Memory card '%1' could not be found. Try starting the game and saving to create it. Cartão de memória '%1' não encotrado. Experimente iniciar o jogo e salvá-lo para que ele seja criado. - + Do not show again Não mostrar de novo - + Using cheats can have unpredictable effects on games, causing crashes, graphical glitches, and corrupted saves. By using the cheat manager, you agree that it is an unsupported configuration, and we will not provide you with any assistance when games break. Cheats persist through save states even after being disabled, please remember to reset/reboot the game after turning off any codes. @@ -9884,17 +9918,17 @@ As trapaças ficam guardadas no estado de salvamento rápido mesmo após serem d Tem certeza de que deseja continuar? - + Updater Error Erro na atualização - + <p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To prevent incompatibilities, the auto-updater is only enabled on official builds.</p><p>To obtain an official build, please follow the instructions under "Downloading and Running" at the link below:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> <p>Desculpe, mas você está tentando atualizar uma versão não oficial do Duckstation. Para evitarmos imcompatibilidade, o atualizador automático só poderá ser usado nas versões oficiais! </p><p>Para obtê-las, siga as instruções de como e onde no link "Baixando e rodando" conforme abaixo:</p><p><a href="https://github.com/stenzek/duckstation/">https://github.com/stenzek/duckstation/</a></p> - + Automatic updating is not supported on the current platform. Atualizações automáticas não são compatíveis com a plataforma atual. @@ -10272,22 +10306,22 @@ Tem certeza de que deseja continuar? MemoryCardType - + No Memory Card Sem cartão de memória - + Shared Between All Games Compartilhada entre jogos - + Separate Card Per Game (Serial) Cartão separado por jogo (Serial) - + Separate Card Per Game (Title) Cartão separado por jogo (título) @@ -10300,12 +10334,12 @@ Tem certeza de que deseja continuar? Separar cartão por jogo (título do jogo) - + Separate Card Per Game (File Title) Separar cartão por jogo (título do arquivo) - + Non-Persistent Card (Do Not Save) Cartão não persistente (não salvar) @@ -10313,17 +10347,17 @@ Tem certeza de que deseja continuar? MultitapMode - + Disabled Desativado - + Enable on Port 1 Only Ativar somente na porta 1 - + Enable on Port 2 Only Ativar somente na porta 2 @@ -10332,7 +10366,7 @@ Tem certeza de que deseja continuar? Ativar somente na Porta 1 - + Enable on Ports 1 and 2 Ativar nas portas 1 e 2 @@ -10458,7 +10492,7 @@ Tem certeza de que deseja continuar? OSDMessage - + System reset. Sistema reiniciado. @@ -10467,12 +10501,12 @@ Tem certeza de que deseja continuar? Estado carregado de '%s'... - + Loading state from '%s' failed. Resetting. Carregamento de estado '%s'.falhou. reiniciando. - + Saving state to '%s' failed. Falha ao salvar estado em '%s'. @@ -10481,27 +10515,27 @@ Tem certeza de que deseja continuar? Estado salvo em '%s'. - + PGXP is incompatible with the software renderer, disabling PGXP. PGXP é incompatível com o rederizador por software, desligando PGXP. - + Rewind is not supported on 32-bit ARM for Android. O retrocesso não é compatível com o a plataforma ARM de 32 bits para Android. - + Runahead is not supported on 32-bit ARM for Android. Pulo de quadros não é compatível com a versão de 32Bits. - + Rewind is disabled because runahead is enabled. Função de retrocesso desligada porque o avanço rápido está ligado. - + Recompiler options changed, flushing all blocks. As opções do recompilador foram alteradas, limpando todos os blocos. @@ -10514,17 +10548,17 @@ Tem certeza de que deseja continuar? Fastmem não disponível nesta plataforma, LUT será usado no lugar. - + Switching to %s%s GPU renderer. Mudando renderizador de GPU para %s%s. - + Switching to %s audio backend. Mudando tipo de saída de som para %s. - + Switching to %s CPU execution mode. Mudando para modo de execução %s. @@ -10545,24 +10579,24 @@ Tem certeza de que deseja continuar? Icache desativado, limpando blocos. - + PGXP enabled, recompiling all blocks. PGXP ativado, reconstruindo blocos. - + PGXP disabled, recompiling all blocks. PGXP desativado, reconstruindo blocos. - + Switching to %s renderer... Mudando para %s... - - + + Failed to load post processing shader chain. Falha ao carregar shader escolhido. @@ -10575,7 +10609,7 @@ Tem certeza de que deseja continuar? Limitador de Velocidade Desativado. - + %n cheats are now active. @@ -10583,7 +10617,7 @@ Tem certeza de que deseja continuar? - + %n cheats are now inactive. @@ -10626,12 +10660,12 @@ Tem certeza de que deseja continuar? Texturas personalizadas recarregadas. - + Failed to save undo load state. Falha ao desfazer carregar estado. - + Rewinding is not enabled. O retrocesso não está habilitado. @@ -10716,37 +10750,37 @@ Tem certeza de que deseja continuar? Perfil de controle carregado de '%s' - + Started dumping audio to '%s'. Iniciado despejo de áudio em '%s'. - + Failed to start dumping audio to '%s'. Falha ao iniciar despejo de áudio em '%s'. - + Stopped dumping audio. Despejo de áudio terminado. - + Screenshot file '%s' already exists. Captura de tela '%s' já existe. - + Failed to save screenshot to '%s' Falha ao salvar captura em '%s' - + Screenshot saved to '%s'. Captura de tela salva em '%s'. - + Controller in port %u (%s) is not supported for %s. Supported controllers: %s Please configure a supported controller from the list above. @@ -10761,12 +10795,12 @@ Please configure a supported controller from the list above. Usando perfil de controle '%s'. - + Failed to load cheats from '%s'. Falha ao carregar '%s' trapaças. - + %n cheats are enabled. This may result in instability. %n trapaças ativadas. Isso pode resultar em instabilidade. @@ -10774,32 +10808,32 @@ Please configure a supported controller from the list above. - + Widescreen hack is now enabled, and aspect ratio is set to %s. Ajuste de tela panorâmica ligado, e a proporção da imagem está definida como %s. - + Widescreen hack is now disabled, and aspect ratio is set to %s. Ajuste de tela panorâmica desligado, e a proporção da imagem está definida como %s. - + Swapped memory card ports. Both ports have a memory card. Portas de cartão de memória trocadas. ambas já contém cartão. - + Swapped memory card ports. Port 2 has a memory card, Port 1 is empty. Portas de cartão de memória trocadas. porta 2 tem um cartão de memória, porta 1 vazia. - + Swapped memory card ports. Port 1 has a memory card, Port 2 is empty. Portas de cartão de memória trocadas. porta 1 tem um cartão de memória, porta 2 vazia. - + Swapped memory card ports. Neither port has a memory card. Portas de cartão de memória trocadas. nenhuma das portas possui cartão de memória. @@ -10816,27 +10850,27 @@ Please configure a supported controller from the list above. Trapaça %u.salva para '%s'. - + Deleted cheat list '%s'. Trapaça '%s' apagada da lista. - + Cheat '%s' enabled. Trapaça '%s' ativada. - + Cheat '%s' disabled. Trapaça '%s' desativada. - + Failed to save cheat list to '%s' Falha ao salvar lista de trapaças para '%s' - + No cheats are loaded. Nenhuma trapaça foi carregada. @@ -10898,7 +10932,7 @@ Please configure a supported controller from the list above. - + Saved %n cheats to '%s'. Salvo %n trapaça em '%s'. @@ -10906,97 +10940,97 @@ Please configure a supported controller from the list above. - + Applied cheat '%s'. Trapaça aplicada '%s'. - + Cheat '%s' is already enabled. Trapaça '%s' já está ativada - + Post-processing is now enabled. Pós-processamento ligado. - + Post-processing is now disabled. Pós-processamento desligado. - + Failed to load post-processing shader chain. Falha ao carregar texturas de pós-processamento. - + Post-processing shaders reloaded. Texturas de pós-processamento recarregadas. - + CPU interpreter forced by game settings. Configurado o interpretador por CPU pela configuração individual. - + Software renderer forced by game settings. Renderização por software forçado pelas configurações individuais. - + Using software renderer for readbacks based on game settings. Usando o renderizador por software para releituras com base nas configurações individuais. - + Interlacing forced by game settings. Entrelaçamento forçado pela configuração individual. - + True color disabled by game settings. Efeito cor real desativado pelas configurações individuais. - + Upscaling disabled by game settings. Escalonamento desativado pelas configurações individuais. - + Scaled dithering disabled by game settings. Dithering escalonado desligado pelas configurações individuais. - + Widescreen disabled by game settings. Ajuste de tela panorâmica desativado pelas configurações. - + Forcing NTSC Timings disallowed by game settings. Temporizadores NTSC não permitidos pela configuração individual. - + PGXP geometry correction disabled by game settings. Correção geométrica desativada pelas configurações individuais. - + PGXP culling disabled by game settings. Correção de curvas desativada pela configuração individual. - + PGXP perspective corrected textures disabled by game settings. Perspectiva PGXP corrigiu as texturas desativadas pelas configurações por jogo. - + PGXP perspective corrected colors disabled by game settings. Perspectiva PGXP corrigiu as cores desativadas pelas configurações por jogo. @@ -11005,17 +11039,17 @@ Please configure a supported controller from the list above. Correção de curvas desligada pela configuração individual. - + PGXP vertex cache forced by game settings. Vértice armazenado forçado pelas configurações individuais. - + PGXP CPU mode forced by game settings. PGXP em modo CPU forçado pelas configurações individuais. - + PGXP Depth Buffer disabled by game settings. PGXP modo eixo Z desativado pelas configurações individuais. @@ -11067,12 +11101,12 @@ Please configure a supported controller from the list above. Cartão de memória %u presente no sistema mas não no estado salvo, removendo cartão. - + CD image preloading not available for multi-disc image '%s' O pré-carregamento da imagem do CD não está disponível para jogos de múltiplos discos '%s' - + Precaching CD image failed, it may be unreliable. Pré-alocação de disco falhou, pode ser que a imagem esteja danificada. @@ -11081,72 +11115,72 @@ Please configure a supported controller from the list above. Falha ao aplicar modificação ppf '%1s', usando imagem limpa. - + Loading state from '{}'... Estado carregado de '{}'... - + Save State Salvar estado - + State saved to '{}'. Estado salvo em '{}'. - + CPU clock speed is set to %u%% (%u / %u). This may result in instability. Velocidade do CPU foi mudada para %u%% isto resultará em instabilidades. - + CD-ROM read speedup set to %ux (effective speed %ux). This may result in instability. Leitura do CD-rom acelerada para %ux (velocidade apropriada %u). pode resultar em instabilidades. - + CD-ROM seek speedup set to instant. This may result in instability. Aumento de velocidade de busca do CD-ROM definida para instantânea. isso pode resultar em instabilidade. - + CD-ROM seek speedup set to %ux. This may result in instability. Aumento de velocidade de busca do CD-ROM definida para % ux. isso pode resultar em instabilidade. - + Failed to initialize %s renderer, falling back to software renderer. Falha ao inicializar o renderizador %s , retornando para renderizador por software. - + This save state was created with a different BIOS version or patch options. This may cause stability issues. O salvamento automático foi criado com uma versão do BIOS diferente ou modificada. Isto acarretará em problemas. - + WARNING: CPU overclock (%u%%) was different in save state (%u%%). ATENÇÃO: Aumento da velocidade (%u%%) era diferente do que no seu save anterior (%u%%). - + Failed to open CD image from save state '%s': %s. Using existing image '%s', this may result in instability. Falha ao abrir imagem do estado salvo '%s': %s. usando imagem existente '%s', isto, resultará em instabilidades. - + Failed to open disc image '%s': %s. Falha ao abrir o disco '%s': %s. - + Failed to switch to subimage %u in '%s': %s. Falha ao trocar para disco %u em '%s': %s. - + Switched to sub-image %s (%u) in '%s'. Mudado para segunda imagem %s (%u) em '%s'. @@ -11159,7 +11193,7 @@ Please configure a supported controller from the list above. Falha ao abrir o disco '%s'. - + Inserted disc '%s' (%s). Disco inserido '%s' (%s). @@ -11494,29 +11528,29 @@ The URL was: %1 QtHost - - - + + + Error Erro - + File '%1' does not exist. O arquivo '%1' não existe. - + The specified save state does not exist. O dado de salvamento não existe. - + Cannot use no-gui mode, because no boot filename was specified. Não é possível usar o modo no-gui, porque nenhum parâmetro de inicialização foi configurado. - + Cannot use batch mode, because no boot filename was specified. Não é possível usar este modo porque nenhum parâmetro de inicialização foi configurado. @@ -12088,53 +12122,53 @@ The saves will not be recoverable. Estado salvo incompatível: versão do mesmo esperada %u não a versão %u. - + Failed to load %s BIOS. Falha ao carregar %s BIOS. - - + + Error Erro - + Failed to load save state file '{}' for booting. Falha ao carregar o dado salvo no compartimento '{}'. - + Incorrect BIOS image size Tamanho da imagem do BIOS incorreta - + Save state is incompatible: minimum version is %u but state is version %u. Estado salvo incompatível: versão esperada %u não versão %u. - + Save state is incompatible: maximum version is %u but state is version %u. Estado salvo incompatível: versão esperada %u não versão %u. - + Failed to open CD image '%s' used by save state: %s. Falha ao abrir imagem do CD '%s' usado pelo estado salvo: %s. - + Failed to switch to subimage %u in CD image '%s' used by save state: %s. Falha ao trocar disco %u do CD '%s' usado pelo estado salvo: %s. - + Per-game memory card cannot be used for slot %u as the running game has no path. Using shared card instead. O cartão de memória individual não pôde ser usado no compartimento %u caminho não configurado. usando cartão compartilhado. - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s @@ -12167,13 +12201,13 @@ Seu despejo está incompleto, você deve adicionar o arquivo SBI para rodá-lo c Falha ao abrir estado salvo: '%s'. - + Per-game memory card cannot be used for slot %u as the running game has no code. Using shared card instead. Caminho para o cartão de memória no compartimento %u não pôde ser usado o jogo iniciado não possui um códido válido. Será usado um cartão compartilhado. - + Per-game memory card cannot be used for slot %u as the running game has no title. Using shared card instead. Caminho para o cartão de memória no compartimento %u não pôde ser usado o jogo iniciado não possui um nome válido. Será usado um cartão compartilhado. @@ -12183,12 +12217,12 @@ o jogo iniciado não possui um nome válido. Será usado um cartão compartilhad Caminho para o Cartão de Memória %u incorreto, usando o padrão. - + Game changed, reloading memory cards. Jogo trocado, recarregando cartões de memória. - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s diff --git a/src/duckstation-qt/translations/duckstation-qt_zh-cn.ts b/src/duckstation-qt/translations/duckstation-qt_zh-cn.ts index f457a78ec5..a3e555dc24 100644 --- a/src/duckstation-qt/translations/duckstation-qt_zh-cn.ts +++ b/src/duckstation-qt/translations/duckstation-qt_zh-cn.ts @@ -356,17 +356,17 @@ Login token generated on %2. Achievements - + Loading state 读档 - + Resuming state 继续档 - + Hardcore mode disabled by state switch. 档开关禁用硬核模式。 @@ -475,25 +475,25 @@ Leaderboard Position: {} of {} - + Log To System Console 记录到系统游戏主机 - + Log To Window 记录到窗口 - + Log To Debug Console 记录到调试游戏主机 - + Log To File 记录到文件 @@ -533,7 +533,7 @@ Leaderboard Position: {} of {} GPU最大优先级: - + PGXP Vertex Cache PGXP顶点缓存 @@ -546,7 +546,7 @@ Leaderboard Position: {} of {} PGXP保持投影精度 - + Show Status Indicators 显示状态指示器 @@ -559,52 +559,52 @@ Leaderboard Position: {} of {} 控制器增强模式 (PS4/PS5) - + Apply Compatibility Settings 应用兼容性设置 - + Multisample Antialiasing 多重采样反锯齿 - + Display Active Start Offset 显示活动起始偏移 - + Display Active End Offset 显示活动结束偏移 - + Display Line Start Offset 显示线起始偏移 - + Display Line End Offset 显示线结束偏移 - + PGXP Geometry Tolerance PGXP几何公差 - + PGXP Depth Clear Threshold PGXP深度清除阈值 - + Enable Recompiler Block Linking 启用块链接重编译器 - + Enable Recompiler Fast Memory Access 启用快速内存访问重编译器 @@ -613,135 +613,155 @@ Leaderboard Position: {} of {} 启用ICache重编译器 - + Use Old MDEC Routines 使用旧的MDEC惯例 - + Enable VRAM Write Texture Replacement 启用显存写入纹理替换 - + Preload Texture Replacements 预加载纹理替换 - + Dump Replaceable VRAM Writes 转储可替换的显存写入 - + Set Dumped VRAM Write Alpha Channel 设置转储的显存写入Alpha通道 - + Minimum Dumped VRAM Write Width 最小转储显存写入宽度 - + Minimum Dumped VRAM Write Height 最小转储显存写入高度 - + DMA Max Slice Ticks DMA最大片段时数 - + DMA Halt Ticks DMA停顿时数 - + GPU FIFO Size GPU的FIFO大小 - + GPU Max Run-Ahead GPU最大预运行 - + Stretch Display Vertically 垂直拉伸显示 - + Allow Booting Without SBI File 没有SBI文件情况下允许启动 - + Create Save State Backups 创建存档备份 - + + Enable PCDrv + 启用PCDrv + + + + Enable PCDrv Writes + 启用PCDrv写入 + + + + PCDrv Root Directory + PCDrv根目录 + + + Log Level 日志级别 - + + Select folder for %1 + 选择%1的文件夹 + + + Information 信息 - + Sets the verbosity of messages logged. Higher levels will log more messages. 设置记录的消息的详细级别。更高的级别将记录更多的消息。 - - - - + + + + User Preference 用户偏好 - + Logs messages to the console window. 将消息记录到游戏主机窗口。 - + Logs messages to the debug console where supported. 将消息记录到所支持的调试游戏主机。 - + Logs messages to the window. 将消息记录到窗口。 - + Logs messages to duckstation.log in the user directory. 将消息记录到用户数据目录中的duckstation.log。 - + Shows a debug menu bar with additional statistics and quick settings. 显示带有额外统计信息和快速设置的调试菜单栏。 - + Show Frame Times 显示帧时间 - + Display FPS Limit 显示帧率限制 - + Disable All Enhancements 禁用全部增强 @@ -750,7 +770,7 @@ Leaderboard Position: {} of {} 显示全屏状态指示器 - + Increase Timer Resolution 提高计时器分辨率 @@ -764,7 +784,7 @@ Leaderboard Position: {} of {} 重置为默认值 - + Enable Recompiler Memory Exceptions 启用内存异常重编译器 @@ -775,17 +795,17 @@ Leaderboard Position: {} of {} - + Show Debug Menu 显示调试菜单 - + Use Debug Host GPU Device 使用调试本机GPU设备 - + Unchecked 不勾选 @@ -859,52 +879,57 @@ Leaderboard Position: {} of {} 翻转左/右+上/下 - + + Forces the controller to analog mode when the console is reset/powered on. + 当游戏主机重启/开机时,强制控制器进入模拟模式。 + + + Analog Deadzone 模拟死区 - + Sets the analog stick deadzone, i.e. the fraction of the stick movement which will be ignored. 设置模拟摇杆死区,也就是摇杆移动中会被忽略的小部分。 - + Analog Sensitivity 模拟灵敏度 - + Sets the analog stick axis scaling factor. A value between 130% and 140% is recommended when using recent controllers, e.g. DualShock 4, Xbox One Controller. 设置模拟摇杆轴缩放系数。使用较新的控制器 (如DualShock-4、Xbox-One控制器) 时,建议使用130%到140%之间的值。 - + Button/Trigger Deadzone 按键/触发器死区 - + Sets the deadzone for activating buttons/triggers, i.e. the fraction of the trigger which will be ignored. 设置激活按键/触发器死区,也就是触发器中会被忽略的小部分。 - + Invert Left Stick 翻转左摇杆 - + Inverts the direction of the left analog stick. 翻转左模拟遥杆的方向。 - + Invert Right Stick 翻转右摇杆 - + Inverts the direction of the right analog stick. 翻转右模拟遥杆的方向。 @@ -998,9 +1023,8 @@ Leaderboard Position: {} of {} 重启时强制进入模拟模式 - Forces the controller to analog mode when the console is reset/powered on. May cause issues with games, so it is recommended to leave this option off. - 当游戏主机重启/开机时,强制控制器进入模拟模式。可能会导致游戏问题,所以建议关闭此选项。 + 当游戏主机重启/开机时,强制控制器进入模拟模式。可能会导致游戏问题,所以建议关闭此选项。 Enable Analog Mode on Reset @@ -1011,12 +1035,12 @@ Leaderboard Position: {} of {} 当主机复位/开机时,自动启用模拟模式。 - + Use Analog Sticks for D-Pad in Digital Mode D-Pad在数字模式下使用模拟摇杆 - + Allows you to use the analog sticks to control the d-pad in digital mode, as well as the buttons. 允许您在数字模式下使用模拟摇杆去控制d-pad,同样还有按键。 @@ -1029,12 +1053,12 @@ Leaderboard Position: {} of {} 设置模拟摇杆轴缩放系数。使用较新的控制器 (如DualShock-4、Xbox-One控制器) 时,建议使用1.30到1.40之间的值。 - + Vibration Bias 震动力 - + Sets the rumble bias value. If rumble in some games is too weak or not functioning, try increasing this value. 设置震动力的值。如果震动在某些游戏中太弱或不起作用,尝试增大这个值。 @@ -1209,17 +1233,17 @@ Leaderboard Position: {} of {} AudioBackend - + Null (No Output) 无 (无输出) - + Cubeb Cubeb - + XAudio2 XAudio2 @@ -1707,17 +1731,17 @@ Leaderboard Position: {} of {} 解释器 (最慢) - + Interpreter (Slowest) 解释器 (最慢) - + Cached Interpreter (Faster) 缓存解释器 (较快) - + Recompiler (Fastest) 重编译器 (最快) @@ -1725,17 +1749,17 @@ Leaderboard Position: {} of {} CPUFastmemMode - + Disabled (Slowest) 禁用 (最慢) - + MMap (Hardware, Fastest, 64-Bit Only) 内存映射 (硬件,最快,仅64位) - + LUT (Faster) LUT (较快) @@ -2382,7 +2406,7 @@ Leaderboard Position: {} of {} 当前进度将会保存。 - + Invalid version %u (%s version %u) 无效版本%u (%s版本%u) @@ -2390,22 +2414,22 @@ Leaderboard Position: {} of {} ConsoleRegion - + Auto-Detect 自动检测 - + NTSC-J (Japan) NTSC-J (日本) - + NTSC-U/C (US, Canada) NTSC-U/C (美国/加拿大) - + PAL (Europe, Australia) PAL (欧洲,澳大利亚) @@ -4155,24 +4179,24 @@ You cannot undo this action. ControllerType - + None - + Digital Controller 数字控制器 - + Analog Controller (DualShock) 模拟控制器 (DualShock) - + Analog Joystick 模拟操纵杆 @@ -4182,27 +4206,32 @@ You cannot undo this action. - + PlayStation Mouse PlayStation鼠标 - + NeGcon NeGcon - + Analog Controller 模拟控制器 - + GunCon 光枪 + + + Not Connected + 未连接 + CoverDownloadDialog @@ -4295,42 +4324,42 @@ You cannot undo this action. DebuggerMessage - + Added breakpoint at 0x%08X. 添加断点在0x%08X。 - + Removed breakpoint at 0x%08X. 移除断点在0x%08X。 - + 0x%08X is not a call instruction. 0x%08X不是调用指令。 - + Can't step over double branch at 0x%08X 无法在0x%08X跨越双分支。 - + Stepping over to 0x%08X. 跨越到0x%08X。 - + Instruction read failed at %08X while searching for function end. 搜索函数终端时无法在%08X下读取指令。 - + Stepping out to 0x%08X. 跨出到0x%08X。 - + No return instruction found after %u instructions for step-out at %08X. 在%u指令用于跨出%08X后没有找到返回指令。 @@ -4784,7 +4813,7 @@ This file can be several gigabytes, so be aware of SSD wear. DiscRegion - + NTSC-J (Japan) NTSC-J (日本) @@ -4793,17 +4822,17 @@ This file can be several gigabytes, so be aware of SSD wear. NTSC-U (美国) - + NTSC-U/C (US, Canada) NTSC-U/C (美国,加拿大) - + PAL (Europe, Australia) PAL (欧洲,澳大利亚) - + Other 其他 @@ -4811,17 +4840,17 @@ This file can be several gigabytes, so be aware of SSD wear. DisplayAlignment - + Left / Top 左/顶 - + Center - + Right / Bottom 右/底 @@ -4829,17 +4858,17 @@ This file can be several gigabytes, so be aware of SSD wear. DisplayAspectRatio - + Auto (Game Native) 自动 (游戏原设) - + Auto (Match Window) 自动 (匹配窗口) - + Custom 自定义 @@ -4847,17 +4876,17 @@ This file can be several gigabytes, so be aware of SSD wear. DisplayCropMode - + None - + Only Overscan Area 仅过扫描区域 - + All Borders 全部边界 @@ -6030,17 +6059,17 @@ Achievements: %5 (%6) GPUDownsampleMode - + Disabled 禁用 - + Box (Downsample 3D/Smooth All) 盒式 (缩减采样3D/平滑全部) - + Adaptive (Preserve 3D/Smooth 2D) 自适应 (保护3D/平滑2D) @@ -6048,27 +6077,27 @@ Achievements: %5 (%6) GPURenderer - + Hardware (D3D11) 硬件 (D3D11) - + Hardware (D3D12) 硬件 (D3D12) - + Hardware (Vulkan) 硬件 (Vulkan) - + Hardware (OpenGL) 硬件 (OpenGL) - + Software 软件 @@ -6187,32 +6216,32 @@ Achievements: %5 (%6) GPUTextureFilter - + Nearest-Neighbor 最近邻 - + Bilinear 双线性 - + JINC2 (Slow) JINC2 (慢) - + JINC2 (Slow, No Edge Blending) JINC2 (慢,无边缘混合) - + xBR (Very Slow) xBR (非常慢) - + xBR (Very Slow, No Edge Blending) xBR (非常慢,无边缘混合) @@ -6221,7 +6250,7 @@ Achievements: %5 (%6) JINC2 - + Bilinear (No Edge Blending) 双线性 (无边缘混合) @@ -8343,22 +8372,22 @@ This will download approximately 4 megabytes over your current internet connecti LogLevel - + None - + Error 错误 - + Warning 警告 - + Performance 性能 @@ -8367,32 +8396,32 @@ This will download approximately 4 megabytes over your current internet connecti 成功 - + Information 信息 - + Developer 开发商 - + Profile 简介 - + Verbose 详尽 - + Debug 调试 - + Trace 追踪 @@ -9862,22 +9891,22 @@ Are you sure you want to continue? MemoryCardType - + No Memory Card 没有记忆卡 - + Shared Between All Games 全部游戏共用记忆卡 - + Separate Card Per Game (Serial) 每个游戏独立记忆卡 (序号) - + Separate Card Per Game (Title) 每个游戏独立记忆卡 (标题) @@ -9890,12 +9919,12 @@ Are you sure you want to continue? 每个游戏独立记忆卡 (游戏标题) - + Separate Card Per Game (File Title) 每个游戏独立记忆卡 (文件标题) - + Non-Persistent Card (Do Not Save) 非持续记忆卡 (不保存) @@ -9903,17 +9932,17 @@ Are you sure you want to continue? MultitapMode - + Disabled 禁用 - + Enable on Port 1 Only 仅启用接口1 - + Enable on Port 2 Only 仅启用接口2 @@ -9922,7 +9951,7 @@ Are you sure you want to continue? 仅启用端口1 - + Enable on Ports 1 and 2 启用接口1和接口2 @@ -10044,7 +10073,7 @@ Are you sure you want to continue? OSDMessage - + System reset. 系统重启。 @@ -10053,12 +10082,12 @@ Are you sure you want to continue? 从'%s'读档... - + Loading state from '%s' failed. Resetting. 从'%s'读档失败,重启中。 - + Saving state to '%s' failed. 存档到'%s'失败。 @@ -10067,27 +10096,27 @@ Are you sure you want to continue? 存档到'%s'。 - + PGXP is incompatible with the software renderer, disabling PGXP. PGXP与软件呈现程序不兼容,禁用PGXP。 - + Rewind is not supported on 32-bit ARM for Android. 倒带不支持安卓系统下32位ARM处理器。 - + Runahead is not supported on 32-bit ARM for Android. 预运行不支持安卓系统下32位ARM处理器。 - + Rewind is disabled because runahead is enabled. 倒带被禁用,因为启用了预运行。 - + Recompiler options changed, flushing all blocks. 重编译器选项已更改,刷新全部区块。 @@ -10100,17 +10129,17 @@ Are you sure you want to continue? 快速内存映射在此平台上不可用,使用LUT代替。 - + Switching to %s%s GPU renderer. 切换到%s%sGPU渲染器。 - + Switching to %s audio backend. 切换到%s音频后端。 - + Switching to %s CPU execution mode. 切换到%sCPU执行模式。 @@ -10131,24 +10160,24 @@ Are you sure you want to continue? CPU的ICache禁用,刷新所有区块。 - + PGXP enabled, recompiling all blocks. PGXP启用,重编译全部区块。 - + PGXP disabled, recompiling all blocks. PGXP禁用,重编译全部区块。 - + Switching to %s renderer... 切换到%s渲染器... - - + + Failed to load post processing shader chain. 无法加载后处理着色器链。 @@ -10161,14 +10190,14 @@ Are you sure you want to continue? 限速器禁用。 - + %n cheats are now active. %n金手指已激活。 - + %n cheats are now inactive. %n金手指未激活。 @@ -10210,12 +10239,12 @@ Are you sure you want to continue? 纹理替换重新加载。 - + Failed to save undo load state. 无法保存撤消的读档。 - + Rewinding is not enabled. 倒带未启用。 @@ -10300,32 +10329,32 @@ Are you sure you want to continue? 从'%s'读取输入配置 - + Started dumping audio to '%s'. 开始转储音频到'%s'。 - + Failed to start dumping audio to '%s'. 无法开始转储音频到'%s'。 - + Stopped dumping audio. 停止转储音频。 - + Screenshot file '%s' already exists. 截图文件'%s'已经存在。 - + Failed to save screenshot to '%s' 无法保存截图到'%s'。 - + Screenshot saved to '%s'. 截图已保存到'%s'。 @@ -10347,44 +10376,44 @@ Please configure a supported controller from the list above. 使用输入配置'%s'。 - + Failed to load cheats from '%s'. 无法从'%s'加载金手指。 - + %n cheats are enabled. This may result in instability. %n金手指启用。这可能会导致不稳定。 - + Widescreen hack is now enabled, and aspect ratio is set to %s. 宽屏调整已启用,高宽比设置为%s。 - + Widescreen hack is now disabled, and aspect ratio is set to %s. 宽屏调整已禁用,高宽比设置为%s。 - + Swapped memory card ports. Both ports have a memory card. 交换记忆卡接口。两个接口都有记忆卡。 - + Swapped memory card ports. Port 2 has a memory card, Port 1 is empty. 交换记忆卡接口。接口2有一个记忆卡,接口1是空的。 - + Swapped memory card ports. Port 1 has a memory card, Port 2 is empty. 交换记忆卡接口。接口1有一个记忆卡,接口2是空的。 - + Swapped memory card ports. Neither port has a memory card. 交换记忆卡接口。两个接口都没有记忆卡。 @@ -10401,27 +10430,27 @@ Please configure a supported controller from the list above. 保存%u金手指到'%s'。 - + Deleted cheat list '%s'. 已删除的金手指列表'%s'。 - + Cheat '%s' enabled. 金手指'%s'启用。 - + Cheat '%s' disabled. 金手指'%s'禁用。 - + Failed to save cheat list to '%s' 无法保存金手指列表到'%s'。 - + No cheats are loaded. 没有加载金手指。 @@ -10480,39 +10509,39 @@ Please configure a supported controller from the list above. - + Saved %n cheats to '%s'. 保存%n金手指到'%s'。 - + Applied cheat '%s'. 应用金手指'%s'。 - + Cheat '%s' is already enabled. 金手指'%s'已经启用。 - + Post-processing is now enabled. 后处理已启用。 - + Post-processing is now disabled. 后处理已禁用。 - + Failed to load post-processing shader chain. 无法加载后处理着色器链。 - + Post-processing shaders reloaded. 后处理着色器重新加载。 @@ -10662,72 +10691,72 @@ Please configure a supported controller from the list above. 无法从'%s'应用ppf补丁,使用无补丁镜像。 - + Loading state from '{}'... 从'{}'读档。 - + Save State 存档 - + State saved to '{}'. 存档到'{}'。 - + CPU clock speed is set to %u%% (%u / %u). This may result in instability. CPU主频设置为%u%% (%u / %u)。这可能会导致不稳定。 - + CD-ROM read speedup set to %ux (effective speed %ux). This may result in instability. CD-ROM读取加速设置为%ux (有效速度%ux)。这可能会导致不稳定。 - + CD-ROM seek speedup set to instant. This may result in instability. CD-ROM寻道加速设置为瞬时。这可能会导致不稳定。 - + CD-ROM seek speedup set to %ux. This may result in instability. CD-ROM寻道加速设置为%ux。这可能会导致不稳定。 - + Failed to initialize %s renderer, falling back to software renderer. 无法初始化%s渲染器,退回到软件渲染器。 - + This save state was created with a different BIOS version or patch options. This may cause stability issues. 此存档是由不同的BIOS版本或补丁选项所创建的。这可能会导致稳定性问题。 - + WARNING: CPU overclock (%u%%) was different in save state (%u%%). 警告: CPU超频 (%u%%) 不同于存档 (%u%%)。 - + Failed to open CD image from save state '%s': %s. Using existing image '%s', this may result in instability. 无法从存档'%s': %s打开光盘镜像。使用现有镜像'%s',这可能会导致不稳定。 - + Failed to open disc image '%s': %s. 无法打开光盘镜像'%s': %s。 - + Failed to switch to subimage %u in '%s': %s. 无法切换到子镜像%u在'%s': %s。 - + Switched to sub-image %s (%u) in '%s'. 切换到子镜像%s (%u) 在'%s'。 @@ -10740,7 +10769,7 @@ Please configure a supported controller from the list above. 无法打开光盘镜像'%s'。 - + Inserted disc '%s' (%s). 已插入光盘'%s' (%s)。 @@ -10840,7 +10869,7 @@ Please configure a supported controller from the list above. 无法从光盘读取可执行文件。成就禁用。 - + OpenGL renderer unavailable, your driver or hardware is not recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required. OpenGL渲染器不可用,您的驱动程序或硬件不够新。需要OpenGL 3.1或OpenGL ES 3.1。 @@ -11672,7 +11701,7 @@ The saves will not be recoverable. 保存状态不兼容: 要求版本%u,但状态为版本%u。 - + Save state is incompatible: minimum version is %u but state is version %u. 存档不兼容: 最低可兼容版本为%u但存档版本为%u。 @@ -11681,28 +11710,28 @@ The saves will not be recoverable. 即时存档不兼容: %s版本为%u但存档版本为%u。 - + Failed to load %s BIOS. 无法加载%sBIOS。 - - + + Error 错误 - + Failed to load save state file '{}' for booting. 无法通过加载存档文件'{}'来启动。 - + Incorrect BIOS image size BIOS文件大小不正确 - + Save state is incompatible: maximum version is %u but state is version %u. 存档不兼容: 最高可兼容版本为%u但存档版本为%u。 @@ -11711,32 +11740,32 @@ The saves will not be recoverable. 无法从存档打开CD镜像: '%s'。 - + Failed to open CD image '%s' used by save state: %s. 无法打开光盘镜像'%s',存档: %s对其占用中。 - + Failed to switch to subimage %u in CD image '%s' used by save state: %s. 无法切换到子镜像%u在光盘镜像'%s',存档: %s对其占用中。 - + Per-game memory card cannot be used for slot %u as the running game has no code. Using shared card instead. 游戏没有编码,档位%u无法使用独立记忆卡的,改用共用记忆卡。 - + Per-game memory card cannot be used for slot %u as the running game has no title. Using shared card instead. 游戏没有标题,档位%u无法使用独立记忆卡的,改用共用记忆卡。 - + Per-game memory card cannot be used for slot %u as the running game has no path. Using shared card instead. 游戏没有路径,档位%u无法使用独立记忆卡的,改用共用记忆卡。 - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s @@ -11769,12 +11798,12 @@ Your dump is incomplete, you must add the SBI file to run this game. 插槽%u的记忆卡路径丢失,使用默认值。 - + Game changed, reloading memory cards. 游戏已改变,重新加载记忆卡。 - + You are attempting to run a libcrypt protected game without an SBI file: %s: %s diff --git a/src/duckstation-regtest/regtest_host_display.cpp b/src/duckstation-regtest/regtest_host_display.cpp index d651e80694..6383c22f2e 100644 --- a/src/duckstation-regtest/regtest_host_display.cpp +++ b/src/duckstation-regtest/regtest_host_display.cpp @@ -176,8 +176,8 @@ bool RegTestHostDisplay::Render(bool skip_present) return true; } -bool RegTestHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) +bool RegTestHostDisplay::RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, + std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) { return false; } diff --git a/src/duckstation-regtest/regtest_host_display.h b/src/duckstation-regtest/regtest_host_display.h index b10f0a379b..505fcc05e8 100644 --- a/src/duckstation-regtest/regtest_host_display.h +++ b/src/duckstation-regtest/regtest_host_display.h @@ -53,8 +53,8 @@ class RegTestHostDisplay final : public HostDisplay void SetVSync(bool enabled) override; bool Render(bool skip_present) override; - bool RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) override; + bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, std::vector* out_pixels, + u32* out_stride, GPUTexture::Format* out_format) override; bool SupportsTextureFormat(GPUTexture::Format format) const override; diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt index 6787bf87c6..1496aad3fd 100644 --- a/src/frontend-common/CMakeLists.txt +++ b/src/frontend-common/CMakeLists.txt @@ -126,6 +126,14 @@ if(USE_X11) target_include_directories(frontend-common PRIVATE "${X11_INCLUDE_DIR}") endif() +if(USE_DBUS) + target_compile_definitions(frontend-common PRIVATE USE_DBUS) + find_package(PkgConfig REQUIRED) + pkg_check_modules(DBUS REQUIRED dbus-1) + target_include_directories(frontend-common PRIVATE ${DBUS_INCLUDE_DIRS}) + target_link_libraries(frontend-common PRIVATE ${DBUS_LINK_LIBRARIES}) +endif() + if(ENABLE_DISCORD_PRESENCE) target_compile_definitions(frontend-common PUBLIC -DWITH_DISCORD_PRESENCE=1) target_link_libraries(frontend-common PRIVATE discord-rpc) diff --git a/src/frontend-common/achievements.cpp b/src/frontend-common/achievements.cpp index 74fbe3b567..5f61aed0b8 100644 --- a/src/frontend-common/achievements.cpp +++ b/src/frontend-common/achievements.cpp @@ -104,7 +104,7 @@ static void SendPingCallback(s32 status_code, std::string content_type, Common:: static void UnlockAchievementCallback(s32 status_code, std::string content_type, Common::HTTPDownloader::Request::Data data); static void SubmitLeaderboardCallback(s32 status_code, std::string content_type, - Common::HTTPDownloader::Request::Data data); + Common::HTTPDownloader::Request::Data data, u32 lboard_id); static bool s_active = false; static bool s_logged_in = false; @@ -137,7 +137,6 @@ static std::string s_rich_presence_string; static Common::Timer s_last_ping_time; static u32 s_last_queried_lboard = 0; -static u32 s_submitting_lboard_id = 0; static std::optional> s_lboard_entries; template @@ -356,7 +355,6 @@ void Achievements::ClearGameInfo(bool clear_achievements, bool clear_leaderboard } s_last_queried_lboard = 0; - s_submitting_lboard_id = 0; s_lboard_entries.reset(); } @@ -470,7 +468,7 @@ void Achievements::Initialize() s_logged_in = (!s_username.empty() && !s_api_token.empty()); if (System::IsValid()) - GameChanged(System::GetRunningPath(), nullptr); + GameChanged(System::GetDiscPath(), nullptr); } void Achievements::UpdateSettings(const Settings& old_config) @@ -1741,7 +1739,7 @@ void Achievements::UnlockAchievementCallback(s32 status_code, std::string conten } void Achievements::SubmitLeaderboardCallback(s32 status_code, std::string content_type, - Common::HTTPDownloader::Request::Data data) + Common::HTTPDownloader::Request::Data data, u32 lboard_id) { if (!System::IsValid()) return; @@ -1755,11 +1753,7 @@ void Achievements::SubmitLeaderboardCallback(s32 status_code, std::string conten // Force the next leaderboard query to repopulate everything, just in case the user wants to see their new score s_last_queried_lboard = 0; - // RA API doesn't send us the leaderboard ID back.. hopefully we don't submit two at once :/ - if (s_submitting_lboard_id == 0) - return; - - const Leaderboard* lb = GetLeaderboardByID(std::exchange(s_submitting_lboard_id, 0u)); + const Leaderboard* lb = GetLeaderboardByID(lboard_id); if (!lb || !FullscreenUI::IsInitialized() || !g_settings.achievements_notifications) return; @@ -1870,17 +1864,16 @@ void Achievements::SubmitLeaderboard(u32 leaderboard_id, int value) return; } - std::unique_lock lock(s_achievements_mutex); - - s_submitting_lboard_id = leaderboard_id; - RAPIRequest request; request.username = s_username.c_str(); request.api_token = s_api_token.c_str(); request.game_hash = s_game_hash.c_str(); request.leaderboard_id = leaderboard_id; request.score = value; - request.Send(SubmitLeaderboardCallback); + request.Send( + [leaderboard_id](s32 status_code, const std::string& content_type, Common::HTTPDownloader::Request::Data data) { + SubmitLeaderboardCallback(status_code, content_type, std::move(data), leaderboard_id); + }); } void Achievements::AchievementPrimed(u32 achievement_id) @@ -2142,7 +2135,7 @@ void Achievements::RAIntegration::RACallbackRebuildMenu() void Achievements::RAIntegration::RACallbackEstimateTitle(char* buf) { - StringUtil::Strlcpy(buf, System::GetRunningTitle(), 256); + StringUtil::Strlcpy(buf, System::GetGameTitle(), 256); } void Achievements::RAIntegration::RACallbackResetEmulator() diff --git a/src/frontend-common/common_host.cpp b/src/frontend-common/common_host.cpp index 1bcca55de8..6ceb7d9a0f 100644 --- a/src/frontend-common/common_host.cpp +++ b/src/frontend-common/common_host.cpp @@ -570,8 +570,8 @@ void CommonHost::UpdateDiscordPresence(bool rich_presence_only) SmallString details_string; if (!System::IsShutdown()) { - details_string.AppendFormattedString("%s (%s)", System::GetRunningTitle().c_str(), - System::GetRunningSerial().c_str()); + details_string.AppendFormattedString("%s (%s)", System::GetGameTitle().c_str(), + System::GetGameSerial().c_str()); } else { @@ -635,7 +635,7 @@ static void HotkeyLoadStateSlot(bool global, s32 slot) if (!System::IsValid()) return; - if (!global && System::GetRunningSerial().empty()) + if (!global && System::GetGameSerial().empty()) { Host::AddKeyedOSDMessage("LoadState", TRANSLATABLE("OSDMessage", "Cannot load state for game without serial."), 5.0f); @@ -643,7 +643,7 @@ static void HotkeyLoadStateSlot(bool global, s32 slot) } std::string path(global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)); + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); if (!FileSystem::FileExists(path.c_str())) { Host::AddKeyedOSDMessage("LoadState", @@ -659,7 +659,7 @@ static void HotkeySaveStateSlot(bool global, s32 slot) if (!System::IsValid()) return; - if (!global && System::GetRunningSerial().empty()) + if (!global && System::GetGameSerial().empty()) { Host::AddKeyedOSDMessage("LoadState", TRANSLATABLE("OSDMessage", "Cannot save state for game without serial."), 5.0f); @@ -667,7 +667,7 @@ static void HotkeySaveStateSlot(bool global, s32 slot) } std::string path(global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)); + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); System::SaveState(path.c_str(), g_settings.create_save_state_backups); } diff --git a/src/frontend-common/d3d11_host_display.cpp b/src/frontend-common/d3d11_host_display.cpp index 8d40248b55..e28e172125 100644 --- a/src/frontend-common/d3d11_host_display.cpp +++ b/src/frontend-common/d3d11_host_display.cpp @@ -705,8 +705,8 @@ bool D3D11HostDisplay::Render(bool skip_present) return true; } -bool D3D11HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) +bool D3D11HostDisplay::RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, + std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) { static constexpr GPUTexture::Format hdformat = GPUTexture::Format::RGBA8; @@ -720,20 +720,18 @@ bool D3D11HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* if (HasDisplayTexture()) { - const auto [left, top, draw_width, draw_height] = CalculateDrawRect(width, height); - if (!m_post_processing_chain.IsEmpty()) { - ApplyPostProcessingChain(render_texture.GetD3DRTV(), left, top, draw_width, draw_height, - static_cast(m_display_texture), m_display_texture_view_x, - m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height, - width, height); + ApplyPostProcessingChain(render_texture.GetD3DRTV(), draw_rect.left, draw_rect.top, draw_rect.GetWidth(), + draw_rect.GetHeight(), static_cast(m_display_texture), + m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, + m_display_texture_view_height, width, height); } else { - RenderDisplay(left, top, draw_width, draw_height, static_cast(m_display_texture), - m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, - m_display_texture_view_height, IsUsingLinearFiltering()); + RenderDisplay(draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(), + static_cast(m_display_texture), m_display_texture_view_x, m_display_texture_view_y, + m_display_texture_view_width, m_display_texture_view_height, IsUsingLinearFiltering()); } } diff --git a/src/frontend-common/d3d11_host_display.h b/src/frontend-common/d3d11_host_display.h index 32154e4ee1..f6c4f725c6 100644 --- a/src/frontend-common/d3d11_host_display.h +++ b/src/frontend-common/d3d11_host_display.h @@ -67,8 +67,8 @@ class D3D11HostDisplay final : public HostDisplay void SetVSync(bool enabled) override; bool Render(bool skip_present) override; - bool RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) override; + bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, std::vector* out_pixels, + u32* out_stride, GPUTexture::Format* out_format) override; static AdapterAndModeList StaticGetAdapterAndModeList(); diff --git a/src/frontend-common/d3d12_host_display.cpp b/src/frontend-common/d3d12_host_display.cpp index faafe2641e..1e3df79ea9 100644 --- a/src/frontend-common/d3d12_host_display.cpp +++ b/src/frontend-common/d3d12_host_display.cpp @@ -615,8 +615,8 @@ bool D3D12HostDisplay::Render(bool skip_present) return true; } -bool D3D12HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) +bool D3D12HostDisplay::RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, + std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) { static constexpr DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM; static constexpr GPUTexture::Format hdformat = GPUTexture::Format::RGBA8; @@ -630,14 +630,13 @@ bool D3D12HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* } ID3D12GraphicsCommandList* cmdlist = g_d3d12_context->GetCommandList(); - const auto [left, top, draw_width, draw_height] = CalculateDrawRect(width, height); if (HasDisplayTexture() && !m_post_processing_chain.IsEmpty()) { - ApplyPostProcessingChain(cmdlist, &render_texture, left, top, width, height, - static_cast(m_display_texture), m_display_texture_view_x, - m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height, - width, height); + ApplyPostProcessingChain(cmdlist, &render_texture, draw_rect.left, draw_rect.top, draw_rect.GetWidth(), + draw_rect.GetHeight(), static_cast(m_display_texture), + m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, + m_display_texture_view_height, width, height); } else { @@ -647,9 +646,9 @@ bool D3D12HostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* if (HasDisplayTexture()) { - RenderDisplay(cmdlist, left, top, draw_width, draw_height, static_cast(m_display_texture), - m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, - m_display_texture_view_height, IsUsingLinearFiltering()); + RenderDisplay(cmdlist, draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(), + static_cast(m_display_texture), m_display_texture_view_x, m_display_texture_view_y, + m_display_texture_view_width, m_display_texture_view_height, IsUsingLinearFiltering()); } } diff --git a/src/frontend-common/d3d12_host_display.h b/src/frontend-common/d3d12_host_display.h index 0aeb2f2c86..dfd49848d1 100644 --- a/src/frontend-common/d3d12_host_display.h +++ b/src/frontend-common/d3d12_host_display.h @@ -66,8 +66,8 @@ class D3D12HostDisplay final : public HostDisplay void SetVSync(bool enabled) override; bool Render(bool skip_present) override; - bool RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) override; + bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, std::vector* out_pixels, + u32* out_stride, GPUTexture::Format* out_format) override; bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index 415e8523f2..c90e5c54e0 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -654,8 +654,8 @@ void FullscreenUI::OnRunningGameChanged() if (!IsInitialized()) return; - const std::string& path = System::GetRunningPath(); - const std::string& serial = System::GetRunningSerial(); + const std::string& path = System::GetDiscPath(); + const std::string& serial = System::GetGameSerial(); if (!serial.empty()) s_current_game_subtitle = fmt::format("{0} - {1}", serial, Path::GetFileName(path)); else @@ -963,7 +963,7 @@ void FullscreenUI::DoChangeDiscFromFile() }; OpenFileSelector(ICON_FA_COMPACT_DISC " Select Disc Image", false, std::move(callback), GetDiscImageFilters(), - std::string(Path::GetDirectory(System::GetRunningPath()))); + std::string(Path::GetDirectory(System::GetDiscPath()))); } void FullscreenUI::DoChangeDisc() @@ -1010,7 +1010,7 @@ void FullscreenUI::DoCheatsMenu() { if (!System::LoadCheatListFromDatabase() || ((cl = System::GetCheatList()) == nullptr)) { - Host::AddKeyedOSDMessage("load_cheat_list", fmt::format("No cheats found for {}.", System::GetRunningTitle()), + Host::AddKeyedOSDMessage("load_cheat_list", fmt::format("No cheats found for {}.", System::GetGameTitle()), 10.0f); ReturnToMainWindow(); return; @@ -2325,14 +2325,14 @@ void FullscreenUI::SwitchToGameSettingsForSerial(const std::string_view& serial) void FullscreenUI::SwitchToGameSettings() { - if (System::GetRunningSerial().empty()) + if (System::GetGameSerial().empty()) return; auto lock = GameList::GetLock(); - const GameList::Entry* entry = GameList::GetEntryForPath(System::GetRunningPath().c_str()); + const GameList::Entry* entry = GameList::GetEntryForPath(System::GetDiscPath().c_str()); if (!entry) { - SwitchToGameSettingsForSerial(System::GetRunningSerial()); + SwitchToGameSettingsForSerial(System::GetGameSerial()); return; } @@ -4561,12 +4561,12 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) // title info { - const std::string& title = System::GetRunningTitle(); - const std::string& serial = System::GetRunningSerial(); + const std::string& title = System::GetGameTitle(); + const std::string& serial = System::GetGameSerial(); if (!serial.empty()) buffer.Format("%s - ", serial.c_str()); - buffer.AppendString(Path::GetFileName(System::GetRunningPath())); + buffer.AppendString(Path::GetFileName(System::GetDiscPath())); const ImVec2 title_size( g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits::max(), -1.0f, title.c_str())); @@ -4624,7 +4624,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) DrawShadowedText(dl, g_large_font, time_pos, IM_COL32(255, 255, 255, 255), buffer.GetCharArray(), buffer.GetCharArray() + buffer.GetLength()); - const std::string& serial = System::GetRunningSerial(); + const std::string& serial = System::GetGameSerial(); if (!serial.empty()) { const std::time_t cached_played_time = GameList::GetCachedPlayedTimeForSerial(serial); @@ -4674,7 +4674,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) case PauseSubMenu::None: { // NOTE: Menu close must come first, because otherwise VM destruction options will race. - const bool has_game = System::IsValid() && !System::GetRunningSerial().empty(); + const bool has_game = System::IsValid() && !System::GetGameSerial().empty(); if (ActiveButton(ICON_FA_PLAY " Resume Game", false) || WantsToCloseMenu()) ClosePauseMenu(); @@ -4698,7 +4698,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type) } if (ActiveButton(ICON_FA_FROWN_OPEN " Cheat List", false, - !System::GetRunningSerial().empty() && !Achievements::ChallengeModeActive())) + !System::GetGameSerial().empty() && !Achievements::ChallengeModeActive())) { s_current_main_window = MainWindowType::None; DoCheatsMenu(); @@ -4944,7 +4944,7 @@ bool FullscreenUI::OpenSaveStateSelector(bool is_loading) s_save_state_selector_game_path = {}; s_save_state_selector_loading = is_loading; s_save_state_selector_resuming = false; - if (PopulateSaveStateListEntries(System::GetRunningTitle().c_str(), System::GetRunningSerial().c_str()) > 0) + if (PopulateSaveStateListEntries(System::GetGameTitle().c_str(), System::GetGameSerial().c_str()) > 0) { s_save_state_selector_open = true; return true; @@ -5380,7 +5380,7 @@ void FullscreenUI::DoSaveState(s32 slot, bool global) return; std::string filename(global ? System::GetGlobalSaveStateFileName(slot) : - System::GetGameSaveStateFileName(System::GetRunningSerial(), slot)); + System::GetGameSaveStateFileName(System::GetGameSerial(), slot)); System::SaveState(filename.c_str(), g_settings.create_save_state_backups); }); } @@ -6172,7 +6172,7 @@ GPUTexture* FullscreenUI::GetCoverForCurrentGame() { auto lock = GameList::GetLock(); - const GameList::Entry* entry = GameList::GetEntryForPath(System::GetRunningPath().c_str()); + const GameList::Entry* entry = GameList::GetEntryForPath(System::GetDiscPath().c_str()); if (!entry) return s_fallback_disc_texture.get(); diff --git a/src/frontend-common/game_list.cpp b/src/frontend-common/game_list.cpp index 9def6ad0e1..f2c37c7312 100644 --- a/src/frontend-common/game_list.cpp +++ b/src/frontend-common/game_list.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "game_list.h" @@ -18,13 +18,14 @@ #include "core/psf_loader.h" #include "core/settings.h" #include "core/system.h" +#include "tinyxml2.h" #include "util/cd_image.h" #include #include #include #include #include -#include +#include #include #include Log_SetChannel(GameList); @@ -37,7 +38,7 @@ namespace GameList { enum : u32 { GAME_LIST_CACHE_SIGNATURE = 0x45434C47, - GAME_LIST_CACHE_VERSION = 32, + GAME_LIST_CACHE_VERSION = 33, PLAYED_TIME_SERIAL_LENGTH = 32, PLAYED_TIME_LAST_TIME_LENGTH = 20, // uint64 @@ -55,6 +56,8 @@ struct PlayedTimeEntry using CacheMap = UnorderedStringMap; using PlayedTimeMap = UnorderedStringMap; +static_assert(std::is_same_v); + static bool GetExeListEntry(const std::string& path, Entry* entry); static bool GetPsfListEntry(const std::string& path, Entry* entry); static bool GetDiscListEntry(const std::string& path, Entry* entry); @@ -204,8 +207,11 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry) entry->type = EntryType::Disc; entry->compatibility = GameDatabase::CompatibilityRating::Unknown; + std::string id; + System::GetGameDetailsFromImage(cdi.get(), &id, &entry->hash); + // try the database first - const GameDatabase::Entry* dentry = GameDatabase::GetEntryForDisc(cdi.get()); + const GameDatabase::Entry* dentry = GameDatabase::GetEntryForId(id); if (dentry) { // pull from database @@ -227,7 +233,7 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry) const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); // no game code, so use the filename title - entry->serial = System::GetGameIdFromImage(cdi.get(), true); + entry->serial = std::move(id); entry->title = Path::GetFileTitle(display_name); entry->compatibility = GameDatabase::CompatibilityRating::Unknown; entry->release_date = 0; @@ -239,9 +245,7 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry) } // region detection - entry->region = System::GetRegionFromSystemArea(cdi.get()); - if (entry->region == DiscRegion::Other) - entry->region = System::GetRegionForSerial(entry->serial); + entry->region = System::GetRegionForImage(cdi.get()); if (cdi->HasSubImages()) { @@ -310,12 +314,12 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream) if (!stream->ReadU8(&type) || !stream->ReadU8(®ion) || !stream->ReadSizePrefixedString(&path) || !stream->ReadSizePrefixedString(&ge.serial) || !stream->ReadSizePrefixedString(&ge.title) || !stream->ReadSizePrefixedString(&ge.genre) || !stream->ReadSizePrefixedString(&ge.publisher) || - !stream->ReadSizePrefixedString(&ge.developer) || !stream->ReadU64(&ge.total_size) || - !stream->ReadU64(reinterpret_cast(&ge.last_modified_time)) || !stream->ReadU64(&ge.release_date) || - !stream->ReadU32(&ge.supported_controllers) || !stream->ReadU8(&ge.min_players) || - !stream->ReadU8(&ge.max_players) || !stream->ReadU8(&ge.min_blocks) || !stream->ReadU8(&ge.max_blocks) || - !stream->ReadU8(&compatibility_rating) || region >= static_cast(DiscRegion::Count) || - type >= static_cast(EntryType::Count) || + !stream->ReadSizePrefixedString(&ge.developer) || !stream->ReadU64(&ge.hash) || + !stream->ReadU64(&ge.total_size) || !stream->ReadU64(reinterpret_cast(&ge.last_modified_time)) || + !stream->ReadU64(&ge.release_date) || !stream->ReadU32(&ge.supported_controllers) || + !stream->ReadU8(&ge.min_players) || !stream->ReadU8(&ge.max_players) || !stream->ReadU8(&ge.min_blocks) || + !stream->ReadU8(&ge.max_blocks) || !stream->ReadU8(&compatibility_rating) || + region >= static_cast(DiscRegion::Count) || type >= static_cast(EntryType::Count) || compatibility_rating >= static_cast(GameDatabase::CompatibilityRating::Count)) { Log_WarningPrintf("Game list cache entry is corrupted"); @@ -348,6 +352,7 @@ bool GameList::WriteEntryToCache(const Entry* entry) result &= s_cache_write_stream->WriteSizePrefixedString(entry->genre); result &= s_cache_write_stream->WriteSizePrefixedString(entry->publisher); result &= s_cache_write_stream->WriteSizePrefixedString(entry->developer); + result &= s_cache_write_stream->WriteU64(entry->hash); result &= s_cache_write_stream->WriteU64(entry->total_size); result &= s_cache_write_stream->WriteU64(entry->last_modified_time); result &= s_cache_write_stream->WriteU64(entry->release_date); @@ -585,6 +590,17 @@ const GameList::Entry* GameList::GetEntryBySerial(const std::string_view& serial return nullptr; } +const GameList::Entry* GameList::GetEntryBySerialAndHash(const std::string_view& serial, u64 hash) +{ + for (const Entry& entry : s_entries) + { + if (entry.serial == serial && entry.hash == hash) + return &entry; + } + + return nullptr; +} + u32 GameList::GetEntryCount() { return static_cast(s_entries.size()); diff --git a/src/frontend-common/game_list.h b/src/frontend-common/game_list.h index e075d174a3..049c5b24b6 100644 --- a/src/frontend-common/game_list.h +++ b/src/frontend-common/game_list.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once @@ -37,6 +37,7 @@ struct Entry std::string genre; std::string publisher; std::string developer; + u64 hash = 0; u64 total_size = 0; std::time_t last_modified_time = 0; std::time_t last_played_time = 0; @@ -70,6 +71,7 @@ std::unique_lock GetLock(); const Entry* GetEntryByIndex(u32 index); const Entry* GetEntryForPath(const char* path); const Entry* GetEntryBySerial(const std::string_view& serial); +const Entry* GetEntryBySerialAndHash(const std::string_view& serial, u64 hash); u32 GetEntryCount(); bool IsGameListLoaded(); diff --git a/src/frontend-common/imgui_overlays.cpp b/src/frontend-common/imgui_overlays.cpp index 0a8c2d368a..1cf32a6983 100644 --- a/src/frontend-common/imgui_overlays.cpp +++ b/src/frontend-common/imgui_overlays.cpp @@ -621,11 +621,11 @@ void SaveStateSelectorUI::RefreshList() if (System::IsShutdown()) return; - if (!System::GetRunningSerial().empty()) + if (!System::GetGameSerial().empty()) { for (s32 i = 1; i <= System::PER_GAME_SAVE_STATE_SLOTS; i++) { - std::string path(System::GetGameSaveStateFileName(System::GetRunningSerial(), i)); + std::string path(System::GetGameSaveStateFileName(System::GetGameSerial(), i)); std::optional ssi = System::GetExtendedSaveStateInfo(path.c_str()); ListEntry li; diff --git a/src/frontend-common/opengl_host_display.cpp b/src/frontend-common/opengl_host_display.cpp index d9e120fe44..c058b56de5 100644 --- a/src/frontend-common/opengl_host_display.cpp +++ b/src/frontend-common/opengl_host_display.cpp @@ -680,8 +680,8 @@ bool OpenGLHostDisplay::Render(bool skip_present) return true; } -bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) +bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, + std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) { GL::Texture texture; if (!texture.Create(width, height, 1, 1, 1, GPUTexture::Format::RGBA8, nullptr, 0) || !texture.CreateFramebuffer()) @@ -692,14 +692,13 @@ bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector glDisable(GL_SCISSOR_TEST); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - const auto [left, top, draw_width, draw_height] = CalculateDrawRect(width, height); - if (HasDisplayTexture() && !m_post_processing_chain.IsEmpty()) { - ApplyPostProcessingChain(texture.GetGLFramebufferID(), left, height - top - draw_height, draw_width, draw_height, - static_cast(m_display_texture), m_display_texture_view_x, - m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height, - width, height); + ApplyPostProcessingChain(texture.GetGLFramebufferID(), draw_rect.left, + height - draw_rect.top - draw_rect.GetHeight(), draw_rect.GetWidth(), + draw_rect.GetHeight(), static_cast(m_display_texture), + m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, + m_display_texture_view_height, width, height); } else { @@ -708,9 +707,10 @@ bool OpenGLHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector if (HasDisplayTexture()) { - RenderDisplay(left, height - top - draw_height, draw_width, draw_height, - static_cast(m_display_texture), m_display_texture_view_x, m_display_texture_view_y, - m_display_texture_view_width, m_display_texture_view_height, IsUsingLinearFiltering()); + RenderDisplay(draw_rect.left, height - draw_rect.top - draw_rect.GetHeight(), draw_rect.GetWidth(), + draw_rect.GetHeight(), static_cast(m_display_texture), m_display_texture_view_x, + m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height, + IsUsingLinearFiltering()); } } diff --git a/src/frontend-common/opengl_host_display.h b/src/frontend-common/opengl_host_display.h index 3ae85976e2..2cc21c120e 100644 --- a/src/frontend-common/opengl_host_display.h +++ b/src/frontend-common/opengl_host_display.h @@ -55,8 +55,8 @@ class OpenGLHostDisplay final : public HostDisplay void SetVSync(bool enabled) override; bool Render(bool skip_present) override; - bool RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) override; + bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, std::vector* out_pixels, + u32* out_stride, GPUTexture::Format* out_format) override; bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; diff --git a/src/frontend-common/platform_misc_unix.cpp b/src/frontend-common/platform_misc_unix.cpp index b333be44e4..c9dd0ad572 100644 --- a/src/frontend-common/platform_misc_unix.cpp +++ b/src/frontend-common/platform_misc_unix.cpp @@ -2,18 +2,20 @@ // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "common/log.h" +#include "common/scoped_guard.h" #include "common/string.h" -#include "platform_misc.h" #include "input_manager.h" +#include "platform_misc.h" #include Log_SetChannel(FrontendCommon); -#ifdef USE_X11 -#include #include -#include #include +#if !defined(USE_DBUS) && defined(USE_X11) +#include +#include + static bool SetScreensaverInhibitX11(bool inhibit, const WindowInfo& wi) { TinyString command; @@ -38,10 +40,89 @@ static bool SetScreensaverInhibitX11(bool inhibit, const WindowInfo& wi) return true; } -#endif // USE_X11 +#elif defined(USE_DBUS) +#include +static bool SetScreensaverInhibitDBus(const bool inhibit_requested, const char* program_name, const char* reason) +{ + static dbus_uint32_t s_cookie; + const char* bus_method = (inhibit_requested) ? "Inhibit" : "UnInhibit"; + DBusError error; + DBusConnection* connection = nullptr; + static DBusConnection* s_comparison_connection; + DBusMessage* message = nullptr; + DBusMessage* response = nullptr; + DBusMessageIter message_itr; + + ScopedGuard cleanup = [&]() { + if (dbus_error_is_set(&error)) + { + Log_ErrorPrintf("SetScreensaverInhibitDBus error: %s", error.message); + dbus_error_free(&error); + } + if (message) + dbus_message_unref(message); + if (response) + dbus_message_unref(response); + }; + + dbus_error_init(&error); + // Calling dbus_bus_get() after the first time returns a pointer to the existing connection. + connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (!connection || (dbus_error_is_set(&error))) + return false; + if (s_comparison_connection != connection) + { + dbus_connection_set_exit_on_disconnect(connection, false); + s_cookie = 0; + s_comparison_connection = connection; + } + message = dbus_message_new_method_call("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", + "org.freedesktop.ScreenSaver", bus_method); + if (!message) + return false; + // Initialize an append iterator for the message, gets freed with the message. + dbus_message_iter_init_append(message, &message_itr); + if (inhibit_requested) + { + // Guard against repeat inhibitions which would add extra inhibitors each generating a different cookie. + if (s_cookie) + return false; + // Append process/window name. + if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &program_name)) + return false; + // Append reason for inhibiting the screensaver. + if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_STRING, &reason)) + return false; + } + else + { + // Only Append the cookie. + if (!dbus_message_iter_append_basic(&message_itr, DBUS_TYPE_UINT32, &s_cookie)) + return false; + } + // Send message and get response. + response = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_USE_DEFAULT, &error); + if (!response || dbus_error_is_set(&error)) + return false; + s_cookie = 0; + if (inhibit_requested) + { + // Get the cookie from the response message. + if (!dbus_message_get_args(response, &error, DBUS_TYPE_UINT32, &s_cookie, DBUS_TYPE_INVALID) || + dbus_error_is_set(&error)) + return false; + } + return true; +} + +#endif static bool SetScreensaverInhibit(bool inhibit) { +#ifdef USE_DBUS + return SetScreensaverInhibitDBus(inhibit, "DuckStation", "DuckStation VM is running."); +#else + std::optional wi(Host::GetTopLevelWindowInfo()); if (!wi.has_value()) { @@ -60,6 +141,7 @@ static bool SetScreensaverInhibit(bool inhibit) Log_ErrorPrintf("Unknown type: %u", static_cast(wi->type)); return false; } +#endif } static bool s_screensaver_suspended; diff --git a/src/frontend-common/vulkan_host_display.cpp b/src/frontend-common/vulkan_host_display.cpp index 990f8704d3..aa0eade9ff 100644 --- a/src/frontend-common/vulkan_host_display.cpp +++ b/src/frontend-common/vulkan_host_display.cpp @@ -677,8 +677,8 @@ bool VulkanHostDisplay::Render(bool skip_present) return true; } -bool VulkanHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) +bool VulkanHostDisplay::RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, + std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) { // in theory we could do this without a swap chain, but postprocessing assumes it for now... if (!m_swap_chain) @@ -746,20 +746,19 @@ bool VulkanHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector "VulkanHostDisplay::RenderScreenshot: %ux%u", width, height); tex.TransitionToLayout(g_vulkan_context->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - const auto [left, top, draw_width, draw_height] = CalculateDrawRect(width, height); - if (!m_post_processing_chain.IsEmpty()) { - ApplyPostProcessingChain(fb, left, top, draw_width, draw_height, static_cast(m_display_texture), - m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, - m_display_texture_view_height, width, height); + ApplyPostProcessingChain(fb, draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(), + static_cast(m_display_texture), m_display_texture_view_x, + m_display_texture_view_y, m_display_texture_view_width, m_display_texture_view_height, + width, height); } else { BeginSwapChainRenderPass(fb, width, height); - RenderDisplay(left, top, draw_width, draw_height, static_cast(m_display_texture), - m_display_texture_view_x, m_display_texture_view_y, m_display_texture_view_width, - m_display_texture_view_height, IsUsingLinearFiltering()); + RenderDisplay(draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(), + static_cast(m_display_texture), m_display_texture_view_x, m_display_texture_view_y, + m_display_texture_view_width, m_display_texture_view_height, IsUsingLinearFiltering()); } vkCmdEndRenderPass(g_vulkan_context->GetCurrentCommandBuffer()); diff --git a/src/frontend-common/vulkan_host_display.h b/src/frontend-common/vulkan_host_display.h index 7106aa88ca..1da6c500a3 100644 --- a/src/frontend-common/vulkan_host_display.h +++ b/src/frontend-common/vulkan_host_display.h @@ -59,8 +59,8 @@ class VulkanHostDisplay final : public HostDisplay void SetVSync(bool enabled) override; bool Render(bool skip_present) override; - bool RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) override; + bool RenderScreenshot(u32 width, u32 height, const Common::Rectangle& draw_rect, std::vector* out_pixels, + u32* out_stride, GPUTexture::Format* out_format) override; bool SetGPUTimingEnabled(bool enabled) override; float GetAndResetAccumulatedGPUTime() override; diff --git a/src/scmversion/gen_scmversion.bat b/src/scmversion/gen_scmversion.bat index 265501d372..388cd0a80a 100644 --- a/src/scmversion/gen_scmversion.bat +++ b/src/scmversion/gen_scmversion.bat @@ -3,7 +3,7 @@ SET VERSIONFILE="scmversion.cpp" FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse HEAD`) do (SET "HASH=%%g") FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse --abbrev-ref HEAD`) do (SET "BRANCH=%%g") -FOR /F "tokens=* USEBACKQ" %%g IN (`git describe --tags --dirty --exclude latest --exclude preview --exclude legacy --exclude play-store-release`) do (SET "TAG=%%g") +FOR /F "tokens=* USEBACKQ" %%g IN (`git describe --tags --dirty --exclude latest --exclude preview --exclude legacy --exclude previous-latest`) do (SET "TAG=%%g") FOR /F "tokens=* USEBACKQ" %%g IN (`git log -1 --date=iso8601-strict "--format=%%cd"`) do (SET "CDATE=%%g") SET SIGNATURELINE=// %HASH% %BRANCH% %TAG% %CDATE% diff --git a/src/scmversion/gen_scmversion.sh b/src/scmversion/gen_scmversion.sh index 7a48624445..3d25e5edeb 100755 --- a/src/scmversion/gen_scmversion.sh +++ b/src/scmversion/gen_scmversion.sh @@ -12,7 +12,7 @@ fi HASH=$(git rev-parse HEAD) BRANCH=$(git rev-parse --abbrev-ref HEAD | tr -d '\r\n') -TAG=$(git describe --tags --dirty --exclude latest --exclude preview --exclude legacy --exclude play-store-release | tr -d '\r\n') +TAG=$(git describe --tags --dirty --exclude latest --exclude preview --exclude legacy --exclude previous-latest | tr -d '\r\n') DATE=$(git log -1 --date=iso8601-strict --format=%cd) cd $CURDIR diff --git a/src/util/iso_reader.cpp b/src/util/iso_reader.cpp index 5ba6ff67ae..2797ed4741 100644 --- a/src/util/iso_reader.cpp +++ b/src/util/iso_reader.cpp @@ -293,3 +293,9 @@ bool ISOReader::ReadFile(const char* path, std::vector* data) data->resize(de->length_le); return true; } + +bool ISOReader::FileExists(const char* path) +{ + auto de = LocateFile(path); + return de.has_value(); +} diff --git a/src/util/iso_reader.h b/src/util/iso_reader.h index d045e25c2b..c757869f35 100644 --- a/src/util/iso_reader.h +++ b/src/util/iso_reader.h @@ -143,6 +143,7 @@ class ISOReader std::vector GetFilesInDirectory(const char* path); bool ReadFile(const char* path, std::vector* data); + bool FileExists(const char* path); private: bool ReadPVD();