diff --git a/include/mgba-util/sfo.h b/include/mgba-util/sfo.h new file mode 100644 index 000000000..89404ceca --- /dev/null +++ b/include/mgba-util/sfo.h @@ -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 + +CXX_GUARD_START + +#include + +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 diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index e4d03d343..df0d84c5c 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -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 diff --git a/src/util/sfo.c b/src/util/sfo.c new file mode 100644 index 000000000..90142897b --- /dev/null +++ b/src/util/sfo.c @@ -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 +#include +#include + +#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; +} diff --git a/src/util/test/sfo.c b/src/util/test/sfo.c new file mode 100644 index 000000000..54f923ae9 --- /dev/null +++ b/src/util/test/sfo.c @@ -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 +#include + +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), +)