more save game load game fixes

more save game load game fixes
This commit is contained in:
Jessica_Natalia
2026-08-15 18:05:58 -03:00
parent 92614e4f77
commit 64409f9e72
7 changed files with 114 additions and 23 deletions
+6 -2
View File
@@ -2,15 +2,19 @@
setlocal EnableExtensions
for %%I in ("%~dp0..\..") do set "REPO=%%~fI"
set "BUILD=%REPO%\out\vcs-release-ninja"
set "MARKER=%BUILD%\.v9_5_host_texture_objects_done"
set "MARKER95=%BUILD%\.v9_5_host_texture_objects_done"
set "MARKER96=%BUILD%\.v9_6_savedata_cancel_objects_done"
call "%~dp0FORCE_V9_5_HOST_TEXTURE_OBJECTS.bat"
if errorlevel 1 exit /b %errorlevel%
call "%~dp0FORCE_V9_6_SAVEDATA_CANCEL_OBJECTS.bat"
if errorlevel 1 exit /b %errorlevel%
call "%~dp0scripts\build_release_ninja.bat"
set "RC=%errorlevel%"
if "%RC%"=="0" (
if not exist "%BUILD%" mkdir "%BUILD%" >nul 2>nul
>"%MARKER%" echo V9.5 host-texture isolation successfully rebuilt critical objects.
>"%MARKER95%" echo V9.5 host-texture isolation successfully rebuilt critical objects.
>"%MARKER96%" echo V9.6 mode-aware savedata cancellation successfully rebuilt vcs_profile.cpp.
)
exit /b %RC%
@@ -0,0 +1,29 @@
@echo off
setlocal EnableExtensions
for %%I in ("%~dp0..\..") do set "REPO=%%~fI"
set "BUILD=%REPO%\out\vcs-release-ninja"
set "MARKER=%BUILD%\.v9_6_savedata_cancel_objects_done"
if exist "%MARKER%" exit /b 0
echo [V9.6] SAVEDATA CANCEL: invalidating stale profile objects...
if not exist "%BUILD%" (
echo [V9.6] Build tree does not exist yet; normal build will create it.
exit /b 0
)
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command ^
"$build=[IO.Path]::GetFullPath('%BUILD%');" ^
"$names=@('vcs_profile.cpp.obj');" ^
"$found=Get-ChildItem -LiteralPath $build -Recurse -File -Filter '*.obj' -ErrorAction SilentlyContinue ^| Where-Object { $names -contains $_.Name };" ^
"Write-Host ('[V9.6] stale objects found: ' + $found.Count);" ^
"foreach($f in $found){Write-Host (' deleting: ' + $f.FullName); Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop};" ^
"$src='%REPO%\profiles\vcs\host\vcs_profile.cpp';" ^
"if(Test-Path -LiteralPath $src){(Get-Item -LiteralPath $src).LastWriteTime=Get-Date}"
if errorlevel 1 (
echo ERROR: failed to invalidate V9.6 savedata cancel objects.
exit /b 1
)
echo [V9.6] Savedata cancel objects invalidated. Ninja must compile vcs_profile.cpp again.
exit /b 0
+35 -12
View File
@@ -2207,6 +2207,14 @@ constexpr std::uint32_t kPspUtilityCircle = 0x002000u;
constexpr std::uint32_t kPspUtilityCross = 0x004000u;
constexpr std::uint32_t kUtilityCommonResultOffset = 0x1Cu;
// PSP utility dialog common.result values. Keep these separate from savedata
// I/O error codes: user cancellation is CANCEL (1), while ABORT (2) is a
// different firmware result. VCS needs a legacy ABORT workaround only for
// the promoted LISTLOAD path; normal LISTSAVE/LISTDELETE cancellation must use
// the real PSP CANCEL result or the retail frontend enters its loading path.
constexpr std::uint32_t kPspUtilityDialogResultSuccess = 0u;
constexpr std::uint32_t kPspUtilityDialogResultCancel = 1u;
constexpr std::uint32_t kPspUtilityDialogResultAbort = 2u;
constexpr std::uint32_t kSavedataModeOffset = 0x30u;
constexpr std::uint32_t kSavedataGameNameOffset = 0x3Cu;
constexpr std::uint32_t kSavedataSaveNameOffset = 0x4Cu;
@@ -2549,7 +2557,7 @@ void initialize_savedata_list_ui(psprecomp::Runtime &runtime) {
savedata_utility.ui_initialized = true;
savedata_utility_ui_begin(savedata_utility.mode, savedata_utility.slots,
savedata_utility.selected);
std::cout << "[savedata] V9.4 list UI initialized mode=" << savedata_utility.mode
std::cout << "[savedata] V9.6 list UI initialized mode=" << savedata_utility.mode
<< " slots=" << savedata_utility.slots.size()
<< " selected=" << savedata_utility.selected << "\n";
}
@@ -2802,19 +2810,34 @@ const char *savedata_failure_message(std::uint32_t mode) noexcept {
}
void cancel_savedata_list_utility(psprecomp::Runtime &runtime) {
// PSP utility cancellation is not success. The retail VCS completion
// handler treats common.result == 0 as a successful load even when
// abortStatus is non-zero, which is why backing out of our promoted
// LOAD->LISTLOAD picker dropped into New Game. VCS' own AOT path has an
// explicit cancel branch for result==2 + abortStatus!=0.
runtime.memory().store32(savedata_utility.parameter_address + kSavedataAbortStatusOffset, 1u);
runtime.memory().store32(savedata_utility.parameter_address + kUtilityCommonResultOffset, 2u);
// Real PSP savedata list dialogs report a user Back/Cancel as
// common.result = CANCEL (1) and do not set abortStatus. V9.4 deliberately
// used ABORT (2) + abortStatus=1 to get VCS' promoted LOAD->LISTLOAD path
// back into gameplay; applying that same workaround to LISTSAVE was wrong:
// VCS interprets it as a load/restore transition and shows a black LOADING
// screen. Keep the proven LOAD compatibility workaround, but use exact PSP
// cancellation semantics for SAVE/DELETE.
const bool load_cancel_workaround = savedata_utility.mode == 4u ||
savedata_utility.startup_picker || savedata_utility.direct_load_picker;
const std::uint32_t common_result = load_cancel_workaround
? kPspUtilityDialogResultAbort
: kPspUtilityDialogResultCancel;
const std::uint32_t abort_status = load_cancel_workaround ? 1u : 0u;
runtime.memory().store32(savedata_utility.parameter_address +
kSavedataAbortStatusOffset, abort_status);
runtime.memory().store32(savedata_utility.parameter_address +
kUtilityCommonResultOffset, common_result);
savedata_utility.operation_complete = true;
savedata_utility.status = UtilityStatus::Quit;
savedata_utility_ui_end();
display_window_set_system_utility_mode(false);
std::cout << "[savedata] V9.4 picker cancelled mode=" << savedata_utility.mode
<< " commonResult=2 abortStatus=1\n";
std::cout << "[savedata] V9.6 picker cancelled mode=" << savedata_utility.mode
<< " commonResult=" << common_result
<< " abortStatus=" << abort_status
<< (load_cancel_workaround ? " policy=load-compat-abort"
: " policy=psp-user-cancel")
<< "\n";
}
void execute_selected_savedata_slot(psprecomp::Runtime &runtime) {
@@ -2837,7 +2860,7 @@ void execute_selected_savedata_slot(psprecomp::Runtime &runtime) {
savedata_utility.status = UtilityStatus::Quit;
savedata_utility_ui_end();
display_window_set_system_utility_mode(false);
std::cout << "[savedata] V9.4 LOAD selected slot=" << slot.save_name
std::cout << "[savedata] V9.6 LOAD selected slot=" << slot.save_name
<< " result=0\n";
} else {
savedata_utility.prompt = SavedataUtilityUiPrompt::Result;
@@ -7722,7 +7745,7 @@ void install_profile(psprecomp::Runtime &runtime, std::uint32_t user_arena_start
initialize_savedata_list_ui(rt);
display_window_set_system_utility_mode(true);
savedata_utility.previous_buttons = effective_controller_buttons();
std::cout << "[savedata] V9.4 LOAD picker active; guest mode="
std::cout << "[savedata] V9.6 LOAD picker active; guest mode="
<< guest_mode << " slots=" << savedata_utility.slots.size() << "\n";
} else if (savedata_mode_has_list_ui(savedata_utility.mode)) {
initialize_savedata_list_ui(rt);
@@ -20,8 +20,10 @@ if 'startup_load_picker_consumed' not in profile or 'savedata_utility.startup_pi
if 'savedata_utility.mode = 4u' not in profile: fail('startup picker is not host-side LISTLOAD')
if 'guest_mode == 0u' not in profile or 'guest_mode == 2u' not in profile: fail('AUTOLOAD/LOAD modes not covered')
if 'if (!savedata_utility.startup_picker)' not in profile: fail('startup picker cancellation guard missing')
if 'kUtilityCommonResultOffset, 2u' not in profile or 'kSavedataAbortStatusOffset, 1u' not in profile:
fail('picker cancel is still reported as successful LOAD')
if 'load_cancel_workaround' not in profile or 'kPspUtilityDialogResultAbort' not in profile:
fail('promoted LOAD picker cancel compatibility path missing')
if 'kPspUtilityDialogResultCancel = 1u' not in profile:
fail('PSP user-cancel result is not defined for non-load dialogs')
if 'MouseMenu=false' not in ini: fail('mouse menu is not disabled by default')
if 'if (mouse_menu_enabled()) enqueue_synthetic_pulse(state, kPspCross, 2);' not in display:
fail('savedata mouse click is not gated by MouseMenu')
@@ -29,5 +31,5 @@ print('LOAD-ONLY STARTUP AUDIT: PASS')
print(' - native GAME frontend boot hook is unlinked')
print(' - V8 generated autoload interception is removed')
print(' - first AUTOLOAD and every explicit LOAD become the in-frame Load Game picker')
print(' - cancel returns result=2 + abortStatus=1 instead of successful LOAD')
print(' - LOAD cancel keeps the proven abort compatibility path; SAVE uses PSP CANCEL')
print(' - menu mouse remains disabled by default and gates savedata clicks')
@@ -43,8 +43,9 @@ require(ui_cpp, '58.0f, 183.0f, 82.0f, 46.0f', 'next slot stays above footer')
require(ui_cpp, 'submit_smooth_text', 'antialiased text atlas path')
require(ui_cpp, 'blend_source_factor = 2u', 'text source-alpha blending')
require(ui_cpp, 'blend_dest_factor = 3u', 'text inverse-alpha blending')
require(profile_cpp, 'kUtilityCommonResultOffset, 2u', 'cancel result is not reported as success')
require(profile_cpp, 'kSavedataAbortStatusOffset, 1u', 'cancel abortStatus is asserted')
require(profile_cpp, 'load_cancel_workaround', 'mode-aware cancel policy')
require(profile_cpp, 'kPspUtilityDialogResultCancel = 1u', 'PSP cancel result')
require(profile_cpp, 'kPspUtilityDialogResultAbort = 2u', 'LOAD compatibility abort result')
require(ui_cpp, 'quad(v, 0, 0, 480, 23', 'PSP utility banner geometry')
require(ui_cpp, 'quad(v, 180, 136, 480, 137', 'PSP save-info separator geometry')
require(profile_cpp, 'slot.icon0_path = icon0.string();', 'savedata ICON0 path preserved')
+6 -4
View File
@@ -10,13 +10,15 @@ force = (root/'FORCE_V9_4_SAVEDATA_UI_OBJECTS.bat').read_text(encoding='utf-8')
errors=[]
def req(text, needle, msg):
if needle not in text: errors.append(msg)
req(profile, 'kUtilityCommonResultOffset, 2u', 'cancel common.result must be 2')
req(profile, 'kSavedataAbortStatusOffset, 1u', 'cancel abortStatus must be 1')
req(profile, 'load_cancel_workaround', 'mode-aware cancellation missing')
req(profile, 'kPspUtilityDialogResultCancel = 1u', 'PSP CANCEL constant missing')
req(profile, 'kPspUtilityDialogResultAbort = 2u', 'LOAD ABORT compatibility constant missing')
req(ui, 'submit_smooth_text', 'smooth atlas text path missing')
req(ui, 'draw_save_thumbnail(v, icons, 58.0f, 37.0f, 82.0f, 46.0f', 'previous slot safe row missing')
req(ui, 'draw_save_thumbnail(v, icons, 58.0f, 183.0f, 82.0f, 46.0f', 'next slot safe row missing')
req(ui, 'Draw text last so slot thumbnails can never cover labels or metadata.', 'text draw ordering guard missing')
req(build, 'FORCE_V9_4_SAVEDATA_UI_OBJECTS.bat', 'V9.4 force rebuild not wired')
if 'FORCE_V9_4_SAVEDATA_UI_OBJECTS.bat' not in build and 'FORCE_V9_5_HOST_TEXTURE_OBJECTS.bat' not in build:
errors.append('savedata UI object invalidation not wired')
req(force, 'savedata_utility_ui.cpp.obj', 'UI object invalidation missing')
req(force, 'vcs_profile.cpp.obj', 'profile object invalidation missing')
if not atlas.is_file() or atlas.stat().st_size < 10000:
@@ -26,7 +28,7 @@ if errors:
for e in errors: print(' - '+e)
sys.exit(1)
print('V9.4 SAVEDATA UI AUDIT: PASS')
print(' - cancel is not reported to VCS as successful load')
print(' - cancel is mode-aware: LOAD compatibility abort, SAVE/DELETE PSP cancel')
print(' - slot scroller stays between banner and footer')
print(' - antialiased text atlas is drawn after slot/icon geometry')
print(' - stale V9.3 UI/profile objects are force-invalidated once')
@@ -0,0 +1,30 @@
#!/usr/bin/env python3
from pathlib import Path
import sys
root = Path(__file__).resolve().parents[1]
profile = (root / 'host/vcs_profile.cpp').read_text(encoding='utf-8', errors='replace')
build = (root / 'BUILD_VCS_NINJA.bat').read_text(encoding='utf-8', errors='replace')
force_path = root / 'FORCE_V9_6_SAVEDATA_CANCEL_OBJECTS.bat'
force = force_path.read_text(encoding='utf-8', errors='replace') if force_path.is_file() else ''
errors = []
def req(text, needle, msg):
if needle not in text:
errors.append(msg)
req(profile, 'kPspUtilityDialogResultCancel = 1u', 'PSP CANCEL must be 1')
req(profile, 'kPspUtilityDialogResultAbort = 2u', 'PSP ABORT must be 2')
req(profile, 'const bool load_cancel_workaround = savedata_utility.mode == 4u', 'LISTLOAD compatibility policy missing')
req(profile, '? kPspUtilityDialogResultAbort', 'LOAD cancel must use ABORT compatibility result')
req(profile, ': kPspUtilityDialogResultCancel', 'SAVE/DELETE cancel must use PSP CANCEL')
req(profile, 'const std::uint32_t abort_status = load_cancel_workaround ? 1u : 0u;', 'SAVE/DELETE abortStatus must remain zero')
req(profile, 'policy=psp-user-cancel', 'physical SAVE cancel diagnostic missing')
req(build, 'FORCE_V9_6_SAVEDATA_CANCEL_OBJECTS.bat', 'V9.6 object invalidation not wired')
req(force, 'vcs_profile.cpp.obj', 'vcs_profile object invalidation missing')
if errors:
print('V9.6 SAVEDATA CANCEL AUDIT: FAIL')
for e in errors:
print(' - ' + e)
sys.exit(1)
print('V9.6 SAVEDATA CANCEL AUDIT: PASS')
print(' - LISTLOAD retains the physically-proven VCS abort compatibility path')
print(' - LISTSAVE/LISTDELETE use real PSP common.result=CANCEL (1), abortStatus=0')
print(' - vcs_profile.cpp is force-invalidated once for existing Ninja trees')