diff --git a/.gitignore b/.gitignore index cd2946a..338b05b 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ $RECYCLE.BIN/ Network Trash Folder Temporary Items .apdisk + +# Build directory +build/ diff --git a/app/index.lua b/app/index.lua index 0ed9e50..aeacfee 100644 --- a/app/index.lua +++ b/app/index.lua @@ -19,98 +19,149 @@ end tbl = System.listDirectory("ux0:/data/TrackPlug") if tbl == nil then - tbl = {} + tbl = {} end -- Convert a 32 bit binary string to an integer function bin2int(str) - local b1, b2, b3, b4 = string.byte(str, 1, 4) - return (b4 << 24) + (b3 << 16) + (b2 << 8) + b1 + local b1, b2, b3, b4 = string.byte(str, 1, 4) + return bit32.lshift(b4, 24) + bit32.lshift(b3, 16) + bit32.lshift(b2, 8) + b1 end -- Format raw time data function FormatTime(val) - local minutes = math.floor(val/60) - local seconds = val%60 - local hours = math.floor(minutes/60) - local minutes = minutes%60 - local res = "" - if hours > 0 then - res = hours .. "h " - end - if minutes > 0 then - res = res .. minutes .. "m " - end - res = res .. seconds .. "s " - return res + local minutes = math.floor(val/60) + local seconds = val%60 + local hours = math.floor(minutes/60) + local minutes = minutes%60 + local res = "" + if hours > 0 then + res = hours .. "h " + end + if minutes > 0 then + res = res .. minutes .. "m " + end + res = res .. seconds .. "s " + return res end -- Recover title from homebrew database function recoverTitle(tid) - local file = System.openFile("ux0:/data/TrackPlugArchive/" .. tid .. ".txt", FREAD) - fsize = System.sizeFile(file) - local title = System.readFile(file, fsize) - System.closeFile(file) - return title + local file = System.openFile("ux0:/data/TrackPlugArchive/" .. tid .. ".txt", FREAD) + fsize = System.sizeFile(file) + local title = System.readFile(file, fsize) + System.closeFile(file) + return title end -- Extracts title name from an SFO file function extractTitle(file, tid) - local data = System.extractSFO(file) - if System.doesFileExist("ux0:/data/TrackPlugArchive/" .. tid .. ".txt") then - System.deleteFile("ux0:/data/TrackPlugArchive/" .. tid .. ".txt") - end - local file = System.openFile("ux0:/data/TrackPlugArchive/" .. tid .. ".txt", FCREATE) - System.writeFile(file, data.title, string.len(data.title)) - System.closeFile(file) - return data.title + local data = System.extractSfo(file) + if System.doesFileExist("ux0:/data/TrackPlugArchive/" .. tid .. ".txt") then + System.deleteFile("ux0:/data/TrackPlugArchive/" .. tid .. ".txt") + end + local file = System.openFile("ux0:/data/TrackPlugArchive/" .. tid .. ".txt", FCREATE) + System.writeFile(file, data.title, string.len(data.title)) + System.closeFile(file) + return data.title +end + +function getRegion(titleid) + local regioncode = string.sub(titleid,1,4) + local prefix = string.sub(regioncode,1,2) + local region = "Unknown" + + -- PSV common + if regioncode == "PCSA" or regioncode == "PCSE" then + region = "USA" + elseif regioncode == "PCSB" or regioncode == "PCSF" then + region = "Europe" + elseif regioncode == "PCSC" or regioncode == "PCSG" then + region = "Japan" + elseif regioncode == "PCSD" or regioncode == "PCSH" then + region = "Asia" + -- Physical & NP releases (PSV/PSP/PS1) + elseif prefix == "VC" or prefix == "VL" or + prefix == "UC" or prefix == "UL" or + prefix == "SC" or prefix == "SL" or + prefix == "NP" then + n1 = string.sub(regioncode,1,1) + n3 = string.sub(regioncode,3,3) + n4 = string.sub(regioncode,4,4) + if n3 == "A" then + region = "Asia" + elseif n3 == "C" then + region = "China" + elseif n3 == "E" then + region = "Europe" + elseif n3 == "H" then + region = "Hong Kong" + elseif n3 == "J" or n3 == "P" then + region = "Japan" + elseif n3 == "K" then + region = "Korea" + elseif n3 == "U" then + region = "USA" + end + + if n1 == "S" then + region = region .. " (PS1)" + elseif n1 == "U" or + (prefix == "NP" and (n4 == "G" or n4 == "H")) then + region = region .. " (PSP)" + elseif prefix == "NP" then + if n4 == "E" or n4 == "F" then + region = region .. " (PS1 - PAL)" + elseif n4 == "I" or n4 == "J" then + region = region .. " (PS1 - NTSC)" + end + end + elseif prefix == "PE" then + region = "Europe (PS1)" + elseif prefix == "PT" then + region = "Asia (PS1)" + elseif prefix == "PU" then + region = "USA (PS1)" + elseif string.sub(file.name,1,6) == "PSPEMU" then + region = "PSP/PS1" + end + + return region end -- Loading unknown icon local unk = Graphics.loadImage("app0:/unk.png") - + -- Getting region, playtime, icon and title name for any game for i, file in pairs(tbl) do - if file.name == "config.lua" then - dofile("ux0:/data/TrackPlug/"..file.name) - cfg_idx = i - else - local titleid = string.sub(file.name,1,-5) - local regioncode = string.sub(file.name,1,4) - if regioncode == "PCSA" or regioncode == "PCSE" then - file.region = "USA" - elseif regioncode == "PCSB" then - file.region = "EUR" - elseif regioncode == "PCSF" then - file.region = "AUS" - elseif regioncode == "PCSG" then - file.region = "JPN" - elseif regioncode == "PCSH" then - file.region = "ASN" - else - file.region = "UNK" - end - if System.doesFileExist("ur0:/appmeta/" .. titleid .. "/icon0.png") then - file.icon = Graphics.loadImage("ur0:/appmeta/" .. titleid .. "/icon0.png") - else - file.icon = unk - end - if System.doesFileExist("ux0:/data/TrackPlugArchive/" .. titleid .. ".txt") then - file.title = recoverTitle(titleid) - elseif System.doesFileExist("ux0:/app/" .. titleid .. "/sce_sys/param.sfo") then - file.title = extractTitle("ux0:/app/" .. titleid .. "/sce_sys/param.sfo", titleid) - else - file.title = "Unknown Title" - end - file.id = titleid - fd = System.openFile("ux0:/data/TrackPlug/" .. file.name, FREAD) - file.rtime = bin2int(System.readFile(fd, 4)) - file.ptime = FormatTime(file.rtime) - System.closeFile(fd) - end + if file.name == "config.lua" then + dofile("ux0:/data/TrackPlug/"..file.name) + cfg_idx = i + else + local titleid = string.sub(file.name,1,-5) + file.region = getRegion(titleid) + + if System.doesFileExist("ur0:/appmeta/" .. titleid .. "/icon0.png") then + file.icon = Graphics.loadImage("ur0:/appmeta/" .. titleid .. "/icon0.png") + else + file.icon = unk + end + if System.doesFileExist("ux0:/data/TrackPlugArchive/" .. titleid .. ".txt") then + file.title = recoverTitle(titleid) + elseif System.doesFileExist("ux0:/app/" .. titleid .. "/sce_sys/param.sfo") then + file.title = extractTitle("ux0:/app/" .. titleid .. "/sce_sys/param.sfo", titleid) + else + file.title = "Unknown Title" + end + file.id = titleid + fd = System.openFile("ux0:/data/TrackPlug/" .. file.name, FREAD) + file.rtime = bin2int(System.readFile(fd, 4)) + file.ptime = FormatTime(file.rtime) + System.closeFile(fd) + end end if cfg_idx ~= nil then - table.remove(tbl, cfg_idx) + table.remove(tbl, cfg_idx) end -- Background wave effect @@ -179,23 +230,23 @@ local grey = Color.new(40, 40, 40) local alarm_val = 128 local alarm_decrease = true function showAlarm(title, select_idx) - if alarm_decrease then - alarm_val = alarm_val - 4 - if alarm_val == 40 then - alarm_decrease = false - end - else - alarm_val = alarm_val + 4 - if alarm_val == 128 then - alarm_decrease = true - end - end - local sclr = Color.new(alarm_val, alarm_val, alarm_val) - Graphics.fillRect(200, 760, 200, 280, grey) - Graphics.debugPrint(205, 205, title, yellow) - Graphics.fillRect(200, 760, 215 + select_idx * 20, 235 + select_idx * 20, sclr) - Graphics.debugPrint(205, 235, "Yes", white) - Graphics.debugPrint(205, 255, "No", white) + if alarm_decrease then + alarm_val = alarm_val - 4 + if alarm_val == 40 then + alarm_decrease = false + end + else + alarm_val = alarm_val + 4 + if alarm_val == 128 then + alarm_decrease = true + end + end + local sclr = Color.new(alarm_val, alarm_val, alarm_val) + Graphics.fillRect(200, 760, 200, 280, grey) + Graphics.debugPrint(205, 205, title, yellow) + Graphics.fillRect(200, 760, 215 + select_idx * 20, 235 + select_idx * 20, sclr) + Graphics.debugPrint(205, 235, "Yes", white) + Graphics.debugPrint(205, 255, "No", white) end -- Scroll-list Renderer local sel_val = 128 @@ -322,10 +373,10 @@ end -- No games played yet apparently while true do - Graphics.initBlend() - Screen.clear() - Graphics.debugPrint(5, 5, "No games tracked yet.", white) - Graphics.termBlend() - Screen.flip() - Screen.waitVblankStart() + Graphics.initBlend() + Screen.clear() + Graphics.debugPrint(5, 5, "No games tracked yet.", white) + Graphics.termBlend() + Screen.flip() + Screen.waitVblankStart() end diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 4379dc2..dbc8444 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -8,7 +8,7 @@ if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) endif() endif() -project(TrackPlug) +project(TrackPlugX) include("${VITASDK}/share/vita.cmake" REQUIRED) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wl,-q -Wall -O3 -std=gnu99") @@ -25,26 +25,25 @@ if (NOT ${RELEASE}) add_definitions(-DENABLE_LOGGING) endif() -add_executable(TrackPlug +add_executable(${PROJECT_NAME} main.c ) -target_link_libraries(TrackPlug +target_link_libraries(${PROJECT_NAME} + gcc taihen_stub - SceAppUtil_stub SceAppMgr_stub SceLibKernel_stub - SceDisplay_stub - k - gcc - ScePower_stub - kuio_stub + SceIofilemgr_stub + SceLibc_stub + SceKernelModulemgr_stub + SceKernelThreadMgr_stub ) -set_target_properties(TrackPlug +set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-nostdlib" ) -vita_create_self(TrackPlug.suprx TrackPlug - CONFIG ${CMAKE_SOURCE_DIR}/TrackPlug.yml +vita_create_self(${PROJECT_NAME}.suprx ${PROJECT_NAME} + CONFIG ${CMAKE_SOURCE_DIR}/${PROJECT_NAME}.yml ) diff --git a/plugin/TrackPlug.yml b/plugin/TrackPlugX.yml old mode 100644 new mode 100755 similarity index 68% rename from plugin/TrackPlug.yml rename to plugin/TrackPlugX.yml index 11cab3b..c8b86b3 --- a/plugin/TrackPlug.yml +++ b/plugin/TrackPlugX.yml @@ -1,8 +1,8 @@ -TrackPlug: +TrackPlugX: attributes: 0 version: - major: 1 - minor: 2 + major: 2 + minor: 1 main: start: module_start stop: module_stop diff --git a/plugin/main.c b/plugin/main.c index 892a9a7..638d627 100644 --- a/plugin/main.c +++ b/plugin/main.c @@ -1,62 +1,180 @@ #include #include -#include -#include -#include -static SceUID hook; -static tai_hook_ref_t ref; -static char titleid[16]; -static char fname[256]; -static uint64_t playtime = 0; -static uint64_t tick = 0; +#define SECOND 1000000 +#define SAVE_PERIOD 10 -int sceDisplaySetFrameBuf_patched(const SceDisplayFrameBuf *pParam, int sync) { - - uint64_t t_tick = sceKernelGetProcessTimeWide(); - - // Saving playtime every 10 seconds - if ((t_tick - tick) > 10000000){ - tick = t_tick; - playtime += 10; - SceUID fd; - kuIoOpen(fname, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, &fd); - kuIoWrite(fd, &playtime, sizeof(uint64_t)); - kuIoClose(fd); - } - - return TAI_CONTINUE(int, ref, pParam, sync); -} +int snprintf(char *s, size_t n, const char *format, ...); + +// ScePspemu +static SceUID sub_810053F8_hookid = -1; +static tai_hook_ref_t sub_810053F8_hookref = {0}; + +static char adrenaline_titleid[12]; + +static uint8_t is_pspemu_loaded = 0; +static uint8_t is_pspemu_custom_bbl = 0; +static uint8_t is_pspemu_title_written = 0; +static uint8_t is_playtime_loaded = 0; + +static char playtime_bin_path[128]; +static uint64_t playtime_start = 0; +static uint64_t tick_start = 0; + +static void load_playtime(const char *titleid) { + snprintf(playtime_bin_path, 128, "ux0:/data/TrackPlug/%s.bin", titleid); + tick_start = sceKernelGetProcessTimeWide(); + is_pspemu_title_written = 0; + + SceUID fd = sceIoOpen(playtime_bin_path, SCE_O_RDONLY, 0777); + if (fd < 0) { + playtime_start = 0; + is_playtime_loaded = 1; + return; + } + + sceIoRead(fd, &playtime_start, sizeof(uint64_t)); + sceIoClose(fd); + + is_playtime_loaded = 1; +} + +static void write_playtime() { + uint32_t playtime = playtime_start + + (sceKernelGetProcessTimeWide() - tick_start) / SECOND; + + SceUID fd = sceIoOpen(playtime_bin_path, + SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777); + if (fd < 0) { + return; + } + + sceIoWrite(fd, &playtime, sizeof(uint64_t)); + sceIoClose(fd); +} + +void write_title(const char *titleid, const char *title) { + char path[128]; + snprintf(path, 128, "ux0:/data/TrackPlugArchive/%s.txt", titleid); + is_pspemu_title_written = 1; + + // Check if already exists + SceUID fd = sceIoOpen(path, SCE_O_RDONLY, 0777); + if (fd >= 0) { + sceIoClose(fd); + return; + } + + fd = sceIoOpen(path, SCE_O_WRONLY | SCE_O_CREAT, 0777); + if (fd < 0) { + return; + } + + sceIoWrite(fd, title, strlen(title)); + sceIoClose(fd); +} + +static int check_adrenaline() { + void *SceAdrenaline = (void *)0x73CDE000; + + char *title = SceAdrenaline + 24; + char *titleid = SceAdrenaline + 152; + + if (title[0] == 0 || !strncmp(title, "XMB", 3)) + return 0; + + // Write custom bubble Title + if (is_pspemu_custom_bbl && !is_pspemu_title_written) + write_title(adrenaline_titleid, title); + + // XMB game changed? + if (!is_pspemu_custom_bbl && strncmp(adrenaline_titleid, titleid, 9)) { + if (is_playtime_loaded) + write_playtime(); // Save closed game + + is_playtime_loaded = 0; + strcpy(adrenaline_titleid, titleid); + + load_playtime(titleid); + write_title(titleid, title); + return 0; + } + + return 1; +} + +int sub_810053F8_patched(int a1, int a2) { + char *pspemu_titleid = (char *)(a2 + 68); + + // If using custom bubble + if (strncmp(pspemu_titleid, "PSPEMUCFW", 9)) { + is_pspemu_custom_bbl = 1; + strcpy(adrenaline_titleid, pspemu_titleid); + + load_playtime(pspemu_titleid); + } + + is_pspemu_loaded = 1; + + return TAI_CONTINUE(int, sub_810053F8_hookref, a1, a2); +} + +static int tracker_thread(SceSize args, void *argp) { + while (1) { + if (is_pspemu_loaded) { + // Check if XMB/game has changed, write Title if necessary + int ret = check_adrenaline(); + if (!is_pspemu_custom_bbl && !ret) { + goto CONT; + } + } + + if (is_playtime_loaded) { + write_playtime(); + } + +CONT: + sceKernelDelayThread(SAVE_PERIOD * SECOND); + } + + return sceKernelExitDeleteThread(0); +} void _start() __attribute__ ((weak, alias ("module_start"))); int module_start(SceSize argc, const void *args) { - - // Getting game Title ID - sceAppMgrAppParamGetString(0, 12, titleid , 256); - - // Getting current playtime - SceUID fd; - sprintf(fname, "ux0:/data/TrackPlug/%s.bin", titleid); - kuIoOpen(fname, SCE_O_RDONLY, &fd); - if (fd >= 0){ - kuIoRead(fd, &playtime, sizeof(uint64_t)); - kuIoClose(fd); - } - - // Getting starting tick - tick = sceKernelGetProcessTimeWide(); - - hook = taiHookFunctionImport(&ref, - TAI_MAIN_MODULE, - TAI_ANY_LIBRARY, - 0x7A410B64, - sceDisplaySetFrameBuf_patched); - - return SCE_KERNEL_START_SUCCESS; + char titleid[12]; + sceAppMgrAppParamGetString(0, 12, titleid, 12); + + if (!strncmp(titleid, "NPXS10028", 9)) { + tai_module_info_t tai_info; + tai_info.size = sizeof(tai_module_info_t); + taiGetModuleInfo("ScePspemu", &tai_info); + + sub_810053F8_hookid = taiHookFunctionOffset( + &sub_810053F8_hookref, + tai_info.modid, + 0, 0x53F8, 1, + sub_810053F8_patched); + } else { + load_playtime(titleid); + } + + SceUID tracker_thread_id = sceKernelCreateThread( + "TrackPlugX", + tracker_thread, + 0x10000100, + 0x10000, + 0, 0, NULL); + if (tracker_thread_id >= 0) + sceKernelStartThread(tracker_thread_id, 0, NULL); + + return SCE_KERNEL_START_SUCCESS; } int module_stop(SceSize argc, const void *args) { + if (sub_810053F8_hookid >= 0) { + taiHookRelease(sub_810053F8_hookid, sub_810053F8_hookref); + } - return SCE_KERNEL_STOP_SUCCESS; - -} \ No newline at end of file + return SCE_KERNEL_STOP_SUCCESS; +}