2022-07-11 16:08:34 +02:00
# include "stdafx.h"
# include "util/types.hpp"
# include "util/logs.hpp"
2023-11-15 20:07:42 +01:00
# include "util/v128.hpp"
# include "util/simd.hpp"
2025-04-08 18:46:57 +02:00
# include "util/File.h"
# include "util/StrFmt.h"
2022-07-11 16:08:34 +02:00
# include "system_config.h"
2023-10-29 01:46:52 +02:00
# include "savestate_utils.hpp"
2022-07-11 16:08:34 +02:00
# include "System.h"
# include <set>
2023-11-15 20:07:42 +01:00
# include <span>
2022-07-11 16:08:34 +02:00
LOG_CHANNEL ( sys_log , " SYS " ) ;
struct serial_ver_t
{
2024-04-12 19:52:37 +02:00
std : : string ver_name ;
2022-07-11 16:08:34 +02:00
bool used = false ;
2023-11-15 20:07:42 +01:00
u16 current_version = 0 ;
std : : set < u16 > compatible_versions ;
2022-07-11 16:08:34 +02:00
} ;
2024-04-12 20:22:48 +02:00
static std : : array < serial_ver_t , 27 > s_serial_versions ;
2022-07-11 16:08:34 +02:00
2025-04-05 21:50:45 +02:00
# define SERIALIZATION_VER(name, identifier, ...) \
\
const bool s_ # # name # # _serialization_fill = [ ] ( ) { \
auto & e = : : s_serial_versions [ identifier ] ; \
if ( e . compatible_versions . empty ( ) ) \
{ \
e . compatible_versions = { __VA_ARGS__ } ; \
e . ver_name = # name ; \
} \
return true ; \
} ( ) ; \
\
extern void using_ # # name # # _serialization ( ) \
{ \
ensure ( Emu . IsStopped ( ) ) ; \
: : s_serial_versions [ identifier ] . used = true ; \
} \
\
extern s32 get_ # # name # # _serialization_version ( ) \
{ \
return : : s_serial_versions [ identifier ] . current_version ; \
2022-07-11 16:08:34 +02:00
}
2025-04-05 21:50:45 +02:00
SERIALIZATION_VER ( global_version , 0 , 19 ) // For stuff not listed here
SERIALIZATION_VER ( ppu , 1 , 1 , 2 /*PPU sleep order*/ , 3 /*PPU FNID and module*/ )
SERIALIZATION_VER ( spu , 2 , 1 )
SERIALIZATION_VER ( lv2_sync , 3 , 1 )
SERIALIZATION_VER ( lv2_vm , 4 , 1 )
SERIALIZATION_VER ( lv2_net , 5 , 1 , 2 /*TCP Feign conection loss*/ )
SERIALIZATION_VER ( lv2_fs , 6 , 1 , 2 /*NPDRM key saving*/ )
SERIALIZATION_VER ( lv2_prx_overlay , 7 , 1 )
SERIALIZATION_VER ( lv2_memory , 8 , 1 )
SERIALIZATION_VER ( lv2_config , 9 , 1 )
2022-07-11 16:08:34 +02:00
namespace rsx
{
2025-04-05 21:50:45 +02:00
SERIALIZATION_VER ( rsx , 10 , 1 , 2 /*Pending flip*/ , 3 /*avconf scan_mode*/ )
2022-07-11 16:08:34 +02:00
}
namespace np
{
2025-04-05 21:50:45 +02:00
SERIALIZATION_VER ( sceNp , 11 , 1 )
2022-07-11 16:08:34 +02:00
}
# ifdef _MSC_VER
2023-01-09 18:03:01 +01:00
// Compiler bug, lambda function body does seem to inherit used namespace atleast for function declaration
2022-07-11 16:08:34 +02:00
SERIALIZATION_VER ( rsx , 10 )
SERIALIZATION_VER ( sceNp , 11 )
# endif
2025-04-05 21:50:45 +02:00
SERIALIZATION_VER ( cellVdec , 12 , 1 )
SERIALIZATION_VER ( cellAudio , 13 , 1 )
SERIALIZATION_VER ( cellCamera , 14 , 1 , 2 /*gem_camera_shared*/ )
SERIALIZATION_VER ( cellGem , 15 , 1 , 2 /*calibration_status_flags*/ , 3 /*video_conversion*/ )
SERIALIZATION_VER ( sceNpTrophy , 16 , 1 )
SERIALIZATION_VER ( cellMusic , 17 , 1 )
SERIALIZATION_VER ( cellVoice , 18 , 1 )
SERIALIZATION_VER ( cellGcm , 19 , 1 )
SERIALIZATION_VER ( sysPrxForUser , 20 , 1 )
SERIALIZATION_VER ( cellSaveData , 21 , 1 )
SERIALIZATION_VER ( cellAudioOut , 22 , 1 )
SERIALIZATION_VER ( sys_io , 23 , 2 )
2023-08-24 18:59:24 +02:00
// Misc versions for HLE/LLE not included so main version would not invalidated
2025-04-05 21:50:45 +02:00
SERIALIZATION_VER ( LLE , 24 , 1 )
SERIALIZATION_VER ( HLE , 25 , 1 )
2022-07-11 16:08:34 +02:00
2025-04-05 21:50:45 +02:00
SERIALIZATION_VER ( cellSysutil , 26 , 1 , 2 /*AVC2 Muting,Volume*/ )
2024-04-12 20:22:48 +02:00
2024-04-12 19:52:37 +02:00
template < >
void fmt_class_string < std : : remove_cvref_t < decltype ( s_serial_versions ) > > : : format ( std : : string & out , u64 arg )
{
bool is_first = true ;
const auto & serials = get_object ( arg ) ;
out + = " { " ;
for ( auto & entry : serials )
{
if ( entry . current_version )
{
if ( ! is_first )
{
out + = " , " ;
}
is_first = false ;
fmt : : append ( out , " %s=%d " , entry . ver_name , entry . current_version ) ;
}
}
out + = " } " ;
}
2023-11-15 20:07:42 +01:00
std : : vector < version_entry > get_savestate_versioning_data ( fs : : file & & file , std : : string_view filepath )
2022-07-11 16:08:34 +02:00
{
if ( ! file )
{
return { } ;
}
file . seek ( 0 ) ;
2023-11-15 20:07:42 +01:00
utils : : serial ar ;
2023-11-27 21:13:49 +01:00
ar . set_reading_state ( { } , true ) ;
2023-11-15 20:07:42 +01:00
2024-05-11 05:44:57 +02:00
if ( filepath . ends_with ( " .zst " ) )
{
ar . m_file_handler = make_compressed_zstd_serialization_file_handler ( std : : move ( file ) ) ;
}
else if ( filepath . ends_with ( " .gz " ) )
{
ar . m_file_handler = make_compressed_serialization_file_handler ( std : : move ( file ) ) ;
}
else
{
ar . m_file_handler = make_uncompressed_serialization_file_handler ( std : : move ( file ) ) ;
}
2023-11-15 20:07:42 +01:00
if ( u64 r = 0 ; ar . try_read ( r ) ! = 0 | | r ! = " RPCS3SAV " _u64 )
2022-07-11 16:08:34 +02:00
{
return { } ;
}
2023-11-15 20:07:42 +01:00
ar . pos = 10 ;
2022-07-11 16:08:34 +02:00
2023-11-15 20:07:42 +01:00
u64 offs = ar . try_read < u64 > ( ) . second ;
2022-07-11 16:08:34 +02:00
2023-11-15 20:07:42 +01:00
const usz fsize = ar . get_size ( offs ) ;
2022-07-11 16:08:34 +02:00
if ( ! offs | | fsize < = offs )
{
return { } ;
}
2023-10-29 01:46:52 +02:00
ar . seek_pos ( offs ) ;
2023-11-15 20:07:42 +01:00
ar . breathe ( true ) ;
std : : vector < version_entry > ver_data = ar . pop < std : : vector < version_entry > > ( ) ;
2023-11-28 21:25:49 +01:00
return ver_data ;
2022-07-11 16:08:34 +02:00
}
2025-01-23 00:30:53 +01:00
std : : shared_ptr < utils : : serial > make_savestate_reader ( const std : : string & path )
{
std : : shared_ptr < utils : : serial > ar ;
fs : : file save { path , fs : : isfile + fs : : read } ;
if ( ! save )
{
return ar ;
}
if ( path . ends_with ( " .SAVESTAT " ) & & save . size ( ) > = 8 & & save . read < u64 > ( ) = = " RPCS3SAV " _u64 )
{
ar = std : : make_shared < utils : : serial > ( ) ;
ar - > set_reading_state ( ) ;
ar - > m_file_handler = make_uncompressed_serialization_file_handler ( std : : move ( save ) ) ;
}
else if ( path . ends_with ( " .zst " ) )
{
ar = std : : make_shared < utils : : serial > ( ) ;
ar - > set_reading_state ( ) ;
ar - > m_file_handler = make_compressed_zstd_serialization_file_handler ( std : : move ( save ) ) ;
if ( ar - > try_read < u64 > ( ) . second ! = " RPCS3SAV " _u64 )
{
ar . reset ( ) ;
}
else
{
ar - > pos = 0 ;
}
}
else if ( path . ends_with ( " .gz " ) )
{
ar = std : : make_shared < utils : : serial > ( ) ;
ar - > set_reading_state ( ) ;
ar - > m_file_handler = make_compressed_serialization_file_handler ( std : : move ( save ) ) ;
if ( ar - > try_read < u64 > ( ) . second ! = " RPCS3SAV " _u64 )
{
ar . reset ( ) ;
}
else
{
ar - > pos = 0 ;
}
}
return ar ;
}
2023-11-15 20:07:42 +01:00
bool is_savestate_version_compatible ( const std : : vector < version_entry > & data , bool is_boot_check )
2022-07-11 16:08:34 +02:00
{
if ( data . empty ( ) )
{
return false ;
}
bool ok = true ;
2024-04-12 19:52:37 +02:00
if ( is_boot_check )
{
for ( auto & entry : s_serial_versions )
{
// Version 0 means that the entire constructor using the version should be skipped
entry . current_version = 0 ;
}
}
auto & channel = ( is_boot_check ? sys_log . error : sys_log . trace ) ;
2024-04-15 15:29:21 +02:00
for ( const auto & entry : data )
2022-07-11 16:08:34 +02:00
{
2024-04-15 15:29:21 +02:00
if ( entry . type > = s_serial_versions . size ( ) )
2022-07-11 16:08:34 +02:00
{
2024-04-15 15:29:21 +02:00
channel ( " Savestate version identifier is unknown! (category=%u, version=%u) " , entry . type , entry . version ) ;
2022-07-11 16:08:34 +02:00
ok = false ; // Log all mismatches
}
2024-04-15 15:29:21 +02:00
else if ( ! s_serial_versions [ entry . type ] . compatible_versions . count ( entry . version ) )
2022-07-11 16:08:34 +02:00
{
2024-04-15 15:29:21 +02:00
channel ( " Savestate version is not supported. (category=%u, version=%u) " , entry . type , entry . version ) ;
2022-07-11 16:08:34 +02:00
ok = false ;
}
2022-07-15 19:32:09 +02:00
else if ( is_boot_check )
2022-07-11 16:08:34 +02:00
{
2024-04-15 15:29:21 +02:00
s_serial_versions [ entry . type ] . current_version = entry . version ;
2022-07-11 16:08:34 +02:00
}
}
2024-04-12 19:52:37 +02:00
if ( is_boot_check )
2022-07-15 19:32:09 +02:00
{
2024-04-12 19:52:37 +02:00
if ( ok )
{
sys_log . success ( " Savestate versions: %s " , s_serial_versions ) ;
}
else
2022-07-15 19:32:09 +02:00
{
2024-04-12 20:01:26 +02:00
for ( auto & entry : s_serial_versions )
2024-04-12 19:52:37 +02:00
{
2024-04-12 20:01:26 +02:00
entry . current_version = 0 ;
2024-04-12 19:52:37 +02:00
}
2022-07-15 19:32:09 +02:00
}
}
2022-07-11 16:08:34 +02:00
return ok ;
}
2023-07-23 09:48:17 +02:00
std : : string get_savestate_file ( std : : string_view title_id , std : : string_view boot_path , s64 abs_id , s64 rel_id )
2022-10-23 08:40:31 +02:00
{
2023-07-23 09:48:17 +02:00
const std : : string title = std : : string { title_id . empty ( ) ? boot_path . substr ( boot_path . find_last_of ( fs : : delim ) + 1 ) : title_id } ;
if ( abs_id = = - 1 & & rel_id = = - 1 )
{
// Return directory
2024-06-01 20:15:02 +02:00
return fs : : get_config_dir ( ) + " savestates/ " + title + " / " ;
2023-07-23 09:48:17 +02:00
}
ensure ( rel_id < 0 | | abs_id > = 0 , " Unimplemented! " ) ;
const std : : string save_id = fmt : : format ( " %d " , abs_id ) ;
// Make sure that savestate file with higher IDs are placed at the bottom of "by name" file ordering in directory view by adding a single character prefix which tells the ID length
// While not needing to keep a 59 chars long suffix at all times for this purpose
const char prefix = : : at32 ( " 0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz " sv , save_id . size ( ) ) ;
2024-06-01 20:15:02 +02:00
std : : string path = fs : : get_config_dir ( ) + " /savestates/ " + title + " / " + title + ' _ ' + prefix + ' _ ' + save_id + " .SAVESTAT " ;
2023-11-15 20:07:42 +01:00
2024-05-11 05:44:57 +02:00
if ( std : : string path_compressed = path + " .zst " ; fs : : is_file ( path_compressed ) )
{
return path_compressed ;
}
2023-11-15 20:07:42 +01:00
if ( std : : string path_compressed = path + " .gz " ; fs : : is_file ( path_compressed ) )
{
2023-11-28 21:25:49 +01:00
return path_compressed ;
2023-11-15 20:07:42 +01:00
}
2023-11-28 21:25:49 +01:00
return path ;
2022-10-23 08:40:31 +02:00
}
2023-11-15 20:07:42 +01:00
bool is_savestate_compatible ( fs : : file & & file , std : : string_view filepath )
2022-07-11 16:08:34 +02:00
{
2023-11-15 20:07:42 +01:00
return is_savestate_version_compatible ( get_savestate_versioning_data ( std : : move ( file ) , filepath ) , false ) ;
2022-07-11 16:08:34 +02:00
}
2025-01-23 00:30:53 +01:00
bool is_savestate_compatible ( const std : : string & filepath )
{
if ( fs : : file file { filepath , fs : : isfile + fs : : read } )
{
return is_savestate_compatible ( std : : move ( file ) , filepath ) ;
}
return false ;
}
2023-11-15 20:07:42 +01:00
std : : vector < version_entry > read_used_savestate_versions ( )
2022-07-11 16:08:34 +02:00
{
2023-11-15 20:07:42 +01:00
std : : vector < version_entry > used_serial ;
2022-07-11 16:08:34 +02:00
used_serial . reserve ( s_serial_versions . size ( ) ) ;
for ( serial_ver_t & ver : s_serial_versions )
{
if ( std : : exchange ( ver . used , false ) )
{
2023-11-15 20:07:42 +01:00
used_serial . push_back ( version_entry { static_cast < u16 > ( & ver - s_serial_versions . data ( ) ) , * ver . compatible_versions . rbegin ( ) } ) ;
2022-07-11 16:08:34 +02:00
}
2022-07-12 15:04:33 +02:00
ver . current_version = 0 ;
2022-07-11 16:08:34 +02:00
}
return used_serial ;
}
2023-03-18 20:35:51 +01:00
bool boot_last_savestate ( bool testing )
2022-07-11 16:08:34 +02:00
{
if ( ! g_cfg . savestate . suspend_emu & & ! Emu . GetTitleID ( ) . empty ( ) & & ( Emu . IsRunning ( ) | | Emu . GetStatus ( ) = = system_state : : paused ) )
{
2024-06-01 20:15:02 +02:00
const std : : string save_dir = get_savestate_file ( Emu . GetTitleID ( ) , Emu . GetBoot ( ) , - 1 , - 1 ) ;
2022-07-11 16:08:34 +02:00
std : : string savestate_path ;
2022-07-15 19:32:09 +02:00
s64 mtime = smin ;
2022-07-11 16:08:34 +02:00
for ( auto & & entry : fs : : dir ( save_dir ) )
{
if ( entry . is_directory )
{
continue ;
}
// Find the latest savestate file compatible with the game (TODO: Check app version and anything more)
2022-07-15 19:32:09 +02:00
if ( entry . name . find ( Emu . GetTitleID ( ) ) ! = umax & & mtime < = entry . mtime )
2022-07-11 16:08:34 +02:00
{
2024-05-11 05:44:57 +02:00
if ( std : : string path = save_dir + entry . name + " .zst " ; is_savestate_compatible ( fs : : file ( path ) , path ) )
{
savestate_path = std : : move ( path ) ;
mtime = entry . mtime ;
}
else if ( std : : string path = save_dir + entry . name + " .gz " ; is_savestate_compatible ( fs : : file ( path ) , path ) )
2023-11-15 20:07:42 +01:00
{
savestate_path = std : : move ( path ) ;
mtime = entry . mtime ;
}
else if ( std : : string path = save_dir + entry . name ; is_savestate_compatible ( fs : : file ( path ) , path ) )
2022-07-11 16:08:34 +02:00
{
savestate_path = std : : move ( path ) ;
mtime = entry . mtime ;
}
}
}
2023-03-18 20:35:51 +01:00
const bool result = fs : : is_file ( savestate_path ) ;
if ( testing )
{
sys_log . trace ( " boot_last_savestate(true) returned %s. " , result ) ;
return result ;
}
if ( result )
2022-07-11 16:08:34 +02:00
{
sys_log . success ( " Booting the most recent savestate \' %s \' using the Reload shortcut. " , savestate_path ) ;
2025-01-04 16:14:09 +01:00
// Make sure we keep the game window opened
Emu . SetContinuousMode ( true ) ;
Emu . GracefulShutdown ( false , false , false , true ) ;
2022-07-11 16:08:34 +02:00
if ( game_boot_result error = Emu . BootGame ( savestate_path , " " , true ) ; error ! = game_boot_result : : no_errors )
{
2023-07-28 12:09:06 +02:00
sys_log . error ( " Failed to boot savestate \' %s \' using the Reload shortcut. (error: %s) " , savestate_path , error ) ;
2022-07-11 16:08:34 +02:00
}
else
{
return true ;
}
}
sys_log . error ( " No compatible savestate file found in \' %s \' ' " , save_dir ) ;
}
return false ;
}
2023-10-29 01:46:52 +02:00
2023-11-15 20:07:42 +01:00
bool load_and_check_reserved ( utils : : serial & ar , usz size )
{
u8 bytes [ 4096 ] ;
2024-04-15 15:44:30 +02:00
ensure ( size < std : : size ( bytes ) ) ;
2023-11-15 20:07:42 +01:00
std : : memset ( & bytes [ size & ( 0 - sizeof ( v128 ) ) ] , 0 , sizeof ( v128 ) ) ;
const usz old_pos = ar . pos ;
ar ( std : : span < u8 > ( bytes , size ) ) ;
// Check if all are 0
for ( usz i = 0 ; i < size ; i + = sizeof ( v128 ) )
{
if ( v128 : : loadu ( & bytes [ i ] ) ! = v128 { } )
{
return false ;
}
}
return old_pos + size = = ar . pos ;
}
2023-10-29 01:46:52 +02:00
namespace stx
{
2023-12-25 13:35:09 +01:00
extern u16 serial_breathe_and_tag ( utils : : serial & ar , std : : string_view name , bool tag_bit )
2023-10-29 01:46:52 +02:00
{
2023-12-25 13:35:09 +01:00
thread_local std : : string_view s_tls_object_name = " none " ;
2023-12-31 20:15:00 +01:00
thread_local u64 s_tls_call_count = 1 , s_tls_current_pos = 0 ;
if ( s_tls_current_pos > = ar . pos )
{
// Reset, probably a new utils::serial object
s_tls_call_count = 1 ;
2024-12-24 15:02:29 +01:00
s_tls_object_name = " none " sv ;
2023-12-31 20:15:00 +01:00
}
s_tls_current_pos = ar . pos ;
2023-12-25 13:35:09 +01:00
constexpr u16 data_mask = 0x7fff ;
if ( ar . m_file_handler & & ar . m_file_handler - > is_null ( ) )
{
return ( tag_bit ? data_mask + 1 : 0 ) ;
}
u16 tag = static_cast < u16 > ( ( static_cast < u16 > ( ar . pos / 2 ) & data_mask ) | ( tag_bit ? data_mask + 1 : 0 ) ) ;
u16 saved = tag ;
ar ( saved ) ;
2023-12-31 20:15:00 +01:00
sys_log . trace ( " serial_breathe_and_tag(%u): %s, object: '%s', next-object: '%s', expected/tag: 0x%x == 0x%x " , s_tls_call_count , ar , s_tls_object_name , name , tag , saved ) ;
2023-12-25 13:35:09 +01:00
if ( ( saved ^ tag ) & data_mask )
{
ensure ( ! ar . is_writing ( ) ) ;
2024-12-25 06:02:42 +01:00
fmt : : throw_exception ( " serial_breathe_and_tag(%u): %s \n object: '%s', next-object: '%s', expected/tag: 0x%x != 0x%x, " , s_tls_call_count , ar , s_tls_object_name , name , tag , saved ) ;
2023-12-25 13:35:09 +01:00
}
s_tls_object_name = name ;
2023-12-31 20:15:00 +01:00
s_tls_call_count + + ;
2023-10-29 01:46:52 +02:00
ar . breathe ( ) ;
2023-12-25 13:35:09 +01:00
return saved ;
2023-10-29 01:46:52 +02:00
}
2025-04-05 21:50:45 +02:00
} // namespace stx
2023-10-29 01:46:52 +02:00
// MSVC bug workaround, see above similar case
2023-12-25 13:35:09 +01:00
extern u16 serial_breathe_and_tag ( utils : : serial & ar , std : : string_view name , bool tag_bit )
2023-10-29 01:46:52 +02:00
{
2023-12-25 13:35:09 +01:00
return : : stx : : serial_breathe_and_tag ( ar , name , tag_bit ) ;
2023-10-29 01:46:52 +02:00
}
2024-03-27 13:44:33 +01:00
[[noreturn]] void hle_locks_t : : lock ( )
{
// Unreachable
2024-04-15 08:49:01 +02:00
fmt : : throw_exception ( " Unreachable " ) ;
2024-03-27 13:44:33 +01:00
}
bool hle_locks_t : : try_lock ( )
{
while ( true )
{
auto [ old , success ] = lock_val . fetch_op ( [ ] ( s64 & value )
{
2025-04-05 21:50:45 +02:00
if ( value > = 0 )
{
value + + ;
return true ;
}
2024-03-27 13:44:33 +01:00
2025-04-05 21:50:45 +02:00
return false ;
} ) ;
2024-03-27 13:44:33 +01:00
if ( success )
{
return true ;
}
if ( old = = finalized )
{
break ;
}
lock_val . wait ( old ) ;
}
return false ;
}
void hle_locks_t : : unlock ( )
{
lock_val - - ;
}
bool hle_locks_t : : try_finalize ( std : : function < bool ( ) > test )
{
if ( ! test ( ) )
{
return false ;
}
if ( ! lock_val . compare_and_swap_test ( 0 , waiting_for_evaluation ) )
{
return false ;
}
if ( ! test ( ) )
{
// Failed
ensure ( lock_val . compare_and_swap_test ( waiting_for_evaluation , 0 ) ) ;
return false ;
}
ensure ( lock_val . compare_and_swap_test ( waiting_for_evaluation , finalized ) ) ;
// Sanity check when debugging (the result is not expected to change after finalization)
2025-04-05 21:50:45 +02:00
// ensure(test());
2024-03-27 13:44:33 +01:00
lock_val . notify_all ( ) ;
return true ;
}