| 1 | /* |
| 2 | main_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 |
|
| 31 | int ps3_net_ok; |
| 32 | static volatile int quit_requested; |
| 33 | static volatile int xmb_open; // the PS button menu is over us |
| 34 |
|
| 35 | /* |
| 36 | ================== |
| 37 | Log |
| 38 | ================== |
| 39 | */ |
| 40 | static FILE *log_file; |
| 41 | static sys_mutex_t log_lock; |
| 42 | static int log_lock_made; |
| 43 |
|
| 44 | void 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 |
|
| 62 | static 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 | ================== |
| 78 | Time, threads, locks |
| 79 | ================== |
| 80 | */ |
| 81 | uint64_t plat_time_us(void) { return sysGetSystemTime(); } |
| 82 | void plat_sleep_us(unsigned us) { usleep(us); } |
| 83 |
|
| 84 | typedef struct { |
| 85 | plat_thread_fn fn; |
| 86 | void *arg; |
| 87 | } thread_start_t; |
| 88 |
|
| 89 | static 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 |
|
| 97 | int 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 |
|
| 113 | struct plat_mutex { |
| 114 | sys_mutex_t m; |
| 115 | }; |
| 116 |
|
| 117 | plat_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 |
|
| 131 | void plat_mutex_lock(plat_mutex_t *m) { if (m) sysMutexLock(m->m, 0); } |
| 132 | void plat_mutex_unlock(plat_mutex_t *m) { if (m) sysMutexUnlock(m->m); } |
| 133 |
|
| 134 | /* |
| 135 | ================== |
| 136 | Files and system |
| 137 | ================== |
| 138 | */ |
| 139 | const char *plat_data_dir(void) { return APP_DIR "/data"; } |
| 140 | const char *plat_save_dir(void) { return APP_DIR; } |
| 141 |
|
| 142 | int 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 |
|
| 149 | unsigned 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 |
|
| 166 | int 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 |
|
| 186 | long 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 |
|
| 194 | long 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 |
|
| 202 | int 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 |
|
| 211 | int 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 |
|
| 227 | long 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 |
|
| 244 | int 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 |
|
| 261 | void 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) |
| 270 | void 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 |
| 277 | int 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 | ================== |
| 290 | main |
| 291 | ================== |
| 292 | */ |
| 293 | static 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 |
|
| 307 | static 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 | ================== |
| 323 | Self-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 | */ |
| 327 | static 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 |
|
| 333 | static 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 |
|
| 362 | int 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 | } |