mirror of
https://github.com/manos555555/PS5-Suite.git
synced 2026-10-06 09:00:27 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c21d73f41d | ||
|
|
5ce9d470bc | ||
|
|
0eb159bf88 | ||
|
|
4ff160232f | ||
|
|
a5e40ce245 | ||
|
|
bb7d90411c | ||
|
|
00013aae76 | ||
|
|
6d8d802962 | ||
|
|
f3a1d5e436 | ||
|
|
bad3029e32 | ||
|
|
a51fffef83 | ||
|
|
f89eaf4e31 |
No files matched your search
@@ -0,0 +1,99 @@
|
||||
# PS5 Upload Suite v2.0.0 - Feature-Rich Release
|
||||
|
||||
## 🎉 What's New - 4 Major Features Added!
|
||||
|
||||
### 1. 📥 Download Files (PS5 → PC)
|
||||
- Right-click any file → "⬇️ Download to PC"
|
||||
- Save file dialog for destination
|
||||
- Real-time progress with speed tracking
|
||||
- Optimized with sendfile for maximum speed
|
||||
|
||||
### 2. 🔍 File Search
|
||||
- Search box in PS5 Files panel
|
||||
- Real-time filtering as you type
|
||||
- Case-insensitive search
|
||||
- Quick "Clear" button
|
||||
|
||||
### 3. ⭐ Favorites/Bookmarks
|
||||
- Save frequently used PS5 paths
|
||||
- Quick dropdown navigation
|
||||
- Add/Remove favorite paths
|
||||
- Persistent JSON storage
|
||||
|
||||
### 4. 🎮 Multi-PS5 Support
|
||||
- Save multiple PS5 profiles (IP + name)
|
||||
- Quick switch between consoles
|
||||
- Dropdown profile selector
|
||||
- Persistent profile storage
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### Server (PS5)
|
||||
1. Copy `ps5_upload_server.elf` to `/data/etaHEN/payloads/`
|
||||
2. Load with elfldr
|
||||
3. Server starts on port 9113
|
||||
|
||||
### Client (Windows)
|
||||
1. Download `PS5Upload.exe`
|
||||
2. Run on Windows 10/11
|
||||
3. Enter PS5 IP and connect
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Performance
|
||||
|
||||
- **Upload Speed:** 100-120 MB/s (Gigabit Ethernet)
|
||||
- **Download Speed:** Same high-speed protocol
|
||||
- **Stability:** Zero connection drops (from v1.3.0)
|
||||
- **Tested:** 42,801+ files uploaded successfully
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Technical Details
|
||||
|
||||
### New Protocol Command
|
||||
- `CMD_DOWNLOAD_FILE` (0x13) - Download files from PS5
|
||||
|
||||
### Server Changes
|
||||
- Added sendfile-based download handler
|
||||
- Fixed LIST_DIR to return actual file sizes
|
||||
- Zero-copy file transfer
|
||||
|
||||
### Client Changes
|
||||
- 4 new UI panels (Search, Favorites, Profiles, Download)
|
||||
- JSON-based persistence (ps5_profiles.json, ps5_favorites.json)
|
||||
- Custom dark theme for all ComboBox dropdowns
|
||||
- ~500+ lines of new code
|
||||
|
||||
---
|
||||
|
||||
## ⬆️ Upgrading from v1.3.0
|
||||
|
||||
1. Replace server: Copy new `ps5_upload_server.elf` to PS5
|
||||
2. Replace client: Download new `PS5Upload.exe`
|
||||
3. Your settings will be preserved
|
||||
|
||||
**New files created:**
|
||||
- `ps5_profiles.json` - Saved PS5 profiles
|
||||
- `ps5_favorites.json` - Saved favorite paths
|
||||
|
||||
---
|
||||
|
||||
## 📋 Files in This Release
|
||||
|
||||
- `ps5_upload_server.elf` (129 KB) - PS5 server payload
|
||||
- `PS5Upload.exe` (8.2 MB) - Windows client (self-contained)
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Credits
|
||||
|
||||
**By Manos**
|
||||
|
||||
Special thanks to the PS5 homebrew community!
|
||||
|
||||
---
|
||||
|
||||
**Enjoy the new features!** 🚀
|
||||
@@ -1,263 +0,0 @@
|
||||
# GitHub Upload Instructions - PS5 Upload Suite
|
||||
|
||||
## 📁 Correct Folder to Upload
|
||||
|
||||
**Source Path:**
|
||||
```
|
||||
C:\Users\HACKMAN\Desktop\ps5 test\ps5_rom_keys\ps5_upload_suite\client\bin\Release\net6.0-windows\win-x64\
|
||||
```
|
||||
|
||||
This folder contains the **complete, self-contained Windows executable** with all dependencies.
|
||||
|
||||
---
|
||||
|
||||
## 🗑️ Step 1: Delete Old Release on GitHub
|
||||
|
||||
1. Go to your GitHub repository
|
||||
2. Navigate to **Releases** tab
|
||||
3. Find the old/incorrect release
|
||||
4. Click **Delete** on the old release
|
||||
5. Confirm deletion
|
||||
|
||||
**Or delete via Git:**
|
||||
```bash
|
||||
# Delete old tag
|
||||
git tag -d v1.0
|
||||
git push origin :refs/tags/v1.0
|
||||
|
||||
# Delete old files from repository
|
||||
git rm -r ps5upload-windows-x64/
|
||||
git commit -m "Remove incorrect Windows build"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Step 2: Create Release Package
|
||||
|
||||
### Option A: Via PowerShell (Automated)
|
||||
|
||||
```powershell
|
||||
# Navigate to project root
|
||||
cd "C:\Users\HACKMAN\Desktop\ps5 test\ps5_rom_keys\ps5_upload_suite"
|
||||
|
||||
# Create release directory
|
||||
New-Item -ItemType Directory -Force -Path ".\github_release"
|
||||
|
||||
# Copy correct files
|
||||
Copy-Item -Path ".\client\bin\Release\net6.0-windows\win-x64\*" -Destination ".\github_release\" -Recurse
|
||||
|
||||
# Copy payload
|
||||
Copy-Item -Path ".\payload\ps5_upload_server.elf" -Destination ".\github_release\"
|
||||
|
||||
# Create ZIP
|
||||
Compress-Archive -Path ".\github_release\*" -DestinationPath ".\PS5Upload-Windows-x64-v1.0.zip" -Force
|
||||
|
||||
Write-Host "Release package created: PS5Upload-Windows-x64-v1.0.zip"
|
||||
```
|
||||
|
||||
### Option B: Manual
|
||||
|
||||
1. Create new folder: `PS5Upload-Windows-x64`
|
||||
2. Copy **ALL files** from `win-x64\` folder
|
||||
3. Add `ps5_upload_server.elf` from `payload\` folder
|
||||
4. Create ZIP: `PS5Upload-Windows-x64-v1.0.zip`
|
||||
|
||||
---
|
||||
|
||||
## ⬆️ Step 3: Upload to GitHub
|
||||
|
||||
### Via GitHub Web Interface:
|
||||
|
||||
1. Go to your repository on GitHub
|
||||
2. Click **Releases** → **Create a new release**
|
||||
3. Fill in:
|
||||
- **Tag:** `v1.0`
|
||||
- **Title:** `PS5 Upload Suite v1.0 - Windows x64`
|
||||
- **Description:**
|
||||
```markdown
|
||||
# PS5 Upload Suite v1.0
|
||||
|
||||
## Windows x64 Release
|
||||
|
||||
Self-contained executable for Windows 10/11 (64-bit)
|
||||
No .NET Runtime installation required!
|
||||
|
||||
## What's Included:
|
||||
- PS5Upload.exe (Windows GUI client)
|
||||
- ps5_upload_server.elf (PS5 server payload)
|
||||
- All required dependencies
|
||||
|
||||
## How to Use:
|
||||
1. Extract ZIP file
|
||||
2. Run PS5Upload.exe
|
||||
3. Load ps5_upload_server.elf to PS5 (via etaHEN)
|
||||
4. Connect and upload files!
|
||||
|
||||
## Requirements:
|
||||
- Windows 10/11 (64-bit)
|
||||
- PS5 with etaHEN 2.0+ or GoldHEN
|
||||
- Network connection to PS5
|
||||
|
||||
## File Size:
|
||||
- Full package: ~50 MB
|
||||
- Includes all .NET 6.0 dependencies
|
||||
```
|
||||
4. **Attach files:**
|
||||
- Upload `PS5Upload-Windows-x64-v1.0.zip`
|
||||
5. Click **Publish release**
|
||||
|
||||
### Via Git Command Line:
|
||||
|
||||
```bash
|
||||
# Navigate to repository
|
||||
cd "C:\Users\HACKMAN\Desktop\ps5 test\ps5_rom_keys\ps5_upload_suite"
|
||||
|
||||
# Add correct files
|
||||
git add client/bin/Release/net6.0-windows/win-x64/
|
||||
git commit -m "Add correct Windows x64 build with all dependencies"
|
||||
git push origin main
|
||||
|
||||
# Create and push tag
|
||||
git tag -a v1.0 -m "PS5 Upload Suite v1.0 - Windows x64"
|
||||
git push origin v1.0
|
||||
|
||||
# Upload release asset via GitHub CLI (if installed)
|
||||
gh release create v1.0 PS5Upload-Windows-x64-v1.0.zip --title "PS5 Upload Suite v1.0" --notes "Self-contained Windows x64 release"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Step 4: Update Repository README
|
||||
|
||||
Add download link to main README.md:
|
||||
|
||||
```markdown
|
||||
## Download
|
||||
|
||||
### Latest Release: v1.0
|
||||
|
||||
**Windows (x64):**
|
||||
- [PS5Upload-Windows-x64-v1.0.zip](https://github.com/YOUR_USERNAME/ps5_upload_suite/releases/download/v1.0/PS5Upload-Windows-x64-v1.0.zip)
|
||||
- Size: ~50 MB
|
||||
- Self-contained (no .NET installation required)
|
||||
|
||||
**PS5 Server:**
|
||||
- Included in Windows package: `ps5_upload_server.elf`
|
||||
- Load via etaHEN or GoldHEN
|
||||
|
||||
### System Requirements:
|
||||
- **Windows:** 10/11 (64-bit)
|
||||
- **PS5:** Firmware with etaHEN 2.0+ or GoldHEN
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification Checklist
|
||||
|
||||
Before uploading, verify:
|
||||
|
||||
- [ ] Folder contains `PS5Upload.exe` (8+ MB)
|
||||
- [ ] Folder contains `PS5Upload.dll`
|
||||
- [ ] Folder contains `PS5Upload.runtimeconfig.json`
|
||||
- [ ] Folder contains all DLL dependencies (100+ files)
|
||||
- [ ] `ps5_upload_server.elf` is included
|
||||
- [ ] ZIP file is created correctly
|
||||
- [ ] Test: Extract ZIP and run `PS5Upload.exe` - should work without errors
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Common Issues
|
||||
|
||||
### "The application requires .NET Runtime"
|
||||
**Problem:** Wrong folder uploaded (missing dependencies)
|
||||
**Solution:** Use the `win-x64` folder, not `release` folder
|
||||
|
||||
### "Missing DLL files"
|
||||
**Problem:** Incomplete upload
|
||||
**Solution:** Make sure ALL files from `win-x64` folder are included
|
||||
|
||||
### "Application won't start"
|
||||
**Problem:** Corrupted ZIP or missing files
|
||||
**Solution:** Re-create ZIP from scratch, include all files
|
||||
|
||||
---
|
||||
|
||||
## 📊 File Structure for GitHub
|
||||
|
||||
```
|
||||
PS5Upload-Windows-x64-v1.0.zip
|
||||
├── PS5Upload.exe (Main executable)
|
||||
├── PS5Upload.dll (Application library)
|
||||
├── PS5Upload.runtimeconfig.json (Runtime configuration)
|
||||
├── ps5_upload_server.elf (PS5 server payload)
|
||||
├── Accessibility.dll (Dependency)
|
||||
├── D3DCompiler_47_cor3.dll (Dependency)
|
||||
├── DirectWriteForwarder.dll (Dependency)
|
||||
├── Microsoft.*.dll (100+ Microsoft dependencies)
|
||||
├── PresentationCore.dll (WPF dependency)
|
||||
├── PresentationFramework.dll (WPF dependency)
|
||||
├── ReachFramework.dll (WPF dependency)
|
||||
├── System.*.dll (System dependencies)
|
||||
├── UIAutomationClient.dll (UI dependency)
|
||||
├── UIAutomationProvider.dll (UI dependency)
|
||||
├── UIAutomationTypes.dll (UI dependency)
|
||||
├── WindowsBase.dll (Windows dependency)
|
||||
├── WindowsFormsIntegration.dll (Windows dependency)
|
||||
├── api-ms-win-*.dll (Windows API dependencies)
|
||||
├── clrcompression.dll (Compression)
|
||||
├── clretwrc.dll (ETW)
|
||||
├── clrjit.dll (JIT compiler)
|
||||
├── coreclr.dll (CoreCLR runtime)
|
||||
├── createdump.exe (Debug tool)
|
||||
├── hostfxr.dll (Host)
|
||||
├── hostpolicy.dll (Host policy)
|
||||
├── mscordaccore.dll (DAC)
|
||||
├── mscordaccore_amd64_amd64_*.dll (DAC)
|
||||
├── mscordbi.dll (Debugger)
|
||||
├── mscorlib.dll (Core library)
|
||||
├── msquic.dll (QUIC)
|
||||
├── ucrtbase.dll (C runtime)
|
||||
├── vcruntime140.dll (VC runtime)
|
||||
└── vcruntime140_cor3.dll (VC runtime)
|
||||
```
|
||||
|
||||
**Total:** ~150 files, ~50 MB
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quick Commands Summary
|
||||
|
||||
```powershell
|
||||
# 1. Create release package
|
||||
cd "C:\Users\HACKMAN\Desktop\ps5 test\ps5_rom_keys\ps5_upload_suite"
|
||||
Compress-Archive -Path ".\client\bin\Release\net6.0-windows\win-x64\*" -DestinationPath ".\PS5Upload-Windows-x64-v1.0.zip" -Force
|
||||
|
||||
# 2. Test locally
|
||||
Expand-Archive -Path ".\PS5Upload-Windows-x64-v1.0.zip" -DestinationPath ".\test_extract" -Force
|
||||
.\test_extract\PS5Upload.exe
|
||||
|
||||
# 3. Upload to GitHub (manual via web interface)
|
||||
# Go to: https://github.com/YOUR_USERNAME/ps5_upload_suite/releases/new
|
||||
# Upload: PS5Upload-Windows-x64-v1.0.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Final Steps
|
||||
|
||||
1. **Delete old release** on GitHub
|
||||
2. **Create ZIP** from `win-x64` folder
|
||||
3. **Test ZIP** locally (extract and run)
|
||||
4. **Upload to GitHub** as new release
|
||||
5. **Update README** with download link
|
||||
6. **Announce** the correct release
|
||||
|
||||
---
|
||||
|
||||
**The correct folder is:**
|
||||
```
|
||||
C:\Users\HACKMAN\Desktop\ps5 test\ps5_rom_keys\ps5_upload_suite\client\bin\Release\net6.0-windows\win-x64\
|
||||
```
|
||||
|
||||
**This contains everything needed for a working Windows release!**
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,11 +2,11 @@
|
||||
|
||||
**By Manos**
|
||||
|
||||
**Version 1.3.0 - Stable Release**
|
||||
**Version 2.0.0 - Feature-Rich Release**
|
||||
|
||||
Custom high-speed file transfer system for PS5 with etaHEN. Achieves **100+ MB/s** transfer speeds using a custom binary protocol.
|
||||
|
||||
⭐ **NEW in v1.3.0:** Complete stability overhaul - **42,801+ files uploaded with ZERO errors!**
|
||||
⭐ **NEW in v2.0.0:** 4 Major Features Added - Download Files, File Search, Favorites/Bookmarks, Multi-PS5 Support!
|
||||
|
||||

|
||||

|
||||
@@ -86,6 +86,10 @@ Custom high-speed file transfer system for PS5 with etaHEN. Achieves **100+ MB/s
|
||||
✅ **Real-time Progress** - Speed & percentage
|
||||
✅ **Folder Upload** - Recursive directory upload
|
||||
✅ **Storage Info** - Free space display
|
||||
✅ **Download Files** - Download from PS5 to PC (NEW v2.0)
|
||||
✅ **File Search** - Real-time filtering of PS5 files (NEW v2.0)
|
||||
✅ **Favorites/Bookmarks** - Quick navigation to saved paths (NEW v2.0)
|
||||
✅ **Multi-PS5 Support** - Save and switch between multiple PS5 profiles (NEW v2.0)
|
||||
|
||||
---
|
||||
|
||||
@@ -154,6 +158,7 @@ See [PROTOCOL.md](PROTOCOL.md) for detailed protocol documentation.
|
||||
- `START_UPLOAD` - Begin file upload
|
||||
- `UPLOAD_CHUNK` - Upload file chunk
|
||||
- `END_UPLOAD` - Finish upload
|
||||
- `DOWNLOAD_FILE` - Download file from PS5 (NEW v2.0)
|
||||
- `SHUTDOWN` - Shutdown server
|
||||
|
||||
---
|
||||
@@ -186,15 +191,38 @@ See [PROTOCOL.md](PROTOCOL.md) for detailed protocol documentation.
|
||||
|
||||
---
|
||||
|
||||
## 📝 What's New in v1.3.0
|
||||
## 📝 What's New in v2.0.0
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for complete list of all 15 bug fixes.
|
||||
### 🎉 4 Major New Features:
|
||||
|
||||
### Highlights:
|
||||
#### 1. 📥 Download Files (PS5 → PC)
|
||||
- Right-click any file → "⬇️ Download to PC"
|
||||
- Save file dialog for destination selection
|
||||
- Real-time progress tracking with speed display
|
||||
- Optimized with sendfile for maximum speed
|
||||
|
||||
#### 2. 🔍 File Search
|
||||
- Search box in PS5 Files panel
|
||||
- Real-time filtering as you type
|
||||
- Case-insensitive search
|
||||
- Quick "Clear" button to reset
|
||||
|
||||
#### 3. ⭐ Favorites/Bookmarks
|
||||
- Save frequently used PS5 paths
|
||||
- Quick dropdown navigation
|
||||
- Add/Remove favorite paths
|
||||
- Persistent storage in JSON
|
||||
|
||||
#### 4. 🎮 Multi-PS5 Support
|
||||
- Save multiple PS5 profiles (IP + name)
|
||||
- Quick switch between different PS5 consoles
|
||||
- Dropdown profile selector
|
||||
- Persistent profile storage
|
||||
|
||||
### Previous Stability (v1.3.0):
|
||||
- ✅ **Zero connection drops** - 5 minute socket timeout + aggressive keepalive
|
||||
- ✅ **Fully responsive UI** - Async updates + throttled logging
|
||||
- ✅ **No memory leaks** - Proper resource disposal
|
||||
- ✅ **Optimal stability** - 6 parallel single-connection uploads
|
||||
- ✅ **Tested:** 42,801 files uploaded with 100% success rate
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
# 🎉 PS5 Upload Suite v2.0.0 - Feature-Rich Release
|
||||
|
||||
## 🚀 What's New
|
||||
|
||||
This is a **major feature release** adding 4 powerful new capabilities to the PS5 Upload Suite!
|
||||
|
||||
---
|
||||
|
||||
## ✨ New Features
|
||||
|
||||
### 1. 📥 Download Files (PS5 → PC)
|
||||
|
||||
Finally! You can now download files from your PS5 back to your PC.
|
||||
|
||||
**How to use:**
|
||||
- Right-click any file in PS5 Files panel
|
||||
- Select "⬇️ Download to PC"
|
||||
- Choose where to save the file
|
||||
- Watch the progress with real-time speed tracking
|
||||
|
||||
**Technical Details:**
|
||||
- Uses sendfile optimization for maximum speed
|
||||
- Zero-copy transfer on PS5 side
|
||||
- Same high-speed protocol as uploads
|
||||
- Full progress tracking
|
||||
|
||||
---
|
||||
|
||||
### 2. 🔍 File Search
|
||||
|
||||
Quickly find files in large directories without scrolling.
|
||||
|
||||
**How to use:**
|
||||
- Type in the Search box below Current Path
|
||||
- Files are filtered in real-time as you type
|
||||
- Case-insensitive search
|
||||
- Click "✖ Clear" to reset
|
||||
|
||||
**Perfect for:**
|
||||
- Finding specific game saves
|
||||
- Locating config files
|
||||
- Searching through large mod folders
|
||||
|
||||
---
|
||||
|
||||
### 3. ⭐ Favorites/Bookmarks
|
||||
|
||||
Save your frequently used PS5 paths for instant navigation.
|
||||
|
||||
**How to use:**
|
||||
- Navigate to any path (e.g., `/data/games`)
|
||||
- Click "⭐ Add" to save it
|
||||
- Use the Favorites dropdown to quickly jump to saved paths
|
||||
- Click "🗑️ Remove" to delete a favorite
|
||||
|
||||
**Saved paths persist between sessions!**
|
||||
|
||||
---
|
||||
|
||||
### 4. 🎮 Multi-PS5 Support
|
||||
|
||||
Manage multiple PS5 consoles with ease.
|
||||
|
||||
**How to use:**
|
||||
- Enter a PS5 IP address
|
||||
- Click "💾 Save Profile" and give it a name (e.g., "Living Room PS5")
|
||||
- Use the Profile dropdown to quickly switch between PS5s
|
||||
- Click "🗑️ Delete Profile" to remove saved profiles
|
||||
|
||||
**Perfect for:**
|
||||
- Users with multiple PS5 consoles
|
||||
- Testing on different systems
|
||||
- Switching between home and friend's PS5
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Technical Improvements
|
||||
|
||||
### Server-Side (payload)
|
||||
- Added `CMD_DOWNLOAD_FILE` (0x13) command
|
||||
- Implemented sendfile-based download handler
|
||||
- Fixed `LIST_DIR` to return actual file sizes (not 0)
|
||||
- Zero-copy file transfer for downloads
|
||||
|
||||
### Client-Side
|
||||
- Added `DownloadFileAsync()` method to Protocol
|
||||
- Implemented search filtering with `ObservableCollection`
|
||||
- Added JSON-based profile and favorites persistence
|
||||
- Custom ComboBox templates for dark theme consistency
|
||||
- Improved UI responsiveness
|
||||
|
||||
---
|
||||
|
||||
## 📊 Statistics
|
||||
|
||||
**Lines of Code Added:** ~500+ lines
|
||||
**New Protocol Commands:** 1 (DOWNLOAD_FILE)
|
||||
**New UI Elements:** 3 panels (Search, Favorites, Profiles)
|
||||
**Persistent Storage Files:** 2 (ps5_profiles.json, ps5_favorites.json)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI Improvements
|
||||
|
||||
- All ComboBox dropdowns now have proper dark theme
|
||||
- All buttons show full text without cutoff
|
||||
- Consistent styling across all new features
|
||||
- Responsive layout that adapts to content
|
||||
|
||||
---
|
||||
|
||||
## 📦 Files Included
|
||||
|
||||
### Server
|
||||
- `payload/ps5_upload_server.elf` - Updated with download support
|
||||
|
||||
### Client
|
||||
- `client/bin/Release/net6.0-windows/win-x64/PS5Upload.exe` - Full v2.0 client
|
||||
|
||||
---
|
||||
|
||||
## ⬆️ Upgrading from v1.3.0
|
||||
|
||||
1. **Replace server payload:**
|
||||
- Copy new `ps5_upload_server.elf` to `/data/etaHEN/payloads/`
|
||||
- Reload with elfldr
|
||||
|
||||
2. **Replace client:**
|
||||
- Close old PS5Upload.exe
|
||||
- Replace with new version
|
||||
- Your settings will be preserved
|
||||
|
||||
**Note:** v2.0 creates two new files:
|
||||
- `ps5_profiles.json` - Saved PS5 profiles
|
||||
- `ps5_favorites.json` - Saved favorite paths
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Known Issues
|
||||
|
||||
None reported yet! This is a stable release built on top of the rock-solid v1.3.0 foundation.
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Future Features (Not in v2.0)
|
||||
|
||||
Still on the roadmap:
|
||||
- Disk Space Warning before upload
|
||||
- Duplicate File Detection
|
||||
- File Verification (SHA256)
|
||||
- Resume Interrupted Uploads
|
||||
- Queue Management (reorder, pause)
|
||||
- Transfer History
|
||||
- Scheduled Uploads
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Thanks
|
||||
|
||||
Special thanks to the PS5 homebrew community for testing and feedback!
|
||||
|
||||
---
|
||||
|
||||
**Enjoy the new features!** 🚀
|
||||
|
||||
**- Manos**
|
||||
@@ -1,8 +1,61 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace PS5Upload
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
private static readonly string LogFilePath = Path.Combine(
|
||||
AppDomain.CurrentDomain.BaseDirectory,
|
||||
$"ps5upload_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
||||
|
||||
private static readonly object _logLock = new object();
|
||||
|
||||
public App()
|
||||
{
|
||||
// Global exception handlers
|
||||
this.DispatcherUnhandledException += App_DispatcherUnhandledException;
|
||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
||||
|
||||
LogToFile("=== Application Started ===");
|
||||
}
|
||||
|
||||
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
LogToFile($"DISPATCHER EXCEPTION: {e.Exception}");
|
||||
e.Handled = true; // Prevent crash, but log it
|
||||
|
||||
MessageBox.Show($"Error: {e.Exception.Message}\n\nCheck log file: {LogFilePath}",
|
||||
"PS5 Upload Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
|
||||
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
var ex = e.ExceptionObject as Exception;
|
||||
LogToFile($"UNHANDLED EXCEPTION: {ex}");
|
||||
}
|
||||
|
||||
private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
LogToFile($"TASK EXCEPTION: {e.Exception}");
|
||||
e.SetObserved(); // Prevent crash
|
||||
}
|
||||
|
||||
public static void LogToFile(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_logLock)
|
||||
{
|
||||
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
|
||||
File.AppendAllText(LogFilePath, $"[{timestamp}] {message}\n");
|
||||
}
|
||||
}
|
||||
catch { /* Ignore logging errors */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
+335
-55
@@ -7,6 +7,12 @@
|
||||
Icon="ps5_logo.png">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- Override system colors for ComboBox dropdown -->
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.WindowBrushKey}" Color="#2D2D30"/>
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.WindowColorKey}" Color="#2D2D30"/>
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="#2D2D30"/>
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.ControlColorKey}" Color="#2D2D30"/>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#007ACC"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
@@ -94,43 +100,128 @@
|
||||
Padding="15"
|
||||
Margin="0,0,0,15">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="PS5 IP Address:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox Grid.Column="1"
|
||||
x:Name="IpAddressTextBox"
|
||||
Text="192.168.0.160"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
x:Name="ConnectButton"
|
||||
Content="Connect"
|
||||
Click="ConnectButton_Click"
|
||||
Width="100"
|
||||
Margin="0,0,10,0"/>
|
||||
|
||||
<StackPanel Grid.Column="3"
|
||||
Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
<Ellipse x:Name="StatusIndicator"
|
||||
Width="12"
|
||||
Height="12"
|
||||
Fill="#DC3545"
|
||||
Margin="0,0,8,0"/>
|
||||
<TextBlock x:Name="StatusText"
|
||||
Text="Disconnected"
|
||||
Foreground="#999999"/>
|
||||
<!-- PS5 Profile Selection -->
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
|
||||
<TextBlock Text="PS5 Profile:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
<ComboBox x:Name="PS5ProfileComboBox"
|
||||
Width="200"
|
||||
VerticalAlignment="Center"
|
||||
SelectionChanged="PS5ProfileComboBox_SelectionChanged"
|
||||
Background="#2D2D30"
|
||||
Foreground="White"
|
||||
BorderBrush="#3E3E42">
|
||||
<ComboBox.Template>
|
||||
<ControlTemplate TargetType="ComboBox">
|
||||
<Grid>
|
||||
<ToggleButton Name="ToggleButton"
|
||||
Grid.Column="2"
|
||||
Focusable="false"
|
||||
IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ClickMode="Press"
|
||||
Background="#2D2D30"
|
||||
BorderBrush="#3E3E42"
|
||||
BorderThickness="1"/>
|
||||
<ContentPresenter Name="ContentSite"
|
||||
IsHitTestVisible="False"
|
||||
Content="{TemplateBinding SelectionBoxItem}"
|
||||
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
|
||||
Margin="5,3,23,3"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left"/>
|
||||
<Popup Name="Popup"
|
||||
Placement="Bottom"
|
||||
IsOpen="{TemplateBinding IsDropDownOpen}"
|
||||
AllowsTransparency="True"
|
||||
Focusable="False"
|
||||
PopupAnimation="Slide">
|
||||
<Grid Name="DropDown"
|
||||
SnapsToDevicePixels="True"
|
||||
MinWidth="{TemplateBinding ActualWidth}"
|
||||
MaxHeight="{TemplateBinding MaxDropDownHeight}">
|
||||
<Border Name="DropDownBorder"
|
||||
Background="#2D2D30"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#3E3E42"/>
|
||||
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
|
||||
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</ComboBox.Template>
|
||||
<ComboBox.ItemContainerStyle>
|
||||
<Style TargetType="ComboBoxItem">
|
||||
<Setter Property="Background" Value="#2D2D30"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Padding" Value="5"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBoxItem">
|
||||
<Border Name="Border"
|
||||
Background="#2D2D30"
|
||||
Padding="5">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#007ACC"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#007ACC"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ComboBox.ItemContainerStyle>
|
||||
</ComboBox>
|
||||
<Button Content="💾 Save Profile"
|
||||
Click="SaveProfileButton_Click"
|
||||
Width="140"
|
||||
Margin="10,0,0,0"
|
||||
Background="#28A745"/>
|
||||
<Button Content="🗑️ Delete Profile"
|
||||
Click="DeleteProfileButton_Click"
|
||||
Width="150"
|
||||
Margin="5,0,0,0"
|
||||
Background="#DC3545"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- IP Address and Connect -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="PS5 IP Address:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
|
||||
<TextBox Grid.Column="1"
|
||||
x:Name="IpAddressTextBox"
|
||||
Text="192.168.0.160"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
|
||||
<Button Grid.Column="2"
|
||||
x:Name="ConnectButton"
|
||||
Content="Connect"
|
||||
Click="ConnectButton_Click"
|
||||
Width="100"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -234,33 +325,145 @@
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
Orientation="Horizontal"
|
||||
Margin="10"
|
||||
Background="#2D2D30">
|
||||
<TextBlock Text="Current Path:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
<TextBox x:Name="CurrentPathTextBox"
|
||||
Text="/data"
|
||||
Width="200"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button Content="Go"
|
||||
Click="GoButton_Click"
|
||||
Width="60"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
|
||||
<TextBlock Text="Current Path:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
<TextBox x:Name="CurrentPathTextBox"
|
||||
Text="/data"
|
||||
Width="200"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
<Button Content="Go"
|
||||
Click="GoButton_Click"
|
||||
Width="60"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,5">
|
||||
<TextBlock Text="⭐ Favorites:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
<ComboBox x:Name="FavoritesComboBox"
|
||||
Width="200"
|
||||
VerticalAlignment="Center"
|
||||
SelectionChanged="FavoritesComboBox_SelectionChanged"
|
||||
Background="#2D2D30"
|
||||
Foreground="White"
|
||||
BorderBrush="#3E3E42">
|
||||
<ComboBox.Template>
|
||||
<ControlTemplate TargetType="ComboBox">
|
||||
<Grid>
|
||||
<ToggleButton Name="ToggleButton"
|
||||
Grid.Column="2"
|
||||
Focusable="false"
|
||||
IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ClickMode="Press"
|
||||
Background="#2D2D30"
|
||||
BorderBrush="#3E3E42"
|
||||
BorderThickness="1"/>
|
||||
<ContentPresenter Name="ContentSite"
|
||||
IsHitTestVisible="False"
|
||||
Content="{TemplateBinding SelectionBoxItem}"
|
||||
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
|
||||
Margin="5,3,23,3"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Left"/>
|
||||
<Popup Name="Popup"
|
||||
Placement="Bottom"
|
||||
IsOpen="{TemplateBinding IsDropDownOpen}"
|
||||
AllowsTransparency="True"
|
||||
Focusable="False"
|
||||
PopupAnimation="Slide">
|
||||
<Grid Name="DropDown"
|
||||
SnapsToDevicePixels="True"
|
||||
MinWidth="{TemplateBinding ActualWidth}"
|
||||
MaxHeight="{TemplateBinding MaxDropDownHeight}">
|
||||
<Border Name="DropDownBorder"
|
||||
Background="#2D2D30"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#3E3E42"/>
|
||||
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
|
||||
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Popup>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</ComboBox.Template>
|
||||
<ComboBox.ItemContainerStyle>
|
||||
<Style TargetType="ComboBoxItem">
|
||||
<Setter Property="Background" Value="#2D2D30"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Padding" Value="5"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBoxItem">
|
||||
<Border Name="Border"
|
||||
Background="#2D2D30"
|
||||
Padding="5">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#007ACC"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Border" Property="Background" Value="#007ACC"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ComboBox.ItemContainerStyle>
|
||||
</ComboBox>
|
||||
<Button Content="⭐ Add"
|
||||
Click="AddFavoriteButton_Click"
|
||||
Width="90"
|
||||
Margin="10,0,0,0"
|
||||
Background="#28A745"/>
|
||||
<Button Content="🗑️ Remove"
|
||||
Click="RemoveFavoriteButton_Click"
|
||||
Width="110"
|
||||
Margin="5,0,0,0"
|
||||
Background="#DC3545"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="🔍 Search:"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,10,0"/>
|
||||
<TextBox x:Name="SearchTextBox"
|
||||
Width="250"
|
||||
VerticalAlignment="Center"
|
||||
TextChanged="SearchTextBox_TextChanged"
|
||||
Background="#1E1E1E"
|
||||
Foreground="White"
|
||||
BorderBrush="#3E3E42"/>
|
||||
<Button Content="✖ Clear"
|
||||
Click="ClearSearchButton_Click"
|
||||
Width="100"
|
||||
Margin="5,0,0,0"
|
||||
Background="#3E3E42"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<ListBox Grid.Row="2"
|
||||
x:Name="PS5FilesListBox"
|
||||
BorderThickness="0"
|
||||
SelectionMode="Extended"
|
||||
MouseDoubleClick="PS5FilesListBox_MouseDoubleClick">
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="⬇️ Download to PC" Click="DownloadMenuItem_Click"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="📝 Rename" Click="RenameMenuItem_Click"/>
|
||||
<MenuItem Header="📋 Copy" Click="CopyMenuItem_Click"/>
|
||||
<MenuItem Header="✂️ Move" Click="MoveMenuItem_Click"/>
|
||||
<Separator/>
|
||||
<MenuItem Header="🗑️ Delete" Click="DeleteMenuItem_Click"/>
|
||||
<MenuItem Header="🗑️ Delete Selected" Click="DeleteSelectedMenuItem_Click"/>
|
||||
</ContextMenu>
|
||||
</ListBox.ContextMenu>
|
||||
<ListBox.ItemTemplate>
|
||||
@@ -281,14 +484,88 @@
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<StackPanel Grid.Row="3"
|
||||
Margin="10">
|
||||
<TextBlock x:Name="StorageInfoText"
|
||||
Text="Storage: --"
|
||||
FontSize="12"
|
||||
Foreground="#999999"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="3"
|
||||
Background="#2D2D30"
|
||||
BorderBrush="#3E3E42"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Margin="10"
|
||||
Padding="10">
|
||||
<StackPanel>
|
||||
<TextBlock Text="💾 PS5 Storage"
|
||||
FontSize="12"
|
||||
FontWeight="Bold"
|
||||
Foreground="#007ACC"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,0,0,8"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
Text="Free:"
|
||||
FontSize="11"
|
||||
Foreground="#999999"
|
||||
Margin="0,0,8,4"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1"
|
||||
x:Name="StorageFreeText"
|
||||
Text="--"
|
||||
FontSize="11"
|
||||
Foreground="#4EC9B0"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,0,0,4"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||
Text="Used:"
|
||||
FontSize="11"
|
||||
Foreground="#999999"
|
||||
Margin="0,0,8,4"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1"
|
||||
x:Name="StorageUsedText"
|
||||
Text="--"
|
||||
FontSize="11"
|
||||
Foreground="#CE9178"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,0,0,4"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
Text="Total:"
|
||||
FontSize="11"
|
||||
Foreground="#999999"
|
||||
Margin="0,0,8,4"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="1"
|
||||
x:Name="StorageTotalText"
|
||||
Text="--"
|
||||
FontSize="11"
|
||||
Foreground="White"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0,0,0,4"/>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<ProgressBar Grid.Row="3" Grid.ColumnSpan="2"
|
||||
x:Name="StorageProgressBar"
|
||||
Height="6"
|
||||
Margin="0,6,0,0"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
Value="0"
|
||||
Foreground="#CE9178"
|
||||
Background="#3E3E42"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -326,6 +603,9 @@
|
||||
Padding="10"
|
||||
TextWrapping="Wrap"
|
||||
IsReadOnly="True"
|
||||
IsReadOnlyCaretVisible="False"
|
||||
Focusable="False"
|
||||
Cursor="Arrow"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto"/>
|
||||
|
||||
|
||||
+867
-207
File diff suppressed because it is too large.
Load diff
+116
-12
@@ -22,6 +22,7 @@ namespace PS5Upload
|
||||
StartUpload = 0x10,
|
||||
UploadChunk = 0x11,
|
||||
EndUpload = 0x12,
|
||||
DownloadFile = 0x13,
|
||||
Shutdown = 0xFF
|
||||
}
|
||||
|
||||
@@ -39,7 +40,7 @@ namespace PS5Upload
|
||||
{
|
||||
private TcpClient? _client;
|
||||
private NetworkStream? _stream;
|
||||
private const int BufferSize = 32 * 1024 * 1024; // 32MB for maximum throughput
|
||||
private const int BufferSize = 4 * 1024 * 1024; // 4MB chunks - MUST match payload BUFFER_SIZE
|
||||
|
||||
public bool IsConnected => _client?.Connected ?? false;
|
||||
|
||||
@@ -130,7 +131,7 @@ namespace PS5Upload
|
||||
return (response, data);
|
||||
}
|
||||
|
||||
private async Task ReadExactAsync(byte[] buffer, int count, int timeoutMs = 30000)
|
||||
private async Task ReadExactAsync(byte[] buffer, int count, int timeoutMs = 120000)
|
||||
{
|
||||
if (_stream == null) throw new InvalidOperationException("Not connected");
|
||||
|
||||
@@ -142,7 +143,13 @@ namespace PS5Upload
|
||||
while (offset < count)
|
||||
{
|
||||
int read = await _stream.ReadAsync(buffer, offset, count - offset, cts.Token);
|
||||
if (read == 0) throw new IOException("Connection closed");
|
||||
if (read == 0)
|
||||
{
|
||||
// Give PS5 a moment to recover before declaring connection dead
|
||||
await Task.Delay(100);
|
||||
read = await _stream.ReadAsync(buffer, offset, count - offset, cts.Token);
|
||||
if (read == 0) throw new IOException("Connection closed");
|
||||
}
|
||||
offset += read;
|
||||
}
|
||||
}
|
||||
@@ -257,7 +264,7 @@ namespace PS5Upload
|
||||
return response == Response.Ok;
|
||||
}
|
||||
|
||||
public event Action<string> OnProgressMessage;
|
||||
public event Action<string>? OnProgressMessage;
|
||||
|
||||
public async Task<bool> DeleteDirAsync(string path)
|
||||
{
|
||||
@@ -313,7 +320,8 @@ namespace PS5Upload
|
||||
}
|
||||
|
||||
// Give server a moment to fully close the deletion thread
|
||||
await Task.Delay(100);
|
||||
// CRITICAL: Increased delay to prevent race condition with next delete
|
||||
await Task.Delay(500);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -405,7 +413,20 @@ namespace PS5Upload
|
||||
|
||||
if (_stream == null) return false;
|
||||
|
||||
await _stream.WriteAsync(sendBuffer, 0, 5 + bytesRead, cancellationToken);
|
||||
// FIX: Add timeout to WriteAsync to prevent hanging if PS5 becomes unresponsive
|
||||
// 30 second timeout per chunk write - should be more than enough for 4MB
|
||||
using var writeTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
||||
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, writeTimeout.Token);
|
||||
|
||||
try
|
||||
{
|
||||
await _stream.WriteAsync(sendBuffer, 0, 5 + bytesRead, linkedCts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) when (writeTimeout.IsCancellationRequested)
|
||||
{
|
||||
// Write timeout - PS5 is unresponsive
|
||||
return false;
|
||||
}
|
||||
|
||||
totalSent += bytesRead;
|
||||
bytesRemaining -= bytesRead;
|
||||
@@ -435,18 +456,101 @@ namespace PS5Upload
|
||||
}
|
||||
}
|
||||
|
||||
// Send END_UPLOAD - fire-and-forget for maximum speed
|
||||
// Don't wait for response to avoid PS5 payload bottleneck
|
||||
// Send END_UPLOAD and wait for response
|
||||
// CRITICAL: Must wait for response to avoid protocol desync on chunked uploads
|
||||
try
|
||||
{
|
||||
await SendCommandAsync(Command.EndUpload);
|
||||
// Immediately return success without waiting for response
|
||||
// This eliminates the PS5 response handling bottleneck
|
||||
return true;
|
||||
var (endResponse, _) = await ReceiveResponseAsync();
|
||||
return endResponse == Response.Ok;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true; // Assume success if connection issues
|
||||
return false; // Connection issue means upload failed
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DownloadFileAsync(string remotePath, string localPath, IProgress<UploadProgress>? progress = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send download command
|
||||
byte[] pathBytes = Encoding.UTF8.GetBytes(remotePath);
|
||||
await SendCommandAsync(Command.DownloadFile, pathBytes);
|
||||
|
||||
// Receive response header (5 bytes: 1 response + 4 data_len)
|
||||
byte[] header = new byte[5];
|
||||
await ReadExactAsync(header, 5);
|
||||
|
||||
Response response = (Response)header[0];
|
||||
uint dataLen = BitConverter.ToUInt32(header, 1);
|
||||
|
||||
// Check if error response
|
||||
if (response == Response.Error)
|
||||
{
|
||||
if (dataLen > 0)
|
||||
{
|
||||
byte[] errorMsg = new byte[dataLen];
|
||||
await ReadExactAsync(errorMsg, (int)dataLen);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Expecting RESP_DATA with 8-byte file size
|
||||
if (response != Response.Data || dataLen != 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read file size (8 bytes)
|
||||
byte[] sizeBytes = new byte[8];
|
||||
await ReadExactAsync(sizeBytes, 8);
|
||||
long fileSize = BitConverter.ToInt64(sizeBytes, 0);
|
||||
|
||||
// Now read raw file data directly from socket
|
||||
using var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.None, BufferSize);
|
||||
|
||||
byte[] buffer = new byte[BufferSize];
|
||||
long totalReceived = 0;
|
||||
var startTime = DateTime.Now;
|
||||
|
||||
while (totalReceived < fileSize)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return false;
|
||||
|
||||
int toRead = (int)Math.Min(BufferSize, fileSize - totalReceived);
|
||||
int received = await _stream!.ReadAsync(buffer, 0, toRead, cancellationToken);
|
||||
|
||||
if (received == 0) break;
|
||||
|
||||
await fs.WriteAsync(buffer, 0, received, cancellationToken);
|
||||
totalReceived += received;
|
||||
|
||||
// Report progress every 5MB or at completion
|
||||
if (totalReceived % (5 * 1024 * 1024) < BufferSize || totalReceived == fileSize)
|
||||
{
|
||||
var elapsed = DateTime.Now - startTime;
|
||||
double speed = elapsed.TotalSeconds > 0 ? totalReceived / elapsed.TotalSeconds : 0;
|
||||
|
||||
progress?.Report(new UploadProgress
|
||||
{
|
||||
BytesSent = totalReceived,
|
||||
TotalBytes = fileSize,
|
||||
SpeedBytesPerSecond = speed,
|
||||
AverageSpeedBytesPerSecond = speed,
|
||||
ElapsedTime = elapsed,
|
||||
EstimatedTimeRemaining = speed > 0 ? TimeSpan.FromSeconds((fileSize - totalReceived) / speed) : TimeSpan.Zero,
|
||||
CurrentFileName = Path.GetFileName(localPath)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return totalReceived == fileSize;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Download error: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
# PS5 Upload Suite - GitHub Release Package Creator
|
||||
# Creates a clean, ready-to-upload ZIP file for GitHub releases
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "PS5 Upload Suite - Release Packager" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Configuration
|
||||
$sourceFolder = ".\client\bin\Release\net6.0-windows\win-x64"
|
||||
$payloadFile = ".\payload\ps5_upload_server.elf"
|
||||
$outputZip = ".\PS5Upload-Windows-x64-v1.0.zip"
|
||||
$tempFolder = ".\github_release_temp"
|
||||
|
||||
# Check if source folder exists
|
||||
if (-not (Test-Path $sourceFolder)) {
|
||||
Write-Host "[ERROR] Source folder not found: $sourceFolder" -ForegroundColor Red
|
||||
Write-Host "Please build the project first!" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "[1/5] Checking source files..." -ForegroundColor Yellow
|
||||
|
||||
# Count files in source
|
||||
$fileCount = (Get-ChildItem -Path $sourceFolder -File).Count
|
||||
Write-Host " Found $fileCount files in source folder" -ForegroundColor Green
|
||||
|
||||
# Check for main executable
|
||||
$mainExe = Join-Path $sourceFolder "PS5Upload.exe"
|
||||
if (-not (Test-Path $mainExe)) {
|
||||
Write-Host "[ERROR] PS5Upload.exe not found!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$exeSize = (Get-Item $mainExe).Length / 1MB
|
||||
Write-Host " PS5Upload.exe: $([math]::Round($exeSize, 2)) MB" -ForegroundColor Green
|
||||
|
||||
# Check for payload
|
||||
if (Test-Path $payloadFile) {
|
||||
$payloadSize = (Get-Item $payloadFile).Length / 1KB
|
||||
Write-Host " ps5_upload_server.elf: $([math]::Round($payloadSize, 2)) KB" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [WARN] ps5_upload_server.elf not found - will skip" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[2/5] Creating temporary folder..." -ForegroundColor Yellow
|
||||
|
||||
# Clean up old temp folder
|
||||
if (Test-Path $tempFolder) {
|
||||
Remove-Item -Path $tempFolder -Recurse -Force
|
||||
}
|
||||
|
||||
# Create temp folder
|
||||
New-Item -ItemType Directory -Path $tempFolder -Force | Out-Null
|
||||
Write-Host " Created: $tempFolder" -ForegroundColor Green
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[3/5] Copying files..." -ForegroundColor Yellow
|
||||
|
||||
# Copy all files from source
|
||||
Copy-Item -Path "$sourceFolder\*" -Destination $tempFolder -Recurse -Force
|
||||
Write-Host " Copied $fileCount files from win-x64 build" -ForegroundColor Green
|
||||
|
||||
# Copy payload if exists
|
||||
if (Test-Path $payloadFile) {
|
||||
Copy-Item -Path $payloadFile -Destination $tempFolder -Force
|
||||
Write-Host " Copied ps5_upload_server.elf" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Create README for the release
|
||||
$readmeContent = @"
|
||||
# PS5 Upload Suite v1.0 - Windows x64
|
||||
|
||||
## What's This?
|
||||
Self-contained Windows application for uploading files to PS5 console via network.
|
||||
|
||||
## Requirements
|
||||
- Windows 10/11 (64-bit)
|
||||
- PS5 with etaHEN 2.0+ or GoldHEN
|
||||
- Network connection between PC and PS5
|
||||
|
||||
## Installation
|
||||
1. Extract all files to a folder
|
||||
2. Run PS5Upload.exe
|
||||
|
||||
## First Time Setup
|
||||
1. Load ps5_upload_server.elf to your PS5 (via etaHEN payload injector)
|
||||
2. Note the IP address shown on PS5 screen
|
||||
3. Open PS5Upload.exe on Windows
|
||||
4. Enter PS5 IP address
|
||||
5. Click Connect
|
||||
|
||||
## Usage
|
||||
1. Click "Browse" to select files
|
||||
2. Click "Upload" to transfer to PS5
|
||||
3. Files will be saved to /data/ on PS5
|
||||
|
||||
## Troubleshooting
|
||||
- **Connection failed:** Make sure PS5 server is running and IP is correct
|
||||
- **Upload failed:** Check PS5 has enough free space
|
||||
- **App won't start:** Make sure all files are extracted
|
||||
|
||||
## Support
|
||||
GitHub: https://github.com/YOUR_USERNAME/ps5_upload_suite
|
||||
Issues: https://github.com/YOUR_USERNAME/ps5_upload_suite/issues
|
||||
|
||||
## License
|
||||
MIT License - See repository for details
|
||||
"@
|
||||
|
||||
Set-Content -Path "$tempFolder\README.txt" -Value $readmeContent -Encoding UTF8
|
||||
Write-Host " Created README.txt" -ForegroundColor Green
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[4/5] Creating ZIP archive..." -ForegroundColor Yellow
|
||||
|
||||
# Remove old ZIP if exists
|
||||
if (Test-Path $outputZip) {
|
||||
Remove-Item -Path $outputZip -Force
|
||||
Write-Host " Removed old ZIP file" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Create ZIP
|
||||
Compress-Archive -Path "$tempFolder\*" -DestinationPath $outputZip -CompressionLevel Optimal -Force
|
||||
|
||||
if (Test-Path $outputZip) {
|
||||
$zipSize = (Get-Item $outputZip).Length / 1MB
|
||||
Write-Host " Created: $outputZip" -ForegroundColor Green
|
||||
Write-Host " Size: $([math]::Round($zipSize, 2)) MB" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[ERROR] Failed to create ZIP file!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[5/5] Cleaning up..." -ForegroundColor Yellow
|
||||
|
||||
# Remove temp folder
|
||||
Remove-Item -Path $tempFolder -Recurse -Force
|
||||
Write-Host " Removed temporary files" -ForegroundColor Green
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "SUCCESS!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Release package created: $outputZip" -ForegroundColor Green
|
||||
Write-Host "Size: $([math]::Round($zipSize, 2)) MB" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:" -ForegroundColor Yellow
|
||||
Write-Host "1. Test the ZIP file locally (extract and run PS5Upload.exe)" -ForegroundColor White
|
||||
Write-Host "2. Go to GitHub: https://github.com/YOUR_USERNAME/ps5_upload_suite/releases/new" -ForegroundColor White
|
||||
Write-Host "3. Create new release with tag 'v1.0'" -ForegroundColor White
|
||||
Write-Host "4. Upload: $outputZip" -ForegroundColor White
|
||||
Write-Host "5. Publish release" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to exit..."
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
@@ -1,33 +0,0 @@
|
||||
@echo off
|
||||
cd /d "%~dp0"
|
||||
"C:\Program Files\GitHub CLI\gh.exe" release create v1.2 PS5Upload-Windows-x64-v1.2-Complete.zip --title "PS5 Upload Suite v1.2 - Complete Bug Fix" --notes "# PS5 Upload Suite v1.2 - Complete Bug Fix Release
|
||||
|
||||
## What's Fixed:
|
||||
|
||||
### CLIENT FIXES:
|
||||
- Bug #1: Connection timeout reduced to 5 seconds (fast fail on wrong IP)
|
||||
- Bug #2: Folder deletion timeout increased to 60 seconds
|
||||
|
||||
### SERVER FIXES:
|
||||
- Bug #3: Async folder deletion - NO MORE TIMEOUT on large folders!
|
||||
- Server responds immediately
|
||||
- Deletion happens in background thread
|
||||
- PS5 notification when complete
|
||||
|
||||
## What's Included:
|
||||
- PS5Upload.exe - Windows client with bug fixes
|
||||
- ps5_upload_server.elf - PS5 server with async deletion
|
||||
- All dependencies included
|
||||
|
||||
## Installation:
|
||||
1. Extract ZIP file
|
||||
2. Run PS5Upload.exe on Windows
|
||||
3. Upload ps5_upload_server.elf to PS5 via FTP
|
||||
4. Load with etaHEN payload injector
|
||||
|
||||
## IMPORTANT:
|
||||
You MUST update BOTH files (client and server)
|
||||
|
||||
## Requirements:
|
||||
- Windows 10/11 (64-bit)
|
||||
- PS5 with etaHEN 2.0+ or GoldHEN"
|
||||
@@ -1,122 +0,0 @@
|
||||
# PS5 Upload Suite v1.1 - Bug Fix Release Package Creator
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "PS5 Upload Suite v1.1 - Bug Fix Release" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Configuration
|
||||
$sourceFolder = ".\client\bin\Release\net6.0-windows\win-x64"
|
||||
$payloadFile = ".\payload\ps5_upload_server.elf"
|
||||
$outputZip = ".\PS5Upload-Windows-x64-v1.1-BugFix.zip"
|
||||
$tempFolder = ".\github_release_v1.1_temp"
|
||||
|
||||
Write-Host "[INFO] Bug Fixes in v1.1:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Connection timeout: 5 seconds (fast fail on wrong IP)" -ForegroundColor Green
|
||||
Write-Host " 2. Folder deletion timeout: 60 seconds (handles large folders)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Check if source folder exists
|
||||
if (-not (Test-Path $sourceFolder)) {
|
||||
Write-Host "[ERROR] Source folder not found: $sourceFolder" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "[1/5] Checking source files..." -ForegroundColor Yellow
|
||||
$fileCount = (Get-ChildItem -Path $sourceFolder -File).Count
|
||||
Write-Host " Found $fileCount files" -ForegroundColor Green
|
||||
|
||||
# Clean up old temp folder
|
||||
if (Test-Path $tempFolder) {
|
||||
Remove-Item -Path $tempFolder -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[2/5] Creating temporary folder..." -ForegroundColor Yellow
|
||||
New-Item -ItemType Directory -Path $tempFolder -Force | Out-Null
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[3/5] Copying files..." -ForegroundColor Yellow
|
||||
Copy-Item -Path "$sourceFolder\*" -Destination $tempFolder -Recurse -Force
|
||||
Write-Host " Copied $fileCount files" -ForegroundColor Green
|
||||
|
||||
if (Test-Path $payloadFile) {
|
||||
Copy-Item -Path $payloadFile -Destination $tempFolder -Force
|
||||
Write-Host " Copied ps5_upload_server.elf" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Create CHANGELOG
|
||||
$changelogContent = @"
|
||||
# PS5 Upload Suite v1.1 - Bug Fix Release
|
||||
|
||||
## What's Fixed:
|
||||
|
||||
### Bug #1: Connection Timeout
|
||||
- **Problem:** Application took 30+ seconds to detect wrong IP address
|
||||
- **Fix:** Added 5-second timeout for connection attempts
|
||||
- **Result:** Fast failure on incorrect IP - immediate feedback
|
||||
|
||||
### Bug #2: Folder Deletion Timeout
|
||||
- **Problem:** Deleting large game folders caused timeout errors
|
||||
- **Fix:** Increased timeout to 60 seconds for folder operations
|
||||
- **Result:** Successfully handles large folders (10GB+ games)
|
||||
|
||||
## Installation:
|
||||
Same as v1.0 - extract and run PS5Upload.exe
|
||||
|
||||
## Upgrading from v1.0:
|
||||
Simply replace your old PS5Upload.exe with the new one.
|
||||
No other changes needed.
|
||||
|
||||
## Requirements:
|
||||
- Windows 10/11 (64-bit)
|
||||
- PS5 with etaHEN 2.0+ or GoldHEN
|
||||
- Network connection
|
||||
|
||||
## Support:
|
||||
GitHub: https://github.com/YOUR_USERNAME/ps5_upload_suite
|
||||
"@
|
||||
|
||||
Set-Content -Path "$tempFolder\CHANGELOG.txt" -Value $changelogContent -Encoding UTF8
|
||||
Write-Host " Created CHANGELOG.txt" -ForegroundColor Green
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[4/5] Creating ZIP archive..." -ForegroundColor Yellow
|
||||
|
||||
if (Test-Path $outputZip) {
|
||||
Remove-Item -Path $outputZip -Force
|
||||
}
|
||||
|
||||
Compress-Archive -Path "$tempFolder\*" -DestinationPath $outputZip -CompressionLevel Optimal -Force
|
||||
|
||||
if (Test-Path $outputZip) {
|
||||
$zipSize = (Get-Item $outputZip).Length / 1MB
|
||||
Write-Host " Created: $outputZip" -ForegroundColor Green
|
||||
Write-Host " Size: $([math]::Round($zipSize, 2)) MB" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[ERROR] Failed to create ZIP!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[5/5] Cleaning up..." -ForegroundColor Yellow
|
||||
Remove-Item -Path $tempFolder -Recurse -Force
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "SUCCESS!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Release package created: $outputZip" -ForegroundColor Green
|
||||
Write-Host "Size: $([math]::Round($zipSize, 2)) MB" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Bug Fixes:" -ForegroundColor Yellow
|
||||
Write-Host " [FIXED] Connection timeout: 5 seconds" -ForegroundColor Green
|
||||
Write-Host " [FIXED] Folder deletion timeout: 60 seconds" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:" -ForegroundColor Yellow
|
||||
Write-Host "1. Test locally: Extract and run PS5Upload.exe" -ForegroundColor White
|
||||
Write-Host "2. Upload to GitHub as v1.1 release" -ForegroundColor White
|
||||
Write-Host "3. Tag: v1.1" -ForegroundColor White
|
||||
Write-Host ""
|
||||
@@ -1,199 +0,0 @@
|
||||
# PS5 Upload Suite v1.2 - Complete Bug Fix Release
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "PS5 Upload Suite v1.2 - Complete Fix" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Configuration
|
||||
$sourceFolder = ".\client\bin\Release\net6.0-windows\win-x64"
|
||||
$payloadFile = ".\payload\ps5_upload_server.elf"
|
||||
$outputZip = ".\PS5Upload-Windows-x64-v1.2-Complete.zip"
|
||||
$tempFolder = ".\github_release_v1.2_temp"
|
||||
|
||||
Write-Host "[INFO] Bug Fixes in v1.2:" -ForegroundColor Yellow
|
||||
Write-Host " CLIENT:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Connection timeout: 5 seconds (fast fail on wrong IP)" -ForegroundColor Green
|
||||
Write-Host " 2. Folder deletion timeout: 60 seconds client-side" -ForegroundColor Green
|
||||
Write-Host " SERVER:" -ForegroundColor Cyan
|
||||
Write-Host " 3. Async folder deletion (no more timeout on large folders!)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Check files
|
||||
if (-not (Test-Path $sourceFolder)) {
|
||||
Write-Host "[ERROR] Client folder not found!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $payloadFile)) {
|
||||
Write-Host "[ERROR] Server payload not found!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "[1/5] Checking files..." -ForegroundColor Yellow
|
||||
$fileCount = (Get-ChildItem -Path $sourceFolder -File).Count
|
||||
$payloadSize = (Get-Item $payloadFile).Length / 1KB
|
||||
Write-Host " Client files: $fileCount" -ForegroundColor Green
|
||||
Write-Host " Server payload: $([math]::Round($payloadSize, 2)) KB" -ForegroundColor Green
|
||||
|
||||
# Clean up
|
||||
if (Test-Path $tempFolder) {
|
||||
Remove-Item -Path $tempFolder -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[2/5] Creating package..." -ForegroundColor Yellow
|
||||
New-Item -ItemType Directory -Path $tempFolder -Force | Out-Null
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[3/5] Copying files..." -ForegroundColor Yellow
|
||||
Copy-Item -Path "$sourceFolder\*" -Destination $tempFolder -Recurse -Force
|
||||
Write-Host " Copied client files" -ForegroundColor Green
|
||||
|
||||
Copy-Item -Path $payloadFile -Destination $tempFolder -Force
|
||||
Write-Host " Copied server payload" -ForegroundColor Green
|
||||
|
||||
# Create detailed changelog
|
||||
$changelogContent = @"
|
||||
# PS5 Upload Suite v1.2 - Complete Bug Fix Release
|
||||
|
||||
## What's Fixed:
|
||||
|
||||
### CLIENT FIXES:
|
||||
|
||||
#### Bug #1: Connection Timeout
|
||||
- **Problem:** Application took 30+ seconds to detect wrong IP address
|
||||
- **Fix:** Added 5-second connection timeout
|
||||
- **Result:** Immediate feedback on connection failure
|
||||
|
||||
#### Bug #2: Client-Side Timeout
|
||||
- **Problem:** Client timeout on folder operations
|
||||
- **Fix:** Increased client timeout to 60 seconds
|
||||
- **Result:** Better handling of server operations
|
||||
|
||||
### SERVER FIXES:
|
||||
|
||||
#### Bug #3: Folder Deletion Timeout (MAJOR FIX!)
|
||||
- **Problem:** Deleting large game folders (10GB+) caused "Read timeout" error
|
||||
- **Fix:** Folder deletion now runs asynchronously in background thread
|
||||
- **How it works:**
|
||||
1. Server responds immediately: "Deleting folder in background..."
|
||||
2. Deletion happens in separate thread
|
||||
3. PS5 notification appears when complete
|
||||
- **Result:** NO MORE TIMEOUT! Works with any folder size!
|
||||
|
||||
## Installation:
|
||||
1. Extract ZIP file
|
||||
2. Run PS5Upload.exe on Windows
|
||||
3. Upload ps5_upload_server.elf to PS5 (via FTP or USB)
|
||||
4. Load ps5_upload_server.elf with etaHEN payload injector
|
||||
5. Connect and enjoy!
|
||||
|
||||
## Upgrading from v1.0/v1.1:
|
||||
Replace both:
|
||||
- PS5Upload.exe (Windows client)
|
||||
- ps5_upload_server.elf (PS5 server)
|
||||
|
||||
IMPORTANT: You MUST update BOTH files for the fixes to work!
|
||||
|
||||
## Testing the Fixes:
|
||||
1. **Connection timeout:** Try wrong IP - should fail in 5 seconds
|
||||
2. **Folder deletion:** Delete large game folder - should work without timeout
|
||||
|
||||
## Requirements:
|
||||
- Windows 10/11 (64-bit)
|
||||
- PS5 with etaHEN 2.0+ or GoldHEN
|
||||
- Network connection
|
||||
|
||||
## Support:
|
||||
GitHub: https://github.com/YOUR_USERNAME/ps5_upload_suite
|
||||
Issues: Report bugs on GitHub Issues page
|
||||
|
||||
---
|
||||
**Version:** 1.2
|
||||
**Release Date:** January 12, 2026
|
||||
**Build:** Complete Bug Fix Release
|
||||
"@
|
||||
|
||||
Set-Content -Path "$tempFolder\CHANGELOG_v1.2.txt" -Value $changelogContent -Encoding UTF8
|
||||
Write-Host " Created CHANGELOG_v1.2.txt" -ForegroundColor Green
|
||||
|
||||
# Create quick start guide
|
||||
$quickStartContent = @"
|
||||
# PS5 Upload Suite v1.2 - Quick Start
|
||||
|
||||
## Step 1: Load Server on PS5
|
||||
1. Copy ps5_upload_server.elf to USB drive
|
||||
2. Insert USB into PS5
|
||||
3. Open etaHEN payload injector
|
||||
4. Load ps5_upload_server.elf
|
||||
5. Note the IP address shown on screen
|
||||
|
||||
## Step 2: Connect from Windows
|
||||
1. Run PS5Upload.exe
|
||||
2. Enter PS5 IP address
|
||||
3. Click Connect
|
||||
4. You're ready!
|
||||
|
||||
## Step 3: Upload Files
|
||||
1. Click "Browse Folder" to select files
|
||||
2. Click "Upload" to transfer
|
||||
3. Files appear in /data/etaHEN/games on PS5
|
||||
|
||||
## Deleting Large Folders:
|
||||
- Now works perfectly with async deletion!
|
||||
- Server responds immediately
|
||||
- Deletion happens in background
|
||||
- PS5 notification when complete
|
||||
|
||||
Enjoy! 🎮
|
||||
"@
|
||||
|
||||
Set-Content -Path "$tempFolder\QUICK_START.txt" -Value $quickStartContent -Encoding UTF8
|
||||
Write-Host " Created QUICK_START.txt" -ForegroundColor Green
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[4/5] Creating ZIP..." -ForegroundColor Yellow
|
||||
|
||||
if (Test-Path $outputZip) {
|
||||
Remove-Item -Path $outputZip -Force
|
||||
}
|
||||
|
||||
Compress-Archive -Path "$tempFolder\*" -DestinationPath $outputZip -CompressionLevel Optimal -Force
|
||||
|
||||
if (Test-Path $outputZip) {
|
||||
$zipSize = (Get-Item $outputZip).Length / 1MB
|
||||
Write-Host " Created: $outputZip" -ForegroundColor Green
|
||||
Write-Host " Size: $([math]::Round($zipSize, 2)) MB" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[ERROR] Failed to create ZIP!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "[5/5] Cleaning up..." -ForegroundColor Yellow
|
||||
Remove-Item -Path $tempFolder -Recurse -Force
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "SUCCESS!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Release: $outputZip" -ForegroundColor Green
|
||||
Write-Host "Size: $([math]::Round($zipSize, 2)) MB" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Bug Fixes Applied:" -ForegroundColor Yellow
|
||||
Write-Host " [CLIENT] Connection timeout: 5 seconds" -ForegroundColor Green
|
||||
Write-Host " [CLIENT] Folder deletion timeout: 60 seconds" -ForegroundColor Green
|
||||
Write-Host " [SERVER] Async folder deletion (NO MORE TIMEOUT!)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "IMPORTANT: Update BOTH client and server!" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:" -ForegroundColor Cyan
|
||||
Write-Host "1. Test locally" -ForegroundColor White
|
||||
Write-Host "2. Upload ps5_upload_server.elf to PS5" -ForegroundColor White
|
||||
Write-Host "3. Load with etaHEN" -ForegroundColor White
|
||||
Write-Host "4. Test folder deletion with large folder" -ForegroundColor White
|
||||
Write-Host "5. Upload to GitHub as v1.2" -ForegroundColor White
|
||||
Write-Host ""
|
||||
+1
-1
@@ -8,7 +8,7 @@ echo ""
|
||||
|
||||
echo "[+] Compiling..."
|
||||
rm -f ps5_upload_server.elf
|
||||
/home/hackman/ps5sdk_copy/bin/prospero-clang -Wall -O3 -pthread -o ps5_upload_server.elf main.c
|
||||
/opt/ps5-payload-sdk/bin/prospero-clang -Wall -O3 -pthread -o ps5_upload_server.elf main.c
|
||||
|
||||
if [ -f "ps5_upload_server.elf" ]; then
|
||||
echo ""
|
||||
|
||||
+126
-31
@@ -22,7 +22,7 @@
|
||||
#include <ifaddrs.h>
|
||||
|
||||
#define SERVER_PORT 9113
|
||||
#define BUFFER_SIZE (8 * 1024 * 1024)
|
||||
#define BUFFER_SIZE (4 * 1024 * 1024) // 4MB - MUST match client BufferSize
|
||||
#define MAX_PATH 2048
|
||||
#define DISK_WORKER_COUNT 4
|
||||
#define QUEUE_MAX_SIZE 32
|
||||
@@ -107,6 +107,7 @@ void release_file_mutex(const char *path) {
|
||||
#define CMD_START_UPLOAD 0x10
|
||||
#define CMD_UPLOAD_CHUNK 0x11
|
||||
#define CMD_END_UPLOAD 0x12
|
||||
#define CMD_DOWNLOAD_FILE 0x13
|
||||
#define CMD_SHUTDOWN 0xFF
|
||||
|
||||
// Protocol responses
|
||||
@@ -508,22 +509,33 @@ void handle_list_dir(client_session_t *session, const char *path) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Determine type - use d_type if available, otherwise fall back to stat()
|
||||
// Determine type and get file size
|
||||
uint8_t type = 0;
|
||||
uint64_t size = 0;
|
||||
uint64_t timestamp = 0;
|
||||
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
|
||||
struct stat st;
|
||||
|
||||
if (entry->d_type == DT_DIR) {
|
||||
type = 1;
|
||||
} else if (entry->d_type == DT_UNKNOWN) {
|
||||
// d_type not supported on this filesystem - use stat() as fallback
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
|
||||
struct stat st;
|
||||
if (stat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
type = 1;
|
||||
if (stat(full_path, &st) == 0) {
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
type = 1;
|
||||
} else {
|
||||
size = st.st_size;
|
||||
timestamp = st.st_mtime;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular file - get size
|
||||
if (stat(full_path, &st) == 0) {
|
||||
size = st.st_size;
|
||||
timestamp = st.st_mtime;
|
||||
}
|
||||
}
|
||||
// else: DT_REG or other = file (type = 0)
|
||||
|
||||
uint64_t size = 0; // Don't need file size for browsing
|
||||
uint64_t timestamp = 0; // Don't need timestamp for browsing
|
||||
|
||||
*ptr++ = type;
|
||||
memcpy(ptr, &name_len, 2);
|
||||
@@ -821,29 +833,47 @@ void handle_start_upload(client_session_t *session, const uint8_t *data, uint32_
|
||||
pthread_mutex_lock(session->file_mutex);
|
||||
|
||||
// Open file for writing
|
||||
// For chunked uploads, we need to pre-allocate the file on first chunk
|
||||
// and open with r+b for subsequent chunks
|
||||
if (chunk_offset > 0) {
|
||||
// Chunk mode: open existing file or create if needed
|
||||
// Subsequent chunk: open existing file
|
||||
session->upload_fp = fopen(path, "r+b");
|
||||
if (!session->upload_fp) {
|
||||
// File doesn't exist, create it with full size
|
||||
session->upload_fp = fopen(path, "wb");
|
||||
if (session->upload_fp) {
|
||||
// Allocate full file size
|
||||
if (fseeko(session->upload_fp, file_size - 1, SEEK_SET) == 0) {
|
||||
fputc(0, session->upload_fp);
|
||||
fflush(session->upload_fp);
|
||||
}
|
||||
fclose(session->upload_fp);
|
||||
session->upload_fp = fopen(path, "r+b");
|
||||
}
|
||||
}
|
||||
if (session->upload_fp) {
|
||||
// Seek to chunk offset
|
||||
fseeko(session->upload_fp, chunk_offset, SEEK_SET);
|
||||
}
|
||||
} else {
|
||||
// Normal mode: create new file
|
||||
// First chunk or small file: create new file
|
||||
// Check if this is a chunked upload (file_size > chunk that will be sent)
|
||||
// We pre-allocate for large files to ensure r+b works for subsequent chunks
|
||||
session->upload_fp = fopen(path, "wb");
|
||||
if (session->upload_fp && file_size > 100 * 1024 * 1024) {
|
||||
// Large file - pre-allocate full size for chunked upload
|
||||
// FIX: Check if pre-allocation succeeds (disk might be full!)
|
||||
int prealloc_ok = 0;
|
||||
if (fseeko(session->upload_fp, file_size - 1, SEEK_SET) == 0) {
|
||||
if (fputc(0, session->upload_fp) != EOF) {
|
||||
if (fflush(session->upload_fp) == 0) {
|
||||
prealloc_ok = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!prealloc_ok) {
|
||||
// Pre-allocation failed - likely disk full
|
||||
fclose(session->upload_fp);
|
||||
session->upload_fp = NULL;
|
||||
pthread_mutex_unlock(session->file_mutex);
|
||||
release_file_mutex(path);
|
||||
session->file_mutex = NULL;
|
||||
unlink(path); // Remove partial file
|
||||
send_error(session->sock, "Disk full - cannot pre-allocate file");
|
||||
return;
|
||||
}
|
||||
|
||||
// Seek back to beginning
|
||||
fseeko(session->upload_fp, 0, SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(session->file_mutex);
|
||||
@@ -855,7 +885,7 @@ void handle_start_upload(client_session_t *session, const uint8_t *data, uint32_
|
||||
return;
|
||||
}
|
||||
|
||||
// Use HUGE buffer (8MB) for maximum write speed
|
||||
// Use 4MB buffer for write speed
|
||||
setvbuf(session->upload_fp, NULL, _IOFBF, BUFFER_SIZE);
|
||||
|
||||
strncpy(session->upload_path, path, sizeof(session->upload_path) - 1);
|
||||
@@ -863,7 +893,7 @@ void handle_start_upload(client_session_t *session, const uint8_t *data, uint32_
|
||||
session->upload_received = chunk_offset;
|
||||
|
||||
// Increase socket receive buffer for this upload session
|
||||
int huge_buf = 8 * 1024 * 1024; // 8MB receive buffer
|
||||
int huge_buf = 4 * 1024 * 1024; // 4MB receive buffer - matches client
|
||||
setsockopt(session->sock, SOL_SOCKET, SO_RCVBUF, &huge_buf, sizeof(huge_buf));
|
||||
|
||||
send_response(session->sock, RESP_READY, NULL, 0);
|
||||
@@ -923,6 +953,66 @@ void handle_end_upload(client_session_t *session) {
|
||||
send_ok(session->sock, "Upload complete");
|
||||
}
|
||||
|
||||
// Handle DOWNLOAD_FILE
|
||||
void handle_download_file(client_session_t *session, const char *path) {
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
send_error(session->sock, "Cannot open file");
|
||||
return;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0) {
|
||||
close(fd);
|
||||
send_error(session->sock, "Cannot stat file");
|
||||
return;
|
||||
}
|
||||
|
||||
// Send file size first
|
||||
uint64_t file_size = st.st_size;
|
||||
send_response(session->sock, RESP_DATA, &file_size, sizeof(file_size));
|
||||
|
||||
// Use sendfile for zero-copy transfer (same as FTP server optimization)
|
||||
#ifdef __FreeBSD__
|
||||
off_t offset = 0;
|
||||
off_t sbytes = 0;
|
||||
|
||||
// Send in chunks for progress tracking
|
||||
const off_t CHUNK_SIZE = 100 * 1024 * 1024; // 100MB chunks
|
||||
while (offset < file_size) {
|
||||
off_t to_send = (file_size - offset > CHUNK_SIZE) ? CHUNK_SIZE : (file_size - offset);
|
||||
sbytes = 0;
|
||||
|
||||
int result = sendfile(fd, session->sock, offset, to_send, NULL, &sbytes, 0);
|
||||
if (result < 0 && errno != EAGAIN) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
offset += sbytes;
|
||||
if (sbytes == 0) break;
|
||||
}
|
||||
#else
|
||||
// Fallback for non-FreeBSD systems
|
||||
char *buffer = malloc(BUFFER_SIZE);
|
||||
if (buffer) {
|
||||
ssize_t n;
|
||||
while ((n = read(fd, buffer, BUFFER_SIZE)) > 0) {
|
||||
ssize_t sent = 0;
|
||||
while (sent < n) {
|
||||
ssize_t s = send(session->sock, buffer + sent, n - sent, 0);
|
||||
if (s <= 0) break;
|
||||
sent += s;
|
||||
}
|
||||
if (sent != n) break;
|
||||
}
|
||||
free(buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// Handle client
|
||||
void *client_thread(void *arg) {
|
||||
client_session_t *session = (client_session_t *)arg;
|
||||
@@ -1025,6 +1115,11 @@ void *client_thread(void *arg) {
|
||||
case CMD_END_UPLOAD:
|
||||
handle_end_upload(session);
|
||||
break;
|
||||
case CMD_DOWNLOAD_FILE:
|
||||
if (data) {
|
||||
handle_download_file(session, (const char *)data);
|
||||
}
|
||||
break;
|
||||
case CMD_SHUTDOWN:
|
||||
send_ok(session->sock, "Shutting down");
|
||||
free(buffer);
|
||||
@@ -1074,8 +1169,8 @@ int main() {
|
||||
int no_sigpipe = 1;
|
||||
setsockopt(server_sock, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe));
|
||||
|
||||
// 8MB buffers for sustained high throughput
|
||||
int buf_size = 8 * 1024 * 1024;
|
||||
// 4MB buffers - matches client
|
||||
int buf_size = 4 * 1024 * 1024;
|
||||
setsockopt(server_sock, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size));
|
||||
setsockopt(server_sock, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size));
|
||||
|
||||
@@ -1128,8 +1223,8 @@ int main() {
|
||||
// Aggressive TCP socket options for sustained high speed
|
||||
setsockopt(client_sock, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof(no_sigpipe));
|
||||
|
||||
// Increase buffers to 8MB for maximum throughput
|
||||
int large_buf = 8 * 1024 * 1024;
|
||||
// Increase buffers to 4MB - matches client
|
||||
int large_buf = 4 * 1024 * 1024;
|
||||
setsockopt(client_sock, SOL_SOCKET, SO_RCVBUF, &large_buf, sizeof(large_buf));
|
||||
setsockopt(client_sock, SOL_SOCKET, SO_SNDBUF, &large_buf, sizeof(large_buf));
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,195 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
GitHub Release Uploader for PS5 Upload Suite
|
||||
Uploads v1.2 release to GitHub using GitHub API
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
from pathlib import Path
|
||||
|
||||
# GitHub Configuration
|
||||
GITHUB_TOKEN = "YOUR_GITHUB_TOKEN_HERE" # Replace with your token
|
||||
GITHUB_REPO = "manos555555/PS5-Upload-Suite"
|
||||
RELEASE_TAG = "v1.2"
|
||||
RELEASE_NAME = "PS5 Upload Suite v1.2 - Complete Bug Fix"
|
||||
RELEASE_BODY = """# PS5 Upload Suite v1.2 - Complete Bug Fix Release
|
||||
|
||||
## 🐛 What's Fixed:
|
||||
|
||||
### CLIENT FIXES:
|
||||
- **Bug #1:** Connection timeout reduced to 5 seconds (fast fail on wrong IP)
|
||||
- **Bug #2:** Folder deletion timeout increased to 60 seconds
|
||||
|
||||
### SERVER FIXES:
|
||||
- **Bug #3:** Async folder deletion - NO MORE TIMEOUT on large folders! ✅
|
||||
- Server responds immediately
|
||||
- Deletion happens in background thread
|
||||
- PS5 notification when complete
|
||||
|
||||
## 📦 What's Included:
|
||||
- `PS5Upload.exe` - Windows client with bug fixes
|
||||
- `ps5_upload_server.elf` - PS5 server with async deletion
|
||||
- All dependencies included
|
||||
- Self-contained (no .NET installation required)
|
||||
|
||||
## 🚀 Installation:
|
||||
1. Extract ZIP file
|
||||
2. Run `PS5Upload.exe` on Windows
|
||||
3. Upload `ps5_upload_server.elf` to PS5 via FTP
|
||||
4. Load with etaHEN payload injector
|
||||
5. Connect and enjoy!
|
||||
|
||||
## ⚠️ IMPORTANT:
|
||||
**You MUST update BOTH files:**
|
||||
- Windows client: `PS5Upload.exe`
|
||||
- PS5 server: `ps5_upload_server.elf`
|
||||
|
||||
## 💡 Testing:
|
||||
- **Connection timeout:** Try wrong IP - fails in 5 seconds
|
||||
- **Folder deletion:** Delete large game folder - works without timeout!
|
||||
|
||||
## 📋 Requirements:
|
||||
- Windows 10/11 (64-bit)
|
||||
- PS5 with etaHEN 2.0+ or GoldHEN
|
||||
- Network connection
|
||||
|
||||
## 🔗 Links:
|
||||
- [Report Issues](https://github.com/manos555555/PS5-Upload-Suite/issues)
|
||||
- [Documentation](https://github.com/manos555555/PS5-Upload-Suite/blob/main/README.md)
|
||||
|
||||
---
|
||||
**Version:** 1.2
|
||||
**Release Date:** January 12, 2026
|
||||
**Build:** Complete Bug Fix Release
|
||||
"""
|
||||
|
||||
def create_github_release(token, repo, tag, name, body):
|
||||
"""Create a GitHub release"""
|
||||
|
||||
url = f"https://api.github.com/repos/{repo}/releases"
|
||||
headers = {
|
||||
"Authorization": f"token {token}",
|
||||
"Accept": "application/vnd.github.v3+json"
|
||||
}
|
||||
|
||||
data = {
|
||||
"tag_name": tag,
|
||||
"name": name,
|
||||
"body": body,
|
||||
"draft": False,
|
||||
"prerelease": False
|
||||
}
|
||||
|
||||
print(f"Creating release {tag}...")
|
||||
response = requests.post(url, headers=headers, json=data)
|
||||
|
||||
if response.status_code == 201:
|
||||
print(f"✅ Release created successfully!")
|
||||
return response.json()
|
||||
else:
|
||||
print(f"❌ Failed to create release: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
return None
|
||||
|
||||
def upload_release_asset(token, upload_url, file_path):
|
||||
"""Upload a file to GitHub release"""
|
||||
|
||||
# Remove the {?name,label} template from upload_url
|
||||
upload_url = upload_url.split("{")[0]
|
||||
|
||||
file_name = os.path.basename(file_path)
|
||||
file_size = os.path.getsize(file_path) / (1024 * 1024) # MB
|
||||
|
||||
print(f"\nUploading {file_name} ({file_size:.2f} MB)...")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"token {token}",
|
||||
"Content-Type": "application/zip"
|
||||
}
|
||||
|
||||
params = {"name": file_name}
|
||||
|
||||
with open(file_path, 'rb') as f:
|
||||
response = requests.post(
|
||||
upload_url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
data=f
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
print(f"✅ {file_name} uploaded successfully!")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Failed to upload {file_name}: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print("PS5 Upload Suite - GitHub Release Uploader")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Check token
|
||||
if GITHUB_TOKEN == "YOUR_GITHUB_TOKEN_HERE":
|
||||
print("❌ ERROR: Please set your GitHub token in the script!")
|
||||
print()
|
||||
print("To get a token:")
|
||||
print("1. Go to: https://github.com/settings/tokens")
|
||||
print("2. Generate new token (classic)")
|
||||
print("3. Select 'repo' scope")
|
||||
print("4. Copy token and paste in this script")
|
||||
return 1
|
||||
|
||||
# Check if release file exists
|
||||
release_file = Path("PS5Upload-Windows-x64-v1.2-Complete.zip")
|
||||
if not release_file.exists():
|
||||
print(f"❌ ERROR: Release file not found: {release_file}")
|
||||
return 1
|
||||
|
||||
print(f"📦 Release file: {release_file}")
|
||||
print(f"📊 Size: {release_file.stat().st_size / (1024*1024):.2f} MB")
|
||||
print()
|
||||
|
||||
# Create release
|
||||
release_data = create_github_release(
|
||||
GITHUB_TOKEN,
|
||||
GITHUB_REPO,
|
||||
RELEASE_TAG,
|
||||
RELEASE_NAME,
|
||||
RELEASE_BODY
|
||||
)
|
||||
|
||||
if not release_data:
|
||||
return 1
|
||||
|
||||
# Upload asset
|
||||
upload_url = release_data.get("upload_url")
|
||||
if not upload_url:
|
||||
print("❌ ERROR: No upload URL in release response")
|
||||
return 1
|
||||
|
||||
success = upload_release_asset(
|
||||
GITHUB_TOKEN,
|
||||
upload_url,
|
||||
str(release_file)
|
||||
)
|
||||
|
||||
if success:
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("✅ SUCCESS! Release published to GitHub")
|
||||
print("=" * 70)
|
||||
print()
|
||||
print(f"🔗 Release URL: {release_data.get('html_url')}")
|
||||
print()
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user