Skip to content

Instantly share code, notes, and snippets.

@ariankordi
Created June 24, 2024 21:25
Show Gist options
  • Save ariankordi/ea0ab58269d62ba8c18d1953d0f567b9 to your computer and use it in GitHub Desktop.
Save ariankordi/ea0ab58269d62ba8c18d1953d0f567b9 to your computer and use it in GitHub Desktop.
Tool using the FFL decomp (not straightforward to run) that extracts FFSDs from an FFL_ODB.dat, aka Wii U Mii Maker database
// NOTE: Meant to be built in my FFL-Testing repo, placed in src.
// LDFLAGS="-lssl -lcrypto" make SRC=src/extract-ffsd-from-ffl-odb.cpp EXEC=extract-ffsd-from-ffl-odb
#include <filedevice/rio_FileDeviceMgr.h>
#include <nn/ffl.h>
// get mii studio title id
#include <nn/ffl/detail/FFLiFileWriteBuffer.h>
#include <nn/ffl/FFLiMiiData.h>
// convert utf 16 to utf 8
#include <codecvt>
#include <locale>
#include <cstdio>
// NOTE: openssl is only included for base64 LOL!
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#include <cstring>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#define getcwd _getcwd
#define mkdir _mkdir
#else
#include <unistd.h>
#endif
#define private public
#include <nn/ffl/FFLiDatabaseFile.h>
#include <nn/ffl/FFLiManager.h>
//#include "../ffl/src/FFLiDatabaseFileAccessor.cpp"
// Function to encode data in Base64
std::string base64_encode(const char* input, int length) {
BIO *bmem, *b64;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); // No newlines
BIO_write(b64, input, length);
BIO_flush(b64);
BIO_get_mem_ptr(b64, &bptr);
std::string encoded(bptr->data, bptr->length);
BIO_free_all(b64);
return encoded;
}
// NOTE: COPIED FROM FFLiDatabaseFileAccessor.cpp
enum FFLiFsFileResult
{
FFLI_FS_FILE_RESULT_OK = 0,
FFLI_FS_FILE_RESULT_READ_BUFFER_EMPTY = 1,
FFLI_FS_FILE_RESULT_WRITE_BUFFER_EMPTY = 2,
FFLI_FS_FILE_RESULT_OUT_OF_MEMORY = 3,
FFLI_FS_FILE_RESULT_NOT_FOUND = 4
};
struct FFLiFsResult
{
FFLiFsFileResult fileResult;
rio::RawErrorCode fsStatus;
};
static bool CheckFFLiFsResult(const FFLiFsResult& result)
{
if (result.fileResult != FFLI_FS_FILE_RESULT_OK)
return false;
if (result.fsStatus != rio::RAW_ERROR_OK)
return false;
return true;
}
FFLiFsResult LoadDatabaseOfficial(FFLiDatabaseFileOfficial* pOfficial, const char* pPath);
// clears stdin
void seek_to_next_line( void )
{
int c;
while( (c = fgetc( stdin )) != EOF && c != '\n' );
}
int main(int argc, char *argv[]) {
// check whether first argument starts with -, to show usage
if(argc > 1 && argv[1][0] == '-' || argc == 2) {
printf("\e[0;34mUsage: %s <path to FFL_ODB.dat> <output folder>\e[0m\n", argv[0]);
return 0;
}
printf("before ffl init\n");
// initialize filedevicemgr
// called by rio::Initialize
rio::FileDeviceMgr::createSingleton();
/*
FFLInitDesc init_desc;
init_desc.fontRegion = FFL_FONT_REGION_0;
init_desc._c = false;
init_desc._10 = true;
// NOTE: NOT LOADING ANY RESOURCES HERE, NOT RENDERING!!!
FFLResourceDesc resource_desc;
FFLResult result = FFLInitResEx(&init_desc, &resource_desc);
if(result != FFL_RESULT_OK) {
printf("FFLInitResEx() failed with result: %d\n", (s32)result);
return 1;
}
int is_ffl_available = FFLIsAvailable();
printf("ffl is available? %d\n", is_ffl_available);
if(is_ffl_available != 1) {
printf("FFLIsAvailable() != 1, aborting\n");
return 1;
}
FFLiManager* pManager = FFLiManager::GetInstance();
FFLiDatabaseManager& database_manager = pManager->GetDatabaseManager();
const int index = 0;
FFLiCharInfo char_info;
const FFLDataSource data_source = FFL_DATA_SOURCE_OFFICIAL;
result = database_manager.PickupCharInfo(&char_info, data_source, NULL, index);
printf("PickupCharInfo result: %d\n", result);
*/
FFLiFileWriteBuffer* file_write_buffer(static_cast<FFLiFileWriteBuffer*>(rio::MemUtil::alloc(sizeof(FFLiFileWriteBuffer), rio::FileDevice::cBufferMinAlignment)));
FFLiDatabaseFile* database_file = static_cast<FFLiDatabaseFile*>(rio::MemUtil::alloc(sizeof(FFLiDatabaseFile), rio::FileDevice::cBufferMinAlignment));
printf("FFLiDatabaseFile->official.Init()\n");
database_file->official.Init();
FFLiDatabaseFileAccessor database_file_accessor(database_file, file_write_buffer);
printf("FFLiDatabaseFileAccessor.Init()\n");
database_file_accessor.Init();
char output_folder[256];
// /home/arian/Downloads/2024-05-31-working/FFL_ODB.dat
// /home/arian/Downloads/1004a100/user/common/db/FFL_ODB.dat
// OBTAIN ARGUMENTS
if(argc < 2) {
// no arguments
// hopefully these scanfs won't allow more than 255 characters
printf("\e[1menter path to you FFL_ODB 🥺?\e[0m\n");
scanf("%255[^\n]", database_file_accessor.m_PathOfficial);
printf("\e[1menter output folder path, we will put FFSD files here:\e[0m\n");
seek_to_next_line();
scanf("%255[^\n]", output_folder);
} else if(argc > 2) {
// all arguments
// copy only 256 characters
strncpy(database_file_accessor.m_PathOfficial, argv[1], 256);
strncpy(output_folder, argv[2], 256);
}
// make folder if it does not exist
#ifdef _WIN32
if (mkdir(output_folder) != 0 && errno != EEXIST) {
#else
if (mkdir(output_folder, 0755) != 0 && errno != EEXIST) {
#endif
perror("Error creating output directory");
return 1;
}
// TODO CHECK RESULT
//database_file_accessor.AfterConstruct(FFLiGetMiiStudioTitleID());
// NOTE: 256 SIZE MAXMIMUM!!!!!!!
//strcpy(database_file_accessor.m_PathOfficial, "/home/arian/.local/share/Cemu/mlc01/usr/save/00050010/1004a100/user/common/db/FFL_ODB.dat");
//printf("calling FFLiDatabaseFileAccessor::BootLoad()...\n");
//database_file_accessor.m_IsPathSet = true;
// TODO CHECK RESULT
//database_file_accessor.BootLoad();
printf("calling LoadDatabaseOfficial()...\n");
FFLiFsResult result = LoadDatabaseOfficial(&database_file->official, database_file_accessor.m_PathOfficial);
if(!CheckFFLiFsResult(result)) {
char error[512];
sprintf(error, "LoadDatabaseOfficial(%s) FAILED!!!!", database_file_accessor.m_PathOfficial);
perror(error);
return 1;
}
//FFLiDatabaseFileAccessor& database_file_accessor = database_manager.GetDatabaseFileAccessor();
FFLiDatabaseFileOfficial database_file_official = database_file_accessor.GetDatabaseFile()->official;
// TODO CHECK RESULT
/* database_file_official.Get(&char_info, index, true, true);
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
// print in bold
printf("mii index 0 name: %s\n",
convert.to_bytes((char16_t*) char_info.name).c_str());
FFLiMiiDataOfficial* firstMii = &database_file_official.m_MiiDataOfficial[0];
printf("no endian swap: ");
for(int i = 0; i < sizeof(FFLiMiiDataOfficial); i++) {
printf("%02x", reinterpret_cast<uint8_t*>(firstMii)[i]);
}
printf("\n%s\n", base64_encode(reinterpret_cast<char*>(firstMii), sizeof(FFLiMiiDataOfficial)).c_str());
*/
int miis_written = 0;
// NOTE: 3000 is the maximum amount of entries in FFLiDatabaseFileOfficial
for(int i = 0; i < 3000; i++) {
FFLiMiiDataOfficial* mii = &database_file_official.m_MiiDataOfficial[i];
if(FFLiIsNullMiiID(&mii->CreatorID())) {
//printf("MII DATA OFFICIAL INDEX %d IS NULL, SKIPPING!!!!!!\n", i);
// skip null mii
continue;
}
/*FFLiCharInfo char_info;
if(!database_file_official.Get(&char_info, i, false, true)) {
continue;
}*/
// NOTE: SET MII VERSION TO 3DS
// the mii maker does this, and clears favorite flag
// whenever making a qr code, though note that it
// makes the mii version for wii u miis 4 in networking + nnas
//char_info.miiVersion = 3;
mii->SetMiiVersion(3);
// 10 characters long
std::u16string u16str((char16_t*)mii->Name(), 10);
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
std::string mii_name_string = convert.to_bytes(u16str);
const char* mii_name = mii_name_string.c_str();
// convert name to utf-8
printf("mii index %d name: %s\ndata: ", i, mii_name);
FFLStoreData store_data;
FFLiMiiDataOfficialToStoreDataCFL(static_cast<FFLiStoreDataCFL&>(store_data), *mii);
// TODO CHECK RESULT
//FFLiCharInfoToStoreDataCFL(reinterpret_cast<FFLiStoreDataCFL*>(&store_data), &char_info);
char* mii_bytes = reinterpret_cast<char*>(&store_data);
printf("%s\n", base64_encode(mii_bytes, sizeof(FFLStoreData)).c_str());
char filename[300];
sprintf(filename, "%s/%d %s.ffsd", output_folder, i, mii_name);
FILE* file = fopen(filename, "w+");
if(file == NULL) {
char error[512];
sprintf(error, "fopen(%s) failed", filename);
perror(error);
// return and EXIT PROGRAM
return 1;//continue;
}
fwrite(mii_bytes, sizeof(FFLStoreData), 1, file);
fclose(file);
miis_written++;
}
// if no miis were written
if(!miis_written) {
printf("hmmm... no miis were written.\n");
} else {
printf("all miis were successfully written to %s :).\n", output_folder);
}
// success... free...
rio::MemUtil::free(database_file);
database_file = nullptr;
rio::MemUtil::free(file_write_buffer);
file_write_buffer = nullptr;
FFLExit();
// press any key to continue, only on windows
#ifdef _WIN32
printf("Press any key to continue...\n");
_getch();
#endif
return 0;
}
// NOTE: COPIED FROM FFLiDatabaseFileAccessor.cpp
FFLiFsResult ReadFile(void* pDst, u32 size, const char* pPath)
{
rio::NativeFileDevice* device = rio::FileDeviceMgr::instance()->getNativeFileDevice();
rio::FileHandle fileHandle;
if (!device->tryOpen(&fileHandle, pPath, rio::FileDevice::FILE_OPEN_FLAG_READ))
{
rio::RawErrorCode status = device->getLastRawError();
RIO_ASSERT(status != rio::RAW_ERROR_OK);
return FFLiFsResult { FFLI_FS_FILE_RESULT_OK, status };
}
u32 readSize = 0;
if (!fileHandle.tryRead(&readSize, static_cast<u8*>(pDst), size))
{
fileHandle.tryClose();
rio::RawErrorCode status = device->getLastRawError();
RIO_ASSERT(status != rio::RAW_ERROR_OK);
return FFLiFsResult { FFLI_FS_FILE_RESULT_OK, status };
}
if (readSize == 0)
{
fileHandle.tryClose();
return FFLiFsResult { FFLI_FS_FILE_RESULT_READ_BUFFER_EMPTY };
}
else
{
if (!fileHandle.tryClose())
{
rio::RawErrorCode status = device->getLastRawError();
RIO_ASSERT(status != rio::RAW_ERROR_OK);
return FFLiFsResult { FFLI_FS_FILE_RESULT_OK, status };
}
}
return FFLiFsResult { FFLI_FS_FILE_RESULT_OK, rio::RAW_ERROR_OK };
}
FFLiFsResult LoadDatabaseOfficial(FFLiDatabaseFileOfficial* pOfficial, const char* pPath)
{
FFLiFsResult result = ReadFile(pOfficial, sizeof(FFLiDatabaseFileOfficial), pPath);
if (!CheckFFLiFsResult(result))
return result;
#if __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__
pOfficial->SwapEndian(false);
#endif // __BYTE_ORDER__
return FFLiFsResult { FFLI_FS_FILE_RESULT_OK, rio::RAW_ERROR_OK };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment