433 lines11 KBC
1/*
2main_ps3.c: the PS3 backend's plat_* functions and main().
3*/
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include <stdarg.h>
8#include <unistd.h>
9#include <time.h>
10
11#include <ppu-types.h>
12#include <lv2/systime.h>
13#include <sys/thread.h>
14#include <sys/mutex.h>
15#include <sysutil/sysutil.h>
16#include <sysmodule/sysmodule.h>
17#include <net/net.h>
18#include <lv2/system.h>
19#include <lv2/sysfs.h>
20#include <sys/file.h>
21#include <ppu-lv2.h>
22
23#include "../plat.h"
24#include "../app.h"
25#include "ps3.h"
26
27#ifndef APP_DIR
28#define APP_DIR "/dev_hdd0/game/PSWD00001/USRDIR"
29#endif
30
31int ps3_net_ok;
32static volatile int quit_requested;
33static volatile int xmb_open; // the PS button menu is over us
34
35/*
36==================
37Log
38==================
39*/
40static FILE *log_file;
41static sys_mutex_t log_lock;
42static int log_lock_made;
43
44void plat_log(const char *fmt, ...)
45{
46 char line[1024];
47 va_list ap;
48 uint64_t t = sysGetSystemTime();
49 int n = snprintf(line, sizeof(line), "[%6.2f] ", t / 1000000.0);
50 va_start(ap, fmt);
51 vsnprintf(line + n, sizeof(line) - n, fmt, ap);
52 va_end(ap);
53 if (log_lock_made) sysMutexLock(log_lock, 0);
54 if (log_file) {
55 fputs(line, log_file);
56 fputc('\n', log_file);
57 fflush(log_file);
58 }
59 if (log_lock_made) sysMutexUnlock(log_lock);
60}
61
62static void Log_Open(void)
63{
64 char path[256], prev[256];
65 sys_mutex_attr_t attr;
66 sysMutexAttrInitialize(attr);
67 if (sysMutexCreate(&log_lock, &attr) == 0)
68 log_lock_made = 1;
69 snprintf(path, sizeof(path), "%s/ps3world_log.txt", APP_DIR);
70 snprintf(prev, sizeof(prev), "%s/ps3world_log_prev.txt", APP_DIR);
71 remove(prev);
72 rename(path, prev);
73 log_file = fopen(path, "w");
74}
75
76/*
77==================
78Time, threads, locks
79==================
80*/
81uint64_t plat_time_us(void) { return sysGetSystemTime(); }
82void plat_sleep_us(unsigned us) { usleep(us); }
83
84typedef struct {
85 plat_thread_fn fn;
86 void *arg;
87} thread_start_t;
88
89static void Thread_Entry(void *p)
90{
91 thread_start_t ts = *(thread_start_t *)p;
92 free(p);
93 ts.fn(ts.arg);
94 sysThreadExit(0);
95}
96
97int plat_thread_start(plat_thread_fn fn, void *arg, int prio, int stack_kb, const char *name)
98{
99 sys_ppu_thread_t id;
100 thread_start_t *ts = malloc(sizeof(*ts));
101 if (!ts) return 0;
102 ts->fn = fn;
103 ts->arg = arg;
104 s32 ret = sysThreadCreate(&id, Thread_Entry, ts, prio, (u64)stack_kb * 1024, 0, (char *)name);
105 if (ret != 0) {
106 plat_log("thread %s: sysThreadCreate = 0x%08x", name, (unsigned)ret);
107 free(ts);
108 return 0;
109 }
110 return 1;
111}
112
113struct plat_mutex {
114 sys_mutex_t m;
115};
116
117plat_mutex_t *plat_mutex_new(void)
118{
119 plat_mutex_t *pm = malloc(sizeof(*pm));
120 sys_mutex_attr_t attr;
121 if (!pm) return NULL;
122 sysMutexAttrInitialize(attr);
123 if (sysMutexCreate(&pm->m, &attr) != 0) {
124 plat_log("FATAL: sysMutexCreate failed");
125 free(pm);
126 return NULL;
127 }
128 return pm;
129}
130
131void plat_mutex_lock(plat_mutex_t *m) { if (m) sysMutexLock(m->m, 0); }
132void plat_mutex_unlock(plat_mutex_t *m) { if (m) sysMutexUnlock(m->m); }
133
134/*
135==================
136Files and system
137==================
138*/
139const char *plat_data_dir(void) { return APP_DIR "/data"; }
140const char *plat_save_dir(void) { return APP_DIR; }
141
142int plat_system_lang(void)
143{
144 s32 v = SYSUTIL_LANG_ENGLISH_US;
145 if (sysUtilGetSystemParamInt(SYSUTIL_SYSTEMPARAM_ID_LANG, &v) != 0) v = SYSUTIL_LANG_ENGLISH_US;
146 return v;
147}
148
149unsigned char *plat_read_file(const char *path, size_t *len)
150{
151 FILE *f = fopen(path, "rb");
152 unsigned char *buf;
153 long n;
154 if (!f) return NULL;
155 fseek(f, 0, SEEK_END);
156 n = ftell(f);
157 fseek(f, 0, SEEK_SET);
158 if (n < 0 || !(buf = malloc(n + 1))) { fclose(f); return NULL; }
159 if (fread(buf, 1, n, f) != (size_t)n) { fclose(f); free(buf); return NULL; }
160 fclose(f);
161 buf[n] = 0;
162 if (len) *len = n;
163 return buf;
164}
165
166int plat_write_file(const char *path, const void *data, size_t len)
167{
168 // a name of its own: two threads can write the same file (an icon and its preview)
169 static volatile int seq;
170 char tmp[1024];
171 int tn = snprintf(tmp, sizeof(tmp), "%s.%d.tmp", path, __sync_add_and_fetch(&seq, 1));
172 if (tn < 0 || tn >= (int)sizeof(tmp)) return 0;
173 FILE *f = fopen(tmp, "wb");
174 if (!f) return 0;
175 int ok = fwrite(data, 1, len, f) == len;
176 ok = (fclose(f) == 0) && ok;
177 if (!ok) { remove(tmp); return 0; }
178 remove(path);
179 return rename(tmp, path) == 0;
180}
181
182#define FS_S_IFMT 0170000 // lv2's st_mode bits, as CELL_FS_S_IFMT / CELL_FS_S_IFDIR
183#define FS_S_IFDIR 0040000
184#define FS_TYPE_DIR 1 // sysFSDirent.d_type for a folder
185
186long long plat_file_size(const char *path)
187{
188 sysFSStat st;
189 if (sysLv2FsStat(path, &st) != 0 || (st.st_mode & FS_S_IFMT) == FS_S_IFDIR)
190 return -1;
191 return (long long)st.st_size;
192}
193
194long long plat_file_mtime(const char *path)
195{
196 sysFSStat st;
197 if (sysLv2FsStat(path, &st) != 0)
198 return 0;
199 return (long long)st.st_mtime;
200}
201
202int plat_dir_exists(const char *path)
203{
204 s32 fd;
205 if (sysLv2FsOpenDir(path, &fd) != 0)
206 return 0;
207 sysLv2FsCloseDir(fd);
208 return 1;
209}
210
211int plat_mkdirs(const char *path)
212{
213 char buf[512];
214 snprintf(buf, sizeof(buf), "%s", path);
215 // from the folder under the device ("/dev_hdd0/packages": "/dev_hdd0" is there already)
216 char *p = strchr(buf + 1, '/');
217 while (p) {
218 *p = 0;
219 if (!plat_dir_exists(buf)) sysLv2FsMkdir(buf, 0777);
220 *p = '/';
221 p = strchr(p + 1, '/');
222 }
223 if (!plat_dir_exists(buf)) sysLv2FsMkdir(buf, 0777);
224 return plat_dir_exists(buf);
225}
226
227long long plat_free_bytes(const char *path)
228{
229 char dev[64];
230 u32 block = 0;
231 u64 blocks = 0;
232 // the device's root: "/dev_usb000/PS3/THEME" -> "/dev_usb000"
233 const char *e = strchr(path + 1, '/');
234 int n = e ? (int)(e - path) : (int)strlen(path);
235 snprintf(dev, sizeof(dev), "%.*s", n, path);
236 s32 r = sysFsGetFreeSize(dev, &block, &blocks);
237 if (r != 0) {
238 plat_log("plat: sysFsGetFreeSize(%s) = 0x%08x", dev, (unsigned)r);
239 return -1;
240 }
241 return (long long)block * (long long)blocks;
242}
243
244int plat_list_dirs(const char *path, char (*names)[64], int max)
245{
246 s32 fd;
247 sysFSDirent ent;
248 u64 nread;
249 int n = 0;
250 if (sysLv2FsOpenDir(path, &fd) != 0)
251 return 0;
252 while (n < max && sysLv2FsReadDir(fd, &ent, &nread) == 0 && nread > 0) {
253 if (ent.d_type != FS_TYPE_DIR || ent.d_name[0] == '.')
254 continue;
255 snprintf(names[n++], 64, "%s", ent.d_name);
256 }
257 sysLv2FsCloseDir(fd);
258 return n;
259}
260
261void plat_random(void *buf, int n)
262{
263 if (sysGetRandomNumber(buf, (u64)n) == 0) return;
264 // no random numbers from the system: the clock will have to do (only a nonce)
265 u64 t = sysGetSystemTime();
266 for (int i = 0; i < n; i++) { t = t * 6364136223846793005ull + 1442695040888963407ull; ((u8 *)buf)[i] = (u8)(t >> 56); }
267}
268
269// the app's own Quit: the main loop ends, as on the XMB's (the download pauses, the log closes)
270void plat_quit(void)
271{
272 plat_log("ui: quit to the XMB");
273 quit_requested = 1;
274}
275
276// the console's confirm button: 0 is O (Japanese and Asian consoles), 1 is X
277int plat_circle_confirms(void)
278{
279 static int v = -1;
280 if (v < 0) {
281 s32 a = 1;
282 v = sysUtilGetSystemParamInt(SYSUTIL_SYSTEMPARAM_ID_ENTER_BUTTON_ASSIGN, &a) == 0 && a == 0;
283 if (v) plat_log("plat: O confirms on this console: swapping O and X");
284 }
285 return v;
286}
287
288/*
289==================
290main
291==================
292*/
293static void Sysutil_Callback(u64 status, u64 param, void *userdata)
294{
295 (void)param;
296 (void)userdata;
297 if (status == SYSUTIL_EXIT_GAME)
298 quit_requested = 1;
299 else if (status == SYSUTIL_MENU_OPEN)
300 xmb_open = 1;
301 else if (status == SYSUTIL_MENU_CLOSE)
302 xmb_open = 0;
303 else if (status >= SYSUTIL_OSK_LOADED && status <= SYSUTIL_OSK_INPUT_CANCELED)
304 ps3_osk_event(status);
305}
306
307static void Net_Init(void)
308{
309 s32 ret;
310 if ((ret = sysModuleLoad(SYSMODULE_NET)) < 0) {
311 plat_log("net: sysModuleLoad = 0x%08x", (unsigned)ret);
312 return;
313 }
314 if ((ret = netInitialize()) < 0) {
315 plat_log("net: netInitialize = 0x%08x", (unsigned)ret);
316 return;
317 }
318 ps3_net_ok = 1;
319}
320
321/*
322==================
323Self-test: two things about lv2's filesystem nobody has checked, written to the log
324(in the app's own folder, and cleaned up after)
325==================
326*/
327static void Write_Test(const char *path, const char *s)
328{
329 FILE *f = fopen(path, "wb");
330 if (f) { fputs(s, f); fclose(f); }
331}
332
333static void Fs_Self_Test(void)
334{
335 const char *dir = APP_DIR "/selftest", *sub = APP_DIR "/selftest/a";
336 const char *dotdot = APP_DIR "/selftest/a/../b", *b = APP_DIR "/selftest/b";
337 const char *x = APP_DIR "/selftest/x", *y = APP_DIR "/selftest/y";
338 plat_mkdirs(sub);
339
340 // does a ".." in a path go up a folder?
341 FILE *f = fopen(dotdot, "wb");
342 if (f) fclose(f);
343 plat_log("selftest: \"..\" in a path is %s", !f ? "refused" : plat_file_size(b) >= 0 ? "followed (goes up a folder)" :
344 "taken literally (doesn't go up)");
345
346 // does rename replace a file that's there?
347 Write_Test(x, "x");
348 Write_Test(y, "yy");
349 int r = rename(x, y);
350 long long ys = plat_file_size(y);
351 plat_log("selftest: rename onto a file that's there %s (returned %d)", ys == 1 ? "replaces it" :
352 ys == 2 ? "leaves it (refused)" : "lost it", r);
353
354 remove(dotdot);
355 remove(b);
356 remove(x);
357 remove(y);
358 sysLv2FsRmdir(sub);
359 sysLv2FsRmdir(dir);
360}
361
362int main(int argc, char **argv)
363{
364 (void)argc;
365 (void)argv;
366 Log_Open();
367 {
368 time_t now = time(NULL);
369 plat_log("PS3 clock: %s", ctime(&now)); // TLS checks certificate dates against it
370 }
371 Fs_Self_Test();
372 if (sysUtilRegisterCallback(SYSUTIL_EVENT_SLOT0, Sysutil_Callback, NULL) != 0)
373 plat_log("WARNING: sysUtilRegisterCallback failed");
374 ps3_input_init(); // (first: holding L1 at start-up keeps the RSX out of it)
375 if (!ps3_video_init())
376 return 1;
377 Net_Init();
378 if (!app_start())
379 return 1;
380 plat_sound_init();
381
382 uint64_t last = plat_time_us(), present_us = 0, presents = 0, present_log = last, last_flip = last;
383 while (!quit_requested) {
384 sysUtilCheckCallback();
385 if (xmb_open || plat_osk_active()) {
386 // the XMB gets the machine: no drawing (downloads carry on by
387 // themselves), and the last frame flipped again ~30 times a
388 // second: the system draws the XMB on our flips
389 static int was_open;
390 if (!was_open) { plat_log("%s: drawing paused", xmb_open ? "XMB open" : "keyboard up"); was_open = 1; }
391 app_background();
392 ps3_video_reflip();
393 usleep(33000);
394 if (!xmb_open && !plat_osk_active()) { plat_log("drawing again"); was_open = 0; app_redraw(); }
395 last = plat_time_us();
396 continue;
397 }
398 int drawn;
399 uint32_t *canvas = app_frame(&drawn);
400 if (drawn) {
401 uint64_t t0 = plat_time_us();
402 ps3_video_present(canvas);
403 present_us += plat_time_us() - t0;
404 presents++;
405 last_flip = plat_time_us();
406 } else {
407 // nothing new: the last frame again, every vblank. A steady 60
408 // (the system's pop-ups are drawn on our flips too, and HEN's
409 // frame counter reads what the TV gets)
410 ps3_video_reflip();
411 last_flip = plat_time_us();
412 }
413 if (plat_time_us() - present_log > 10000000) {
414 if (presents)
415 plat_log("present: %.1f ms each (the copy, and the wait for the vblank)", present_us / 1000.0 / presents);
416 present_us = presents = 0;
417 present_log = plat_time_us();
418 }
419 // no sleep here: every pass flips, and each flip waits for the one
420 // before it, so the vblank sets the pace (a sleep of our own as well
421 // beat against it and settled around 34 fps)
422 last = plat_time_us();
423 }
424 plat_log("XMB: quit");
425 app_shutdown();
426 plat_sound_quit();
427 ps3_video_shutdown();
428 sysUtilUnregisterCallback(SYSUTIL_EVENT_SLOT0);
429 if (log_lock_made) sysMutexLock(log_lock, 0);
430 if (log_file) { fclose(log_file); log_file = NULL; }
431 if (log_lock_made) sysMutexUnlock(log_lock);
432 return 0;
433}