mirror of
https://github.com/ps5-payload-dev/ftpsrv.git
synced 2026-10-18 18:03:30 -05:00
ps5: add shell UI installer
This commit is contained in:
1 parent
8dd2f2403d
commit
be2e4d2c8e
9 files changed
+257
-7
No files matched your search
@@ -41,7 +41,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Payload
|
||||
path: ftpsrv-ps5.elf
|
||||
path: ftpsrv-ps5.elf ftpsrv-ps5-install.elf
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
@@ -63,4 +63,4 @@ jobs:
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
prerelease: true
|
||||
files: ftpsrv-ps5.elf
|
||||
files: ftpsrv-ps5.elf ftpsrv-ps5-install.elf
|
||||
+13
-5
@@ -23,23 +23,31 @@ else
|
||||
$(error PS5_PAYLOAD_SDK is undefined)
|
||||
endif
|
||||
|
||||
ELF := ftpsrv-ps5.elf
|
||||
|
||||
VERSION_TAG := $(shell git describe --abbrev=6 --dirty --always --tags)
|
||||
TITLE_ID := BREW10001
|
||||
ASSETS := assets/param.json assets/icon0.png assets/websrv.html
|
||||
|
||||
CFLAGS := -Wall -Werror -DVERSION_TAG=\"$(VERSION_TAG)\" -I.
|
||||
CFLAGS += -DTITLE_ID=\"$(TITLE_ID)\"
|
||||
|
||||
SOURCE := srv.c cmd.c notify.c io.c self.c sha256.c
|
||||
SOURCE += main-prospero.c cmd-prospero.c self-prospero.c
|
||||
SOURCE += io-freebsd.c
|
||||
|
||||
all: $(ELF)
|
||||
all: ftpsrv-ps5.elf ftpsrv-ps5-install.elf
|
||||
|
||||
$(ELF): $(SOURCE)
|
||||
ftpsrv-ps5.elf: $(SOURCE)
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
ftpsrv-ps5-install.elf: install-ps5.c $(ASSETS)
|
||||
$(CC) $(CFLAGS) -o $@ $< -lSceIpmi -lSceAppInstUtil
|
||||
|
||||
clean:
|
||||
rm -f *.elf
|
||||
|
||||
test: $(ELF)
|
||||
install: ftpsrv-ps5-install.elf
|
||||
$(PS5_DEPLOY) -h $(PS5_HOST) -p $(PS5_PORT) $^
|
||||
|
||||
test: ftpsrv-ps5.elf
|
||||
$(PS5_DEPLOY) -h $(PS5_HOST) -p $(PS5_PORT) $^
|
||||
|
||||
@@ -39,6 +39,19 @@ the FTP server can be compiled using the following command:
|
||||
john@localhost:ftpsrv$ make -f Makefile.posix
|
||||
```
|
||||
|
||||
## Installing on the PS5
|
||||
On the PS5, ftpsrv can be installed to disk and then launched from the standard
|
||||
Sony shell UI assuming [websrv][websrv] is running. To perform the install,
|
||||
deploy the install payload to an ELF loader, e.g.,
|
||||
:```console
|
||||
john@localhost:~$ export PS5_HOST=ps5
|
||||
john@localhost:~$ export PS5_PORT=9021
|
||||
john@localhost:~$ export PS5_PAYLOAD=https://github.com/ps5-payload-dev/ftpsrv/releases/latest/download/ftpsrv-ps5-install.elf
|
||||
john@localhost:~$ echo "$PS5_PAYLOAD" | nc -q0 $PS5_HOST $PS5_PORT
|
||||
```
|
||||
If the install is successful, a new item appears in the Sony shell UI that can
|
||||
be used launch ftpsrv when [websrv][websrv] is running.
|
||||
|
||||
## Known issues
|
||||
Some PS5 firmwares below vesion 4 contains a kernel bug where reading from some SELF
|
||||
files causes the read syscall to stall.
|
||||
@@ -54,4 +67,5 @@ ftpsrv is licensed under the GPLv3+.
|
||||
|
||||
[sdk-ps4]: https://github.com/ps4-payload-dev/sdk
|
||||
[sdk-ps5]: https://github.com/ps5-payload-dev/sdk
|
||||
[websrv]: https://github.com/ps5-payload-dev/websrv
|
||||
[issues]: https://github.com/ps5-payload-dev/ftpsrv/issues/new
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"titleId": "BREW10001",
|
||||
"deeplinkUri": "http://127.0.0.1:8080/fs/user/app/BREW10001/websrv.html",
|
||||
"localizedParameters": {
|
||||
"defaultLanguage": "en-US",
|
||||
"en-US": {
|
||||
"titleName": "FTP server"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- dont request favicon -->
|
||||
<link rel="icon" href="data:,">
|
||||
<!-- https://cdn.jsdelivr.net/npm/xterm@5.0.0/css/xterm.min.css -->
|
||||
<link rel="stylesheet" href="xterm.css">
|
||||
<!-- cdn.jsdelivr.net/npm/xterm@5.0.0/lib/xterm.min.js -->
|
||||
<script src="xterm.js"></script>
|
||||
</head>
|
||||
<body id="terminal" bgcolor="black">
|
||||
<script>
|
||||
async function render_stdout(response) {
|
||||
try {
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
let decodedValue = decoder.decode(value);
|
||||
term.write(decodedValue);
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
|
||||
async function deploy(uri) {
|
||||
const params = new URLSearchParams({
|
||||
elf: uri,
|
||||
pipe: "1"
|
||||
});
|
||||
|
||||
let response = await fetch('/elfldr?' + params);
|
||||
if(!response.ok) {
|
||||
return false;
|
||||
}
|
||||
render_stdout(response);
|
||||
return true;
|
||||
}
|
||||
|
||||
const term = new Terminal({
|
||||
convertEol: true,
|
||||
altClickMovesCursor: false,
|
||||
disableStdin: true,
|
||||
fontSize: 18,
|
||||
cols: 90,
|
||||
rows: 25
|
||||
});
|
||||
term.open(document.getElementById('terminal'));
|
||||
|
||||
//const uri = 'https://github.com/ps5-payload-dev/ftpsrv/releases/latest/download/ftpsrv-ps5.elf';
|
||||
const uri = 'file://user/app/BREW10001/payload.elf';
|
||||
deploy(uri);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Minified by jsDelivr using clean-css v5.3.3.
|
||||
* Original file: /npm/xterm@5.0.0/css/xterm.css
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:.5}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-decoration-overview-ruler{z-index:7;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}
|
||||
/*# sourceMappingURL=/sm/759960db13483cbea296b282927ade815a482ec7aed0b5948cd61bc0bc3b1486.map */
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Skipped minification because the original files appears to be already minified.
|
||||
* Original file: /npm/xterm@5.0.0/lib/xterm.js
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i=t();for(var s in i)("object"==typeof exports?exports:e)[s]=i[s]}}(self,(function(){return(()=>{"use strict";var e={4567:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.AccessibilityManager=void 0;const s=i(9042),r=i(6114),n=i(9924),o=i(3656),a=i(844),h=i(5596),c=i(9631);class l extends a.Disposable{constructor(e,t){super(),this._terminal=e,this._renderService=t,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityTreeRoot=document.createElement("div"),this._accessibilityTreeRoot.classList.add("xterm-accessibility"),this._accessibilityTreeRoot.tabIndex=0,this._rowContainer=document.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let e=0;e<this._terminal.rows;e++)this._rowElements[e]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[e]);if(this._topBoundaryFocusListener=e=>this._onBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._onBoundaryFocus(e,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions(),this._accessibilityTreeRoot.appendChild(this._rowContainer),this._renderRowsDebouncer=new n.TimeBasedDebouncer(this._renderRows.bind(this)),this._refreshRows(),this._liveRegion=document.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityTreeRoot.appendChild(this._liveRegion),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityTreeRoot),this.register(this._renderRowsDebouncer),this.register(this._terminal.onResize((e=>this._onResize(e.rows)))),this.register(this._terminal.onRender((e=>this._refreshRows(e.start,e.end)))),this.register(this._terminal.onScroll((()=>this._refreshRows()))),this.register(this._terminal.onA11yChar((e=>this._onChar(e)))),this.register(this._terminal.onLineFeed((()=>this._onChar("\n")))),this.register(this._terminal.onA11yTab((e=>this._onTab(e)))),this.register(this._terminal.onKey((e=>this._onKey(e.key)))),this.register(this._terminal.onBlur((()=>this._clearLiveRegion()))),this.register(this._renderService.onDimensionsChange((()=>this._refreshRowsDimensions()))),this._screenDprMonitor=new h.ScreenDprMonitor(window),this.register(this._screenDprMonitor),this._screenDprMonitor.setListener((()=>this._refreshRowsDimensions())),this.register((0,o.addDisposableDomListener)(window,"resize",(()=>this._refreshRowsDimensions())))}dispose(){super.dispose(),(0,c.removeElementFromParent)(this._accessibilityTreeRoot),this._rowElements.length=0}_onBoundaryFocus(e,t){const i=e.target,s=this._rowElements[0===t?1:this._rowElements.length-2];if(i.getAttribute("aria-posinset")===(0===t?"1":`${this._terminal.buffer.lines.length}`))return;if(e.relatedTarget!==s)return;let r,n;if(0===t?(r=i,n=this._rowElements.pop(),this._rowContainer.removeChild(n)):(r=this._rowElements.shift(),n=i,this._rowContainer.removeChild(r)),r.removeEventListener("focus",this._topBoundaryFocusListener),n.removeEventListener("focus",this._bottomBoundaryFocusListener),0===t){const e=this._createAccessibilityTreeNode();this._rowElements.unshift(e),this._rowContainer.insertAdjacentElement("afterbegin",e)}else{const e=this._createAccessibilityTreeNode();this._rowElements.push(e),this._rowContainer.appendChild(e)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(0===t?-1:1),this._rowElements[0===t?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_onResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let e=this._rowContainer.children.length;e<this._terminal.rows;e++)this._rowElements[e]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[e]);for(;this._rowElements.length>e;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=document.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_onTab(e){for(let t=0;t<e;t++)this._onChar(" ")}_onChar(e){this._liveRegionLineCount<21&&(this._charsToConsume.length>0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,"\n"==Line truncated
|
||||
//# sourceMappingURL=xterm.js.map
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/* Copyright (C) 2026 John Törnblom
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the
|
||||
Free Software Foundation; either version 3, or (at your option) any
|
||||
later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; see the file COPYING. If not, see
|
||||
<http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <ps5/kernel.h>
|
||||
|
||||
|
||||
#define INCASSET(name, file) \
|
||||
__asm__(".section .rodata\n" \
|
||||
".global " #name "\n" \
|
||||
".global " #name "_end\n" \
|
||||
".global " #name "_size\n" \
|
||||
".align 16\n" \
|
||||
#name ":\n" \
|
||||
".incbin \"" file "\"\n" \
|
||||
#name "_end:\n" \
|
||||
#name "_size:\n" \
|
||||
".quad " #name "_end - " #name "\n" \
|
||||
".previous\n"); \
|
||||
extern const uint8_t name[]; \
|
||||
extern const size_t name##_size;
|
||||
|
||||
|
||||
int sceAppInstUtilInitialize(void);
|
||||
int sceAppInstUtilAppInstallAll(void*);
|
||||
int sceAppInstUtilAppUnInstall(const char*);
|
||||
|
||||
|
||||
INCASSET(payload, "ftpsrv-ps5.elf");
|
||||
INCASSET(param, "assets/param.json");
|
||||
INCASSET(icon0, "assets/icon0.png");
|
||||
INCASSET(websrv, "assets/websrv.html");
|
||||
INCASSET(xterm_js, "assets/xterm.min.js");
|
||||
INCASSET(xterm_css, "assets/xterm.min.css");
|
||||
|
||||
|
||||
static int
|
||||
install_file(const char* path, const uint8_t* data, size_t size) {
|
||||
FILE* f;
|
||||
|
||||
if(!(f=fopen(path, "w"))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(fwrite(data, size, 1, f) != 1) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
install_app(const char* title_id, const char* dir) {
|
||||
int (*sceAppInstUtilAppInstallTitleDir)(const char*, const char*, void*) = 0;
|
||||
const char* nid = "Wudg3Xe3heE";
|
||||
uint32_t handle;
|
||||
|
||||
if(!kernel_dynlib_handle(-1, "libSceAppInstUtil.sprx", &handle)) {
|
||||
sceAppInstUtilAppInstallTitleDir = (void*)kernel_dynlib_resolve(-1, handle, nid);
|
||||
}
|
||||
|
||||
if(sceAppInstUtilAppInstallTitleDir) {
|
||||
return sceAppInstUtilAppInstallTitleDir(title_id, dir, 0);
|
||||
}
|
||||
|
||||
return sceAppInstUtilAppInstallAll(0);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char *argv[]) {
|
||||
int err;
|
||||
|
||||
if((err=sceAppInstUtilInitialize())) {
|
||||
printf("sceAppInstUtilInitialize: error 0x%08X\n", err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sceAppInstUtilAppUnInstall(TITLE_ID);
|
||||
|
||||
if(mkdir("/user/app/"TITLE_ID, 0755)) {
|
||||
perror("mkdir");
|
||||
return -1;
|
||||
}
|
||||
if(mkdir("/user/app/"TITLE_ID"/sce_sys", 0755)) {
|
||||
perror("mkdir");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(install_file("/user/app/"TITLE_ID"/payload.elf", payload, payload_size)) {
|
||||
perror("install_file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(install_file("/user/app/"TITLE_ID"/websrv.html", websrv, websrv_size)) {
|
||||
perror("install_file");
|
||||
return -1;
|
||||
}
|
||||
if(install_file("/user/app/"TITLE_ID"/xterm.js", xterm_js, xterm_js_size)) {
|
||||
perror("install_file");
|
||||
return -1;
|
||||
}
|
||||
if(install_file("/user/app/"TITLE_ID"/xterm.css", xterm_css, xterm_css_size)) {
|
||||
perror("install_file");
|
||||
return -1;
|
||||
}
|
||||
if(install_file("/user/app/"TITLE_ID"/sce_sys/icon0.png", icon0, icon0_size)) {
|
||||
perror("install_file");
|
||||
return -1;
|
||||
}
|
||||
if(install_file("/user/app/"TITLE_ID"/sce_sys/param.json", param, param_size)) {
|
||||
perror("install_file");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if((err=install_app(TITLE_ID, "/user/app/"))) {
|
||||
printf("install_app: error 0x%08X\n", err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user