feat: detect and repair corrupted WebKit cache with clear-and-retry flow

When AppCache download fails due to corrupted on-disk WebKit data, the
installer page detects the failure and presents a prompt offering to wipe the
current user's webkit data (/user/home/<userid>/webkit/shell/) and retry.

- Add webkit_cleaner module to safely wipe webkit/shell for the foreground user
- Add /clear-webkit-data and /exit HTTP server routes and lifecycle handling
- Pass uid parameter to the installer page to display the exact target path
- Support one-shot retry (?retry=1) with persistent error reporting and clean exit
- Add corruption simulation build modes (SIMULATE=1|2) and helper script for testing
This commit is contained in:
najdek committed 2026-08-16 02:37:51 +02:00
1 parent a86320f9bb
commit 653d3f359d
12 files changed
+527 -7

No files matched your search

+16 -1
View File
@@ -16,7 +16,8 @@ LIBS := $(TARGET)/lib/libmicrohttpd.a \
# Source Files
SRCS := src/main.c src/http_server.c src/app_installer.c \
src/notification.c src/ps5_launcher.c src/log.c src/inflate.c
src/notification.c src/ps5_launcher.c src/log.c src/inflate.c \
src/webkit_cleaner.c src/simulate_corrupt.c
ELF := installer.elf
# Generated file registry
@@ -55,6 +56,20 @@ WKAL_HOST_SOURCES := pc-host/host.py $(FRONTEND_FILES)
CFLAGS := -Os -Wall -ffunction-sections -fdata-sections $(INCLUDES)
LDFLAGS := -Wl,--gc-sections
# Test builds that simulate a corrupted WebKit AppCache (see tools/build_cache_corruption_test_elfs.sh):
# SIMULATE=0 (default) - production behavior, corruption detection only
# SIMULATE=1 - every cache download fails until /clear-webkit-data succeeds
# (tests the clear-and-retry repair flow end to end)
# SIMULATE=2 - every cache download always fails (tests the terminal-error retry path)
# NOTE: CFLAGS changes are not tracked by make, so always `make clean all` when
# switching SIMULATE modes.
SIMULATE ?= 0
ifeq ($(SIMULATE),1)
CFLAGS += -DWKALI_SIMULATE_CACHE_CORRUPTION=1
else ifeq ($(SIMULATE),2)
CFLAGS += -DWKALI_SIMULATE_CACHE_CORRUPTION=2
endif
all: $(ELF)
# Regenerate the version header on every build (cheap, only rewrites on change)
+202 -1
View File
@@ -155,6 +155,82 @@
#logContainer.error {
color: #f00;
}
#cacheCorruptOverlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(2, 6, 23, 0.85);
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
z-index: 100;
}
#cacheCorruptOverlay .card {
background: #1b2436;
border: 1px solid #2c3e5a;
border-radius: 12px;
max-width: 34rem;
padding: 2rem;
text-align: center;
}
#cacheCorruptOverlay h2 {
font-size: 1.6rem;
font-weight: 600;
color: #fbbf24;
margin-bottom: 1rem;
}
#cacheCorruptOverlay p {
font-size: 1.125rem;
line-height: 1.6;
color: #cbd5e1;
margin-bottom: 1.5rem;
}
#cacheCorruptOverlay .actions {
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
justify-content: center;
gap: 1rem;
}
#cacheCorruptOverlay .actions[hidden] {
display: none !important;
}
#cacheCorruptOverlay .btn {
font-size: 1.125rem;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 8px;
border: none;
cursor: pointer;
}
#cacheCorruptOverlay .btn.primary {
background: #2563eb;
background: -webkit-linear-gradient(90deg, #2563eb 0%, #38bdf8 100%);
color: #fff;
}
#cacheCorruptOverlay .btn.secondary {
background: #334155;
color: #e2e8f0;
}
</style>
</head>
@@ -186,6 +262,21 @@
var progressBar = document.getElementById('progressBar');
var progressPct = document.getElementById('progressPct');
var completed = false;
var isDownloading = false;
var isRetry = false;
var currentUid = '';
var simulateCorrupt = false;
var corruptionPromptShown = false;
(function () {
var params = new URLSearchParams(window.location.search);
isRetry = params.get('retry') === '1';
currentUid = params.get('uid') || '';
/* Test hook: ?simulate=corrupt forces the corruption prompt without
* needing a real AppCache error (e.g. for quick UI testing in a
* desktop browser via `make dev`). */
simulateCorrupt = params.get('simulate') === 'corrupt';
})();
var logContainer = document.getElementById('logContainer');
var seenLogs = "";
@@ -281,6 +372,8 @@
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var hdrUid = xhr.getResponseHeader("X-User-Id");
if (hdrUid) currentUid = hdrUid;
probeOk = true;
setCaching();
} else {
@@ -327,7 +420,109 @@
installApp();
}
function setCacheCorrupted() {
if (corruptionPromptShown) {
return;
}
corruptionPromptShown = true;
completed = true;
progressWrap.hidden = true;
logContainer.hidden = true;
statusEl.textContent = 'Caching failed';
detailEl.textContent = '';
var overlay = document.createElement('div');
overlay.id = 'cacheCorruptOverlay';
var card = document.createElement('div');
card.className = 'card';
var title = document.createElement('h2');
var msg = document.createElement('p');
var actions = null;
var webkitShellPath = '/user/home/' + (currentUid || '<userid>') + '/webkit/shell/';
if (isRetry) {
title.textContent = 'Cache issue persists';
msg.textContent = 'Clearing WebKit data from ' + webkitShellPath + ' didn\'t seem to fix the issue. You can close this page.';
var exitXhr = new XMLHttpRequest();
exitXhr.open('GET', '/exit', true);
exitXhr.send();
} else {
title.textContent = 'WebKit cache appears corrupted';
msg.textContent = 'Your PS5\'s WebKit browser data may be corrupted, preventing the cache from completing. Clearing it may fix the issue. ';
var strong = document.createElement('strong');
strong.textContent = 'This will remove all browser data in ' + webkitShellPath + ' (AppCache, cookies, local storage) for the current user.';
msg.appendChild(strong);
actions = document.createElement('div');
actions.className = 'actions';
var clearBtn = document.createElement('button');
clearBtn.className = 'btn primary';
clearBtn.textContent = 'Clear WebKit Data & Retry';
clearBtn.addEventListener('click', function () {
actions.hidden = true;
actions.style.display = 'none';
msg.textContent = 'Clearing data and restarting browser...';
var xhr = new XMLHttpRequest();
xhr.open('GET', '/clear-webkit-data', true);
xhr.timeout = 30000;
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status !== 200) {
msg.textContent = 'Failed to clear WebKit data';
actions.hidden = false;
actions.style.display = '';
}
};
xhr.onerror = function () {
msg.textContent = 'Failed to clear WebKit data';
actions.hidden = false;
actions.style.display = '';
};
xhr.ontimeout = function () {
msg.textContent = 'Failed to clear WebKit data';
actions.hidden = false;
actions.style.display = '';
};
xhr.send();
});
actions.appendChild(clearBtn);
var cancelBtn = document.createElement('button');
cancelBtn.className = 'btn secondary';
cancelBtn.textContent = 'Cancel';
cancelBtn.addEventListener('click', function () {
msg.textContent = 'You can close this page. Re-run the installer ELF to try again.';
actions.hidden = true;
actions.style.display = 'none';
var xhr = new XMLHttpRequest();
xhr.open('GET', '/exit', true);
xhr.send();
});
actions.appendChild(cancelBtn);
}
card.appendChild(title);
card.appendChild(msg);
if (actions) {
card.appendChild(actions);
}
overlay.appendChild(card);
document.body.appendChild(overlay);
}
window.addEventListener('load', function () {
/* Test hook: ?simulate=corrupt forces the corruption prompt as if an
* AppCache error fired during download, and skips the probe/install
* polling so the prompt stays up for UI testing (e.g. make dev). */
if (simulateCorrupt) {
isDownloading = true;
setCacheCorrupted();
return;
}
probeServer();
/* The shortcut is created only once the AppCache is current, so the
@@ -368,6 +563,7 @@
appendLog("[AppCache] Checking for manifest...");
});
window.applicationCache.addEventListener('downloading', function () {
isDownloading = true;
appendLog("[AppCache] Downloading cache...");
});
window.applicationCache.addEventListener('progress', function (e) {
@@ -380,20 +576,25 @@
window.applicationCache.addEventListener('error', function (e) {
appendLog("[AppCache] Error occurred! Status: " + window.applicationCache.status);
logContainer.className = 'error';
if (isDownloading) {
setCacheCorrupted();
}
});
window.applicationCache.addEventListener('obsolete', function (e) {
appendLog("[AppCache] Manifest is obsolete.");
});
window.applicationCache.addEventListener('cached', function() {
isDownloading = false;
appendLog("[AppCache] Caching complete.");
maybeInstall();
});
window.applicationCache.addEventListener('noupdate', function() {
isDownloading = false;
appendLog("[AppCache] No update needed.");
maybeInstall();
});
window.applicationCache.addEventListener('updateready', function () {
isDownloading = false;
if (window.applicationCache.status === window.applicationCache.UPDATEREADY) {
window.applicationCache.swapCache();
window.location.reload();
+7 -1
View File
@@ -3,9 +3,15 @@
#include <microhttpd.h>
#include <stdatomic.h>
/* Shared flag — set to 0 by a successful /install, read by the main loop. */
/* Shared flag — set to 0 by a successful /install or /exit, read by the main loop. */
extern atomic_int http_keep_running;
/* Set to 1 only when /install succeeds so shutdown knows to notify success. */
extern atomic_int install_completed;
/* Set to 1 by /clear-webkit-data, read by main loop to re-launch browser. */
extern atomic_int webkit_data_cleared;
/* MHD request handler callback — dispatches all routes. */
enum MHD_Result http_on_request(void *cls, struct MHD_Connection *conn,
const char *url, const char *method,
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <microhttpd.h>
#include <stddef.h>
#if defined(WKALI_SIMULATE_CACHE_CORRUPTION) && WKALI_SIMULATE_CACHE_CORRUPTION > 0
void simulate_corrupt_manifest(const char *url, void **payload, size_t *payload_size,
enum MHD_ResponseMemoryMode *mem_mode);
void simulate_on_clear_success(void);
#else
static inline void simulate_corrupt_manifest(const char *url, void **payload,
size_t *payload_size,
enum MHD_ResponseMemoryMode *mem_mode) {
(void)url;
(void)payload;
(void)payload_size;
(void)mem_mode;
}
static inline void simulate_on_clear_success(void) {}
#endif
+6
View File
@@ -0,0 +1,6 @@
#pragma once
/* Recursively deletes all contents of the webkit/shell folder under the
* current (foreground) user's home directory. Returns 0 on success, -1 on
* error. */
int wkali_clear_webkit_data(void);
+2
View File
@@ -25,6 +25,8 @@
#define ROUTE_CACHE_MANIFEST "/cache.appcache"
#define ROUTE_INSTALL "/install"
#define ROUTE_VERSION "/version"
#define ROUTE_CLEAR_WEBKIT_DATA "/clear-webkit-data"
#define ROUTE_EXIT "/exit"
/* Logging (implementation in log.c) */
void wkali_log(const char *fmt, ...);
+47 -1
View File
@@ -15,6 +15,8 @@
#include "file_registry.h"
#include "inflate.h"
#include "app_installer.h"
#include "webkit_cleaner.h"
#include "simulate_corrupt.h"
/* CORS is intentionally `*`: the installer page is also served from the PC
* host (manuals.playstation.net over HTTPS), which cross-origin XHRs this
@@ -23,10 +25,18 @@
* Do not restrict unless that flow changes. */
#define CORS_ORIGIN "*"
/* Shared flag — set to 0 by a successful /install, read by the main loop.
/* Shared flag — set to 0 by a successful /install or /exit, read by the main loop.
* atomic so the store in a connection thread is visible to the main loop. */
atomic_int http_keep_running = 1;
/* Set to 1 only when /install succeeds so shutdown knows to notify success. */
atomic_int install_completed = 0;
/* Set to 1 by a successful /clear-webkit-data, read by the main loop to
* re-launch the browser. atomic so the store in a connection thread is
* visible to the main loop. */
atomic_int webkit_data_cleared = 0;
static void add_cors_headers(struct MHD_Response *resp) {
MHD_add_response_header(resp, "Access-Control-Allow-Origin", CORS_ORIGIN);
}
@@ -88,6 +98,7 @@ enum MHD_Result http_on_request(void *cls, struct MHD_Connection *conn,
MHD_RESPMEM_PERSISTENT);
MHD_add_response_header(resp, "Content-Type", "text/plain");
http_status = MHD_HTTP_OK;
atomic_store(&install_completed, 1);
atomic_store(&http_keep_running, 0);
} else {
wkali_log("[WKALI] App install failed (%d). Staying up.\n", err);
@@ -97,11 +108,25 @@ enum MHD_Result http_on_request(void *cls, struct MHD_Connection *conn,
MHD_add_response_header(resp, "Content-Type", "text/plain");
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
}
} else if (strcmp(url, ROUTE_EXIT) == 0) {
wkali_log("[WKALI] Exit requested by client. Stopping server...\n");
resp = MHD_create_response_from_buffer(2, (void *)"OK",
MHD_RESPMEM_PERSISTENT);
MHD_add_response_header(resp, "Content-Type", "text/plain");
http_status = MHD_HTTP_OK;
atomic_store(&http_keep_running, 0);
} else if (strcmp(url, ROUTE_VERSION) == 0) {
resp = MHD_create_response_from_buffer(strlen(WKAL_FULL_VERSION),
(void *)WKAL_FULL_VERSION,
MHD_RESPMEM_PERSISTENT);
MHD_add_response_header(resp, "Content-Type", "text/plain");
extern int sceUserServiceGetForegroundUser(int *);
int uid = -1;
if (sceUserServiceGetForegroundUser(&uid) == 0 && uid > 0) {
char uid_hdr[32];
snprintf(uid_hdr, sizeof(uid_hdr), "%08x", (unsigned int)uid);
MHD_add_response_header(resp, "X-User-Id", uid_hdr);
}
} else if (strcmp(url, "/logs") == 0) {
const char *pos_str = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "pos");
size_t pos = 0;
@@ -120,6 +145,24 @@ enum MHD_Result http_on_request(void *cls, struct MHD_Connection *conn,
const char *oom = "500 Internal Server Error\n";
resp = MHD_create_response_from_buffer(strlen(oom), (void *)oom, MHD_RESPMEM_PERSISTENT);
}
} else if (strcmp(url, ROUTE_CLEAR_WEBKIT_DATA) == 0) {
int err = wkali_clear_webkit_data();
if (err == 0) {
wkali_log("[WKALI] WebKit data cleared successfully. Will re-launch browser.\n");
simulate_on_clear_success();
resp = MHD_create_response_from_buffer(2, (void *)"OK",
MHD_RESPMEM_PERSISTENT);
MHD_add_response_header(resp, "Content-Type", "text/plain");
http_status = MHD_HTTP_OK;
atomic_store(&webkit_data_cleared, 1);
} else {
wkali_log("[WKALI] WebKit data clear failed.\n");
const char *fail = "Clear failed";
resp = MHD_create_response_from_buffer(strlen(fail), (void *)fail,
MHD_RESPMEM_PERSISTENT);
MHD_add_response_header(resp, "Content-Type", "text/plain");
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
}
} else {
const FileEntry *entry = registry_lookup(url);
if (entry) {
@@ -223,6 +266,9 @@ enum MHD_Result http_on_request(void *cls, struct MHD_Connection *conn,
}
}
/* Test simulation hook (no-op unless compiled with SIMULATE=1|2) */
simulate_corrupt_manifest(url, &payload, &payload_size, &mem_mode);
resp = MHD_create_response_from_buffer(payload_size, payload,
mem_mode);
MHD_add_response_header(resp, "Content-Type", entry->content_type);
+35 -2
View File
@@ -69,6 +69,7 @@ static pid_t find_pid(const char *name) {
/* PS5 System Calls (Internal) */
extern int sceNetCtlInit();
extern int sceUserServiceInitialize(void *);
extern int sceUserServiceGetForegroundUser(int *);
__attribute__((used)) volatile const char wkali_version_sig[] =
"WKALI_VER:" WKAL_FULL_VERSION;
@@ -128,20 +129,52 @@ int main(void) {
wkali_log("[WKALI] Server running. Waiting for the browser to cache content...\n");
/* Query foreground user ID to pass to the frontend URL so the UI can
* display the exact /user/home/<userid>/webkit/shell/ path in prompts. */
int uid = -1;
char uid_param[32] = "";
if (sceUserServiceGetForegroundUser(&uid) == 0 && uid > 0) {
snprintf(uid_param, sizeof(uid_param), "&uid=%08x", (unsigned int)uid);
}
/* Launch the browser at a versioned URL so the old AppCache master entry
* for "/" is never served from the previous install. */
char browser_url[256];
snprintf(browser_url, sizeof(browser_url),
"http://127.0.0.1:%d/?v=%s", WKALI_PORT, WKAL_FULL_VERSION);
"http://127.0.0.1:%d/?v=%s%s", WKALI_PORT, WKAL_FULL_VERSION, uid_param);
ps5_launch_browser(browser_url);
/* Main loop — runs until /install succeeds (which also installs the
* homescreen app) and sets http_keep_running to 0 */
int webkit_clear_attempts = 0;
while (atomic_load(&http_keep_running)) {
/* Check if the frontend requested a WebKit data clear */
if (atomic_load(&webkit_data_cleared)) {
atomic_store(&webkit_data_cleared, 0);
webkit_clear_attempts++;
if (webkit_clear_attempts <= 1) {
/* Give the HTTP response time to flush before re-launching */
usleep(500000);
wkali_log("[WKALI] Re-launching browser after WebKit data clear (attempt %d)...\n",
webkit_clear_attempts);
char retry_url[256];
snprintf(retry_url, sizeof(retry_url),
"http://127.0.0.1:%d/?v=%s%s&retry=1",
WKALI_PORT, WKAL_FULL_VERSION, uid_param);
ps5_launch_browser(retry_url);
} else {
wkali_log("[WKALI] WebKit clear already attempted %d time(s), not re-launching.\n",
webkit_clear_attempts);
}
}
usleep(100000); /* 100ms sleep */
}
wkali_notify("WebKit Autoloader v%s cached successfully!", WKAL_FULL_VERSION);
if (atomic_load(&install_completed)) {
wkali_notify("WebKit Autoloader v%s cached successfully!", WKAL_FULL_VERSION);
}
wkali_log_wakeup();
/* Give the /logs thread half a second to wake up and flush the final logs
+67
View File
@@ -0,0 +1,67 @@
#include "simulate_corrupt.h"
#if defined(WKALI_SIMULATE_CACHE_CORRUPTION) && WKALI_SIMULATE_CACHE_CORRUPTION > 0
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include "wkali.h"
/* Test-only: set to 1 once /clear-webkit-data succeeds. Used by the one-shot
* (mode 1) simulation so the post-clear retry download runs a clean manifest,
* mirroring a wipe actually fixing a corrupted on-disk cache. */
static atomic_int simulate_healed = 0;
void simulate_on_clear_success(void) {
#if WKALI_SIMULATE_CACHE_CORRUPTION == 1
atomic_store(&simulate_healed, 1);
#endif
}
void simulate_corrupt_manifest(const char *url, void **payload, size_t *payload_size,
enum MHD_ResponseMemoryMode *mem_mode) {
if (strcmp(url, ROUTE_CACHE_MANIFEST) != 0)
return;
if (WKALI_SIMULATE_CACHE_CORRUPTION == 1 && atomic_load(&simulate_healed))
return;
static const char fake[] = "simulate_corrupted.bin\n";
size_t fake_len = sizeof(fake) - 1;
const char *src = (const char *)*payload;
size_t src_len = *payload_size;
/* Insert after the CACHE: header so the fake entry is treated as a regular
* cache member (and 404s). Fall back to the top of the file if the header
* is somehow missing. */
const char *marker = NULL;
for (const char *p = src; p + 6 <= src + src_len; p++) {
if (memcmp(p, "CACHE:", 6) == 0) {
marker = p;
break;
}
}
const char *insert_at = src;
if (marker) {
const char *nl = memchr(marker, '\n', (src + src_len) - marker);
if (nl) insert_at = nl + 1;
}
size_t off = (size_t)(insert_at - src);
char *out = malloc(src_len + fake_len + 1);
if (!out) return;
memcpy(out, src, off);
memcpy(out + off, fake, fake_len);
memcpy(out + off + fake_len, src + off, src_len - off);
out[src_len + fake_len] = '\0';
if (*mem_mode == MHD_RESPMEM_MUST_FREE) free((void *)src);
*payload = out;
*payload_size = src_len + fake_len;
*mem_mode = MHD_RESPMEM_MUST_FREE;
wkali_log("[WKALI] SIMULATED corrupted cache: manifest now includes simulate_corrupted.bin (mode %d)\n",
WKALI_SIMULATE_CACHE_CORRUPTION);
}
#endif
+71
View File
@@ -0,0 +1,71 @@
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "wkali.h"
#include "webkit_cleaner.h"
#define USER_HOME_BASE "/user/home"
/* Recursively delete all contents of dir_path (files + subdirs),
* but preserve dir_path itself. Returns 0 on success, -1 on error. */
static int rm_rf_contents(const char *dir_path) {
DIR *d = opendir(dir_path);
if (!d) return -1;
struct dirent *entry;
int ret = 0;
while ((entry = readdir(d)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
char path[512];
snprintf(path, sizeof(path), "%s/%s", dir_path, entry->d_name);
struct stat st;
if (stat(path, &st) != 0) { ret = -1; continue; }
if (S_ISDIR(st.st_mode)) {
if (rm_rf_contents(path) != 0) ret = -1;
if (rmdir(path) != 0) ret = -1;
} else {
if (unlink(path) != 0) ret = -1;
}
}
closedir(d);
return ret;
}
int wkali_clear_webkit_data(void) {
/* Only the foreground (signed-in) user's data is cleared. */
extern int sceUserServiceGetForegroundUser(int *);
int user_id = -1;
if (sceUserServiceGetForegroundUser(&user_id) != 0 || user_id <= 0) {
wkali_log("[WKALI] Cannot determine current user\n");
return -1;
}
char ws_path[512];
snprintf(ws_path, sizeof(ws_path), "%s/%08x/webkit/shell",
USER_HOME_BASE, (unsigned int)user_id);
struct stat st;
if (stat(ws_path, &st) != 0 || !S_ISDIR(st.st_mode)) {
wkali_log("[WKALI] No webkit/shell for current user %08x, nothing to clear\n",
(unsigned int)user_id);
return 0;
}
wkali_log("[WKALI] Clearing %s ...\n", ws_path);
if (rm_rf_contents(ws_path) == 0) {
wkali_log("[WKALI] Cleared webkit/shell for current user %08x\n",
(unsigned int)user_id);
return 0;
}
wkali_log("[WKALI] Errors clearing %s\n", ws_path);
return -1;
}
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# Build test ELFs that simulate a corrupted WebKit AppCache.
#
# Produces:
# installer-simulate-once.elf - every cache download fails until the user
# confirms "Clear WebKit Data & Retry", then
# the post-clear retry succeeds (tests the
# full repair flow end to end)
# installer-simulate-always.elf - every cache download always fails, so the
# retry also errors (tests the terminal
# "Cache issue persists" path)
#
# The build ends with a normal (SIMULATE=0) installer.elf restored.
#
# Usage from the repo root:
# bash tools/build_cache_corruption_test_elfs.sh
# or inside the SDK container:
# docker run --rm -v "$(pwd)":/src -w /src ps5-webkit-autoloader-sdk \
# bash tools/build_cache_corruption_test_elfs.sh
set -e
cd "$(dirname "$0")/.."
build() {
local mode="$1"
if [ -x /opt/ps5-payload-sdk/bin/prospero-clang ]; then
make clean all "SIMULATE=$mode"
else
echo " (no local SDK - building via Docker ps5-webkit-autoloader-sdk)"
docker run --rm -v "$(pwd)":/src -w /src ps5-webkit-autoloader-sdk \
make clean all "SIMULATE=$mode"
fi
}
echo "=== [1/3] SIMULATE=1 (fail until clear, then heal) ==="
build 1
cp installer.elf installer-simulate-once.elf
echo "=== [2/3] SIMULATE=2 (always fail) ==="
build 2
cp installer.elf installer-simulate-always.elf
echo "=== [3/3] Restoring normal SIMULATE=0 build ==="
build 0
echo ""
echo "Done. Test payloads (send to PS5 via elfldr / Payload Manager):"
ls -la installer.elf installer-simulate-once.elf installer-simulate-always.elf
+1 -1
View File
@@ -55,7 +55,7 @@ def git(*args):
"""Run a git command in the repo; returns stdout stripped or ''."""
try:
return subprocess.run(
["git", *args], cwd=REPO, capture_output=True, text=True
["git", *args], cwd=REPO, capture_output=True, text=True, errors="replace"
).stdout.strip()
except OSError:
return ""