mirror of
https://github.com/erickdavestech/ps5-dualsense-overlay.git
synced 2026-10-06 12:00:14 +02:00
Normalize line endings to LF and enforce them with .gitattributes
CRLF line endings broke scripts/prepare_ps5_deps.sh in CI. Only line endings change; the build output is identical. Signed-off-by: erickdavestech <54048831+erickdavestech@users.noreply.github.com>
This commit is contained in:
17 files changed
+17915
-17904
No files matched your search
@@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.png binary
|
||||
*.webp binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.zip binary
|
||||
*.elf binary
|
||||
*.plugin binary
|
||||
+23
-23
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"project": "ps5-dualsense-overlay",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"ps5_payload_sdk": {
|
||||
"repository": "https://github.com/ps5-payload-dev/sdk",
|
||||
"version": "v0.43",
|
||||
"commit": "d9c9519",
|
||||
"purpose": "PS5 payload toolchain and system libraries"
|
||||
},
|
||||
"etahen": {
|
||||
"repository": "https://github.com/etaHEN/etaHEN",
|
||||
"version": "2.4B",
|
||||
"commit": "d47f99bd37f349ae59b3c4b66e09e93ba69f56cd",
|
||||
"purpose": "Pinned GPL source baseline used by the process/module sampling adapter. Runtime hardware test used etaHEN 2.6."
|
||||
},
|
||||
"ps5_payload_shsrv": {
|
||||
"repository": "https://github.com/ps5-payload-dev/shsrv",
|
||||
"version": "v0.20",
|
||||
"commit": "6f320637d56d344a0e7797753099e33238bbf146",
|
||||
"purpose": "Pinned GPL ptrace and ELF-loader primitives used by the source-built ShellUI bootstrap."
|
||||
}
|
||||
}
|
||||
{
|
||||
"project": "ps5-dualsense-overlay",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"ps5_payload_sdk": {
|
||||
"repository": "https://github.com/ps5-payload-dev/sdk",
|
||||
"version": "v0.43",
|
||||
"commit": "d9c9519",
|
||||
"purpose": "PS5 payload toolchain and system libraries"
|
||||
},
|
||||
"etahen": {
|
||||
"repository": "https://github.com/etaHEN/etaHEN",
|
||||
"version": "2.4B",
|
||||
"commit": "d47f99bd37f349ae59b3c4b66e09e93ba69f56cd",
|
||||
"purpose": "Pinned GPL source baseline used by the process/module sampling adapter. Runtime hardware test used etaHEN 2.6."
|
||||
},
|
||||
"ps5_payload_shsrv": {
|
||||
"repository": "https://github.com/ps5-payload-dev/shsrv",
|
||||
"version": "v0.20",
|
||||
"commit": "6f320637d56d344a0e7797753099e33238bbf146",
|
||||
"purpose": "Pinned GPL ptrace and ELF-loader primitives used by the source-built ShellUI bootstrap."
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
window.LAYOUT = {"canvas": [640, 480], "sizes": {"1280": 2.0, "640": 1.0, "320": 0.5}, "units": "Todas las coordenadas están en unidades de 640x480; multiplica por sizes[carpeta].", "base": "base.png", "base_full": "base_full.png", "buttons": {"triangle": {"sprite": "pressed/triangle.png", "rect": [482, 106, 64, 64], "bit": "0x1000"}, "circle": {"sprite": "pressed/circle.png", "rect": [528, 152, 64, 64], "bit": "0x2000"}, "cross": {"sprite": "pressed/cross.png", "rect": [482, 198, 64, 64], "bit": "0x4000"}, "square": {"sprite": "pressed/square.png", "rect": [436, 152, 64, 64], "bit": "0x8000"}, "dpad_up": {"sprite": "pressed/dpad_up.png", "rect": [96, 122, 58, 66], "bit": "0x10"}, "dpad_down": {"sprite": "pressed/dpad_down.png", "rect": [96, 180, 58, 68], "bit": "0x40"}, "dpad_left": {"sprite": "pressed/dpad_left.png", "rect": [62, 154, 66, 60], "bit": "0x80"}, "dpad_right": {"sprite": "pressed/dpad_right.png", "rect": [122, 154, 66, 60], "bit": "0x20"}, "l1": {"sprite": "pressed/l1.png", "rect": [76, 56, 106, 52], "bit": "0x400"}, "r1": {"sprite": "pressed/r1.png", "rect": [458, 56, 106, 52], "bit": "0x800"}, "create": {"sprite": "pressed/create.png", "rect": [152, 88, 44, 56], "bit": "0x1"}, "options": {"sprite": "pressed/options.png", "rect": [444, 88, 42, 56], "bit": "0x8"}, "mute": {"sprite": "pressed/mute.png", "rect": [292, 288, 56, 30], "bit": null}, "ps": {"sprite": "pressed/ps.png", "rect": [286, 238, 64, 54], "bit": null}, "touchpad": {"sprite": "pressed/touchpad.png", "rect": [182, 60, 276, 160], "bit": "0x100000"}}, "sticks": {"left": {"center": [218.58, 270.67], "travel": 12, "size": [96, 96], "sprite": "stick_cap.png", "sprite_pressed": "stick_cap_l3.png", "click": "l3", "bit": "0x2"}, "right": {"center": [420.17, 270.36], "travel": 12, "size": [96, 96], "sprite": "stick_cap.png", "sprite_pressed": "stick_cap_l3.png", "click": "r3", "bit": "0x4"}}, "triggers": {"l2": {"sprite": "l2_fill.png", "rect": [100, 10, 56, 58], "fill": "bottom_up", "bit": "0x100"}, "r2": {"sprite": "r2_fill.png", "rect": [482, 10, 56, 58], "fill": "bottom_up", "bit": "0x200"}}, "touchpad": {"rect": [195.21, 72.77, 248.96, 133.28], "dot": "touch_dot.png", "dot_size": [44, 44], "note": "Mapear ScePadTouch.x/y con la resolución de scePadGetControllerInformation()."}, "credits": "Button Icons and Controls by Zacksly (CC BY 3.0 | zacksly.itch.io) — modificado"};
|
||||
window.LAYOUT = {"canvas": [640, 480], "sizes": {"1280": 2.0, "640": 1.0, "320": 0.5}, "units": "Todas las coordenadas están en unidades de 640x480; multiplica por sizes[carpeta].", "base": "base.png", "base_full": "base_full.png", "buttons": {"triangle": {"sprite": "pressed/triangle.png", "rect": [482, 106, 64, 64], "bit": "0x1000"}, "circle": {"sprite": "pressed/circle.png", "rect": [528, 152, 64, 64], "bit": "0x2000"}, "cross": {"sprite": "pressed/cross.png", "rect": [482, 198, 64, 64], "bit": "0x4000"}, "square": {"sprite": "pressed/square.png", "rect": [436, 152, 64, 64], "bit": "0x8000"}, "dpad_up": {"sprite": "pressed/dpad_up.png", "rect": [96, 122, 58, 66], "bit": "0x10"}, "dpad_down": {"sprite": "pressed/dpad_down.png", "rect": [96, 180, 58, 68], "bit": "0x40"}, "dpad_left": {"sprite": "pressed/dpad_left.png", "rect": [62, 154, 66, 60], "bit": "0x80"}, "dpad_right": {"sprite": "pressed/dpad_right.png", "rect": [122, 154, 66, 60], "bit": "0x20"}, "l1": {"sprite": "pressed/l1.png", "rect": [76, 56, 106, 52], "bit": "0x400"}, "r1": {"sprite": "pressed/r1.png", "rect": [458, 56, 106, 52], "bit": "0x800"}, "create": {"sprite": "pressed/create.png", "rect": [152, 88, 44, 56], "bit": "0x1"}, "options": {"sprite": "pressed/options.png", "rect": [444, 88, 42, 56], "bit": "0x8"}, "mute": {"sprite": "pressed/mute.png", "rect": [292, 288, 56, 30], "bit": null}, "ps": {"sprite": "pressed/ps.png", "rect": [286, 238, 64, 54], "bit": null}, "touchpad": {"sprite": "pressed/touchpad.png", "rect": [182, 60, 276, 160], "bit": "0x100000"}}, "sticks": {"left": {"center": [218.58, 270.67], "travel": 12, "size": [96, 96], "sprite": "stick_cap.png", "sprite_pressed": "stick_cap_l3.png", "click": "l3", "bit": "0x2"}, "right": {"center": [420.17, 270.36], "travel": 12, "size": [96, 96], "sprite": "stick_cap.png", "sprite_pressed": "stick_cap_l3.png", "click": "r3", "bit": "0x4"}}, "triggers": {"l2": {"sprite": "l2_fill.png", "rect": [100, 10, 56, 58], "fill": "bottom_up", "bit": "0x100"}, "r2": {"sprite": "r2_fill.png", "rect": [482, 10, 56, 58], "fill": "bottom_up", "bit": "0x200"}}, "touchpad": {"rect": [195.21, 72.77, 248.96, 133.28], "dot": "touch_dot.png", "dot_size": [44, 44], "note": "Mapear ScePadTouch.x/y con la resolución de scePadGetControllerInformation()."}, "credits": "Button Icons and Controls by Zacksly (CC BY 3.0 | zacksly.itch.io) — modificado"};
|
||||
+236
-236
@@ -1,237 +1,237 @@
|
||||
{
|
||||
"canvas": [
|
||||
640,
|
||||
480
|
||||
],
|
||||
"sizes": {
|
||||
"1280": 2.0,
|
||||
"640": 1.0,
|
||||
"320": 0.5
|
||||
},
|
||||
"units": "Todas las coordenadas están en unidades de 640x480; multiplica por sizes[carpeta].",
|
||||
"base": "base.png",
|
||||
"base_full": "base_full.png",
|
||||
"buttons": {
|
||||
"triangle": {
|
||||
"sprite": "pressed/triangle.png",
|
||||
"rect": [
|
||||
482,
|
||||
106,
|
||||
64,
|
||||
64
|
||||
],
|
||||
"bit": "0x1000"
|
||||
},
|
||||
"circle": {
|
||||
"sprite": "pressed/circle.png",
|
||||
"rect": [
|
||||
528,
|
||||
152,
|
||||
64,
|
||||
64
|
||||
],
|
||||
"bit": "0x2000"
|
||||
},
|
||||
"cross": {
|
||||
"sprite": "pressed/cross.png",
|
||||
"rect": [
|
||||
482,
|
||||
198,
|
||||
64,
|
||||
64
|
||||
],
|
||||
"bit": "0x4000"
|
||||
},
|
||||
"square": {
|
||||
"sprite": "pressed/square.png",
|
||||
"rect": [
|
||||
436,
|
||||
152,
|
||||
64,
|
||||
64
|
||||
],
|
||||
"bit": "0x8000"
|
||||
},
|
||||
"dpad_up": {
|
||||
"sprite": "pressed/dpad_up.png",
|
||||
"rect": [
|
||||
96,
|
||||
122,
|
||||
58,
|
||||
66
|
||||
],
|
||||
"bit": "0x10"
|
||||
},
|
||||
"dpad_down": {
|
||||
"sprite": "pressed/dpad_down.png",
|
||||
"rect": [
|
||||
96,
|
||||
180,
|
||||
58,
|
||||
68
|
||||
],
|
||||
"bit": "0x40"
|
||||
},
|
||||
"dpad_left": {
|
||||
"sprite": "pressed/dpad_left.png",
|
||||
"rect": [
|
||||
62,
|
||||
154,
|
||||
66,
|
||||
60
|
||||
],
|
||||
"bit": "0x80"
|
||||
},
|
||||
"dpad_right": {
|
||||
"sprite": "pressed/dpad_right.png",
|
||||
"rect": [
|
||||
122,
|
||||
154,
|
||||
66,
|
||||
60
|
||||
],
|
||||
"bit": "0x20"
|
||||
},
|
||||
"l1": {
|
||||
"sprite": "pressed/l1.png",
|
||||
"rect": [
|
||||
76,
|
||||
56,
|
||||
106,
|
||||
52
|
||||
],
|
||||
"bit": "0x400"
|
||||
},
|
||||
"r1": {
|
||||
"sprite": "pressed/r1.png",
|
||||
"rect": [
|
||||
458,
|
||||
56,
|
||||
106,
|
||||
52
|
||||
],
|
||||
"bit": "0x800"
|
||||
},
|
||||
"create": {
|
||||
"sprite": "pressed/create.png",
|
||||
"rect": [
|
||||
152,
|
||||
88,
|
||||
44,
|
||||
56
|
||||
],
|
||||
"bit": "0x1"
|
||||
},
|
||||
"options": {
|
||||
"sprite": "pressed/options.png",
|
||||
"rect": [
|
||||
444,
|
||||
88,
|
||||
42,
|
||||
56
|
||||
],
|
||||
"bit": "0x8"
|
||||
},
|
||||
"mute": {
|
||||
"sprite": "pressed/mute.png",
|
||||
"rect": [
|
||||
292,
|
||||
288,
|
||||
56,
|
||||
30
|
||||
],
|
||||
"bit": null
|
||||
},
|
||||
"ps": {
|
||||
"sprite": "pressed/ps.png",
|
||||
"rect": [
|
||||
286,
|
||||
238,
|
||||
64,
|
||||
54
|
||||
],
|
||||
"bit": null
|
||||
},
|
||||
"touchpad": {
|
||||
"sprite": "pressed/touchpad.png",
|
||||
"rect": [
|
||||
182,
|
||||
60,
|
||||
276,
|
||||
160
|
||||
],
|
||||
"bit": "0x100000"
|
||||
}
|
||||
},
|
||||
"sticks": {
|
||||
"left": {
|
||||
"center": [
|
||||
218.58,
|
||||
270.67
|
||||
],
|
||||
"travel": 12,
|
||||
"size": [
|
||||
96,
|
||||
96
|
||||
],
|
||||
"sprite": "stick_cap.png",
|
||||
"sprite_pressed": "stick_cap_l3.png",
|
||||
"click": "l3",
|
||||
"bit": "0x2"
|
||||
},
|
||||
"right": {
|
||||
"center": [
|
||||
420.17,
|
||||
270.36
|
||||
],
|
||||
"travel": 12,
|
||||
"size": [
|
||||
96,
|
||||
96
|
||||
],
|
||||
"sprite": "stick_cap.png",
|
||||
"sprite_pressed": "stick_cap_l3.png",
|
||||
"click": "r3",
|
||||
"bit": "0x4"
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"l2": {
|
||||
"sprite": "l2_fill.png",
|
||||
"rect": [
|
||||
100,
|
||||
10,
|
||||
56,
|
||||
58
|
||||
],
|
||||
"fill": "bottom_up",
|
||||
"bit": "0x100"
|
||||
},
|
||||
"r2": {
|
||||
"sprite": "r2_fill.png",
|
||||
"rect": [
|
||||
482,
|
||||
10,
|
||||
56,
|
||||
58
|
||||
],
|
||||
"fill": "bottom_up",
|
||||
"bit": "0x200"
|
||||
}
|
||||
},
|
||||
"touchpad": {
|
||||
"rect": [
|
||||
195.21,
|
||||
72.77,
|
||||
248.96,
|
||||
133.28
|
||||
],
|
||||
"dot": "touch_dot.png",
|
||||
"dot_size": [
|
||||
44,
|
||||
44
|
||||
],
|
||||
"note": "Mapear ScePadTouch.x/y con la resolución de scePadGetControllerInformation()."
|
||||
},
|
||||
"credits": "Button Icons and Controls by Zacksly (CC BY 3.0 | zacksly.itch.io) — modificado"
|
||||
{
|
||||
"canvas": [
|
||||
640,
|
||||
480
|
||||
],
|
||||
"sizes": {
|
||||
"1280": 2.0,
|
||||
"640": 1.0,
|
||||
"320": 0.5
|
||||
},
|
||||
"units": "Todas las coordenadas están en unidades de 640x480; multiplica por sizes[carpeta].",
|
||||
"base": "base.png",
|
||||
"base_full": "base_full.png",
|
||||
"buttons": {
|
||||
"triangle": {
|
||||
"sprite": "pressed/triangle.png",
|
||||
"rect": [
|
||||
482,
|
||||
106,
|
||||
64,
|
||||
64
|
||||
],
|
||||
"bit": "0x1000"
|
||||
},
|
||||
"circle": {
|
||||
"sprite": "pressed/circle.png",
|
||||
"rect": [
|
||||
528,
|
||||
152,
|
||||
64,
|
||||
64
|
||||
],
|
||||
"bit": "0x2000"
|
||||
},
|
||||
"cross": {
|
||||
"sprite": "pressed/cross.png",
|
||||
"rect": [
|
||||
482,
|
||||
198,
|
||||
64,
|
||||
64
|
||||
],
|
||||
"bit": "0x4000"
|
||||
},
|
||||
"square": {
|
||||
"sprite": "pressed/square.png",
|
||||
"rect": [
|
||||
436,
|
||||
152,
|
||||
64,
|
||||
64
|
||||
],
|
||||
"bit": "0x8000"
|
||||
},
|
||||
"dpad_up": {
|
||||
"sprite": "pressed/dpad_up.png",
|
||||
"rect": [
|
||||
96,
|
||||
122,
|
||||
58,
|
||||
66
|
||||
],
|
||||
"bit": "0x10"
|
||||
},
|
||||
"dpad_down": {
|
||||
"sprite": "pressed/dpad_down.png",
|
||||
"rect": [
|
||||
96,
|
||||
180,
|
||||
58,
|
||||
68
|
||||
],
|
||||
"bit": "0x40"
|
||||
},
|
||||
"dpad_left": {
|
||||
"sprite": "pressed/dpad_left.png",
|
||||
"rect": [
|
||||
62,
|
||||
154,
|
||||
66,
|
||||
60
|
||||
],
|
||||
"bit": "0x80"
|
||||
},
|
||||
"dpad_right": {
|
||||
"sprite": "pressed/dpad_right.png",
|
||||
"rect": [
|
||||
122,
|
||||
154,
|
||||
66,
|
||||
60
|
||||
],
|
||||
"bit": "0x20"
|
||||
},
|
||||
"l1": {
|
||||
"sprite": "pressed/l1.png",
|
||||
"rect": [
|
||||
76,
|
||||
56,
|
||||
106,
|
||||
52
|
||||
],
|
||||
"bit": "0x400"
|
||||
},
|
||||
"r1": {
|
||||
"sprite": "pressed/r1.png",
|
||||
"rect": [
|
||||
458,
|
||||
56,
|
||||
106,
|
||||
52
|
||||
],
|
||||
"bit": "0x800"
|
||||
},
|
||||
"create": {
|
||||
"sprite": "pressed/create.png",
|
||||
"rect": [
|
||||
152,
|
||||
88,
|
||||
44,
|
||||
56
|
||||
],
|
||||
"bit": "0x1"
|
||||
},
|
||||
"options": {
|
||||
"sprite": "pressed/options.png",
|
||||
"rect": [
|
||||
444,
|
||||
88,
|
||||
42,
|
||||
56
|
||||
],
|
||||
"bit": "0x8"
|
||||
},
|
||||
"mute": {
|
||||
"sprite": "pressed/mute.png",
|
||||
"rect": [
|
||||
292,
|
||||
288,
|
||||
56,
|
||||
30
|
||||
],
|
||||
"bit": null
|
||||
},
|
||||
"ps": {
|
||||
"sprite": "pressed/ps.png",
|
||||
"rect": [
|
||||
286,
|
||||
238,
|
||||
64,
|
||||
54
|
||||
],
|
||||
"bit": null
|
||||
},
|
||||
"touchpad": {
|
||||
"sprite": "pressed/touchpad.png",
|
||||
"rect": [
|
||||
182,
|
||||
60,
|
||||
276,
|
||||
160
|
||||
],
|
||||
"bit": "0x100000"
|
||||
}
|
||||
},
|
||||
"sticks": {
|
||||
"left": {
|
||||
"center": [
|
||||
218.58,
|
||||
270.67
|
||||
],
|
||||
"travel": 12,
|
||||
"size": [
|
||||
96,
|
||||
96
|
||||
],
|
||||
"sprite": "stick_cap.png",
|
||||
"sprite_pressed": "stick_cap_l3.png",
|
||||
"click": "l3",
|
||||
"bit": "0x2"
|
||||
},
|
||||
"right": {
|
||||
"center": [
|
||||
420.17,
|
||||
270.36
|
||||
],
|
||||
"travel": 12,
|
||||
"size": [
|
||||
96,
|
||||
96
|
||||
],
|
||||
"sprite": "stick_cap.png",
|
||||
"sprite_pressed": "stick_cap_l3.png",
|
||||
"click": "r3",
|
||||
"bit": "0x4"
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"l2": {
|
||||
"sprite": "l2_fill.png",
|
||||
"rect": [
|
||||
100,
|
||||
10,
|
||||
56,
|
||||
58
|
||||
],
|
||||
"fill": "bottom_up",
|
||||
"bit": "0x100"
|
||||
},
|
||||
"r2": {
|
||||
"sprite": "r2_fill.png",
|
||||
"rect": [
|
||||
482,
|
||||
10,
|
||||
56,
|
||||
58
|
||||
],
|
||||
"fill": "bottom_up",
|
||||
"bit": "0x200"
|
||||
}
|
||||
},
|
||||
"touchpad": {
|
||||
"rect": [
|
||||
195.21,
|
||||
72.77,
|
||||
248.96,
|
||||
133.28
|
||||
],
|
||||
"dot": "touch_dot.png",
|
||||
"dot_size": [
|
||||
44,
|
||||
44
|
||||
],
|
||||
"note": "Mapear ScePadTouch.x/y con la resolución de scePadGetControllerInformation()."
|
||||
},
|
||||
"credits": "Button Icons and Controls by Zacksly (CC BY 3.0 | zacksly.itch.io) — modificado"
|
||||
}
|
||||
+175
-175
@@ -1,175 +1,175 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Pad Overlay Preview</title>
|
||||
<style>
|
||||
:root { --bg: #11151c; --panel: #1a2029; --text: #d8dde6; --muted: #8a93a3; --accent: #2f7bff; }
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: var(--bg); color: var(--text); font: 14px/1.4 system-ui, "Segoe UI", sans-serif; }
|
||||
header { display: flex; flex-wrap: wrap; gap: 12px 20px; align-items: center; padding: 12px 16px; background: var(--panel); }
|
||||
header h1 { font-size: 15px; margin: 0 8px 0 0; }
|
||||
label { color: var(--muted); }
|
||||
select { background: #0d1117; color: var(--text); border: 1px solid #2c3442; border-radius: 6px; padding: 3px 6px; }
|
||||
#src { margin-left: auto; padding: 3px 10px; border-radius: 999px; background: #0d1117; }
|
||||
#src.pad { color: #2bd99f; }
|
||||
#stage { display: grid; place-items: center; padding: 24px 16px; min-height: 60vh; overflow: auto; }
|
||||
#stage.game { background: radial-gradient(ellipse at 30% 20%, #3b5b7a, #16222f 55%, #0b0f14); }
|
||||
#stage.light { background: #c4c9be; }
|
||||
#stage.checker { background: repeating-conic-gradient(#2a2f38 0 25%, #20242b 0 50%) 0 0 / 24px 24px; }
|
||||
#ov { position: relative; flex: none; }
|
||||
#ov img { position: absolute; display: block; user-select: none; pointer-events: none; }
|
||||
#ov .off { visibility: hidden; }
|
||||
.trig { position: absolute; overflow: hidden; }
|
||||
.trig img { left: 0; bottom: 0; }
|
||||
#help { padding: 0 16px 20px; color: var(--muted); max-width: 900px; margin: 0 auto; }
|
||||
kbd { background: #0d1117; border: 1px solid #2c3442; border-radius: 4px; padding: 0 5px; color: var(--text); font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Overlay del mando — simulador</h1>
|
||||
<label>Tamaño <select id="size"><option>1280</option><option selected>640</option><option>320</option></select></label>
|
||||
<label>Fondo <select id="bgsel"><option value="game">juego</option><option value="checker">transparente</option><option value="light">claro</option></select></label>
|
||||
<span id="src">teclado</span>
|
||||
</header>
|
||||
<div id="stage" class="game"><div id="ov"></div></div>
|
||||
<div id="help">
|
||||
Conecta un DualSense al PC (USB o Bluetooth) y pulsa un botón, o usa el teclado:
|
||||
<kbd>←↑→↓</kbd> cruceta · <kbd>I</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> △ □ ✕ ○ ·
|
||||
<kbd>Q</kbd>/<kbd>E</kbd> L1/R1 · <kbd>1</kbd>/<kbd>3</kbd> L2/R2 (analógico) ·
|
||||
<kbd>WASD</kbd> stick izq · <kbd>TFGH</kbd> stick der · <kbd>Z</kbd>/<kbd>X</kbd> L3/R3 ·
|
||||
<kbd>C</kbd> Create · <kbd>V</kbd> Options · <kbd>P</kbd> PS · <kbd>M</kbd> Mute · <kbd>Espacio</kbd> clic touchpad ·
|
||||
ratón sobre el touchpad = dedo.<br>
|
||||
Simula en el PC lo que dibujará el renderer en la PS5: mismos PNG, mismo layout.json.
|
||||
</div>
|
||||
<script src="layout.js"></script>
|
||||
<script>
|
||||
(() => {
|
||||
const L = window.LAYOUT;
|
||||
const qs = new URLSearchParams(location.search);
|
||||
const ov = document.getElementById('ov');
|
||||
const sizeSel = document.getElementById('size');
|
||||
const bgSel = document.getElementById('bgsel');
|
||||
const srcTag = document.getElementById('src');
|
||||
if (L.sizes[qs.get('size')]) sizeSel.value = qs.get('size');
|
||||
|
||||
// Estado de entrada, idéntico al que mandará el ELF: botones + ejes en [-1,1] + gatillos [0,1] + toque.
|
||||
const st = { btn: new Set(), lx: 0, ly: 0, rx: 0, ry: 0, l2: 0, r2: 0, touch: null };
|
||||
let els = {};
|
||||
|
||||
function img(src, rect, s, extra) {
|
||||
const i = document.createElement('img');
|
||||
i.src = `png/${sizeSel.value}/${src}`;
|
||||
i.style.left = rect[0] * s + 'px'; i.style.top = rect[1] * s + 'px';
|
||||
i.style.width = rect[2] * s + 'px'; i.style.height = rect[3] * s + 'px';
|
||||
if (extra) i.className = extra;
|
||||
return i;
|
||||
}
|
||||
|
||||
function build() {
|
||||
const s = L.sizes[sizeSel.value];
|
||||
ov.innerHTML = '';
|
||||
ov.style.width = L.canvas[0] * s + 'px'; ov.style.height = L.canvas[1] * s + 'px';
|
||||
els = { btn: {}, trig: {}, stick: {}, dot: null, s };
|
||||
ov.append(img(L.base, [0, 0, ...L.canvas], s));
|
||||
for (const [t, d] of Object.entries(L.triggers)) {
|
||||
const box = document.createElement('div');
|
||||
box.className = 'trig';
|
||||
box.style.left = d.rect[0] * s + 'px'; box.style.width = d.rect[2] * s + 'px';
|
||||
const fill = img(d.sprite, [0, 0, d.rect[2], d.rect[3]], s);
|
||||
fill.style.top = ''; fill.style.bottom = '0';
|
||||
box.append(fill); ov.append(box);
|
||||
els.trig[t] = { box, d };
|
||||
}
|
||||
for (const [n, d] of Object.entries(L.buttons)) ov.append(els.btn[n] = img(d.sprite, d.rect, s, 'off'));
|
||||
for (const [side, d] of Object.entries(L.sticks)) ov.append(els.stick[side] = img(d.sprite, [0, 0, ...d.size], s));
|
||||
const tp = L.touchpad;
|
||||
ov.append(els.dot = img(tp.dot, [0, 0, ...tp.dot_size], s, 'off'));
|
||||
draw();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
const s = els.s;
|
||||
for (const [n, i] of Object.entries(els.btn)) i.classList.toggle('off', !st.btn.has(n));
|
||||
for (const [t, { box, d }] of Object.entries(els.trig)) {
|
||||
const v = Math.max(0, Math.min(1, st[t]));
|
||||
box.style.top = (d.rect[1] + d.rect[3] * (1 - v)) * s + 'px';
|
||||
box.style.height = d.rect[3] * v * s + 'px';
|
||||
}
|
||||
for (const [side, i] of Object.entries(els.stick)) {
|
||||
const d = L.sticks[side];
|
||||
const [x, y] = side === 'left' ? [st.lx, st.ly] : [st.rx, st.ry];
|
||||
i.style.left = (d.center[0] + x * d.travel - d.size[0] / 2) * s + 'px';
|
||||
i.style.top = (d.center[1] + y * d.travel - d.size[1] / 2) * s + 'px';
|
||||
const want = `png/${sizeSel.value}/${st.btn.has(d.click) ? d.sprite_pressed : d.sprite}`;
|
||||
if (!i.src.endsWith(want)) i.src = want;
|
||||
}
|
||||
const tp = L.touchpad, dot = els.dot;
|
||||
dot.classList.toggle('off', !st.touch);
|
||||
if (st.touch) {
|
||||
dot.style.left = (tp.rect[0] + st.touch[0] * tp.rect[2] - tp.dot_size[0] / 2) * s + 'px';
|
||||
dot.style.top = (tp.rect[1] + st.touch[1] * tp.rect[3] - tp.dot_size[1] / 2) * s + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Teclado ---
|
||||
const KEYS = {
|
||||
ArrowUp: 'dpad_up', ArrowDown: 'dpad_down', ArrowLeft: 'dpad_left', ArrowRight: 'dpad_right',
|
||||
KeyI: 'triangle', KeyJ: 'square', KeyK: 'cross', KeyL: 'circle', KeyQ: 'l1', KeyE: 'r1',
|
||||
KeyZ: 'l3', KeyX: 'r3', KeyC: 'create', KeyV: 'options', KeyP: 'ps', KeyM: 'mute', Space: 'touchpad',
|
||||
};
|
||||
const held = new Set();
|
||||
addEventListener('keydown', e => { if (KEYS[e.code] || /^(Key[WASDTFGH]|Digit[13])$/.test(e.code)) { held.add(e.code); e.preventDefault(); } });
|
||||
addEventListener('keyup', e => held.delete(e.code));
|
||||
addEventListener('blur', () => held.clear());
|
||||
const axis = (neg, pos) => (held.has(pos) ? 1 : 0) - (held.has(neg) ? 1 : 0);
|
||||
|
||||
// --- Ratón sobre el touchpad = un dedo ---
|
||||
ov.addEventListener('pointermove', e => {
|
||||
const r = ov.getBoundingClientRect(), s = els.s, tp = L.touchpad.rect;
|
||||
const u = ((e.clientX - r.left) / s - tp[0]) / tp[2], v = ((e.clientY - r.top) / s - tp[1]) / tp[3];
|
||||
st.mouseTouch = u >= 0 && u <= 1 && v >= 0 && v <= 1 ? [u, v] : null;
|
||||
});
|
||||
ov.addEventListener('pointerleave', () => { st.mouseTouch = null; });
|
||||
|
||||
// --- Gamepad API (mapeo "standard": así ve Chrome al DualSense) ---
|
||||
const GP = ['cross', 'circle', 'square', 'triangle', 'l1', 'r1', null, null, 'create', 'options',
|
||||
'l3', 'r3', 'dpad_up', 'dpad_down', 'dpad_left', 'dpad_right', 'ps', 'touchpad'];
|
||||
const dz = v => Math.abs(v) < 0.08 ? 0 : v;
|
||||
let last = performance.now();
|
||||
|
||||
function tick(now) {
|
||||
const dt = Math.min(0.1, (now - last) / 1000); last = now;
|
||||
const pad = [...(navigator.getGamepads?.() || [])].find(p => p && p.connected);
|
||||
st.btn.clear();
|
||||
if (pad) {
|
||||
srcTag.textContent = pad.id.replace(/\s*\(.*$/, '') || 'mando'; srcTag.className = 'pad';
|
||||
GP.forEach((n, i) => { if (n && pad.buttons[i]?.pressed) st.btn.add(n); });
|
||||
st.l2 = pad.buttons[6]?.value || 0; st.r2 = pad.buttons[7]?.value || 0;
|
||||
[st.lx, st.ly, st.rx, st.ry] = [0, 1, 2, 3].map(i => dz(pad.axes[i] || 0));
|
||||
} else {
|
||||
srcTag.textContent = 'teclado'; srcTag.className = '';
|
||||
for (const c of held) if (KEYS[c]) st.btn.add(KEYS[c]);
|
||||
const ramp = (v, on) => on ? Math.min(1, v + dt * 4) : Math.max(0, v - dt * 6);
|
||||
st.l2 = ramp(st.l2, held.has('Digit1')); st.r2 = ramp(st.r2, held.has('Digit3'));
|
||||
const norm = (x, y) => { const m = Math.hypot(x, y) || 1; return [x / Math.max(1, m), y / Math.max(1, m)]; };
|
||||
[st.lx, st.ly] = norm(axis('KeyA', 'KeyD'), axis('KeyW', 'KeyS'));
|
||||
[st.rx, st.ry] = norm(axis('KeyF', 'KeyH'), axis('KeyT', 'KeyG'));
|
||||
}
|
||||
if (st.l2 > 0.05) st.btn.add('l2');
|
||||
if (st.r2 > 0.05) st.btn.add('r2');
|
||||
st.touch = st.mouseTouch;
|
||||
draw();
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
sizeSel.addEventListener('change', build);
|
||||
bgSel.addEventListener('change', () => { document.getElementById('stage').className = bgSel.value; });
|
||||
build();
|
||||
requestAnimationFrame(tick);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Pad Overlay Preview</title>
|
||||
<style>
|
||||
:root { --bg: #11151c; --panel: #1a2029; --text: #d8dde6; --muted: #8a93a3; --accent: #2f7bff; }
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; background: var(--bg); color: var(--text); font: 14px/1.4 system-ui, "Segoe UI", sans-serif; }
|
||||
header { display: flex; flex-wrap: wrap; gap: 12px 20px; align-items: center; padding: 12px 16px; background: var(--panel); }
|
||||
header h1 { font-size: 15px; margin: 0 8px 0 0; }
|
||||
label { color: var(--muted); }
|
||||
select { background: #0d1117; color: var(--text); border: 1px solid #2c3442; border-radius: 6px; padding: 3px 6px; }
|
||||
#src { margin-left: auto; padding: 3px 10px; border-radius: 999px; background: #0d1117; }
|
||||
#src.pad { color: #2bd99f; }
|
||||
#stage { display: grid; place-items: center; padding: 24px 16px; min-height: 60vh; overflow: auto; }
|
||||
#stage.game { background: radial-gradient(ellipse at 30% 20%, #3b5b7a, #16222f 55%, #0b0f14); }
|
||||
#stage.light { background: #c4c9be; }
|
||||
#stage.checker { background: repeating-conic-gradient(#2a2f38 0 25%, #20242b 0 50%) 0 0 / 24px 24px; }
|
||||
#ov { position: relative; flex: none; }
|
||||
#ov img { position: absolute; display: block; user-select: none; pointer-events: none; }
|
||||
#ov .off { visibility: hidden; }
|
||||
.trig { position: absolute; overflow: hidden; }
|
||||
.trig img { left: 0; bottom: 0; }
|
||||
#help { padding: 0 16px 20px; color: var(--muted); max-width: 900px; margin: 0 auto; }
|
||||
kbd { background: #0d1117; border: 1px solid #2c3442; border-radius: 4px; padding: 0 5px; color: var(--text); font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Overlay del mando — simulador</h1>
|
||||
<label>Tamaño <select id="size"><option>1280</option><option selected>640</option><option>320</option></select></label>
|
||||
<label>Fondo <select id="bgsel"><option value="game">juego</option><option value="checker">transparente</option><option value="light">claro</option></select></label>
|
||||
<span id="src">teclado</span>
|
||||
</header>
|
||||
<div id="stage" class="game"><div id="ov"></div></div>
|
||||
<div id="help">
|
||||
Conecta un DualSense al PC (USB o Bluetooth) y pulsa un botón, o usa el teclado:
|
||||
<kbd>←↑→↓</kbd> cruceta · <kbd>I</kbd><kbd>J</kbd><kbd>K</kbd><kbd>L</kbd> △ □ ✕ ○ ·
|
||||
<kbd>Q</kbd>/<kbd>E</kbd> L1/R1 · <kbd>1</kbd>/<kbd>3</kbd> L2/R2 (analógico) ·
|
||||
<kbd>WASD</kbd> stick izq · <kbd>TFGH</kbd> stick der · <kbd>Z</kbd>/<kbd>X</kbd> L3/R3 ·
|
||||
<kbd>C</kbd> Create · <kbd>V</kbd> Options · <kbd>P</kbd> PS · <kbd>M</kbd> Mute · <kbd>Espacio</kbd> clic touchpad ·
|
||||
ratón sobre el touchpad = dedo.<br>
|
||||
Simula en el PC lo que dibujará el renderer en la PS5: mismos PNG, mismo layout.json.
|
||||
</div>
|
||||
<script src="layout.js"></script>
|
||||
<script>
|
||||
(() => {
|
||||
const L = window.LAYOUT;
|
||||
const qs = new URLSearchParams(location.search);
|
||||
const ov = document.getElementById('ov');
|
||||
const sizeSel = document.getElementById('size');
|
||||
const bgSel = document.getElementById('bgsel');
|
||||
const srcTag = document.getElementById('src');
|
||||
if (L.sizes[qs.get('size')]) sizeSel.value = qs.get('size');
|
||||
|
||||
// Estado de entrada, idéntico al que mandará el ELF: botones + ejes en [-1,1] + gatillos [0,1] + toque.
|
||||
const st = { btn: new Set(), lx: 0, ly: 0, rx: 0, ry: 0, l2: 0, r2: 0, touch: null };
|
||||
let els = {};
|
||||
|
||||
function img(src, rect, s, extra) {
|
||||
const i = document.createElement('img');
|
||||
i.src = `png/${sizeSel.value}/${src}`;
|
||||
i.style.left = rect[0] * s + 'px'; i.style.top = rect[1] * s + 'px';
|
||||
i.style.width = rect[2] * s + 'px'; i.style.height = rect[3] * s + 'px';
|
||||
if (extra) i.className = extra;
|
||||
return i;
|
||||
}
|
||||
|
||||
function build() {
|
||||
const s = L.sizes[sizeSel.value];
|
||||
ov.innerHTML = '';
|
||||
ov.style.width = L.canvas[0] * s + 'px'; ov.style.height = L.canvas[1] * s + 'px';
|
||||
els = { btn: {}, trig: {}, stick: {}, dot: null, s };
|
||||
ov.append(img(L.base, [0, 0, ...L.canvas], s));
|
||||
for (const [t, d] of Object.entries(L.triggers)) {
|
||||
const box = document.createElement('div');
|
||||
box.className = 'trig';
|
||||
box.style.left = d.rect[0] * s + 'px'; box.style.width = d.rect[2] * s + 'px';
|
||||
const fill = img(d.sprite, [0, 0, d.rect[2], d.rect[3]], s);
|
||||
fill.style.top = ''; fill.style.bottom = '0';
|
||||
box.append(fill); ov.append(box);
|
||||
els.trig[t] = { box, d };
|
||||
}
|
||||
for (const [n, d] of Object.entries(L.buttons)) ov.append(els.btn[n] = img(d.sprite, d.rect, s, 'off'));
|
||||
for (const [side, d] of Object.entries(L.sticks)) ov.append(els.stick[side] = img(d.sprite, [0, 0, ...d.size], s));
|
||||
const tp = L.touchpad;
|
||||
ov.append(els.dot = img(tp.dot, [0, 0, ...tp.dot_size], s, 'off'));
|
||||
draw();
|
||||
}
|
||||
|
||||
function draw() {
|
||||
const s = els.s;
|
||||
for (const [n, i] of Object.entries(els.btn)) i.classList.toggle('off', !st.btn.has(n));
|
||||
for (const [t, { box, d }] of Object.entries(els.trig)) {
|
||||
const v = Math.max(0, Math.min(1, st[t]));
|
||||
box.style.top = (d.rect[1] + d.rect[3] * (1 - v)) * s + 'px';
|
||||
box.style.height = d.rect[3] * v * s + 'px';
|
||||
}
|
||||
for (const [side, i] of Object.entries(els.stick)) {
|
||||
const d = L.sticks[side];
|
||||
const [x, y] = side === 'left' ? [st.lx, st.ly] : [st.rx, st.ry];
|
||||
i.style.left = (d.center[0] + x * d.travel - d.size[0] / 2) * s + 'px';
|
||||
i.style.top = (d.center[1] + y * d.travel - d.size[1] / 2) * s + 'px';
|
||||
const want = `png/${sizeSel.value}/${st.btn.has(d.click) ? d.sprite_pressed : d.sprite}`;
|
||||
if (!i.src.endsWith(want)) i.src = want;
|
||||
}
|
||||
const tp = L.touchpad, dot = els.dot;
|
||||
dot.classList.toggle('off', !st.touch);
|
||||
if (st.touch) {
|
||||
dot.style.left = (tp.rect[0] + st.touch[0] * tp.rect[2] - tp.dot_size[0] / 2) * s + 'px';
|
||||
dot.style.top = (tp.rect[1] + st.touch[1] * tp.rect[3] - tp.dot_size[1] / 2) * s + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// --- Teclado ---
|
||||
const KEYS = {
|
||||
ArrowUp: 'dpad_up', ArrowDown: 'dpad_down', ArrowLeft: 'dpad_left', ArrowRight: 'dpad_right',
|
||||
KeyI: 'triangle', KeyJ: 'square', KeyK: 'cross', KeyL: 'circle', KeyQ: 'l1', KeyE: 'r1',
|
||||
KeyZ: 'l3', KeyX: 'r3', KeyC: 'create', KeyV: 'options', KeyP: 'ps', KeyM: 'mute', Space: 'touchpad',
|
||||
};
|
||||
const held = new Set();
|
||||
addEventListener('keydown', e => { if (KEYS[e.code] || /^(Key[WASDTFGH]|Digit[13])$/.test(e.code)) { held.add(e.code); e.preventDefault(); } });
|
||||
addEventListener('keyup', e => held.delete(e.code));
|
||||
addEventListener('blur', () => held.clear());
|
||||
const axis = (neg, pos) => (held.has(pos) ? 1 : 0) - (held.has(neg) ? 1 : 0);
|
||||
|
||||
// --- Ratón sobre el touchpad = un dedo ---
|
||||
ov.addEventListener('pointermove', e => {
|
||||
const r = ov.getBoundingClientRect(), s = els.s, tp = L.touchpad.rect;
|
||||
const u = ((e.clientX - r.left) / s - tp[0]) / tp[2], v = ((e.clientY - r.top) / s - tp[1]) / tp[3];
|
||||
st.mouseTouch = u >= 0 && u <= 1 && v >= 0 && v <= 1 ? [u, v] : null;
|
||||
});
|
||||
ov.addEventListener('pointerleave', () => { st.mouseTouch = null; });
|
||||
|
||||
// --- Gamepad API (mapeo "standard": así ve Chrome al DualSense) ---
|
||||
const GP = ['cross', 'circle', 'square', 'triangle', 'l1', 'r1', null, null, 'create', 'options',
|
||||
'l3', 'r3', 'dpad_up', 'dpad_down', 'dpad_left', 'dpad_right', 'ps', 'touchpad'];
|
||||
const dz = v => Math.abs(v) < 0.08 ? 0 : v;
|
||||
let last = performance.now();
|
||||
|
||||
function tick(now) {
|
||||
const dt = Math.min(0.1, (now - last) / 1000); last = now;
|
||||
const pad = [...(navigator.getGamepads?.() || [])].find(p => p && p.connected);
|
||||
st.btn.clear();
|
||||
if (pad) {
|
||||
srcTag.textContent = pad.id.replace(/\s*\(.*$/, '') || 'mando'; srcTag.className = 'pad';
|
||||
GP.forEach((n, i) => { if (n && pad.buttons[i]?.pressed) st.btn.add(n); });
|
||||
st.l2 = pad.buttons[6]?.value || 0; st.r2 = pad.buttons[7]?.value || 0;
|
||||
[st.lx, st.ly, st.rx, st.ry] = [0, 1, 2, 3].map(i => dz(pad.axes[i] || 0));
|
||||
} else {
|
||||
srcTag.textContent = 'teclado'; srcTag.className = '';
|
||||
for (const c of held) if (KEYS[c]) st.btn.add(KEYS[c]);
|
||||
const ramp = (v, on) => on ? Math.min(1, v + dt * 4) : Math.max(0, v - dt * 6);
|
||||
st.l2 = ramp(st.l2, held.has('Digit1')); st.r2 = ramp(st.r2, held.has('Digit3'));
|
||||
const norm = (x, y) => { const m = Math.hypot(x, y) || 1; return [x / Math.max(1, m), y / Math.max(1, m)]; };
|
||||
[st.lx, st.ly] = norm(axis('KeyA', 'KeyD'), axis('KeyW', 'KeyS'));
|
||||
[st.rx, st.ry] = norm(axis('KeyF', 'KeyH'), axis('KeyT', 'KeyG'));
|
||||
}
|
||||
if (st.l2 > 0.05) st.btn.add('l2');
|
||||
if (st.r2 > 0.05) st.btn.add('r2');
|
||||
st.touch = st.mouseTouch;
|
||||
draw();
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
sizeSel.addEventListener('change', build);
|
||||
bgSel.addEventListener('change', () => { document.getElementById('stage').className = bgSel.value; });
|
||||
build();
|
||||
requestAnimationFrame(tick);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+40
-40
@@ -1,41 +1,41 @@
|
||||
This work is licensed under the Creative Commons Attribution 3.0 Unported License.
|
||||
To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
This allow you the right to:
|
||||
- Share: copy and redistribute the material in any medium or format
|
||||
- Adapt: remix, transform, and build upon the material for any purpose, even commercially
|
||||
|
||||
Provided you follow the following terms:
|
||||
- Attribution: You must give appropriate credit, provide a link to the license and indicate if
|
||||
changes were made. You may do so in any reasonable manner, but not in any way that suggests
|
||||
the licensor endorses you or your use.
|
||||
- No additional restrictions — You may not apply legal terms or technological measures that
|
||||
legally restrict others from doing anything the license permits.
|
||||
|
||||
How to give credit for asset use, redistributions, or derivative works:
|
||||
|
||||
Must include:
|
||||
- Product Title: PS5 Button Icons and Controls
|
||||
- Author: Zacksly
|
||||
- Source: https://zacksly.itch.io
|
||||
- License: “CC BY 3.0"
|
||||
- If icons were edited from their original form it must be stated that it has been modified.
|
||||
|
||||
Example Attributions:
|
||||
Credits Section:
|
||||
"PS5 Button Icons and Controls - Zacksly
|
||||
Licensed under CC BY 3.0 - https://zacksly.itch.io"
|
||||
Statement:
|
||||
"Button Icons and Controls were created by Zacksly (Licensed under CC BY 3.0 - https://zacksly.itch.io")"
|
||||
Derivative Work:
|
||||
"Button Icons and Controls are based on project by Zacksly (Licensed under CC BY 3.0 - https://zacksly.itch.io)"
|
||||
Casual Thanks:
|
||||
"Special thanks to Zacksly for the Button Icons and Controls (Licensed under CC BY 3.0 - https://zacksly.itch.io)"
|
||||
Short form Credit:
|
||||
"Button Icons and Controls by Zacksly (CC BY 3.0 Licensed | zacksly.itch.io)"
|
||||
|
||||
Notices:
|
||||
- You do not have to comply with the license for elements of the material in the public domain or
|
||||
where your use is permitted by an applicable exception or limitation.
|
||||
- No warranties are given. The license may not give you all of the permissions necessary for your
|
||||
This work is licensed under the Creative Commons Attribution 3.0 Unported License.
|
||||
To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
This allow you the right to:
|
||||
- Share: copy and redistribute the material in any medium or format
|
||||
- Adapt: remix, transform, and build upon the material for any purpose, even commercially
|
||||
|
||||
Provided you follow the following terms:
|
||||
- Attribution: You must give appropriate credit, provide a link to the license and indicate if
|
||||
changes were made. You may do so in any reasonable manner, but not in any way that suggests
|
||||
the licensor endorses you or your use.
|
||||
- No additional restrictions — You may not apply legal terms or technological measures that
|
||||
legally restrict others from doing anything the license permits.
|
||||
|
||||
How to give credit for asset use, redistributions, or derivative works:
|
||||
|
||||
Must include:
|
||||
- Product Title: PS5 Button Icons and Controls
|
||||
- Author: Zacksly
|
||||
- Source: https://zacksly.itch.io
|
||||
- License: “CC BY 3.0"
|
||||
- If icons were edited from their original form it must be stated that it has been modified.
|
||||
|
||||
Example Attributions:
|
||||
Credits Section:
|
||||
"PS5 Button Icons and Controls - Zacksly
|
||||
Licensed under CC BY 3.0 - https://zacksly.itch.io"
|
||||
Statement:
|
||||
"Button Icons and Controls were created by Zacksly (Licensed under CC BY 3.0 - https://zacksly.itch.io")"
|
||||
Derivative Work:
|
||||
"Button Icons and Controls are based on project by Zacksly (Licensed under CC BY 3.0 - https://zacksly.itch.io)"
|
||||
Casual Thanks:
|
||||
"Special thanks to Zacksly for the Button Icons and Controls (Licensed under CC BY 3.0 - https://zacksly.itch.io)"
|
||||
Short form Credit:
|
||||
"Button Icons and Controls by Zacksly (CC BY 3.0 Licensed | zacksly.itch.io)"
|
||||
|
||||
Notices:
|
||||
- You do not have to comply with the license for elements of the material in the public domain or
|
||||
where your use is permitted by an applicable exception or limitation.
|
||||
- No warranties are given. The license may not give you all of the permissions necessary for your
|
||||
intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
|
||||
@@ -1,284 +1,284 @@
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 khalifa007 (SimpleFPS)
|
||||
* Modifications Copyright (C) 2026 erickdavestech
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace common_fps {
|
||||
|
||||
inline constexpr const char* kShellUiHookRequestPath =
|
||||
"/system_tmp/commonfps_v121_hook_request.bin";
|
||||
inline constexpr const char* kShellUiHookRequestTempPath =
|
||||
"/system_tmp/commonfps_v121_hook_request.tmp";
|
||||
inline constexpr const char* kShellUiHookAckPath =
|
||||
"/system_tmp/commonfps_v121_hook_ack.bin";
|
||||
inline constexpr const char* kShellUiHookAckTempPath =
|
||||
"/system_tmp/commonfps_v121_hook_ack.tmp";
|
||||
|
||||
inline constexpr std::uint64_t kShellUiHookRequestMagic =
|
||||
0x395145524b484643ULL; /* "CFHKREQ9" */
|
||||
inline constexpr std::uint64_t kShellUiHookAckMagic =
|
||||
0x394b43414b484643ULL; /* "CFHKACK9" */
|
||||
inline constexpr std::uint32_t kShellUiHookProtocolVersion = 9;
|
||||
inline constexpr std::size_t kShellUiHookPatchSize = 16;
|
||||
inline constexpr std::uint8_t kShellUiAbsoluteJumpPrefix[6] = {
|
||||
0xff, 0x25, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
enum class ShellUiHookBackend { Unsupported, Mdbg, PtraceIo };
|
||||
|
||||
enum class ShellUiHookAckPhase : std::uint8_t {
|
||||
Patch = 0,
|
||||
Probe = 1,
|
||||
MainThreadGuard = 2,
|
||||
};
|
||||
|
||||
inline ShellUiHookBackend shellui_hook_backend(
|
||||
std::uint32_t sdk, bool eta_chain) noexcept {
|
||||
const std::uint32_t family = sdk & 0xffff0000U;
|
||||
if ((family >= 0x03000000U && family <= 0x08200000U) ||
|
||||
(family >= 0x11000000U && family <= 0x13ff0000U))
|
||||
return ShellUiHookBackend::Mdbg;
|
||||
if (family == 0x09600000U && eta_chain)
|
||||
return ShellUiHookBackend::PtraceIo;
|
||||
return ShellUiHookBackend::Unsupported;
|
||||
}
|
||||
|
||||
/*
|
||||
* The background-PUI path never replaces Application.Update. It changes only
|
||||
* the first byte of Diagnostics.CheckRunningOnMainThread to RET while ShellUI
|
||||
* is ptrace-stopped. FW 1.xx-8.20 uses MDBG; selected higher firmware uses
|
||||
* ptrace I/O. FW 9.60 deliberately keeps the hardware-proven update-chain
|
||||
* backend. Unknown future firmware fails closed.
|
||||
*/
|
||||
inline ShellUiHookBackend shellui_main_thread_guard_backend(
|
||||
std::uint32_t sdk) noexcept {
|
||||
const std::uint32_t family = sdk & 0xffff0000U;
|
||||
|
||||
/*
|
||||
* Low/mid firmware uses the controller MDBG backend. Higher firmware
|
||||
* keeps the same one-byte, expected-byte-verified guard transaction but
|
||||
* uses ptrace I/O, which is already required by the ShellUI loader.
|
||||
*
|
||||
* FW 9.60 deliberately stays on the hardware-proven Application.Update
|
||||
* chain path; returning Unsupported here selects that existing backend.
|
||||
*/
|
||||
if ((family >= 0x01000000U && family <= 0x08200000U) ||
|
||||
family == 0x09000000U ||
|
||||
(family >= 0x11000000U && family <= 0x13ff0000U))
|
||||
return ShellUiHookBackend::Mdbg;
|
||||
if ((family >= 0x08300000U && family < 0x09000000U) ||
|
||||
(family > 0x09000000U && family < 0x09600000U) ||
|
||||
(family > 0x09600000U && family <= 0x13ff0000U))
|
||||
return ShellUiHookBackend::PtraceIo;
|
||||
return ShellUiHookBackend::Unsupported;
|
||||
}
|
||||
|
||||
enum class ShellUiHookStatus : std::int32_t {
|
||||
Success = 0,
|
||||
InvalidRequest = -1,
|
||||
UnsupportedFirmware = -2,
|
||||
AuthFailure = -3,
|
||||
AttachFailure = -4,
|
||||
ExpectedBytesMismatch = -5,
|
||||
WriteFailure = -6,
|
||||
VerifyFailure = -7,
|
||||
DetachFailure = -8,
|
||||
AuthRestoreFailure = -9,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ShellUiHookRequest {
|
||||
std::uint64_t magic = kShellUiHookRequestMagic;
|
||||
std::uint32_t version = kShellUiHookProtocolVersion;
|
||||
std::int32_t pid = -1;
|
||||
std::uint64_t nonce = 0;
|
||||
std::uint64_t method_address = 0;
|
||||
std::uint64_t hook_address = 0;
|
||||
std::uint64_t original_call_address = 0;
|
||||
std::uint32_t patch_size = kShellUiHookPatchSize;
|
||||
std::uint32_t displaced_size = 0;
|
||||
std::uint8_t expected[kShellUiHookPatchSize]{};
|
||||
std::uint8_t desired[kShellUiHookPatchSize]{};
|
||||
std::uint32_t checksum = 0;
|
||||
};
|
||||
|
||||
struct ShellUiHookAck {
|
||||
std::uint64_t magic = kShellUiHookAckMagic;
|
||||
std::uint32_t version = kShellUiHookProtocolVersion;
|
||||
std::int32_t pid = -1;
|
||||
std::uint64_t nonce = 0;
|
||||
std::int32_t status =
|
||||
static_cast<std::int32_t>(ShellUiHookStatus::InvalidRequest);
|
||||
std::int32_t read_rc = -1;
|
||||
std::int32_t write_rc = -1;
|
||||
std::uint8_t verified = 0;
|
||||
std::uint8_t restored = 0;
|
||||
std::uint8_t detached = 0;
|
||||
std::uint8_t auth_restored = 0;
|
||||
std::uint8_t phase =
|
||||
static_cast<std::uint8_t>(ShellUiHookAckPhase::Patch);
|
||||
std::uint8_t observed[kShellUiHookPatchSize]{};
|
||||
std::uint32_t checksum = 0;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(ShellUiHookRequest) == 92);
|
||||
static_assert(sizeof(ShellUiHookAck) == 61);
|
||||
|
||||
inline std::uint32_t shellui_hook_checksum(
|
||||
const void* data,
|
||||
std::size_t size) noexcept {
|
||||
|
||||
const auto* bytes = static_cast<const std::uint8_t*>(data);
|
||||
std::uint32_t hash = 2166136261U;
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
hash ^= bytes[i];
|
||||
hash *= 16777619U;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline std::uint32_t shellui_hook_request_checksum(
|
||||
const ShellUiHookRequest& request) noexcept {
|
||||
|
||||
return shellui_hook_checksum(
|
||||
&request,
|
||||
offsetof(ShellUiHookRequest, checksum));
|
||||
}
|
||||
|
||||
inline std::uint32_t shellui_hook_ack_checksum(
|
||||
const ShellUiHookAck& ack) noexcept {
|
||||
|
||||
return shellui_hook_checksum(
|
||||
&ack,
|
||||
offsetof(ShellUiHookAck, checksum));
|
||||
}
|
||||
|
||||
inline bool shellui_hook_is_eta_chain(
|
||||
const ShellUiHookRequest& request) noexcept {
|
||||
return std::memcmp(request.expected, kShellUiAbsoluteJumpPrefix,
|
||||
sizeof(kShellUiAbsoluteJumpPrefix)) == 0;
|
||||
}
|
||||
|
||||
inline bool shellui_user_address(std::uint64_t address) noexcept {
|
||||
return address >= 0x10000ULL && address < 0x0000800000000000ULL;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_request_common_is_valid(
|
||||
const ShellUiHookRequest& request, std::int32_t pid) noexcept {
|
||||
if (request.magic != kShellUiHookRequestMagic ||
|
||||
request.version != kShellUiHookProtocolVersion ||
|
||||
request.pid != pid || pid <= 0 || request.nonce == 0 ||
|
||||
!shellui_user_address(request.method_address) ||
|
||||
!shellui_user_address(request.method_address + kShellUiHookPatchSize - 1) ||
|
||||
!shellui_user_address(request.hook_address) ||
|
||||
request.patch_size != kShellUiHookPatchSize ||
|
||||
request.checksum != shellui_hook_request_checksum(request))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_request_is_probe(
|
||||
const ShellUiHookRequest& request) noexcept {
|
||||
return request.displaced_size == 0;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_request_is_main_thread_guard(
|
||||
const ShellUiHookRequest& request) noexcept {
|
||||
return request.displaced_size == 1 &&
|
||||
request.original_call_address == 0 &&
|
||||
request.hook_address == request.method_address;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_probe_request_is_valid(
|
||||
const ShellUiHookRequest& request, std::int32_t pid) noexcept {
|
||||
|
||||
if (!shellui_hook_request_common_is_valid(request, pid) ||
|
||||
request.original_call_address != 0 ||
|
||||
!shellui_hook_request_is_probe(request))
|
||||
return false;
|
||||
|
||||
for (std::size_t i = 0; i < kShellUiHookPatchSize; ++i)
|
||||
if (request.expected[i] != 0 || request.desired[i] != 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool shellui_main_thread_guard_request_is_valid(
|
||||
const ShellUiHookRequest& request, std::int32_t pid) noexcept {
|
||||
|
||||
if (!shellui_hook_request_common_is_valid(request, pid) ||
|
||||
!shellui_hook_request_is_main_thread_guard(request) ||
|
||||
request.expected[0] == 0xc3 ||
|
||||
request.desired[0] != 0xc3)
|
||||
return false;
|
||||
|
||||
for (std::size_t i = 1; i < kShellUiHookPatchSize; ++i)
|
||||
if (request.desired[i] != request.expected[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_request_is_valid(
|
||||
const ShellUiHookRequest& request, std::int32_t pid) noexcept {
|
||||
|
||||
if (!shellui_hook_request_common_is_valid(request, pid) ||
|
||||
!shellui_user_address(request.original_call_address) ||
|
||||
request.original_call_address == request.hook_address ||
|
||||
request.original_call_address == request.method_address ||
|
||||
request.displaced_size < 14 || request.displaced_size > 16 ||
|
||||
shellui_hook_request_is_probe(request) ||
|
||||
shellui_hook_request_is_main_thread_guard(request) ||
|
||||
std::memcmp(request.desired, kShellUiAbsoluteJumpPrefix, 6) != 0)
|
||||
return false;
|
||||
|
||||
std::uint64_t target = 0;
|
||||
std::memcpy(&target, request.desired + 6, sizeof(target));
|
||||
if (target != request.hook_address)
|
||||
return false;
|
||||
|
||||
if (shellui_hook_is_eta_chain(request)) {
|
||||
std::uint64_t previous = 0;
|
||||
std::memcpy(&previous, request.expected + 6, sizeof(previous));
|
||||
if (request.displaced_size != 14 ||
|
||||
previous != request.original_call_address)
|
||||
return false;
|
||||
} else {
|
||||
for (std::size_t i = 14; i < request.displaced_size; ++i)
|
||||
if (request.desired[i] != 0x90)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (std::size_t i = request.displaced_size;
|
||||
i < kShellUiHookPatchSize;
|
||||
++i)
|
||||
if (request.desired[i] != request.expected[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline ShellUiHookBackend shellui_hook_probe_backend(
|
||||
std::uint32_t sdk) noexcept {
|
||||
const std::uint32_t family = sdk & 0xffff0000U;
|
||||
if ((family >= 0x01000000U && family <= 0x08200000U) ||
|
||||
family == 0x09000000U ||
|
||||
(family >= 0x11000000U && family <= 0x13ff0000U))
|
||||
return ShellUiHookBackend::Mdbg;
|
||||
if (family >= 0x08300000U && family <= 0x13ff0000U)
|
||||
return ShellUiHookBackend::PtraceIo;
|
||||
return ShellUiHookBackend::Unsupported;
|
||||
}
|
||||
|
||||
} // namespace common_fps
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 khalifa007 (SimpleFPS)
|
||||
* Modifications Copyright (C) 2026 erickdavestech
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace common_fps {
|
||||
|
||||
inline constexpr const char* kShellUiHookRequestPath =
|
||||
"/system_tmp/commonfps_v121_hook_request.bin";
|
||||
inline constexpr const char* kShellUiHookRequestTempPath =
|
||||
"/system_tmp/commonfps_v121_hook_request.tmp";
|
||||
inline constexpr const char* kShellUiHookAckPath =
|
||||
"/system_tmp/commonfps_v121_hook_ack.bin";
|
||||
inline constexpr const char* kShellUiHookAckTempPath =
|
||||
"/system_tmp/commonfps_v121_hook_ack.tmp";
|
||||
|
||||
inline constexpr std::uint64_t kShellUiHookRequestMagic =
|
||||
0x395145524b484643ULL; /* "CFHKREQ9" */
|
||||
inline constexpr std::uint64_t kShellUiHookAckMagic =
|
||||
0x394b43414b484643ULL; /* "CFHKACK9" */
|
||||
inline constexpr std::uint32_t kShellUiHookProtocolVersion = 9;
|
||||
inline constexpr std::size_t kShellUiHookPatchSize = 16;
|
||||
inline constexpr std::uint8_t kShellUiAbsoluteJumpPrefix[6] = {
|
||||
0xff, 0x25, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
enum class ShellUiHookBackend { Unsupported, Mdbg, PtraceIo };
|
||||
|
||||
enum class ShellUiHookAckPhase : std::uint8_t {
|
||||
Patch = 0,
|
||||
Probe = 1,
|
||||
MainThreadGuard = 2,
|
||||
};
|
||||
|
||||
inline ShellUiHookBackend shellui_hook_backend(
|
||||
std::uint32_t sdk, bool eta_chain) noexcept {
|
||||
const std::uint32_t family = sdk & 0xffff0000U;
|
||||
if ((family >= 0x03000000U && family <= 0x08200000U) ||
|
||||
(family >= 0x11000000U && family <= 0x13ff0000U))
|
||||
return ShellUiHookBackend::Mdbg;
|
||||
if (family == 0x09600000U && eta_chain)
|
||||
return ShellUiHookBackend::PtraceIo;
|
||||
return ShellUiHookBackend::Unsupported;
|
||||
}
|
||||
|
||||
/*
|
||||
* The background-PUI path never replaces Application.Update. It changes only
|
||||
* the first byte of Diagnostics.CheckRunningOnMainThread to RET while ShellUI
|
||||
* is ptrace-stopped. FW 1.xx-8.20 uses MDBG; selected higher firmware uses
|
||||
* ptrace I/O. FW 9.60 deliberately keeps the hardware-proven update-chain
|
||||
* backend. Unknown future firmware fails closed.
|
||||
*/
|
||||
inline ShellUiHookBackend shellui_main_thread_guard_backend(
|
||||
std::uint32_t sdk) noexcept {
|
||||
const std::uint32_t family = sdk & 0xffff0000U;
|
||||
|
||||
/*
|
||||
* Low/mid firmware uses the controller MDBG backend. Higher firmware
|
||||
* keeps the same one-byte, expected-byte-verified guard transaction but
|
||||
* uses ptrace I/O, which is already required by the ShellUI loader.
|
||||
*
|
||||
* FW 9.60 deliberately stays on the hardware-proven Application.Update
|
||||
* chain path; returning Unsupported here selects that existing backend.
|
||||
*/
|
||||
if ((family >= 0x01000000U && family <= 0x08200000U) ||
|
||||
family == 0x09000000U ||
|
||||
(family >= 0x11000000U && family <= 0x13ff0000U))
|
||||
return ShellUiHookBackend::Mdbg;
|
||||
if ((family >= 0x08300000U && family < 0x09000000U) ||
|
||||
(family > 0x09000000U && family < 0x09600000U) ||
|
||||
(family > 0x09600000U && family <= 0x13ff0000U))
|
||||
return ShellUiHookBackend::PtraceIo;
|
||||
return ShellUiHookBackend::Unsupported;
|
||||
}
|
||||
|
||||
enum class ShellUiHookStatus : std::int32_t {
|
||||
Success = 0,
|
||||
InvalidRequest = -1,
|
||||
UnsupportedFirmware = -2,
|
||||
AuthFailure = -3,
|
||||
AttachFailure = -4,
|
||||
ExpectedBytesMismatch = -5,
|
||||
WriteFailure = -6,
|
||||
VerifyFailure = -7,
|
||||
DetachFailure = -8,
|
||||
AuthRestoreFailure = -9,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct ShellUiHookRequest {
|
||||
std::uint64_t magic = kShellUiHookRequestMagic;
|
||||
std::uint32_t version = kShellUiHookProtocolVersion;
|
||||
std::int32_t pid = -1;
|
||||
std::uint64_t nonce = 0;
|
||||
std::uint64_t method_address = 0;
|
||||
std::uint64_t hook_address = 0;
|
||||
std::uint64_t original_call_address = 0;
|
||||
std::uint32_t patch_size = kShellUiHookPatchSize;
|
||||
std::uint32_t displaced_size = 0;
|
||||
std::uint8_t expected[kShellUiHookPatchSize]{};
|
||||
std::uint8_t desired[kShellUiHookPatchSize]{};
|
||||
std::uint32_t checksum = 0;
|
||||
};
|
||||
|
||||
struct ShellUiHookAck {
|
||||
std::uint64_t magic = kShellUiHookAckMagic;
|
||||
std::uint32_t version = kShellUiHookProtocolVersion;
|
||||
std::int32_t pid = -1;
|
||||
std::uint64_t nonce = 0;
|
||||
std::int32_t status =
|
||||
static_cast<std::int32_t>(ShellUiHookStatus::InvalidRequest);
|
||||
std::int32_t read_rc = -1;
|
||||
std::int32_t write_rc = -1;
|
||||
std::uint8_t verified = 0;
|
||||
std::uint8_t restored = 0;
|
||||
std::uint8_t detached = 0;
|
||||
std::uint8_t auth_restored = 0;
|
||||
std::uint8_t phase =
|
||||
static_cast<std::uint8_t>(ShellUiHookAckPhase::Patch);
|
||||
std::uint8_t observed[kShellUiHookPatchSize]{};
|
||||
std::uint32_t checksum = 0;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(ShellUiHookRequest) == 92);
|
||||
static_assert(sizeof(ShellUiHookAck) == 61);
|
||||
|
||||
inline std::uint32_t shellui_hook_checksum(
|
||||
const void* data,
|
||||
std::size_t size) noexcept {
|
||||
|
||||
const auto* bytes = static_cast<const std::uint8_t*>(data);
|
||||
std::uint32_t hash = 2166136261U;
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
hash ^= bytes[i];
|
||||
hash *= 16777619U;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline std::uint32_t shellui_hook_request_checksum(
|
||||
const ShellUiHookRequest& request) noexcept {
|
||||
|
||||
return shellui_hook_checksum(
|
||||
&request,
|
||||
offsetof(ShellUiHookRequest, checksum));
|
||||
}
|
||||
|
||||
inline std::uint32_t shellui_hook_ack_checksum(
|
||||
const ShellUiHookAck& ack) noexcept {
|
||||
|
||||
return shellui_hook_checksum(
|
||||
&ack,
|
||||
offsetof(ShellUiHookAck, checksum));
|
||||
}
|
||||
|
||||
inline bool shellui_hook_is_eta_chain(
|
||||
const ShellUiHookRequest& request) noexcept {
|
||||
return std::memcmp(request.expected, kShellUiAbsoluteJumpPrefix,
|
||||
sizeof(kShellUiAbsoluteJumpPrefix)) == 0;
|
||||
}
|
||||
|
||||
inline bool shellui_user_address(std::uint64_t address) noexcept {
|
||||
return address >= 0x10000ULL && address < 0x0000800000000000ULL;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_request_common_is_valid(
|
||||
const ShellUiHookRequest& request, std::int32_t pid) noexcept {
|
||||
if (request.magic != kShellUiHookRequestMagic ||
|
||||
request.version != kShellUiHookProtocolVersion ||
|
||||
request.pid != pid || pid <= 0 || request.nonce == 0 ||
|
||||
!shellui_user_address(request.method_address) ||
|
||||
!shellui_user_address(request.method_address + kShellUiHookPatchSize - 1) ||
|
||||
!shellui_user_address(request.hook_address) ||
|
||||
request.patch_size != kShellUiHookPatchSize ||
|
||||
request.checksum != shellui_hook_request_checksum(request))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_request_is_probe(
|
||||
const ShellUiHookRequest& request) noexcept {
|
||||
return request.displaced_size == 0;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_request_is_main_thread_guard(
|
||||
const ShellUiHookRequest& request) noexcept {
|
||||
return request.displaced_size == 1 &&
|
||||
request.original_call_address == 0 &&
|
||||
request.hook_address == request.method_address;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_probe_request_is_valid(
|
||||
const ShellUiHookRequest& request, std::int32_t pid) noexcept {
|
||||
|
||||
if (!shellui_hook_request_common_is_valid(request, pid) ||
|
||||
request.original_call_address != 0 ||
|
||||
!shellui_hook_request_is_probe(request))
|
||||
return false;
|
||||
|
||||
for (std::size_t i = 0; i < kShellUiHookPatchSize; ++i)
|
||||
if (request.expected[i] != 0 || request.desired[i] != 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool shellui_main_thread_guard_request_is_valid(
|
||||
const ShellUiHookRequest& request, std::int32_t pid) noexcept {
|
||||
|
||||
if (!shellui_hook_request_common_is_valid(request, pid) ||
|
||||
!shellui_hook_request_is_main_thread_guard(request) ||
|
||||
request.expected[0] == 0xc3 ||
|
||||
request.desired[0] != 0xc3)
|
||||
return false;
|
||||
|
||||
for (std::size_t i = 1; i < kShellUiHookPatchSize; ++i)
|
||||
if (request.desired[i] != request.expected[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool shellui_hook_request_is_valid(
|
||||
const ShellUiHookRequest& request, std::int32_t pid) noexcept {
|
||||
|
||||
if (!shellui_hook_request_common_is_valid(request, pid) ||
|
||||
!shellui_user_address(request.original_call_address) ||
|
||||
request.original_call_address == request.hook_address ||
|
||||
request.original_call_address == request.method_address ||
|
||||
request.displaced_size < 14 || request.displaced_size > 16 ||
|
||||
shellui_hook_request_is_probe(request) ||
|
||||
shellui_hook_request_is_main_thread_guard(request) ||
|
||||
std::memcmp(request.desired, kShellUiAbsoluteJumpPrefix, 6) != 0)
|
||||
return false;
|
||||
|
||||
std::uint64_t target = 0;
|
||||
std::memcpy(&target, request.desired + 6, sizeof(target));
|
||||
if (target != request.hook_address)
|
||||
return false;
|
||||
|
||||
if (shellui_hook_is_eta_chain(request)) {
|
||||
std::uint64_t previous = 0;
|
||||
std::memcpy(&previous, request.expected + 6, sizeof(previous));
|
||||
if (request.displaced_size != 14 ||
|
||||
previous != request.original_call_address)
|
||||
return false;
|
||||
} else {
|
||||
for (std::size_t i = 14; i < request.displaced_size; ++i)
|
||||
if (request.desired[i] != 0x90)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (std::size_t i = request.displaced_size;
|
||||
i < kShellUiHookPatchSize;
|
||||
++i)
|
||||
if (request.desired[i] != request.expected[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline ShellUiHookBackend shellui_hook_probe_backend(
|
||||
std::uint32_t sdk) noexcept {
|
||||
const std::uint32_t family = sdk & 0xffff0000U;
|
||||
if ((family >= 0x01000000U && family <= 0x08200000U) ||
|
||||
family == 0x09000000U ||
|
||||
(family >= 0x11000000U && family <= 0x13ff0000U))
|
||||
return ShellUiHookBackend::Mdbg;
|
||||
if (family >= 0x08300000U && family <= 0x13ff0000U)
|
||||
return ShellUiHookBackend::PtraceIo;
|
||||
return ShellUiHookBackend::Unsupported;
|
||||
}
|
||||
|
||||
} // namespace common_fps
|
||||
@@ -1,44 +1,44 @@
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 khalifa007 (SimpleFPS)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace common_fps {
|
||||
|
||||
using ProcessId = int;
|
||||
|
||||
enum class Corner {
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
};
|
||||
|
||||
struct Vec2 {
|
||||
float x{};
|
||||
float y{};
|
||||
};
|
||||
|
||||
struct OverlayConfig {
|
||||
Corner corner = Corner::TopLeft;
|
||||
int font_size = 24;
|
||||
float margin_x = 10.0f;
|
||||
float margin_y = 10.0f;
|
||||
};
|
||||
|
||||
struct OverlayFrame {
|
||||
bool visible = false;
|
||||
bool loading = true;
|
||||
int fps = 0;
|
||||
OverlayConfig config{};
|
||||
Vec2 anchor{};
|
||||
};
|
||||
|
||||
} // namespace common_fps
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 khalifa007 (SimpleFPS)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace common_fps {
|
||||
|
||||
using ProcessId = int;
|
||||
|
||||
enum class Corner {
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
};
|
||||
|
||||
struct Vec2 {
|
||||
float x{};
|
||||
float y{};
|
||||
};
|
||||
|
||||
struct OverlayConfig {
|
||||
Corner corner = Corner::TopLeft;
|
||||
int font_size = 24;
|
||||
float margin_x = 10.0f;
|
||||
float margin_y = 10.0f;
|
||||
};
|
||||
|
||||
struct OverlayFrame {
|
||||
bool visible = false;
|
||||
bool loading = true;
|
||||
int fps = 0;
|
||||
OverlayConfig config{};
|
||||
Vec2 anchor{};
|
||||
};
|
||||
|
||||
} // namespace common_fps
|
||||
+54
-54
@@ -1,54 +1,54 @@
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 khalifa007 (SimpleFPS)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common_fps/types.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace common_fps {
|
||||
|
||||
inline constexpr std::uint32_t kWireMagic = 0x53465043; // "CFPS"
|
||||
inline constexpr std::uint32_t kWireShutdownMagic = 0x58465043; // "CFPX"
|
||||
inline constexpr std::uint16_t kWireVersion = 1;
|
||||
inline constexpr std::uint16_t kDefaultIpcPort = 39028;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct WirePacket {
|
||||
std::uint32_t magic = kWireMagic;
|
||||
std::uint16_t version = kWireVersion;
|
||||
std::uint16_t size = sizeof(WirePacket);
|
||||
|
||||
std::uint64_t sequence = 0;
|
||||
|
||||
std::int32_t fps = 0;
|
||||
std::uint8_t loading = 1;
|
||||
std::uint8_t corner = 0; // TopLeft
|
||||
std::uint16_t font_size = 24;
|
||||
|
||||
float margin_x = 10.0f;
|
||||
float margin_y = 10.0f;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
WirePacket make_wire_packet(
|
||||
const OverlayFrame& frame,
|
||||
std::uint64_t sequence);
|
||||
|
||||
/* Reserved controller-to-ShellUI quiesce packet; v1.2.0 does not send it. */
|
||||
WirePacket make_shutdown_wire_packet(std::uint64_t sequence);
|
||||
|
||||
[[nodiscard]] bool
|
||||
is_shutdown_wire_packet(const WirePacket& packet) noexcept;
|
||||
|
||||
std::optional<OverlayFrame>
|
||||
decode_wire_packet(const WirePacket& packet);
|
||||
|
||||
} // namespace common_fps
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 khalifa007 (SimpleFPS)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common_fps/types.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace common_fps {
|
||||
|
||||
inline constexpr std::uint32_t kWireMagic = 0x53465043; // "CFPS"
|
||||
inline constexpr std::uint32_t kWireShutdownMagic = 0x58465043; // "CFPX"
|
||||
inline constexpr std::uint16_t kWireVersion = 1;
|
||||
inline constexpr std::uint16_t kDefaultIpcPort = 39028;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct WirePacket {
|
||||
std::uint32_t magic = kWireMagic;
|
||||
std::uint16_t version = kWireVersion;
|
||||
std::uint16_t size = sizeof(WirePacket);
|
||||
|
||||
std::uint64_t sequence = 0;
|
||||
|
||||
std::int32_t fps = 0;
|
||||
std::uint8_t loading = 1;
|
||||
std::uint8_t corner = 0; // TopLeft
|
||||
std::uint16_t font_size = 24;
|
||||
|
||||
float margin_x = 10.0f;
|
||||
float margin_y = 10.0f;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
WirePacket make_wire_packet(
|
||||
const OverlayFrame& frame,
|
||||
std::uint64_t sequence);
|
||||
|
||||
/* Reserved controller-to-ShellUI quiesce packet; v1.2.0 does not send it. */
|
||||
WirePacket make_shutdown_wire_packet(std::uint64_t sequence);
|
||||
|
||||
[[nodiscard]] bool
|
||||
is_shutdown_wire_packet(const WirePacket& packet) noexcept;
|
||||
|
||||
std::optional<OverlayFrame>
|
||||
decode_wire_packet(const WirePacket& packet);
|
||||
|
||||
} // namespace common_fps
|
||||
+49
-49
@@ -1,49 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DEPS="${ROOT}/.deps"
|
||||
SDK_ZIP="/tmp/ps5-payload-sdk-v0.43.zip"
|
||||
|
||||
ETAHEN_VERSION="2.4B"
|
||||
ETAHEN_COMMIT="d47f99bd37f349ae59b3c4b66e09e93ba69f56cd"
|
||||
|
||||
mkdir -p "${DEPS}"
|
||||
|
||||
echo "[1/4] PS5 Payload SDK v0.43"
|
||||
wget -q \
|
||||
https://github.com/ps5-payload-dev/sdk/releases/download/v0.43/ps5-payload-sdk.zip \
|
||||
-O "${SDK_ZIP}"
|
||||
|
||||
sudo rm -rf /opt/ps5-payload-sdk
|
||||
sudo unzip -q "${SDK_ZIP}" -d /opt
|
||||
|
||||
export PS5_PAYLOAD_SDK=/opt/ps5-payload-sdk
|
||||
test -x "${PS5_PAYLOAD_SDK}/bin/prospero-cmake"
|
||||
|
||||
echo "[2/4] etaHEN ${ETAHEN_VERSION} source (${ETAHEN_COMMIT})"
|
||||
rm -rf "${DEPS}/etahen"
|
||||
git clone -q https://github.com/etaHEN/etaHEN.git "${DEPS}/etahen"
|
||||
git -C "${DEPS}/etahen" checkout -q "${ETAHEN_COMMIT}"
|
||||
|
||||
RESOLVED_ETAHEN="$(git -C "${DEPS}/etahen" rev-parse HEAD)"
|
||||
if [ "${RESOLVED_ETAHEN}" != "${ETAHEN_COMMIT}" ]; then
|
||||
echo "ERROR: etaHEN revision mismatch"
|
||||
echo "Expected: ${ETAHEN_COMMIT}"
|
||||
echo "Actual: ${RESOLVED_ETAHEN}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[3/4] ps5-payload-dev/shsrv v0.20 source"
|
||||
rm -rf "${DEPS}/shsrv"
|
||||
git clone -q --branch v0.20 --depth 1 \
|
||||
https://github.com/ps5-payload-dev/shsrv.git "${DEPS}/shsrv"
|
||||
|
||||
echo "[4/4] Record exact resolved revisions"
|
||||
{
|
||||
echo "PS5 Payload SDK: v0.43"
|
||||
echo "etaHEN: ${RESOLVED_ETAHEN} (${ETAHEN_VERSION})"
|
||||
echo "shsrv: $(git -C "${DEPS}/shsrv" rev-parse HEAD)"
|
||||
} > "${ROOT}/RESOLVED_BUILD_DEPENDENCIES.txt"
|
||||
|
||||
cat "${ROOT}/RESOLVED_BUILD_DEPENDENCIES.txt"
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DEPS="${ROOT}/.deps"
|
||||
SDK_ZIP="/tmp/ps5-payload-sdk-v0.43.zip"
|
||||
|
||||
ETAHEN_VERSION="2.4B"
|
||||
ETAHEN_COMMIT="d47f99bd37f349ae59b3c4b66e09e93ba69f56cd"
|
||||
|
||||
mkdir -p "${DEPS}"
|
||||
|
||||
echo "[1/4] PS5 Payload SDK v0.43"
|
||||
wget -q \
|
||||
https://github.com/ps5-payload-dev/sdk/releases/download/v0.43/ps5-payload-sdk.zip \
|
||||
-O "${SDK_ZIP}"
|
||||
|
||||
sudo rm -rf /opt/ps5-payload-sdk
|
||||
sudo unzip -q "${SDK_ZIP}" -d /opt
|
||||
|
||||
export PS5_PAYLOAD_SDK=/opt/ps5-payload-sdk
|
||||
test -x "${PS5_PAYLOAD_SDK}/bin/prospero-cmake"
|
||||
|
||||
echo "[2/4] etaHEN ${ETAHEN_VERSION} source (${ETAHEN_COMMIT})"
|
||||
rm -rf "${DEPS}/etahen"
|
||||
git clone -q https://github.com/etaHEN/etaHEN.git "${DEPS}/etahen"
|
||||
git -C "${DEPS}/etahen" checkout -q "${ETAHEN_COMMIT}"
|
||||
|
||||
RESOLVED_ETAHEN="$(git -C "${DEPS}/etahen" rev-parse HEAD)"
|
||||
if [ "${RESOLVED_ETAHEN}" != "${ETAHEN_COMMIT}" ]; then
|
||||
echo "ERROR: etaHEN revision mismatch"
|
||||
echo "Expected: ${ETAHEN_COMMIT}"
|
||||
echo "Actual: ${RESOLVED_ETAHEN}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[3/4] ps5-payload-dev/shsrv v0.20 source"
|
||||
rm -rf "${DEPS}/shsrv"
|
||||
git clone -q --branch v0.20 --depth 1 \
|
||||
https://github.com/ps5-payload-dev/shsrv.git "${DEPS}/shsrv"
|
||||
|
||||
echo "[4/4] Record exact resolved revisions"
|
||||
{
|
||||
echo "PS5 Payload SDK: v0.43"
|
||||
echo "etaHEN: ${RESOLVED_ETAHEN} (${ETAHEN_VERSION})"
|
||||
echo "shsrv: $(git -C "${DEPS}/shsrv" rev-parse HEAD)"
|
||||
} > "${ROOT}/RESOLVED_BUILD_DEPENDENCIES.txt"
|
||||
|
||||
cat "${ROOT}/RESOLVED_BUILD_DEPENDENCIES.txt"
|
||||
+332
-332
@@ -1,332 +1,332 @@
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 khalifa007 (SimpleFPS)
|
||||
* Modifications Copyright (C) 2026 erickdavestech
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "shellui_hook_controller.hpp"
|
||||
|
||||
#include "stable_sampler/proc_rw_v960.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
extern "C" {
|
||||
#include <ps5/kernel.h>
|
||||
#include <ps5/mdbg.h>
|
||||
#include "pt.h"
|
||||
}
|
||||
|
||||
namespace common_fps::ps5 {
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t kPtraceAuthId = 0x4800000000010003ULL;
|
||||
constexpr std::uint64_t kPs5CodePageSize = 0x4000ULL;
|
||||
|
||||
template <typename T>
|
||||
bool read_exact_file(const char* path, T& value) noexcept {
|
||||
FILE* fp = std::fopen(path, "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
const std::size_t count = std::fread(&value, 1, sizeof(value), fp);
|
||||
const int trailing = std::fgetc(fp);
|
||||
std::fclose(fp);
|
||||
return count == sizeof(value) && trailing == EOF;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool write_exact_file_atomic(
|
||||
const char* temporary_path,
|
||||
const char* final_path,
|
||||
const T& value) noexcept {
|
||||
|
||||
FILE* fp = std::fopen(temporary_path, "wb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
const bool complete =
|
||||
std::fwrite(&value, 1, sizeof(value), fp) == sizeof(value) &&
|
||||
std::fflush(fp) == 0;
|
||||
const bool closed = std::fclose(fp) == 0;
|
||||
if (!complete || !closed) {
|
||||
(void)unlink(temporary_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::rename(temporary_path, final_path) != 0) {
|
||||
(void)unlink(temporary_path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void write_ack(
|
||||
const ShellUiHookRequest& request,
|
||||
const ShellUiHookPatchReport& report) noexcept {
|
||||
|
||||
ShellUiHookAck ack{};
|
||||
ack.pid = request.pid;
|
||||
ack.nonce = request.nonce;
|
||||
ack.status = static_cast<std::int32_t>(report.status);
|
||||
ack.read_rc = report.read_rc;
|
||||
ack.write_rc = report.write_rc;
|
||||
ack.verified = report.verified ? 1U : 0U;
|
||||
ack.restored = report.restored ? 1U : 0U;
|
||||
ack.detached = report.detached ? 1U : 0U;
|
||||
ack.auth_restored = report.auth_restored ? 1U : 0U;
|
||||
ack.phase = static_cast<std::uint8_t>(
|
||||
report.probe
|
||||
? ShellUiHookAckPhase::Probe
|
||||
: (report.main_thread_guard
|
||||
? ShellUiHookAckPhase::MainThreadGuard
|
||||
: ShellUiHookAckPhase::Patch));
|
||||
std::memcpy(
|
||||
ack.observed,
|
||||
report.observed.data(),
|
||||
report.observed.size());
|
||||
ack.checksum = shellui_hook_ack_checksum(ack);
|
||||
(void)write_exact_file_atomic(
|
||||
kShellUiHookAckTempPath,
|
||||
kShellUiHookAckPath,
|
||||
ack);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void clear_shellui_hook_protocol_files() noexcept {
|
||||
(void)unlink(kShellUiHookRequestPath);
|
||||
(void)unlink(kShellUiHookRequestTempPath);
|
||||
(void)unlink(kShellUiHookAckPath);
|
||||
(void)unlink(kShellUiHookAckTempPath);
|
||||
}
|
||||
|
||||
ShellUiHookPollResult poll_and_apply_shellui_hook(
|
||||
pid_t shellui_pid,
|
||||
ShellUiHookPatchReport& report) noexcept {
|
||||
|
||||
report = {};
|
||||
ShellUiHookRequest request{};
|
||||
if (!read_exact_file(kShellUiHookRequestPath, request))
|
||||
return ShellUiHookPollResult::NoRequest;
|
||||
|
||||
/* Consume one complete request; short files are retried on the next poll. */
|
||||
(void)unlink(kShellUiHookRequestPath);
|
||||
|
||||
report.sdk_version = stable_sampler::firmware_sdk_version();
|
||||
report.method_address = request.method_address;
|
||||
report.displaced_size = request.displaced_size;
|
||||
|
||||
report.probe = shellui_hook_request_is_probe(request);
|
||||
report.main_thread_guard =
|
||||
shellui_hook_request_is_main_thread_guard(request);
|
||||
const bool valid = report.probe
|
||||
? shellui_hook_probe_request_is_valid(request, shellui_pid)
|
||||
: (report.main_thread_guard
|
||||
? shellui_main_thread_guard_request_is_valid(
|
||||
request, shellui_pid)
|
||||
: shellui_hook_request_is_valid(request, shellui_pid));
|
||||
if (!valid) {
|
||||
report.status = ShellUiHookStatus::InvalidRequest;
|
||||
write_ack(request, report);
|
||||
return ShellUiHookPollResult::Failed;
|
||||
}
|
||||
|
||||
report.eta_chain =
|
||||
!report.probe &&
|
||||
!report.main_thread_guard &&
|
||||
shellui_hook_is_eta_chain(request);
|
||||
const ShellUiHookBackend backend = report.probe
|
||||
? shellui_hook_probe_backend(report.sdk_version)
|
||||
: (report.main_thread_guard
|
||||
? shellui_main_thread_guard_backend(report.sdk_version)
|
||||
: shellui_hook_backend(report.sdk_version, report.eta_chain));
|
||||
if (backend == ShellUiHookBackend::Unsupported) {
|
||||
report.status = ShellUiHookStatus::UnsupportedFirmware;
|
||||
write_ack(request, report);
|
||||
return ShellUiHookPollResult::Failed;
|
||||
}
|
||||
|
||||
const std::uint32_t firmware_family =
|
||||
report.sdk_version & 0xffff0000U;
|
||||
const bool high_family =
|
||||
firmware_family >= 0x11000000U && firmware_family <= 0x13ff0000U;
|
||||
const bool fw_needs_mprotect =
|
||||
(firmware_family == 0x09000000U || high_family) &&
|
||||
backend == ShellUiHookBackend::Mdbg;
|
||||
|
||||
report.backend = fw_needs_mprotect
|
||||
? "mdbg-rwx"
|
||||
: (backend == ShellUiHookBackend::Mdbg ? "mdbg" : "ptrace-io");
|
||||
|
||||
const auto read_target = [&](void* destination, std::size_t size) {
|
||||
if (backend == ShellUiHookBackend::Mdbg)
|
||||
return mdbg_copyout(shellui_pid,
|
||||
static_cast<intptr_t>(request.method_address), destination, size);
|
||||
return pt_copyout(shellui_pid,
|
||||
static_cast<intptr_t>(request.method_address), destination, size);
|
||||
};
|
||||
const auto write_target = [&](const std::uint8_t* bytes) {
|
||||
/*
|
||||
* FW 9.00 and high firmware (e.g. 13.60) can read JIT code through MDBG
|
||||
* but reject a write while the JIT page is RX. For these firmware families,
|
||||
* temporarily make the code page RWX while ShellUI is stopped, write the bytes,
|
||||
* then restore RX before verification. No game process memory is written.
|
||||
*/
|
||||
const std::size_t offset =
|
||||
report.main_thread_guard ? 0U : (report.eta_chain ? 6U : 0U);
|
||||
const std::size_t size =
|
||||
report.main_thread_guard ? 1U :
|
||||
(report.eta_chain ? 8U : kShellUiHookPatchSize);
|
||||
const intptr_t address =
|
||||
static_cast<intptr_t>(request.method_address + offset);
|
||||
|
||||
if (fw_needs_mprotect) {
|
||||
if (high_family &&
|
||||
mdbg_copyin(shellui_pid, bytes + offset, address, size) == 0)
|
||||
return 0;
|
||||
const std::uint64_t page_start =
|
||||
request.method_address & ~(kPs5CodePageSize - 1ULL);
|
||||
const std::uint64_t page_end =
|
||||
(request.method_address + size + (kPs5CodePageSize - 1ULL)) &
|
||||
~(kPs5CodePageSize - 1ULL);
|
||||
const std::size_t prot_len =
|
||||
static_cast<std::size_t>(page_end - page_start);
|
||||
|
||||
if (kernel_mprotect(
|
||||
shellui_pid,
|
||||
static_cast<intptr_t>(page_start),
|
||||
prot_len,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
|
||||
return -1000;
|
||||
}
|
||||
|
||||
int rc = mdbg_copyin(
|
||||
shellui_pid, bytes + offset, address, size);
|
||||
if (rc != 0) {
|
||||
rc = pt_copyin(
|
||||
shellui_pid, bytes + offset, address, size);
|
||||
}
|
||||
|
||||
const int restore_prot_rc = kernel_mprotect(
|
||||
shellui_pid,
|
||||
static_cast<intptr_t>(page_start),
|
||||
prot_len,
|
||||
PROT_READ | PROT_EXEC);
|
||||
if (restore_prot_rc != 0)
|
||||
return -1001;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (backend == ShellUiHookBackend::Mdbg)
|
||||
return mdbg_copyin(shellui_pid, bytes + offset, address, size);
|
||||
return pt_copyin(shellui_pid, bytes + offset, address, size);
|
||||
};
|
||||
|
||||
const pid_t self = getpid();
|
||||
const std::uint64_t original_auth = kernel_get_ucred_authid(self);
|
||||
if (original_auth == 0 ||
|
||||
kernel_set_ucred_authid(self, kPtraceAuthId) != 0) {
|
||||
report.status = ShellUiHookStatus::AuthFailure;
|
||||
write_ack(request, report);
|
||||
return ShellUiHookPollResult::Failed;
|
||||
}
|
||||
|
||||
bool attached = false;
|
||||
bool patch_applied = false;
|
||||
std::array<std::uint8_t, kShellUiHookPatchSize> observed{};
|
||||
std::array<std::uint8_t, kShellUiHookPatchSize> verified{};
|
||||
|
||||
if (pt_attach(shellui_pid) != 0) {
|
||||
report.status = ShellUiHookStatus::AttachFailure;
|
||||
} else {
|
||||
attached = true;
|
||||
report.read_rc = read_target(observed.data(), observed.size());
|
||||
|
||||
if (report.probe) {
|
||||
if (report.read_rc == 0) {
|
||||
report.observed = observed;
|
||||
report.eta_chain =
|
||||
std::memcmp(
|
||||
observed.data(),
|
||||
kShellUiAbsoluteJumpPrefix,
|
||||
sizeof(kShellUiAbsoluteJumpPrefix)) == 0;
|
||||
report.expected_matched = true;
|
||||
report.verified = true;
|
||||
report.status = ShellUiHookStatus::Success;
|
||||
} else {
|
||||
report.status = ShellUiHookStatus::ExpectedBytesMismatch;
|
||||
}
|
||||
} else if (report.read_rc != 0 ||
|
||||
std::memcmp(
|
||||
observed.data(),
|
||||
request.expected,
|
||||
observed.size()) != 0) {
|
||||
report.status = ShellUiHookStatus::ExpectedBytesMismatch;
|
||||
} else {
|
||||
report.expected_matched = true;
|
||||
report.write_rc = write_target(request.desired);
|
||||
|
||||
const int verify_rc = read_target(verified.data(), verified.size());
|
||||
report.verified = verify_rc == 0 &&
|
||||
std::memcmp(
|
||||
verified.data(),
|
||||
request.desired,
|
||||
verified.size()) == 0;
|
||||
if (report.verified && report.write_rc == 0) {
|
||||
patch_applied = true;
|
||||
report.status = ShellUiHookStatus::Success;
|
||||
} else {
|
||||
report.status = report.write_rc == 0
|
||||
? ShellUiHookStatus::VerifyFailure
|
||||
: ShellUiHookStatus::WriteFailure;
|
||||
|
||||
const bool still_original = verify_rc == 0 &&
|
||||
std::memcmp(
|
||||
verified.data(),
|
||||
request.expected,
|
||||
verified.size()) == 0;
|
||||
if (still_original) {
|
||||
report.restored = true;
|
||||
} else {
|
||||
const int restore_rc = write_target(request.expected);
|
||||
std::array<std::uint8_t, kShellUiHookPatchSize>
|
||||
restore_check{};
|
||||
report.restored = restore_rc == 0 &&
|
||||
read_target(restore_check.data(), restore_check.size()) == 0 &&
|
||||
std::memcmp(
|
||||
restore_check.data(),
|
||||
request.expected,
|
||||
restore_check.size()) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attached) {
|
||||
report.detached = pt_detach(shellui_pid, 0) == 0;
|
||||
(void)kill(shellui_pid, SIGCONT);
|
||||
if (!report.detached && patch_applied)
|
||||
report.status = ShellUiHookStatus::DetachFailure;
|
||||
}
|
||||
|
||||
if (kernel_set_ucred_authid(self, original_auth) == 0) {
|
||||
report.auth_restored =
|
||||
kernel_get_ucred_authid(self) == original_auth;
|
||||
}
|
||||
if (!report.auth_restored && patch_applied)
|
||||
report.status = ShellUiHookStatus::AuthRestoreFailure;
|
||||
|
||||
write_ack(request, report);
|
||||
return report.status == ShellUiHookStatus::Success
|
||||
? ShellUiHookPollResult::Applied
|
||||
: ShellUiHookPollResult::Failed;
|
||||
}
|
||||
|
||||
} // namespace common_fps::ps5
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 khalifa007 (SimpleFPS)
|
||||
* Modifications Copyright (C) 2026 erickdavestech
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "shellui_hook_controller.hpp"
|
||||
|
||||
#include "stable_sampler/proc_rw_v960.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
extern "C" {
|
||||
#include <ps5/kernel.h>
|
||||
#include <ps5/mdbg.h>
|
||||
#include "pt.h"
|
||||
}
|
||||
|
||||
namespace common_fps::ps5 {
|
||||
namespace {
|
||||
|
||||
constexpr std::uint64_t kPtraceAuthId = 0x4800000000010003ULL;
|
||||
constexpr std::uint64_t kPs5CodePageSize = 0x4000ULL;
|
||||
|
||||
template <typename T>
|
||||
bool read_exact_file(const char* path, T& value) noexcept {
|
||||
FILE* fp = std::fopen(path, "rb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
const std::size_t count = std::fread(&value, 1, sizeof(value), fp);
|
||||
const int trailing = std::fgetc(fp);
|
||||
std::fclose(fp);
|
||||
return count == sizeof(value) && trailing == EOF;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool write_exact_file_atomic(
|
||||
const char* temporary_path,
|
||||
const char* final_path,
|
||||
const T& value) noexcept {
|
||||
|
||||
FILE* fp = std::fopen(temporary_path, "wb");
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
const bool complete =
|
||||
std::fwrite(&value, 1, sizeof(value), fp) == sizeof(value) &&
|
||||
std::fflush(fp) == 0;
|
||||
const bool closed = std::fclose(fp) == 0;
|
||||
if (!complete || !closed) {
|
||||
(void)unlink(temporary_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::rename(temporary_path, final_path) != 0) {
|
||||
(void)unlink(temporary_path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void write_ack(
|
||||
const ShellUiHookRequest& request,
|
||||
const ShellUiHookPatchReport& report) noexcept {
|
||||
|
||||
ShellUiHookAck ack{};
|
||||
ack.pid = request.pid;
|
||||
ack.nonce = request.nonce;
|
||||
ack.status = static_cast<std::int32_t>(report.status);
|
||||
ack.read_rc = report.read_rc;
|
||||
ack.write_rc = report.write_rc;
|
||||
ack.verified = report.verified ? 1U : 0U;
|
||||
ack.restored = report.restored ? 1U : 0U;
|
||||
ack.detached = report.detached ? 1U : 0U;
|
||||
ack.auth_restored = report.auth_restored ? 1U : 0U;
|
||||
ack.phase = static_cast<std::uint8_t>(
|
||||
report.probe
|
||||
? ShellUiHookAckPhase::Probe
|
||||
: (report.main_thread_guard
|
||||
? ShellUiHookAckPhase::MainThreadGuard
|
||||
: ShellUiHookAckPhase::Patch));
|
||||
std::memcpy(
|
||||
ack.observed,
|
||||
report.observed.data(),
|
||||
report.observed.size());
|
||||
ack.checksum = shellui_hook_ack_checksum(ack);
|
||||
(void)write_exact_file_atomic(
|
||||
kShellUiHookAckTempPath,
|
||||
kShellUiHookAckPath,
|
||||
ack);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void clear_shellui_hook_protocol_files() noexcept {
|
||||
(void)unlink(kShellUiHookRequestPath);
|
||||
(void)unlink(kShellUiHookRequestTempPath);
|
||||
(void)unlink(kShellUiHookAckPath);
|
||||
(void)unlink(kShellUiHookAckTempPath);
|
||||
}
|
||||
|
||||
ShellUiHookPollResult poll_and_apply_shellui_hook(
|
||||
pid_t shellui_pid,
|
||||
ShellUiHookPatchReport& report) noexcept {
|
||||
|
||||
report = {};
|
||||
ShellUiHookRequest request{};
|
||||
if (!read_exact_file(kShellUiHookRequestPath, request))
|
||||
return ShellUiHookPollResult::NoRequest;
|
||||
|
||||
/* Consume one complete request; short files are retried on the next poll. */
|
||||
(void)unlink(kShellUiHookRequestPath);
|
||||
|
||||
report.sdk_version = stable_sampler::firmware_sdk_version();
|
||||
report.method_address = request.method_address;
|
||||
report.displaced_size = request.displaced_size;
|
||||
|
||||
report.probe = shellui_hook_request_is_probe(request);
|
||||
report.main_thread_guard =
|
||||
shellui_hook_request_is_main_thread_guard(request);
|
||||
const bool valid = report.probe
|
||||
? shellui_hook_probe_request_is_valid(request, shellui_pid)
|
||||
: (report.main_thread_guard
|
||||
? shellui_main_thread_guard_request_is_valid(
|
||||
request, shellui_pid)
|
||||
: shellui_hook_request_is_valid(request, shellui_pid));
|
||||
if (!valid) {
|
||||
report.status = ShellUiHookStatus::InvalidRequest;
|
||||
write_ack(request, report);
|
||||
return ShellUiHookPollResult::Failed;
|
||||
}
|
||||
|
||||
report.eta_chain =
|
||||
!report.probe &&
|
||||
!report.main_thread_guard &&
|
||||
shellui_hook_is_eta_chain(request);
|
||||
const ShellUiHookBackend backend = report.probe
|
||||
? shellui_hook_probe_backend(report.sdk_version)
|
||||
: (report.main_thread_guard
|
||||
? shellui_main_thread_guard_backend(report.sdk_version)
|
||||
: shellui_hook_backend(report.sdk_version, report.eta_chain));
|
||||
if (backend == ShellUiHookBackend::Unsupported) {
|
||||
report.status = ShellUiHookStatus::UnsupportedFirmware;
|
||||
write_ack(request, report);
|
||||
return ShellUiHookPollResult::Failed;
|
||||
}
|
||||
|
||||
const std::uint32_t firmware_family =
|
||||
report.sdk_version & 0xffff0000U;
|
||||
const bool high_family =
|
||||
firmware_family >= 0x11000000U && firmware_family <= 0x13ff0000U;
|
||||
const bool fw_needs_mprotect =
|
||||
(firmware_family == 0x09000000U || high_family) &&
|
||||
backend == ShellUiHookBackend::Mdbg;
|
||||
|
||||
report.backend = fw_needs_mprotect
|
||||
? "mdbg-rwx"
|
||||
: (backend == ShellUiHookBackend::Mdbg ? "mdbg" : "ptrace-io");
|
||||
|
||||
const auto read_target = [&](void* destination, std::size_t size) {
|
||||
if (backend == ShellUiHookBackend::Mdbg)
|
||||
return mdbg_copyout(shellui_pid,
|
||||
static_cast<intptr_t>(request.method_address), destination, size);
|
||||
return pt_copyout(shellui_pid,
|
||||
static_cast<intptr_t>(request.method_address), destination, size);
|
||||
};
|
||||
const auto write_target = [&](const std::uint8_t* bytes) {
|
||||
/*
|
||||
* FW 9.00 and high firmware (e.g. 13.60) can read JIT code through MDBG
|
||||
* but reject a write while the JIT page is RX. For these firmware families,
|
||||
* temporarily make the code page RWX while ShellUI is stopped, write the bytes,
|
||||
* then restore RX before verification. No game process memory is written.
|
||||
*/
|
||||
const std::size_t offset =
|
||||
report.main_thread_guard ? 0U : (report.eta_chain ? 6U : 0U);
|
||||
const std::size_t size =
|
||||
report.main_thread_guard ? 1U :
|
||||
(report.eta_chain ? 8U : kShellUiHookPatchSize);
|
||||
const intptr_t address =
|
||||
static_cast<intptr_t>(request.method_address + offset);
|
||||
|
||||
if (fw_needs_mprotect) {
|
||||
if (high_family &&
|
||||
mdbg_copyin(shellui_pid, bytes + offset, address, size) == 0)
|
||||
return 0;
|
||||
const std::uint64_t page_start =
|
||||
request.method_address & ~(kPs5CodePageSize - 1ULL);
|
||||
const std::uint64_t page_end =
|
||||
(request.method_address + size + (kPs5CodePageSize - 1ULL)) &
|
||||
~(kPs5CodePageSize - 1ULL);
|
||||
const std::size_t prot_len =
|
||||
static_cast<std::size_t>(page_end - page_start);
|
||||
|
||||
if (kernel_mprotect(
|
||||
shellui_pid,
|
||||
static_cast<intptr_t>(page_start),
|
||||
prot_len,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
|
||||
return -1000;
|
||||
}
|
||||
|
||||
int rc = mdbg_copyin(
|
||||
shellui_pid, bytes + offset, address, size);
|
||||
if (rc != 0) {
|
||||
rc = pt_copyin(
|
||||
shellui_pid, bytes + offset, address, size);
|
||||
}
|
||||
|
||||
const int restore_prot_rc = kernel_mprotect(
|
||||
shellui_pid,
|
||||
static_cast<intptr_t>(page_start),
|
||||
prot_len,
|
||||
PROT_READ | PROT_EXEC);
|
||||
if (restore_prot_rc != 0)
|
||||
return -1001;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (backend == ShellUiHookBackend::Mdbg)
|
||||
return mdbg_copyin(shellui_pid, bytes + offset, address, size);
|
||||
return pt_copyin(shellui_pid, bytes + offset, address, size);
|
||||
};
|
||||
|
||||
const pid_t self = getpid();
|
||||
const std::uint64_t original_auth = kernel_get_ucred_authid(self);
|
||||
if (original_auth == 0 ||
|
||||
kernel_set_ucred_authid(self, kPtraceAuthId) != 0) {
|
||||
report.status = ShellUiHookStatus::AuthFailure;
|
||||
write_ack(request, report);
|
||||
return ShellUiHookPollResult::Failed;
|
||||
}
|
||||
|
||||
bool attached = false;
|
||||
bool patch_applied = false;
|
||||
std::array<std::uint8_t, kShellUiHookPatchSize> observed{};
|
||||
std::array<std::uint8_t, kShellUiHookPatchSize> verified{};
|
||||
|
||||
if (pt_attach(shellui_pid) != 0) {
|
||||
report.status = ShellUiHookStatus::AttachFailure;
|
||||
} else {
|
||||
attached = true;
|
||||
report.read_rc = read_target(observed.data(), observed.size());
|
||||
|
||||
if (report.probe) {
|
||||
if (report.read_rc == 0) {
|
||||
report.observed = observed;
|
||||
report.eta_chain =
|
||||
std::memcmp(
|
||||
observed.data(),
|
||||
kShellUiAbsoluteJumpPrefix,
|
||||
sizeof(kShellUiAbsoluteJumpPrefix)) == 0;
|
||||
report.expected_matched = true;
|
||||
report.verified = true;
|
||||
report.status = ShellUiHookStatus::Success;
|
||||
} else {
|
||||
report.status = ShellUiHookStatus::ExpectedBytesMismatch;
|
||||
}
|
||||
} else if (report.read_rc != 0 ||
|
||||
std::memcmp(
|
||||
observed.data(),
|
||||
request.expected,
|
||||
observed.size()) != 0) {
|
||||
report.status = ShellUiHookStatus::ExpectedBytesMismatch;
|
||||
} else {
|
||||
report.expected_matched = true;
|
||||
report.write_rc = write_target(request.desired);
|
||||
|
||||
const int verify_rc = read_target(verified.data(), verified.size());
|
||||
report.verified = verify_rc == 0 &&
|
||||
std::memcmp(
|
||||
verified.data(),
|
||||
request.desired,
|
||||
verified.size()) == 0;
|
||||
if (report.verified && report.write_rc == 0) {
|
||||
patch_applied = true;
|
||||
report.status = ShellUiHookStatus::Success;
|
||||
} else {
|
||||
report.status = report.write_rc == 0
|
||||
? ShellUiHookStatus::VerifyFailure
|
||||
: ShellUiHookStatus::WriteFailure;
|
||||
|
||||
const bool still_original = verify_rc == 0 &&
|
||||
std::memcmp(
|
||||
verified.data(),
|
||||
request.expected,
|
||||
verified.size()) == 0;
|
||||
if (still_original) {
|
||||
report.restored = true;
|
||||
} else {
|
||||
const int restore_rc = write_target(request.expected);
|
||||
std::array<std::uint8_t, kShellUiHookPatchSize>
|
||||
restore_check{};
|
||||
report.restored = restore_rc == 0 &&
|
||||
read_target(restore_check.data(), restore_check.size()) == 0 &&
|
||||
std::memcmp(
|
||||
restore_check.data(),
|
||||
request.expected,
|
||||
restore_check.size()) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attached) {
|
||||
report.detached = pt_detach(shellui_pid, 0) == 0;
|
||||
(void)kill(shellui_pid, SIGCONT);
|
||||
if (!report.detached && patch_applied)
|
||||
report.status = ShellUiHookStatus::DetachFailure;
|
||||
}
|
||||
|
||||
if (kernel_set_ucred_authid(self, original_auth) == 0) {
|
||||
report.auth_restored =
|
||||
kernel_get_ucred_authid(self) == original_auth;
|
||||
}
|
||||
if (!report.auth_restored && patch_applied)
|
||||
report.status = ShellUiHookStatus::AuthRestoreFailure;
|
||||
|
||||
write_ack(request, report);
|
||||
return report.status == ShellUiHookStatus::Success
|
||||
? ShellUiHookPollResult::Applied
|
||||
: ShellUiHookPollResult::Failed;
|
||||
}
|
||||
|
||||
} // namespace common_fps::ps5
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,45 +1,45 @@
|
||||
/*
|
||||
* Generated by tools/build_assets.py - do not edit by hand.
|
||||
* Embedded artwork: "PS5 Button Icons and Controls" by Zacksly
|
||||
* (https://zacksly.itch.io), licensed CC BY 3.0, modified.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later AND CC-BY-3.0
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct { const char *file; const uint8_t *data; uint32_t size; } pad_png_t;
|
||||
typedef struct { const char *name; uint32_t bit; int16_t x, y, w, h; uint8_t png; } pad_sprite_t;
|
||||
typedef struct { float cx, cy, travel; int16_t w, h; uint32_t click_bit; } pad_stick_t;
|
||||
typedef struct { float x, y, w, h; int16_t dot_w, dot_h; } pad_touchpad_t;
|
||||
|
||||
enum {
|
||||
PAD_PNG_BASE,
|
||||
PAD_PNG_TRIANGLE,
|
||||
PAD_PNG_CIRCLE,
|
||||
PAD_PNG_CROSS,
|
||||
PAD_PNG_SQUARE,
|
||||
PAD_PNG_DPAD_UP,
|
||||
PAD_PNG_DPAD_DOWN,
|
||||
PAD_PNG_DPAD_LEFT,
|
||||
PAD_PNG_DPAD_RIGHT,
|
||||
PAD_PNG_L1,
|
||||
PAD_PNG_R1,
|
||||
PAD_PNG_CREATE,
|
||||
PAD_PNG_OPTIONS,
|
||||
PAD_PNG_MUTE,
|
||||
PAD_PNG_PS,
|
||||
PAD_PNG_TOUCHPAD,
|
||||
PAD_PNG_STICK_CAP,
|
||||
PAD_PNG_STICK_CAP_L3,
|
||||
PAD_PNG_L2_FILL,
|
||||
PAD_PNG_R2_FILL,
|
||||
PAD_PNG_TOUCH_DOT,
|
||||
PAD_PNG_COUNT
|
||||
};
|
||||
|
||||
extern const int pad_overlay_w, pad_overlay_h;
|
||||
extern const pad_png_t pad_pngs[PAD_PNG_COUNT];
|
||||
extern const pad_sprite_t pad_buttons[15];
|
||||
extern const pad_sprite_t pad_triggers[2];
|
||||
extern const pad_stick_t pad_sticks[2];
|
||||
extern const pad_touchpad_t pad_touchpad;
|
||||
/*
|
||||
* Generated by tools/build_assets.py - do not edit by hand.
|
||||
* Embedded artwork: "PS5 Button Icons and Controls" by Zacksly
|
||||
* (https://zacksly.itch.io), licensed CC BY 3.0, modified.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later AND CC-BY-3.0
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct { const char *file; const uint8_t *data; uint32_t size; } pad_png_t;
|
||||
typedef struct { const char *name; uint32_t bit; int16_t x, y, w, h; uint8_t png; } pad_sprite_t;
|
||||
typedef struct { float cx, cy, travel; int16_t w, h; uint32_t click_bit; } pad_stick_t;
|
||||
typedef struct { float x, y, w, h; int16_t dot_w, dot_h; } pad_touchpad_t;
|
||||
|
||||
enum {
|
||||
PAD_PNG_BASE,
|
||||
PAD_PNG_TRIANGLE,
|
||||
PAD_PNG_CIRCLE,
|
||||
PAD_PNG_CROSS,
|
||||
PAD_PNG_SQUARE,
|
||||
PAD_PNG_DPAD_UP,
|
||||
PAD_PNG_DPAD_DOWN,
|
||||
PAD_PNG_DPAD_LEFT,
|
||||
PAD_PNG_DPAD_RIGHT,
|
||||
PAD_PNG_L1,
|
||||
PAD_PNG_R1,
|
||||
PAD_PNG_CREATE,
|
||||
PAD_PNG_OPTIONS,
|
||||
PAD_PNG_MUTE,
|
||||
PAD_PNG_PS,
|
||||
PAD_PNG_TOUCHPAD,
|
||||
PAD_PNG_STICK_CAP,
|
||||
PAD_PNG_STICK_CAP_L3,
|
||||
PAD_PNG_L2_FILL,
|
||||
PAD_PNG_R2_FILL,
|
||||
PAD_PNG_TOUCH_DOT,
|
||||
PAD_PNG_COUNT
|
||||
};
|
||||
|
||||
extern const int pad_overlay_w, pad_overlay_h;
|
||||
extern const pad_png_t pad_pngs[PAD_PNG_COUNT];
|
||||
extern const pad_sprite_t pad_buttons[15];
|
||||
extern const pad_sprite_t pad_triggers[2];
|
||||
extern const pad_stick_t pad_sticks[2];
|
||||
extern const pad_touchpad_t pad_touchpad;
|
||||
+3915
-3915
File diff suppressed because it is too large.
Load diff
+10203
-10203
File diff suppressed because it is too large.
Load diff
+569
-569
File diff suppressed because it is too large.
Load diff
+41
-41
@@ -1,41 +1,41 @@
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 erickdavestech
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "common_fps/config.hpp"
|
||||
#include "common_fps/types.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace common_fps;
|
||||
|
||||
int main() {
|
||||
const auto cfg = default_config();
|
||||
|
||||
// Current public UI defaults.
|
||||
assert(cfg.corner == Corner::TopLeft);
|
||||
assert(cfg.font_size == 24);
|
||||
|
||||
// Integer-only public transport.
|
||||
OverlayFrame frame;
|
||||
frame.loading = false;
|
||||
frame.fps = 59;
|
||||
static_assert(std::is_same_v<decltype(frame.fps), int>);
|
||||
|
||||
// Ensure our source model never needs a decimal string.
|
||||
const std::string display =
|
||||
std::string("FPS: ") + std::to_string(frame.fps);
|
||||
|
||||
assert(display == "FPS: 59");
|
||||
assert(display.find('.') == std::string::npos);
|
||||
|
||||
std::cout << "v1.2.0 public defaults: PASS\n";
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* Common FPS for PS5
|
||||
* Copyright (C) 2026 porhe911
|
||||
* Modifications Copyright (C) 2026 erickdavestech
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "common_fps/config.hpp"
|
||||
#include "common_fps/types.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace common_fps;
|
||||
|
||||
int main() {
|
||||
const auto cfg = default_config();
|
||||
|
||||
// Current public UI defaults.
|
||||
assert(cfg.corner == Corner::TopLeft);
|
||||
assert(cfg.font_size == 24);
|
||||
|
||||
// Integer-only public transport.
|
||||
OverlayFrame frame;
|
||||
frame.loading = false;
|
||||
frame.fps = 59;
|
||||
static_assert(std::is_same_v<decltype(frame.fps), int>);
|
||||
|
||||
// Ensure our source model never needs a decimal string.
|
||||
const std::string display =
|
||||
std::string("FPS: ") + std::to_string(frame.fps);
|
||||
|
||||
assert(display == "FPS: 59");
|
||||
assert(display.find('.') == std::string::npos);
|
||||
|
||||
std::cout << "v1.2.0 public defaults: PASS\n";
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user