Add license database refresh

A new menu feature "Refresh license database" is now available from the top context menu.

Its action is to parse all the .rif files under ux0:licence/app and ux0:licence/addcont,
and import them to the ux0:licence/license.db SQLite database (which gets created it it
doesn't already exists).

Used in conjunction with the new App/DLC refresh feature, this allows Vita users to
restore their content straight from PKG downloads, and let VitaShell do the job of also
restoring the relevant license from the DB backup.

This also fixes a small typo in LANGUAGE_CONTAINER_SIZE and adds a .editorconfig, to
ensure that the indentation settings of VitaShell are followed by modern code editors.
This commit is contained in:
VitaSmith 2017-10-29 01:10:36 +01:00
parent 048b822e9f
commit 7f2d1134d9
17 changed files with 622 additions and 91 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*]
indent_style = space
indent_size = 2

View File

@ -83,6 +83,8 @@ add_executable(VitaShell
text.c
hex.c
sfo.c
rif.c
sqlite3.c
coredump.c
settings.c
property_dialog.c

Binary file not shown.

View File

@ -1,18 +0,0 @@
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/

13
init.c
View File

@ -22,6 +22,7 @@
#include "package_installer.h"
#include "utils.h"
#include "qr.h"
#include "rif.h"
#include "audio/vita_audio.h"
@ -253,12 +254,11 @@ static void finishVita2dLib() {
}
static int initSQLite() {
SceSqliteMallocMethods mf = {
(void* (*) (int)) malloc,
(void* (*) (void*, int)) realloc,
free
};
return sceSqliteConfigMallocMethods(&mf);
return sqlite_init();
}
static int finishSQLite() {
return sqlite_exit();
}
static void initNet() {
@ -394,6 +394,7 @@ void initVitaShell() {
void finishVitaShell() {
// Finish
finishSQLite();
finishNet();
finishSceAppUtil();
finishVita2dLib();

View File

@ -44,11 +44,11 @@ static char *lang[] ={
"turkish"
};
char *language_container[LANGUAGE_CONTRAINER_SIZE];
char *language_container[LANGUAGE_CONTAINER_SIZE];
void freeLanguageContainer() {
int i;
for (i = 0; i < LANGUAGE_CONTRAINER_SIZE; i++) {
for (i = 0; i < LANGUAGE_CONTAINER_SIZE; i++) {
if (language_container[i]) {
free(language_container[i]);
language_container[i] = NULL;
@ -101,6 +101,7 @@ void loadLanguage(int id) {
// Context menu strings
LANGUAGE_ENTRY(REFRESH_LIVEAREA),
LANGUAGE_ENTRY(REFRESH_LICENSE_DB),
LANGUAGE_ENTRY(MOUNT_UMA0),
LANGUAGE_ENTRY(MOUNT_IMC0),
LANGUAGE_ENTRY(MOUNT_USB_UX0),
@ -161,6 +162,7 @@ void loadLanguage(int id) {
LANGUAGE_ENTRY(COPIED_FILE),
LANGUAGE_ENTRY(COPIED_FOLDER),
LANGUAGE_ENTRY(COPIED_FILES_FOLDERS),
LANGUAGE_ENTRY(IMPORTED_LICENSES),
// Dialog questions
LANGUAGE_ENTRY(DELETE_FILE_QUESTION),
@ -185,6 +187,7 @@ void loadLanguage(int id) {
LANGUAGE_ENTRY(HASH_FILE_QUESTION),
LANGUAGE_ENTRY(SAVE_MODIFICATIONS),
LANGUAGE_ENTRY(REFRESH_LIVEAREA_QUESTION),
LANGUAGE_ENTRY(REFRESH_LICENSE_DB_QUESTION),
// HENkaku settings strings
LANGUAGE_ENTRY(HENKAKU_SETTINGS),

View File

@ -60,6 +60,7 @@ enum LanguageContainer {
// Context menu strings
REFRESH_LIVEAREA,
REFRESH_LICENSE_DB,
MOUNT_UMA0,
MOUNT_IMC0,
MOUNT_USB_UX0,
@ -120,6 +121,7 @@ enum LanguageContainer {
COPIED_FILE,
COPIED_FOLDER,
COPIED_FILES_FOLDERS,
IMPORTED_LICENSES,
// Dialog questions
DELETE_FILE_QUESTION,
@ -144,6 +146,7 @@ enum LanguageContainer {
HASH_FILE_QUESTION,
SAVE_MODIFICATIONS,
REFRESH_LIVEAREA_QUESTION,
REFRESH_LICENSE_DB_QUESTION,
// HENkaku settings strings
HENKAKU_SETTINGS,
@ -207,10 +210,10 @@ enum LanguageContainer {
UPDATE_QUESTION,
ARCHIVE_NAME,
COMPRESSION_LEVEL,
LANGUAGE_CONTRAINER_SIZE,
LANGUAGE_CONTAINER_SIZE,
};
extern char *language_container[LANGUAGE_CONTRAINER_SIZE];
extern char *language_container[LANGUAGE_CONTAINER_SIZE];
void freeLanguageContainer();
void loadLanguage(int id);

16
main.c
View File

@ -660,6 +660,22 @@ static int dialogSteps() {
break;
}
case DIALOG_STEP_REFRESH_LICENSE_DB_QUESTION:
{
if (msg_result == MESSAGE_DIALOG_RESULT_YES) {
initMessageDialog(MESSAGE_DIALOG_PROGRESS_BAR, language_container[REFRESHING]);
setDialogStep(DIALOG_STEP_REFRESHING);
SceUID thid = sceKernelCreateThread("license_thread", (SceKernelThreadEntry)license_thread, 0x40, 0x100000, 0, 0, NULL);
if (thid >= 0)
sceKernelStartThread(thid, 0, NULL);
} else if (msg_result == MESSAGE_DIALOG_RESULT_NO) {
setDialogStep(DIALOG_STEP_NONE);
}
break;
}
case DIALOG_STEP_USB_ATTACH_WAIT:
{
if (msg_result == MESSAGE_DIALOG_RESULT_RUNNING) {

1
main.h
View File

@ -189,6 +189,7 @@ enum DialogSteps {
DIALOG_STEP_SYSTEM,
DIALOG_STEP_REFRESH_LIVEAREA_QUESTION,
DIALOG_STEP_REFRESH_LICENSE_DB_QUESTION,
DIALOG_STEP_REFRESHING,
DIALOG_STEP_USB_ATTACH_WAIT,

View File

@ -33,6 +33,7 @@ char pfs_mount_point[MAX_MOUNT_POINT_LENGTH];
enum MenuHomeEntrys {
MENU_HOME_ENTRY_REFRESH_LIVEAREA,
MENU_HOME_ENTRY_REFRESH_LICENSE_DB,
MENU_HOME_ENTRY_MOUNT_UMA0,
MENU_HOME_ENTRY_MOUNT_IMC0,
MENU_HOME_ENTRY_MOUNT_USB_UX0,
@ -40,11 +41,12 @@ enum MenuHomeEntrys {
};
MenuEntry menu_home_entries[] = {
{ REFRESH_LIVEAREA, 0, 0, CTX_INVISIBLE },
{ MOUNT_UMA0, 1, 0, CTX_INVISIBLE },
{ MOUNT_IMC0, 2, 0, CTX_INVISIBLE },
{ MOUNT_USB_UX0, 4, 0, CTX_INVISIBLE },
{ UMOUNT_USB_UX0, 5, 0, CTX_INVISIBLE },
{ REFRESH_LIVEAREA, 0, 0, CTX_INVISIBLE },
{ REFRESH_LICENSE_DB, 1, 0, CTX_INVISIBLE },
{ MOUNT_UMA0, 2, 0, CTX_INVISIBLE },
{ MOUNT_IMC0, 3, 0, CTX_INVISIBLE },
{ MOUNT_USB_UX0, 5, 0, CTX_INVISIBLE },
{ UMOUNT_USB_UX0, 6, 0, CTX_INVISIBLE },
};
#define N_MENU_HOME_ENTRIES (sizeof(menu_home_entries) / sizeof(MenuEntry))
@ -466,6 +468,18 @@ static int contextMenuHomeEnterCallback(int sel, void *context) {
break;
}
case MENU_HOME_ENTRY_REFRESH_LICENSE_DB:
{
if (is_safe_mode) {
infoDialog(language_container[EXTENDED_PERMISSIONS_REQUIRED]);
} else {
initMessageDialog(SCE_MSG_DIALOG_BUTTON_TYPE_YESNO, language_container[REFRESH_LICENSE_DB_QUESTION]);
setDialogStep(DIALOG_STEP_REFRESH_LICENSE_DB_QUESTION);
}
break;
}
case MENU_HOME_ENTRY_MOUNT_UMA0:
{
if (is_safe_mode) {

167
refresh.c
View File

@ -27,16 +27,13 @@
#include "message_dialog.h"
#include "language.h"
#include "utils.h"
#include "sqlite3.h"
#include "rif.h"
// 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"
#define RIF_SIZE 512
#define MAX_QUERY_LENGTH 128
#define MAX_DLC_PER_TITLE 1024
int isCustomHomebrew(const char* path)
@ -53,39 +50,6 @@ int isCustomHomebrew(const char* path)
return 1;
}
// 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;
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];
@ -157,7 +121,7 @@ int refreshApp(const char *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);
uint8_t* rif = query_rif(LICENSE_DB, contentid);
if (rif != NULL) {
int fh = sceIoOpen(work_bin_path, SCE_O_WRONLY | SCE_O_CREAT, 0777);
if (fh > 0) {
@ -175,7 +139,8 @@ int refreshApp(const char *app_path)
return (res < 0) ? res : 1;
}
int parse_dir_with_callback(const char* path, void(*callback)(void*, const char*, const char*), void* data)
// target_type should be either SCE_S_IFREG for files or SCE_S_IFDIR for directories
int parse_dir_with_callback(int target_type, const char* path, void(*callback)(void*, const char*, const char*), void* data)
{
SceUID dfd = sceIoDopen(path);
if (dfd >= 0) {
@ -187,7 +152,7 @@ int parse_dir_with_callback(const char* path, void(*callback)(void*, const char*
res = sceIoDread(dfd, &dir);
if (res > 0) {
if (SCE_S_ISDIR(dir.d_stat.st_mode)) {
if ((dir.d_stat.st_mode & SCE_S_IFMT) == target_type) {
callback(data, path, dir.d_name);
if (cancelHandler()) {
closeWaitDialog();
@ -215,6 +180,16 @@ typedef struct {
int list_size;
} dlc_data_t;
typedef struct {
int copy_pass;
int count;
int processed;
int copied;
int cur_depth;
int max_depth;
uint8_t* rif;
} license_data_t;
void app_callback(void* data, const char* dir, const char* subdir)
{
refresh_data_t *refresh_data = (refresh_data_t*)data;
@ -269,7 +244,7 @@ void dlc_callback_outer(void* data, const char* dir, const char* subdir)
// 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);
parse_dir_with_callback(SCE_S_IFDIR, path, dlc_callback_inner, &dlc_data);
if (refresh_data->refresh_pass) {
// For dlc, the process happens in two phases to avoid promotion errors:
@ -315,11 +290,11 @@ int refresh_thread(SceSize args, void *argp)
sceKernelDelayThread(DIALOG_WAIT); // Needed to see the percentage
// Get the app count
if (parse_dir_with_callback("ux0:app", app_callback, &refresh_data) < 0)
if (parse_dir_with_callback(SCE_S_IFDIR, "ux0:app", app_callback, &refresh_data) < 0)
goto EXIT;
// Get the dlc count
if (parse_dir_with_callback("ux0:addcont", dlc_callback_outer, &refresh_data) < 0)
if (parse_dir_with_callback(SCE_S_IFDIR, "ux0:addcont", dlc_callback_outer, &refresh_data) < 0)
goto EXIT;
// Update thread
@ -331,11 +306,11 @@ int refresh_thread(SceSize args, void *argp)
refresh_data.refresh_pass = 1;
// Refresh apps
if (parse_dir_with_callback("ux0:app", app_callback, &refresh_data) < 0)
if (parse_dir_with_callback(SCE_S_IFDIR, "ux0:app", app_callback, &refresh_data) < 0)
goto EXIT;
// Refresh dlc
if (parse_dir_with_callback("ux0:addcont", dlc_callback_outer, &refresh_data) < 0)
if (parse_dir_with_callback(SCE_S_IFDIR, "ux0:addcont", dlc_callback_outer, &refresh_data) < 0)
goto EXIT;
sceIoRmdir("ux0:temp/addcont");
@ -358,3 +333,105 @@ EXIT:
return sceKernelExitDeleteThread(0);
}
// Note: This is currently not optimized AT ALL.
// Ultimately, we want to use a single transaction and avoid trying to
// re-insert rifs that are already present.
void license_file_callback(void* data, const char* dir, const char* file)
{
license_data_t *license_data = (license_data_t*)data;
char path[MAX_PATH_LENGTH];
// Ignore non rif content
if ((strlen(file) < 4) || (strcmp(&file[strlen(file) - 4], ".rif") != 0))
return;
if (license_data->copy_pass) {
snprintf(path, sizeof(path), "%s/%s", dir, file);
SceUID fd = sceIoOpen(path, SCE_O_RDONLY, 0777);
if (fd > 0) {
int read = sceIoRead(fd, license_data->rif, RIF_SIZE);
if (read == RIF_SIZE) {
if (insert_rif(LICENSE_DB, license_data->rif) == 0)
license_data->copied++;
}
sceIoClose(fd);
}
SetProgress(++license_data->processed, license_data->count);
} else {
license_data->count++;
}
}
void license_dir_callback(void* data, const char* dir, const char* subdir)
{
license_data_t *license_data = (license_data_t*)data;
char path[MAX_PATH_LENGTH];
snprintf(path, sizeof(path), "%s/%s", dir, subdir);
if (++license_data->cur_depth == license_data->max_depth)
parse_dir_with_callback(SCE_S_IFREG, path, license_file_callback, data);
else
parse_dir_with_callback(SCE_S_IFDIR, path, license_dir_callback, data);
license_data->cur_depth--;
}
int license_thread(SceSize args, void *argp)
{
SceUID thid = -1;
license_data_t license_data = { 0, 0, 0, 0, 0, 1, malloc(RIF_SIZE) };
if (license_data.rif == NULL)
goto EXIT;
// Lock power timers
powerLock();
// Set progress to 0%
sceMsgDialogProgressBarSetValue(SCE_MSG_DIALOG_PROGRESSBAR_TARGET_BAR_DEFAULT, 0);
sceKernelDelayThread(DIALOG_WAIT); // Needed to see the percentage
// NB: ux0:license access requires elevated permisions
if (parse_dir_with_callback(SCE_S_IFDIR, "ux0:license/app", license_dir_callback, &license_data) < 0)
goto EXIT;
license_data.max_depth++;
if (parse_dir_with_callback(SCE_S_IFDIR, "ux0:license/addcont", license_dir_callback, &license_data) < 0)
goto EXIT;
// Update thread
thid = createStartUpdateThread(license_data.count, 0);
// Create the DB if needed
SceUID fd = sceIoOpen(LICENSE_DB, SCE_O_RDONLY, 0777);
if (fd > 0) {
sceIoClose(fd);
} else if (create_db(LICENSE_DB, LICENSE_DB_SCHEMA) != 0) {
goto EXIT;
}
// Insert the licenses
license_data.copy_pass = 1;
license_data.max_depth = 1;
if (parse_dir_with_callback(SCE_S_IFDIR, "ux0:license/app", license_dir_callback, &license_data) < 0)
goto EXIT;
license_data.max_depth++;
if (parse_dir_with_callback(SCE_S_IFDIR, "ux0:license/addcont", license_dir_callback, &license_data) < 0)
goto EXIT;
// Set progress to 100%
sceMsgDialogProgressBarSetValue(SCE_MSG_DIALOG_PROGRESSBAR_TARGET_BAR_DEFAULT, 100);
sceKernelDelayThread(COUNTUP_WAIT);
// Close
closeWaitDialog();
infoDialog(language_container[IMPORTED_LICENSES], license_data.copied);
EXIT:
if (thid >= 0)
sceKernelWaitThreadEnd(thid, NULL, NULL);
// Unlock power timers
powerUnlock();
return sceKernelExitDeleteThread(0);
}

View File

@ -20,5 +20,6 @@
#define __REFRESH_H__
int refresh_thread(SceSize args, void *argp);
int license_thread(SceSize args, void *argp);
#endif

View File

@ -15,6 +15,7 @@ FOLDER = "Folder"
MOVING = "Moving..."
COPYING = "Copying..."
DELETING = "Deleting..."
IMPORTING = "Importing..."
EXPORTING = "Exporting..."
INSTALLING = "Installing..."
DOWNLOADING = "Downloading..."
@ -40,6 +41,7 @@ ENTER_SEARCH_TERM = "Enter search term"
# Context menu strings
REFRESH_LIVEAREA = "Refresh livearea"
REFRESH_LICENSE_DB = "Refresh license database"
MOUNT_UMA0 = "Mount uma0:"
MOUNT_IMC0 = "Mount imc0:"
MOUNT_USB_UX0 = "Mount USB ux0:"
@ -100,6 +102,7 @@ REFRESHED = "Refreshed %d items."
COPIED_FILE = "Copied %d file(s)."
COPIED_FOLDER = "Copied %d folder(s)."
COPIED_FILES_FOLDERS = "Copied %d file(s)/folder(s)."
IMPORTED_LICENSES = "Imported %d license(s)."
# Dialog questions
DELETE_FILE_QUESTION = "Are you sure you want to delete this file?"
@ -124,6 +127,7 @@ INSTALL_BRICK_WARNING = "This package uses functions that remount
HASH_FILE_QUESTION = "SHA1 hashing may take a long time. Continue?"
SAVE_MODIFICATIONS = "Do you want to save your modifications?"
REFRESH_LIVEAREA_QUESTION = "Refreshing the livearea may take a long time. Continue?"
REFRESH_LICENSE_DB_QUESTION = "Refreshing the license database may take a long time. Continue?"
# HENkaku settings strings
HENKAKU_SETTINGS = "HENkaku settings"

111
rif.c Normal file
View File

@ -0,0 +1,111 @@
/*
VitaShell - RIF and license.db handling
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "rif.h"
#include "sqlite3.h"
#define MAX_QUERY_LENGTH 128
int create_db(const char* db_path, const char* schema)
{
int rc;
sqlite3 *db = NULL;
rc = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL);
if (rc != SQLITE_OK)
goto out;
rc = sqlite3_exec(db, schema, NULL, NULL, NULL);
if (rc != SQLITE_OK)
goto out;
out:
sqlite3_close(db);
return rc;
}
// Insert a new RIF into the DB
int insert_rif(const char* db_path, const uint8_t* rif)
{
int rc;
sqlite3 *db = NULL;
sqlite3_stmt *stmt;
char query[MAX_QUERY_LENGTH];
rc = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READWRITE, NULL);
if (rc != SQLITE_OK)
goto out;
snprintf(query, sizeof(query), "INSERT INTO Licenses VALUES('%s', ?)", &rif[0x10]);
rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
if (rc != SQLITE_OK)
goto out;
rc = sqlite3_bind_blob(stmt, 1, rif, RIF_SIZE, SQLITE_STATIC);
if (rc != SQLITE_OK)
goto out;
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
goto out;
rc = sqlite3_finalize(stmt);
out:
sqlite3_close(db);
return rc;
}
// Query a RIF from the license 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;
const uint8_t *db_rif;
rc = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READONLY, NULL);
if (rc != SQLITE_OK)
goto out;
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)
goto out;
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW)
rif_size = sqlite3_column_bytes(stmt, 0);
if (rif_size != RIF_SIZE)
goto out;
rif = malloc(rif_size);
db_rif = sqlite3_column_blob(stmt, 0);
if ((rif != NULL) && (db_rif != NULL))
memcpy(rif, db_rif, rif_size);
rc = sqlite3_finalize(stmt);
out:
sqlite3_close(db);
return rif;
}

37
rif.h Normal file
View File

@ -0,0 +1,37 @@
/*
VitaShell - RIF handling functions
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#define RIF_SIZE 512
#define LICENSE_DB "ux0:license/license.db"
#define LICENSE_DB_SCHEMA \
"CREATE TABLE Licenses (" \
"CONTENT_ID TEXT NOT NULL UNIQUE," \
"RIF BLOB NOT NULL," \
"PRIMARY KEY(CONTENT_ID)" \
")"
int create_db(const char* db_path, const char* schema);
int insert_rif(const char* db_path, const uint8_t* rif);
uint8_t* query_rif(const char* db_path, const char* content_id);
// From sqlite3.c
int sqlite_init();
int sqlite_exit();

170
sqlite3.c Normal file
View File

@ -0,0 +1,170 @@
/*
PS Vita override for R/W SQLite functionality
Copyright (C) 2017 VitaSmith
Based on original work (C) 2015 xyzz
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Note: We must override part of the default SQlite VFS for full SQLite
DB access because the native Vita one, called "psp2", only allows read
operations (which Sony probably did to avoid exploit propagation from
potential SQLite vulnerabilities).
Short of using the VFS overrides below, any attempt to create a new DB
or write to an existing one will result in SQLITE_CANTOPEN...
*/
#include <stdlib.h>
#include <string.h>
#include <psp2/io/fcntl.h>
#include <psp2/sqlite.h>
#include "sqlite3.h"
//#define VERBOSE 1
#if VERBOSE
extern int psvDebugScreenPrintf(const char *format, ...);
#define LOG psvDebugScreenPrintf
#else
#define LOG(...)
#endif
#define IS_ERROR(x) ((unsigned)x & 0x80000000)
static sqlite3_io_methods* rw_methods = NULL;
static sqlite3_vfs *rw_vfs = NULL;
// The file structure used by Sony
typedef struct {
sqlite3_file file;
int* fd;
} vfs_file;
static int vita_xWrite(sqlite3_file *file, const void *buf, int count, sqlite_int64 offset)
{
vfs_file *p = (vfs_file*)file;
int seek = sceIoLseek(*p->fd, offset, SCE_SEEK_SET);
LOG("seek %x %x => %x\n", *p->fd, offset, seek);
if (seek != offset)
return SQLITE_IOERR_WRITE;
int write = sceIoWrite(*p->fd, buf, count);
LOG("write %x %x %x => %x\n", *p->fd, buf, count);
if (write != count) {
LOG("write error %08x\n", write);
return SQLITE_IOERR_WRITE;
}
return SQLITE_OK;
}
static int vita_xOpen(sqlite3_vfs *vfs, const char *name, sqlite3_file *file, int flags, int *out_flags)
{
sqlite3_vfs* org_vfs = (sqlite3_vfs*)vfs->pAppData;
LOG("open %s: flags = %08x, ", name, flags);
// Default xOpen() does not create files => do that ourselves
// TODO: handle SQLITE_OPEN_EXCLUSIVE
if (flags & SQLITE_OPEN_CREATE) {
SceUID fd = sceIoOpen(name, SCE_O_RDONLY, 0777);
if (IS_ERROR(fd))
fd = sceIoOpen(name, SCE_O_WRONLY | SCE_O_CREAT, 0777);
if (!IS_ERROR(fd))
sceIoClose(fd);
}
// Call the original xOpen()
int r = org_vfs->xOpen(org_vfs, name, file, flags, out_flags);
vfs_file *p = (vfs_file*)file;
LOG("fd = %08x, r = %d\n", (p == NULL) ? 0 : *p->fd, r);
// Default xOpen() also forces read-only on SQLITE_OPEN_READWRITE files
if ((file->pMethods != NULL) && (flags & SQLITE_OPEN_READWRITE)) {
if (!IS_ERROR(*p->fd)) {
// Reopen the file with write access
sceIoClose(*p->fd);
*p->fd = sceIoOpen(name, SCE_O_RDWR, 0777);
LOG("override fd = %08x\n", *p->fd);
if (IS_ERROR(*p->fd))
return SQLITE_IOERR_WRITE;
}
// Need to override xWrite() as well
if (rw_methods == NULL) {
rw_methods = malloc(sizeof(sqlite3_io_methods));
if (rw_methods != NULL) {
memcpy(rw_methods, file->pMethods, sizeof(sqlite3_io_methods));
rw_methods->xWrite = vita_xWrite;
}
}
if (rw_methods != NULL)
file->pMethods = rw_methods;
}
return r;
}
int vita_xDelete(sqlite3_vfs *vfs, const char *name, int syncDir)
{
int ret = sceIoRemove(name);
LOG("delete %s: 0x%08x\n", name, ret);
if (IS_ERROR(ret))
return SQLITE_IOERR_DELETE;
return SQLITE_OK;
}
int sqlite_init()
{
int rc;
if (rw_vfs != NULL)
return SQLITE_OK;
SceSqliteMallocMethods mf = {
(void* (*) (int)) malloc,
(void* (*) (void*, int)) realloc,
free
};
sceSqliteConfigMallocMethods(&mf);
rw_vfs = malloc(sizeof(sqlite3_vfs));
sqlite3_vfs *vfs = sqlite3_vfs_find(NULL);
if ((vfs != NULL) && (rw_vfs != NULL)) {
// Override xOpen() and xDelete()
memcpy(rw_vfs, vfs, sizeof(sqlite3_vfs));
rw_vfs->zName = "psp2_rw";
rw_vfs->xOpen = vita_xOpen;
rw_vfs->xDelete = vita_xDelete;
// Keep a copy of the original vfs pointer
rw_vfs->pAppData = vfs;
rc = sqlite3_vfs_register(rw_vfs, 1);
if (rc != SQLITE_OK) {
LOG("sqlite_init: could not register vfs: %d\n", rc);
return rc;
}
}
return SQLITE_OK;
}
int sqlite_exit()
{
int rc = SQLITE_OK;
free(rw_methods);
rw_methods = NULL;
if (rw_vfs != NULL) {
rc = sqlite3_vfs_unregister(rw_vfs);
if (rc != SQLITE_OK)
LOG("sqlite_exit: error unregistering vfs: %d\n", rc);
free(rw_vfs);
rw_vfs = NULL;
}
return rc;
}

125
sqlite3.h
View File

@ -10,8 +10,8 @@
**
*************************************************************************
**
** This header was intentionally truncated from the original version to
** only include the elements needed by this application.
** This header was intentionally truncated from the original version
** to include only the elements needed by our application.
**
*/
@ -60,6 +60,35 @@ extern "C" {
#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */
#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8))
#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8))
#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8))
#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6<<8))
#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7<<8))
#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8<<8))
#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9<<8))
#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10<<8))
#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11<<8))
#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12<<8))
#define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13<<8))
#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14<<8))
#define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15<<8))
#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16<<8))
#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17<<8))
#define SQLITE_IOERR_SHMOPEN (SQLITE_IOERR | (18<<8))
#define SQLITE_IOERR_SHMSIZE (SQLITE_IOERR | (19<<8))
#define SQLITE_IOERR_SHMLOCK (SQLITE_IOERR | (20<<8))
#define SQLITE_IOERR_SHMMAP (SQLITE_IOERR | (21<<8))
#define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22<<8))
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8))
#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8))
#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8))
#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8))
#define SQLITE_OPEN_READONLY 0x00000001
#define SQLITE_OPEN_READWRITE 0x00000002
#define SQLITE_OPEN_CREATE 0x00000004
@ -69,22 +98,98 @@ extern "C" {
#define SQLITE_OPEN_SHAREDCACHE 0x00020000
#define SQLITE_OPEN_PRIVATECACHE 0x00040000
#ifdef SQLITE_INT64_TYPE
typedef SQLITE_INT64_TYPE sqlite_int64;
typedef unsigned SQLITE_INT64_TYPE sqlite_uint64;
#elif defined(_MSC_VER) || defined(__BORLANDC__)
typedef __int64 sqlite_int64;
typedef unsigned __int64 sqlite_uint64;
#else
typedef long long int sqlite_int64;
typedef unsigned long long int sqlite_uint64;
#endif
typedef sqlite_int64 sqlite3_int64;
typedef sqlite_uint64 sqlite3_uint64;
typedef void (*sqlite3_destructor_type)(void*);
#define SQLITE_STATIC ((sqlite3_destructor_type)0)
#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
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);
typedef struct sqlite3_file sqlite3_file;
struct sqlite3_file {
const struct sqlite3_io_methods *pMethods;
};
typedef struct sqlite3_io_methods sqlite3_io_methods;
struct sqlite3_io_methods {
int iVersion;
int(*xClose)(sqlite3_file*);
int(*xRead)(sqlite3_file*, void*, int, sqlite3_int64);
int(*xWrite)(sqlite3_file*, const void*, int, sqlite3_int64);
int(*xTruncate)(sqlite3_file*, sqlite3_int64);
int(*xSync)(sqlite3_file*, int);
int(*xFileSize)(sqlite3_file*, sqlite3_int64*);
int(*xLock)(sqlite3_file*, int);
int(*xUnlock)(sqlite3_file*, int);
int(*xCheckReservedLock)(sqlite3_file*, int*);
int(*xFileControl)(sqlite3_file*, int, void*);
int(*xSectorSize)(sqlite3_file*);
int(*xDeviceCharacteristics)(sqlite3_file*);
int(*xShmMap)(sqlite3_file*, int, int, int, void volatile**);
int(*xShmLock)(sqlite3_file*, int, int, int);
void(*xShmBarrier)(sqlite3_file*);
int(*xShmUnmap)(sqlite3_file*, int);
};
typedef struct sqlite3_vfs sqlite3_vfs;
typedef void(*sqlite3_syscall_ptr)(void);
struct sqlite3_vfs {
int iVersion;
int szOsFile;
int mxPathname;
sqlite3_vfs *pNext;
const char *zName;
void *pAppData;
int(*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*, int flags, int *pOutFlags);
int(*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
int(*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
int(*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
void(*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
void(*(*xDlSym)(sqlite3_vfs*, void*, const char *zSymbol))(void);
void(*xDlClose)(sqlite3_vfs*, void*);
int(*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
int(*xSleep)(sqlite3_vfs*, int microseconds);
int(*xCurrentTime)(sqlite3_vfs*, double*);
int(*xGetLastError)(sqlite3_vfs*, int, char *);
int(*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
int(*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr);
sqlite3_syscall_ptr(*xGetSystemCall)(sqlite3_vfs*, const char *zName);
const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName);
};
SQLITE_API int sqlite3_errcode(sqlite3*);
SQLITE_API int sqlite3_extended_errcode(sqlite3*);
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_open(const char*, sqlite3**);
SQLITE_API int sqlite3_open_v2(const char *filename, sqlite3**, int, const char*);
SQLITE_API int sqlite3_close(sqlite3*);
SQLITE_API int sqlite3_exec(sqlite3*, const char *sql, int (*)(void*,int,char**,char**), void*, char**);
SQLITE_API int sqlite3_prepare_v2(sqlite3*, const char*, int, sqlite3_stmt**, const char**);
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);
SQLITE_API int sqlite3_finalize(sqlite3_stmt*);
SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int);
SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int);
SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int, void(*)(void*));
SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *);
SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int);
SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
#ifdef __cplusplus
}