Util: Add PS Vita SFO generator

This commit is contained in:
Vicki Pfau 2022-10-28 00:01:21 -07:00
parent 15e8b20537
commit c49f09dabc
4 changed files with 367 additions and 0 deletions

30
include/mgba-util/sfo.h Normal file
View File

@ -0,0 +1,30 @@
/* Copyright (c) 2013-2022 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef SFO_H
#define SFO_H
#include <mgba-util/common.h>
CXX_GUARD_START
#include <mgba-util/table.h>
void SfoInit(struct Table* sfo);
static inline void SfoDeinit(struct Table* sfo) {
HashTableDeinit(sfo);
}
struct VFile;
bool SfoWrite(struct Table* sfo, struct VFile* vf);
bool SfoAddU32Value(struct Table* sfo, const char* name, uint32_t value);
bool SfoAddStrValue(struct Table* sfo, const char* name, const char* value);
bool SfoSetTitle(struct Table* sfo, const char* title);
CXX_GUARD_END
#endif

View File

@ -21,6 +21,7 @@ set(SOURCE_FILES
patch-ups.c
png-io.c
ring-fifo.c
sfo.c
text-codec.c)
set(GUI_FILES
@ -31,6 +32,7 @@ set(GUI_FILES
gui/menu.c)
set(TEST_FILES
test/sfo.c
test/string-parser.c
test/string-utf8.c
test/table.c

228
src/util/sfo.c Normal file
View File

@ -0,0 +1,228 @@
/* Copyright (c) 2013-2022 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* This code is loosely based on vita-mksfoex.c from the vitasdk
* Copyright (c) 2015 Sergi Granell
* Copyright (c) 2015 Danielle Church
* Used under the MIT license
*
* Which itself is based on mksfoex.c from the pspsdk
* Copyright (c) 2005 adresd
* Copyright (c) 2005 Marcus R. Brown
* Copyright (c) 2005 James Forshaw
* Copyright (c) 2005 John Kelley
* Copyright (c) 2005 Jesper Svennevid
* Used under the BSD 3-clause license
*/
#include <mgba-util/common.h>
#include <mgba-util/table.h>
#include <mgba-util/vfs.h>
#define PSF_MAGIC 0x46535000
#define PSF_VERSION 0x00000101
struct SfoHeader {
uint32_t magic;
uint32_t version;
uint32_t keyofs;
uint32_t valofs;
uint32_t count;
};
struct SfoEntry {
uint16_t nameofs;
uint8_t alignment;
uint8_t type;
uint32_t valsize;
uint32_t totalsize;
uint32_t dataofs;
};
enum PSFType {
PSF_TYPE_BIN = 0,
PSF_TYPE_STR = 2,
PSF_TYPE_U32 = 4,
};
struct SfoEntryContainer {
const char* name;
enum PSFType type;
union {
const char* str;
uint32_t u32;
} data;
uint32_t size;
};
static struct SfoEntryContainer sfoDefaults[] = {
{ "APP_VER", PSF_TYPE_STR, { .str = "00.00" } },
{ "ATTRIBUTE", PSF_TYPE_U32, { .u32 = 0x8000 } },
{ "ATTRIBUTE2", PSF_TYPE_U32, { .u32 = 0 } },
{ "ATTRIBUTE_MINOR", PSF_TYPE_U32, { .u32 = 0x10 } },
{ "BOOT_FILE", PSF_TYPE_STR, { .str = ""}, 0x20 },
{ "CATEGORY", PSF_TYPE_STR, { .str = "gd" } },
{ "CONTENT_ID", PSF_TYPE_STR, { .str = "" }, 0x30 },
{ "EBOOT_APP_MEMSIZE", PSF_TYPE_U32, { .u32 = 0 } },
{ "EBOOT_ATTRIBUTE", PSF_TYPE_U32, { .u32 = 0 } },
{ "EBOOT_PHY_MEMSIZE", PSF_TYPE_U32, { .u32 = 0 } },
{ "LAREA_TYPE", PSF_TYPE_U32, { .u32 = 0 } },
{ "NP_COMMUNICATION_ID", PSF_TYPE_STR, { .str = "" }, 0x10 },
{ "PARENTAL_LEVEL", PSF_TYPE_U32, { .u32 = 0 } },
{ "PSP2_DISP_VER", PSF_TYPE_STR, { .str = "00.000" } },
{ "PSP2_SYSTEM_VER", PSF_TYPE_U32, { .u32 = 0 } },
{ "STITLE", PSF_TYPE_STR, { .str = "Homebrew" }, 52 },
{ "TITLE", PSF_TYPE_STR, { .str = "Homebrew" }, 0x80 },
{ "TITLE_ID", PSF_TYPE_STR, { .str = "ABCD99999" } },
{ "VERSION", PSF_TYPE_STR, { .str = "00.00" } },
};
bool SfoAddStrValue(struct Table* sfo, const char* name, const char* value) {
struct SfoEntryContainer* entry = HashTableLookup(sfo, name);
if (!entry) {
entry = calloc(1, sizeof(*entry));
if (!entry) {
return false;
}
entry->name = name;
HashTableInsert(sfo, name, entry);
}
entry->type = PSF_TYPE_STR;
entry->data.str = value;
return true;
}
bool SfoAddU32Value(struct Table* sfo, const char* name, uint32_t value) {
struct SfoEntryContainer* entry = HashTableLookup(sfo, name);
if (!entry) {
entry = calloc(1, sizeof(*entry));
if (!entry) {
return false;
}
entry->name = name;
HashTableInsert(sfo, name, entry);
}
entry->type = PSF_TYPE_U32;
entry->data.u32 = value;
return true;
}
bool SfoSetTitle(struct Table* sfo, const char* title) {
return SfoAddStrValue(sfo, "TITLE", title) && SfoAddStrValue(sfo, "STITLE", title);
}
void SfoInit(struct Table* sfo) {
HashTableInit(sfo, 32, free);
size_t i;
for (i = 0; i < sizeof(sfoDefaults) / sizeof(sfoDefaults[0]); ++i) {
struct SfoEntryContainer* entry = calloc(1, sizeof(*entry));
memcpy(entry, &sfoDefaults[i], sizeof(*entry));
HashTableInsert(sfo, entry->name, entry);
}
}
#define ALIGN4(X) (((X) + 3) & ~3)
static int _sfoSort(const void* a, const void* b) {
const struct SfoEntryContainer* ea = a;
const struct SfoEntryContainer* eb = b;
return strcmp(ea->name, eb->name);
}
bool SfoWrite(struct Table* sfo, struct VFile* vf) {
struct SfoHeader header;
size_t count = HashTableSize(sfo);
STORE_32LE(PSF_MAGIC, 0, &header.magic);
STORE_32LE(PSF_VERSION, 0, &header.version);
STORE_32LE(count, 0, &header.count);
struct TableIterator iter;
if (!TableIteratorStart(sfo, &iter)) {
return false;
}
struct SfoEntryContainer* sortedEntries = calloc(count, sizeof(struct SfoEntryContainer));
uint32_t keysSize = 0;
uint32_t dataSize = 0;
size_t i = 0;
do {
memcpy(&sortedEntries[i], TableIteratorGetValue(sfo, &iter), sizeof(struct SfoEntryContainer));
keysSize += strlen(sortedEntries[i].name) + 1;
if (!sortedEntries[i].size) {
switch (sortedEntries[i].type) {
case PSF_TYPE_STR:
sortedEntries[i].size = strlen(sortedEntries[i].data.str) + 1;
break;
case PSF_TYPE_U32:
sortedEntries[i].size = 4;
break;
}
}
dataSize += ALIGN4(sortedEntries[i].size);
++i;
} while (TableIteratorNext(sfo, &iter));
keysSize = ALIGN4(keysSize);
qsort(sortedEntries, count, sizeof(struct SfoEntryContainer), _sfoSort);
uint32_t keysOffset = 0;
uint32_t dataOffset = 0;
char* keys = calloc(1, keysSize);
char* data = calloc(1, dataSize);
struct SfoEntry* entries = calloc(count, sizeof(struct SfoEntry));
for (i = 0; i < count; ++i) {
STORE_16LE(keysOffset, 0, &entries[i].nameofs);
STORE_32LE(dataOffset, 0, &entries[i].dataofs);
entries[i].alignment = 4;
entries[i].type = sortedEntries[i].type;
strcpy(&keys[keysOffset], sortedEntries[i].name);
keysOffset += strlen(sortedEntries[i].name) + 1;
if (sortedEntries[i].type == PSF_TYPE_U32) {
STORE_32LE(4, 0, &entries[i].valsize);
STORE_32LE(4, 0, &entries[i].totalsize);
STORE_32LE(sortedEntries[i].data.u32, dataOffset, data);
dataOffset += 4;
} else {
STORE_32LE(ALIGN4(sortedEntries[i].size), 0, &entries[i].totalsize);
memset(&data[dataOffset], 0, ALIGN4(sortedEntries[i].size));
if (sortedEntries[i].data.str) {
STORE_32LE(strlen(sortedEntries[i].data.str) + 1, 0, &entries[i].valsize);
strncpy(&data[dataOffset], sortedEntries[i].data.str, sortedEntries[i].size);
} else {
STORE_32LE(sortedEntries[i].size, 0, &entries[i].valsize);
}
dataOffset += ALIGN4(sortedEntries[i].size);
}
}
if (keysSize != ALIGN4(keysOffset) || dataSize != dataOffset) {
abort();
}
free(sortedEntries);
STORE_32LE(count * sizeof(struct SfoEntry) + sizeof(header), 0, &header.keyofs);
STORE_32LE(count * sizeof(struct SfoEntry) + sizeof(header) + keysSize, 0, &header.valofs);
vf->write(vf, &header, sizeof(header));
vf->write(vf, entries, sizeof(entries[0]) * count);
vf->write(vf, keys, keysSize);
vf->write(vf, data, dataSize);
free(entries);
free(keys);
free(data);
return true;
}

107
src/util/test/sfo.c Normal file
View File

@ -0,0 +1,107 @@
/* Copyright (c) 2013-2022 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "util/test/suite.h"
#include <mgba-util/sfo.h>
#include <mgba-util/vfs.h>
const char defaultMksfoex[] = {
0x00, 0x50, 0x53, 0x46, 0x01, 0x01, 0x00, 0x00, 0x44, 0x01, 0x00, 0x00,
0x30, 0x02, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02,
0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x08, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00, 0x12, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x04, 0x04,
0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x2d, 0x00, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x14, 0x00, 0x00, 0x00, 0x37, 0x00, 0x04, 0x02, 0x03, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x40, 0x00, 0x04, 0x02,
0x01, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00,
0x4b, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x68, 0x00, 0x00, 0x00, 0x5d, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x04, 0x04,
0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
0x7f, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x74, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x04, 0x04,
0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00,
0xad, 0x00, 0x04, 0x02, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x8c, 0x00, 0x00, 0x00, 0xbb, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x04, 0x02,
0x09, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00,
0xd2, 0x00, 0x04, 0x02, 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
0xcc, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x04, 0x02, 0x0a, 0x00, 0x00, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0xe1, 0x00, 0x04, 0x02,
0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x58, 0x01, 0x00, 0x00,
0x41, 0x50, 0x50, 0x5f, 0x56, 0x45, 0x52, 0x00, 0x41, 0x54, 0x54, 0x52,
0x49, 0x42, 0x55, 0x54, 0x45, 0x00, 0x41, 0x54, 0x54, 0x52, 0x49, 0x42,
0x55, 0x54, 0x45, 0x32, 0x00, 0x41, 0x54, 0x54, 0x52, 0x49, 0x42, 0x55,
0x54, 0x45, 0x5f, 0x4d, 0x49, 0x4e, 0x4f, 0x52, 0x00, 0x42, 0x4f, 0x4f,
0x54, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x00, 0x43, 0x41, 0x54, 0x45, 0x47,
0x4f, 0x52, 0x59, 0x00, 0x43, 0x4f, 0x4e, 0x54, 0x45, 0x4e, 0x54, 0x5f,
0x49, 0x44, 0x00, 0x45, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x41, 0x50, 0x50,
0x5f, 0x4d, 0x45, 0x4d, 0x53, 0x49, 0x5a, 0x45, 0x00, 0x45, 0x42, 0x4f,
0x4f, 0x54, 0x5f, 0x41, 0x54, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, 0x45,
0x00, 0x45, 0x42, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x48, 0x59, 0x5f, 0x4d,
0x45, 0x4d, 0x53, 0x49, 0x5a, 0x45, 0x00, 0x4c, 0x41, 0x52, 0x45, 0x41,
0x5f, 0x54, 0x59, 0x50, 0x45, 0x00, 0x4e, 0x50, 0x5f, 0x43, 0x4f, 0x4d,
0x4d, 0x55, 0x4e, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49,
0x44, 0x00, 0x50, 0x41, 0x52, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x5f, 0x4c,
0x45, 0x56, 0x45, 0x4c, 0x00, 0x50, 0x53, 0x50, 0x32, 0x5f, 0x44, 0x49,
0x53, 0x50, 0x5f, 0x56, 0x45, 0x52, 0x00, 0x50, 0x53, 0x50, 0x32, 0x5f,
0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x56, 0x45, 0x52, 0x00, 0x53,
0x54, 0x49, 0x54, 0x4c, 0x45, 0x00, 0x54, 0x49, 0x54, 0x4c, 0x45, 0x00,
0x54, 0x49, 0x54, 0x4c, 0x45, 0x5f, 0x49, 0x44, 0x00, 0x56, 0x45, 0x52,
0x53, 0x49, 0x4f, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x2e, 0x30,
0x30, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x67, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x2e, 0x30, 0x30, 0x30, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x6f, 0x6d, 0x65, 0x62, 0x72, 0x65, 0x77,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x6f, 0x6d, 0x65,
0x62, 0x72, 0x65, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x41, 0x42, 0x43, 0x44, 0x39, 0x39, 0x39, 0x39,
0x39, 0x00, 0x00, 0x00, 0x30, 0x30, 0x2e, 0x30, 0x30, 0x00, 0x00, 0x00
};
M_TEST_DEFINE(defaultSfo) {
struct VFile* vf = VFileMemChunk(NULL, 0);
struct Table sfo;
SfoInit(&sfo);
SfoWrite(&sfo, vf);
SfoDeinit(&sfo);
assert_int_equal(vf->size(vf), sizeof(defaultMksfoex));
void* buffer = vf->map(vf, sizeof(defaultMksfoex), MAP_READ);
assert_memory_equal(defaultMksfoex, buffer, sizeof(defaultMksfoex));
vf->unmap(vf, buffer, sizeof(defaultMksfoex));
vf->close(vf);
}
M_TEST_SUITE_DEFINE(Sfo,
cmocka_unit_test(defaultSfo),
)