Merge pull request #310 from VitaSmith/VitaSmith

Add DLC refresh + restore license from Sqlite DB
This commit is contained in:
TheOfficialFloW 2017-10-27 08:21:59 +02:00 committed by GitHub
commit 048b822e9f
7 changed files with 382 additions and 108 deletions

View File

@ -156,6 +156,7 @@ target_link_libraries(VitaShell
SceNpDrm_stub
SceRegistryMgr_stub
SceShellSvc_stub
SceSqlite_stub
SceSsl_stub
SceSysmodule_stub
ScePgf_stub

BIN
db/licenses.db Normal file

Binary file not shown.

18
db/readme.txt Normal file
View File

@ -0,0 +1,18 @@
This directory contains an example of the type of database one can use to
store personal licenses, so that VitaShell can automatically restore them
when reinstalling content from PKG data.
As opposed to this sample, which does not contain any usable data, your
CONTENT_ID columns should contain valid content id for the PS Vita app,
game or DLC you own, and the RIF columns should contain a 512-byte .rif
blob that includes your AID, a CONTENT_ID that matches the one from the
first column as well as your personal license data.
To edit this database manually, you can use the Windows/Linux/MacOS
compatible DB Browser for SQLite (http://sqlitebrowser.org/) to drag and
drop your .rif files and edit the relevant CONTENT_ID fields.
Or you can wait until someone writes an application that automates that
process or you... ;)
This database should be copied to ux0:license/

11
init.c
View File

@ -252,6 +252,15 @@ static void finishVita2dLib() {
font = NULL;
}
static int initSQLite() {
SceSqliteMallocMethods mf = {
(void* (*) (int)) malloc,
(void* (*) (void*, int)) realloc,
free
};
return sceSqliteConfigMallocMethods(&mf);
}
static void initNet() {
static char memory[16 * 1024];
@ -356,6 +365,7 @@ void initVitaShell() {
sceSysmoduleLoadModule(SCE_SYSMODULE_PHOTO_EXPORT);
sceSysmoduleLoadModule(SCE_SYSMODULE_NET);
sceSysmoduleLoadModule(SCE_SYSMODULE_HTTPS);
sceSysmoduleLoadModule(SCE_SYSMODULE_SQLITE);
// Init
vitaAudioInit(0x40);
@ -363,6 +373,7 @@ void initVitaShell() {
initSceAppUtil();
initNet();
initQR();
initSQLite();
// Init power tick thread
initPowerTickThread();

1
main.h
View File

@ -38,6 +38,7 @@
#include <psp2/rtc.h>
#include <psp2/registrymgr.h>
#include <psp2/shellutil.h>
#include <psp2/sqlite.h>
#include <psp2/sysmodule.h>
#include <psp2/system_param.h>
#include <psp2/touch.h>

356
refresh.c
View File

@ -1,6 +1,7 @@
/*
VitaShell
Copyright (C) 2015-2017, TheFloW
Copyright (C) 2017, VitaSmith
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -26,41 +27,85 @@
#include "message_dialog.h"
#include "language.h"
#include "utils.h"
#include "sqlite3.h"
int isCustomHomebrew() {
uint32_t work[512/4];
// Note: The promotion process is *VERY* sensitive to the directories used below
// Don't change them unless you know what you are doing!
#define APP_TEMP "ux0:temp/app"
#define DLC_TEMP "ux0:temp/addcont"
#define DB_PATH "ux0:/license/licenses.db"
if (ReadFile("ux0:temp/game/sce_sys/package/work.bin", work, sizeof(work)) != sizeof(work))
#define RIF_SIZE 512
#define MAX_QUERY_LENGTH 128
#define MAX_DLC_PER_TITLE 1024
int isCustomHomebrew(const char* path)
{
uint32_t work[RIF_SIZE/4];
if (ReadFile(path, work, sizeof(work)) != sizeof(work))
return 0;
int i;
for (i = 0; i < sizeof(work) / sizeof(uint32_t); i++)
for (int i = 0; i < sizeof(work) / sizeof(uint32_t); i++)
if (work[i] != 0)
return 0;
return 1;
}
int refreshApp(const char *name) {
char app_path[MAX_PATH_LENGTH], param_path[MAX_PATH_LENGTH], license_path[MAX_PATH_LENGTH];
int res;
// Query a rif from an sqlite database. Returned data must be freed by the caller.
uint8_t* query_rif(const char* db_path, const char* content_id)
{
int rc, rif_size = 0;
sqlite3 *db = NULL;
sqlite3_stmt *stmt;
char query[MAX_QUERY_LENGTH];
uint8_t *rif = NULL;
// Path
snprintf(app_path, MAX_PATH_LENGTH, "ux0:app/%s", name);
snprintf(param_path, MAX_PATH_LENGTH, "ux0:app/%s/sce_sys/param.sfo", name);
rc = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK)
return NULL;
snprintf(query, sizeof(query), "SELECT RIF FROM Licenses WHERE CONTENT_ID = '%s';", content_id);
rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
if (rc != SQLITE_OK)
return NULL;
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW)
rif_size = sqlite3_column_bytes(stmt, 0);
if (rif_size != RIF_SIZE)
return NULL;
rif = malloc(rif_size);
if (rif != NULL)
memcpy(rif, sqlite3_column_blob(stmt, 0), rif_size);
sqlite3_close(db);
return rif;
}
int refreshNeeded(const char *app_path)
{
char sfo_path[MAX_PATH_LENGTH];
// TODO: Check app vs dlc from SFO
int res, is_app = (app_path[6] == 'p');
// Read param.sfo
snprintf(sfo_path, MAX_PATH_LENGTH, "%s/sce_sys/param.sfo", app_path);
void *sfo_buffer = NULL;
int sfo_size = allocateReadFile(param_path, &sfo_buffer);
int sfo_size = allocateReadFile(sfo_path, &sfo_buffer);
if (sfo_size < 0) {
if (sfo_buffer)
free(sfo_buffer);
return sfo_size;
}
// Get titleid
char titleid[12];
// Get title and content ids
char titleid[12], contentid[50];
getSfoString(sfo_buffer, "TITLE_ID", titleid, sizeof(titleid));
getSfoString(sfo_buffer, "CONTENT_ID", contentid, sizeof(contentid));
// Free sfo buffer
free(sfo_buffer);
@ -74,49 +119,193 @@ int refreshApp(const char *name) {
// Check if bounded rif file exits
_sceNpDrmGetRifName(rif_name, 0, aid);
snprintf(license_path, MAX_PATH_LENGTH, "ux0:license/app/%s/%s", titleid, rif_name);
if (checkFileExist(license_path))
if (is_app)
snprintf(sfo_path, MAX_PATH_LENGTH, "ux0:license/app/%s/%s", titleid, rif_name);
else
snprintf(sfo_path, MAX_PATH_LENGTH, "ux0:license/addcont/%s/%s/%s", titleid, &contentid[20], rif_name);
if (checkFileExist(sfo_path))
return 0;
// Check if fixed rif file exits
_sceNpDrmGetFixedRifName(rif_name, 0, 0);
snprintf(license_path, MAX_PATH_LENGTH, "ux0:license/app/%s/%s", titleid, rif_name);
if (checkFileExist(license_path))
if (is_app)
snprintf(sfo_path, MAX_PATH_LENGTH, "ux0:license/app/%s/%s", titleid, rif_name);
else
snprintf(sfo_path, MAX_PATH_LENGTH, "ux0:license/addcont/%s/%s/%s", titleid, &contentid[20], rif_name);
if (checkFileExist(sfo_path))
return 0;
}
// Clean
removePath("ux0:temp/game", NULL);
sceIoMkdir("ux0:temp", 0006);
// Rename app
res = sceIoRename(app_path, "ux0:temp/game");
if (res < 0)
return res;
// Remove work.bin for custom homebrews
if (isCustomHomebrew())
sceIoRemove("ux0:temp/game/sce_sys/package/work.bin");
// Promote app
res = promoteApp("ux0:temp/game");
// Rename back if it failed
if (res < 0) {
sceIoRename("ux0:temp/game", app_path);
return res;
}
// Return success
return 1;
}
int refresh_thread(SceSize args, void *argp) {
int refreshApp(const char *app_path)
{
char work_bin_path[MAX_PATH_LENGTH];
int res;
snprintf(work_bin_path, MAX_PATH_LENGTH, "%s/sce_sys/package/work.bin", app_path);
// Remove work.bin for custom homebrews
if (isCustomHomebrew(work_bin_path)) {
sceIoRemove(work_bin_path);
} else if (!checkFileExist(work_bin_path)) {
// If available, restore work.bin from licenses.db
void *sfo_buffer = NULL;
char sfo_path[MAX_PATH_LENGTH], contentid[50];
snprintf(sfo_path, MAX_PATH_LENGTH, "%s/sce_sys/param.sfo", app_path);
int sfo_size = allocateReadFile(sfo_path, &sfo_buffer);
if (sfo_size > 0) {
getSfoString(sfo_buffer, "CONTENT_ID", contentid, sizeof(contentid));
uint8_t* rif = query_rif(DB_PATH, contentid);
if (rif != NULL) {
int fh = sceIoOpen(work_bin_path, SCE_O_WRONLY | SCE_O_CREAT, 0777);
if (fh > 0) {
sceIoWrite(fh, rif, RIF_SIZE);
sceIoClose(fh);
}
free(rif);
}
}
free(sfo_buffer);
}
// Promote app/dlc
res = promoteApp(app_path);
return (res < 0) ? res : 1;
}
int parse_dir_with_callback(const char* path, void(*callback)(void*, const char*, const char*), void* data)
{
SceUID dfd = sceIoDopen(path);
if (dfd >= 0) {
int res = 0;
do {
SceIoDirent dir;
memset(&dir, 0, sizeof(SceIoDirent));
res = sceIoDread(dfd, &dir);
if (res > 0) {
if (SCE_S_ISDIR(dir.d_stat.st_mode)) {
callback(data, path, dir.d_name);
if (cancelHandler()) {
closeWaitDialog();
setDialogStep(DIALOG_STEP_CANCELLED);
return -1;
}
}
}
} while (res > 0);
sceIoDclose(dfd);
}
return 0;
}
typedef struct {
int refresh_pass;
int count;
int processed;
int refreshed;
} refresh_data_t;
typedef struct {
refresh_data_t *refresh_data;
char* list[MAX_DLC_PER_TITLE];
int list_size;
} dlc_data_t;
void app_callback(void* data, const char* dir, const char* subdir)
{
refresh_data_t *refresh_data = (refresh_data_t*)data;
char path[MAX_PATH_LENGTH];
if (strcmp(subdir, vitashell_titleid) == 0)
return;
if (refresh_data->refresh_pass) {
snprintf(path, MAX_PATH_LENGTH, "%s/%s", dir, subdir);
if (refreshNeeded(path)) {
// Move the directory to temp for installation
removePath(APP_TEMP, NULL);
sceIoRename(path, APP_TEMP);
if (refreshApp(APP_TEMP) == 1)
refresh_data->refreshed++;
else
// Restore folder on error
sceIoRename(APP_TEMP, path);
}
SetProgress(++refresh_data->processed, refresh_data->count);
} else {
refresh_data->count++;
}
}
void dlc_callback_inner(void* data, const char* dir, const char* subdir)
{
dlc_data_t *dlc_data = (dlc_data_t*)data;
char path[MAX_PATH_LENGTH];
// Ignore "sce_sys" and "sce_pfs" directories
if (strncmp(subdir, "sce_", 4) == 0)
return;
if (dlc_data->refresh_data->refresh_pass) {
snprintf(path, MAX_PATH_LENGTH, "%s/%s", dir, subdir);
if (dlc_data->list_size < MAX_DLC_PER_TITLE)
dlc_data->list[dlc_data->list_size++] = strdup(path);
} else {
dlc_data->refresh_data->count++;
}
}
void dlc_callback_outer(void* data, const char* dir, const char* subdir)
{
refresh_data_t *refresh_data = (refresh_data_t*)data;
dlc_data_t dlc_data;
dlc_data.refresh_data = refresh_data;
dlc_data.list_size = 0;
char path[MAX_PATH_LENGTH];
// Get the title's dlc subdirectories
int len = snprintf(path, sizeof(path), "%s/%s", dir, subdir);
parse_dir_with_callback(path, dlc_callback_inner, &dlc_data);
if (refresh_data->refresh_pass) {
// For dlc, the process happens in two phases to avoid promotion errors:
// 1. Move all dlc that require refresh out of addcont/title_id
// 2. Refresh the moved dlc_data
for (int i = 0; i < dlc_data.list_size; i++) {
if (refreshNeeded(dlc_data.list[i])) {
snprintf(path, MAX_PATH_LENGTH, DLC_TEMP "/%s", &dlc_data.list[i][len + 1]);
removePath(path, NULL);
sceIoRename(dlc_data.list[i], path);
} else {
free(dlc_data.list[i]);
dlc_data.list[i] = NULL;
SetProgress(++refresh_data->processed, refresh_data->count);
}
}
// Now that the dlc we need are out of addcont/title_id, refresh them
for (int i = 0; i < dlc_data.list_size; i++) {
if (dlc_data.list[i] != NULL) {
snprintf(path, MAX_PATH_LENGTH, DLC_TEMP "/%s", &dlc_data.list[i][len + 1]);
if (refreshApp(path) == 1)
refresh_data->refreshed++;
else
sceIoRename(path, dlc_data.list[i]);
SetProgress(++refresh_data->processed, refresh_data->count);
free(dlc_data.list[i]);
}
}
}
}
int refresh_thread(SceSize args, void *argp)
{
SceUID thid = -1;
SceUID dfd = -1;
int folders = 0;
int count = 0;
int items = 0;
refresh_data_t refresh_data = { 0, 0, 0, 0 };
// Lock power timers
powerLock();
@ -125,70 +314,31 @@ int refresh_thread(SceSize args, void *argp) {
sceMsgDialogProgressBarSetValue(SCE_MSG_DIALOG_PROGRESSBAR_TARGET_BAR_DEFAULT, 0);
sceKernelDelayThread(DIALOG_WAIT); // Needed to see the percentage
// Get number of folders
dfd = sceIoDopen("ux0:app");
if (dfd >= 0) {
int res = 0;
do {
SceIoDirent dir;
memset(&dir, 0, sizeof(SceIoDirent));
res = sceIoDread(dfd, &dir);
if (res > 0) {
if (SCE_S_ISDIR(dir.d_stat.st_mode)) {
if (strcmp(dir.d_name, vitashell_titleid) == 0)
continue;
// Count
folders++;
if (cancelHandler()) {
closeWaitDialog();
setDialogStep(DIALOG_STEP_CANCELLED);
// Get the app count
if (parse_dir_with_callback("ux0:app", app_callback, &refresh_data) < 0)
goto EXIT;
}
}
}
} while (res > 0);
sceIoDclose(dfd);
}
// Get the dlc count
if (parse_dir_with_callback("ux0:addcont", dlc_callback_outer, &refresh_data) < 0)
goto EXIT;
// Update thread
thid = createStartUpdateThread(folders, 0);
thid = createStartUpdateThread(refresh_data.count, 0);
dfd = sceIoDopen("ux0:app");
if (dfd >= 0) {
int res = 0;
// Make sure we have the temp directories we need
sceIoMkdir("ux0:temp", 0006);
sceIoMkdir("ux0:temp/addcont", 0006);
refresh_data.refresh_pass = 1;
do {
SceIoDirent dir;
memset(&dir, 0, sizeof(SceIoDirent));
res = sceIoDread(dfd, &dir);
if (res > 0) {
if (SCE_S_ISDIR(dir.d_stat.st_mode)) {
if (strcmp(dir.d_name, vitashell_titleid) == 0)
continue;
// Refresh app
if (refreshApp(dir.d_name) == 1)
items++;
SetProgress(++count, folders);
if (cancelHandler()) {
closeWaitDialog();
setDialogStep(DIALOG_STEP_CANCELLED);
// Refresh apps
if (parse_dir_with_callback("ux0:app", app_callback, &refresh_data) < 0)
goto EXIT;
}
}
}
} while (res > 0);
sceIoDclose(dfd);
}
// Refresh dlc
if (parse_dir_with_callback("ux0:addcont", dlc_callback_outer, &refresh_data) < 0)
goto EXIT;
sceIoRmdir("ux0:temp/addcont");
// Set progress to 100%
sceMsgDialogProgressBarSetValue(SCE_MSG_DIALOG_PROGRESSBAR_TARGET_BAR_DEFAULT, 100);
@ -197,7 +347,7 @@ int refresh_thread(SceSize args, void *argp) {
// Close
closeWaitDialog();
infoDialog(language_container[REFRESHED], items);
infoDialog(language_container[REFRESHED], refresh_data.refreshed);
EXIT:
if (thid >= 0)

93
sqlite3.h Normal file
View File

@ -0,0 +1,93 @@
/*
** 2001 September 15
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
**
** This header was intentionally truncated from the original version to
** only include the elements needed by this application.
**
*/
#ifndef _SQLITE3_H_
#define _SQLITE3_H_
#ifdef __cplusplus
extern "C" {
#endif
#ifndef SQLITE_EXTERN
# define SQLITE_EXTERN extern
#endif
#ifndef SQLITE_API
# define SQLITE_API
#endif
#define SQLITE_OK 0 /* Successful result */
#define SQLITE_ERROR 1 /* SQL error or missing database */
#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */
#define SQLITE_PERM 3 /* Access permission denied */
#define SQLITE_ABORT 4 /* Callback routine requested an abort */
#define SQLITE_BUSY 5 /* The database file is locked */
#define SQLITE_LOCKED 6 /* A table in the database is locked */
#define SQLITE_NOMEM 7 /* A malloc() failed */
#define SQLITE_READONLY 8 /* Attempt to write a readonly database */
#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/
#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */
#define SQLITE_CORRUPT 11 /* The database disk image is malformed */
#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */
#define SQLITE_FULL 13 /* Insertion failed because database is full */
#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
#define SQLITE_PROTOCOL 15 /* Database lock protocol error */
#define SQLITE_EMPTY 16 /* Database is empty */
#define SQLITE_SCHEMA 17 /* The database schema changed */
#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */
#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
#define SQLITE_MISMATCH 20 /* Data type mismatch */
#define SQLITE_MISUSE 21 /* Library used incorrectly */
#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */
#define SQLITE_AUTH 23 /* Authorization denied */
#define SQLITE_FORMAT 24 /* Auxiliary database format error */
#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */
#define SQLITE_NOTADB 26 /* File opened that is not a database file */
#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
#define SQLITE_OPEN_READONLY 0x00000001
#define SQLITE_OPEN_READWRITE 0x00000002
#define SQLITE_OPEN_CREATE 0x00000004
#define SQLITE_OPEN_URI 0x00000040
#define SQLITE_OPEN_NOMUTEX 0x00008000
#define SQLITE_OPEN_FULLMUTEX 0x00010000
#define SQLITE_OPEN_SHAREDCACHE 0x00020000
#define SQLITE_OPEN_PRIVATECACHE 0x00040000
typedef struct sqlite3 sqlite3;
typedef struct sqlite3_stmt sqlite3_stmt;
SQLITE_API int sqlite3_errcode(sqlite3 *db);
SQLITE_API int sqlite3_extended_errcode(sqlite3 *db);
SQLITE_API const char *sqlite3_errmsg(sqlite3*);
SQLITE_API void *sqlite3_malloc(int);
SQLITE_API void *sqlite3_realloc(void*, int);
SQLITE_API void sqlite3_free(void*);
SQLITE_API int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs);
SQLITE_API int sqlite3_close(sqlite3 *);
SQLITE_API int sqlite3_exec(sqlite3*, const char *sql, int (*callback)(void*,int,char**,char**), void *, char **errmsg);
SQLITE_API int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail);
SQLITE_API int sqlite3_step(sqlite3_stmt*);
SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol);
SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol);
#ifdef __cplusplus
}
#endif
#endif