Seregon/BD-EJ

A simple payload for ejecting disks

C/44 B/No license
disc_eject/main.c
BD-EJ / disc_eject / main.c
1/* Copyright (C) 2026 - SeregonWar
2 *
3 * PS4/PS5 Disc Eject Payload
4 *
5 * Ejects the Blu-ray disc from the console's optical drive
6 * using standard FreeBSD ioctls (CDIOCEJECT) on the
7 * /dev/cd0 device, managed by the SceBdSvc daemon.
8 *
9 * Compatible with PS4 and PS5 (FreeBSD-based kernel).
10 * Requires a jailbroken environment with device access.
11 *
12 * Compile with: ps5-payload-dev/sdk (prospero.mk toolchain)
13 *
14 * This program is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU General Public License as published by the
16 * Free Software Foundation; either version 3, or (at your option) any
17 * later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; see the file COPYING. If not, see
26 * <http://www.gnu.org/licenses/>.
27 */
28 
29#include <fcntl.h>
30#include <stdio.h>
31#include <string.h>
32#include <strings.h>
33#include <unistd.h>
34#include <errno.h>
35#include <sys/ioctl.h>
36#include <sys/cdio.h>
37 
38/*
39 * ============================================================
40 * Technical Documentation - Disc Ejection System
41 * ============================================================
42 *
43 * SYSTEM DAEMON:
44 * SceBdSvc - The PS4/PS5 kernel daemon responsible for
45 * managing the Blu-ray drive (BD). It handles:
46 * - Disc insertion/removal detection
47 * - Drive motor control
48 * - Tray lock/unlock state management
49 * - Interfacing with the CAM (Common Access Method) subsystem
50 *
51 * DEVICE:
52 * /dev/cd0 - Device node for the optical drive
53 * Mounted on /mnt/disc when a disc is inserted
54 * Accessible via ioctl() after open() with adequate permissions
55 *
56 * IOCTLs USED (from <sys/cdio.h>):
57 * CDIOCALLOW - Unlocks the ejection prevention mechanism
58 * Some drives block software ejection;
59 * this ioctl removes the lock.
60 * CDIOCEJECT - Commands physical disc ejection.
61 * Sends the SCSI START STOP UNIT command
62 * with LoEj=1 bit to the drive.
63 *
64 * SYSCALLS INVOLVED:
65 * open() - SYS_open (syscall #5) - Device opening
66 * ioctl() - SYS_ioctl (syscall #54) - Sending commands to driver
67 * close() - SYS_close (syscall #6) - File descriptor closing
68 *
69 * ON-SCREEN NOTIFICATIONS:
70 * Method 1 (legacy, PS4/PS5):
71 * sceKernelSendNotificationRequest() - Kernel function
72 * that sends a message to the notification system. Uses
73 * a notify_request_t structure with a 3075 byte message
74 * field. The message appears as a toast notification.
75 *
76 * Method 2 (modern, recent PS5 SDK):
77 * sceNotificationSend() - libSceNotification API that
78 * accepts a JSON payload for interactive notifications with
79 * custom icons, sub-messages, and deep-link actions.
80 *
81 * COMPATIBILITY NOTES:
82 * - PS4: Tested on jailbroken firmware (5.05, 6.72, 7.02, 9.00 -> 13.00)
83 * - PS5: Tested on exploited firmware (1.xx - 12.xx)
84 * - The Blu-ray drive must be present and functioning
85 * - Access to /dev/cd0 requires adequate privileges
86 * (normally obtained through kernel exploit)
87 * - On Digital Edition consoles (no drive) the payload
88 * will fail with ENOENT on open()
89 *
90 * ============================================================
91 */
92 
93/* ---------- Device paths ---------- */
94#define BD_DEVICE_PATH "/dev/cd0"
95#define BD_DEVICE_PATH_ALT "/dev/cd1" /* Rare fallback */
96 
97/* ---------- Legacy notification (sceKernelSendNotificationRequest) ---------- */
98 
99typedef struct notify_request {
100 char useless1[45];
101 char message[3075];
102} notify_request_t;
103 
104__attribute__((weak)) int sceKernelSendNotificationRequest(int, notify_request_t *, size_t, int);
105 
106/*
107 * Sends an on-screen notification using the legacy method.
108 * Compatible with PS4 and PS5.
109 */
110static int
111notify_legacy(const char *msg)
112{
113 if (sceKernelSendNotificationRequest == NULL) {
114 return -1;
115 }
116 
117 notify_request_t req;
118 
119 bzero(&req, sizeof(req));
120 strncpy(req.message, msg, sizeof(req.message) - 1);
121 
122 return sceKernelSendNotificationRequest(0, &req, sizeof(req), 0);
123}
124 
125/* ---------- Modern notification (sceNotificationSend / libSceNotification) ----------
126 *
127 * Available only with recent SDKs that include libSceNotification.
128 * Compile with -lSceNotification to enable.
129 * If not available, the linker will fail and only the legacy method can be used.
130 */
131 
132#ifdef USE_SCE_NOTIFICATION
133 
134#define SCE_NOTIFICATION_LOCAL_USER_ID_SYSTEM 0xFE
135 
136__attribute__((weak)) int sceNotificationSend(int userId, _Bool isLogged, const char *payload);
137 
138/*
139 * JSON template for interactive notification.
140 * The main message and sub-message are
141 * formatted dynamically.
142 */
143static int
144notify_modern(const char *title, const char *subtitle)
145{
146 if (sceNotificationSend == NULL) {
147 return -1;
148 }
149 
150 char payload[4096];
151 
152 snprintf(payload, sizeof(payload),
153 "{\n"
154 " \"rawData\": {\n"
155 " \"viewTemplateType\": \"InteractiveToastTemplateB\",\n"
156 " \"channelType\": \"Downloads\",\n"
157 " \"useCaseId\": \"IDC\",\n"
158 " \"toastOverwriteType\": \"No\",\n"
159 " \"isImmediate\": true,\n"
160 " \"priority\": 100,\n"
161 " \"viewData\": {\n"
162 " \"icon\": {\n"
163 " \"type\": \"Predefined\",\n"
164 " \"parameters\": {\n"
165 " \"icon\": \"download\"\n"
166 " }\n"
167 " },\n"
168 " \"message\": {\n"
169 " \"body\": \"%s\"\n"
170 " },\n"
171 " \"subMessage\": {\n"
172 " \"body\": \"%s\"\n"
173 " }\n"
174 " },\n"
175 " \"platformViews\": {\n"
176 " \"previewDisabled\": {\n"
177 " \"viewData\": {\n"
178 " \"icon\": {\n"
179 " \"type\": \"Predefined\",\n"
180 " \"parameters\": {\n"
181 " \"icon\": \"download\"\n"
182 " }\n"
183 " },\n"
184 " \"message\": {\n"
185 " \"body\": \"%s\"\n"
186 " }\n"
187 " }\n"
188 " }\n"
189 " }\n"
190 " },\n"
191 " \"createdDateTime\": \"2026-01-01T00:00:00.000Z\",\n"
192 " \"localNotificationId\": \"disc_eject_001\"\n"
193 "}",
194 title, subtitle, title);
195 
196 return sceNotificationSend(SCE_NOTIFICATION_LOCAL_USER_ID_SYSTEM,
197 1, payload);
198}
199 
200#endif /* USE_SCE_NOTIFICATION */
201 
202/* ---------- Wrapper notification function ---------- */
203 
204static void
205send_notification(const char *title, const char *subtitle)
206{
207#ifdef USE_SCE_NOTIFICATION
208 if (notify_modern(title, subtitle) == 0)
209 return;
210 /* Fallback to legacy method if modern fails */
211#endif
212 
213 /* Compose a single message for the legacy method */
214 char combined[3075];
215 if (subtitle && subtitle[0] != '\0')
216 snprintf(combined, sizeof(combined), "%s\n%s", title, subtitle);
217 else
218 snprintf(combined, sizeof(combined), "%s", title);
219 
220 notify_legacy(combined);
221}
222 
223/* ---------- Disc ejection ---------- */
224 
225/*
226 * Attempts to eject the disc from the Blu-ray drive.
227 *
228 * Procedure:
229 * 1. Opens /dev/cd0 in read-only with O_NONBLOCK
230 * (O_NONBLOCK avoids blocking if drive is busy)
231 * 2. Sends CDIOCALLOW to unlock the tray
232 * (failure is ignored: not all drives support it)
233 * 3. Sends CDIOCEJECT to eject the disc
234 * 4. Closes the file descriptor
235 *
236 * Returns:
237 * 0 = success
238 * -1 = error (errno set)
239 */
240static int
241eject_disc(void)
242{
243 int fd;
244 int ret = -1;
245 
246 /* Attempt to open the main device */
247 fd = open(BD_DEVICE_PATH, O_RDONLY | O_NONBLOCK);
248 if (fd < 0) {
249 /* Attempt with alternative device */
250 fd = open(BD_DEVICE_PATH_ALT, O_RDONLY | O_NONBLOCK);
251 if (fd < 0)
252 return -1;
253 }
254 
255 /*
256 * CDIOCALLOW: unlocks the ejection prevention mechanism.
257 * Some drives/firmware block software ejection by default
258 * (for example during game playback).
259 * Failure of this ioctl is not critical.
260 */
261 (void)ioctl(fd, CDIOCALLOW);
262 
263 /*
264 * CDIOCEJECT: ejects the disc.
265 * Internally translated to the SCSI command:
266 * START STOP UNIT (opcode 0x1B) with LoEj=1, Start=0
267 * The kernel cd(4) driver handles the translation.
268 */
269 if (ioctl(fd, CDIOCEJECT) == 0)
270 ret = 0;
271 
272 close(fd);
273 return ret;
274}
275 
276/* ---------- Entry point ---------- */
277 
278int
279main(int argc, char *argv[])
280{
281 int result;
282 
283 /* Notify operation start */
284 send_notification("Disc Eject", "Ejecting disc...");
285 
286 /* Execute ejection */
287 result = eject_disc();
288 
289 if (result == 0) {
290 send_notification("Disc Eject",
291 "Disc ejected successfully!");
292 } else {
293 /*
294 * Possible error causes:
295 * - ENOENT: /dev/cd0 does not exist (Digital Edition?)
296 * - EACCES: insufficient permissions
297 * - ENXIO: drive not ready / no disc inserted
298 * - EIO: drive hardware error
299 */
300 char errmsg[256];
301 snprintf(errmsg, sizeof(errmsg),
302 "Disc ejection error: %s (errno %d)",
303 strerror(errno), errno);
304 send_notification("Disc Eject - Error", errmsg);
305 }
306 
307 return result;
308}
309