Initial Commit

This commit is contained in:
Niklas080208
2026-01-17 19:39:53 +01:00
commit 75b2b603ab
148 changed files with 45979 additions and 0 deletions

103
source/backup.c Normal file
View File

@@ -0,0 +1,103 @@
/*
* OmniNX Installer - Backup and Restore Operations
*/
#include "backup.h"
#include "fs.h"
#include <libs/fatfs/ff.h>
#include <string.h>
#include <utils/sprintf.h>
// Check if file/directory exists
static bool path_exists(const char *path) {
FILINFO fno;
return (f_stat(path, &fno) == FR_OK);
}
// Backup user data before clean install
int backup_user_data(void) {
int res;
// Create temp backup directory
res = f_mkdir(TEMP_BACKUP_PATH);
if (res != FR_OK && res != FR_EXIST) {
return res;
}
// Backup DBI if it exists
if (path_exists("sd:/switch/DBI")) {
res = folder_copy("sd:/switch/DBI", TEMP_BACKUP_PATH);
if (res != FR_OK) {
return res;
}
}
// Backup Tinfoil if it exists
if (path_exists("sd:/switch/tinfoil")) {
res = folder_copy("sd:/switch/tinfoil", TEMP_BACKUP_PATH);
if (res != FR_OK) {
return res;
}
}
// Backup prod.keys if it exists
if (path_exists("sd:/switch/prod.keys")) {
res = file_copy("sd:/switch/prod.keys", "sd:/temp_backup/prod.keys");
if (res != FR_OK) {
return res;
}
}
return FR_OK;
}
// Restore user data after clean install
int restore_user_data(void) {
int res;
// Recreate switch directory (should already exist, but be safe)
res = f_mkdir("sd:/switch");
if (res != FR_OK && res != FR_EXIST) {
return res;
}
// Restore DBI if backup exists
if (path_exists("sd:/temp_backup/DBI")) {
res = folder_copy("sd:/temp_backup/DBI", "sd:/switch");
if (res == FR_OK) {
// Delete old DBI .nro files
f_unlink("sd:/switch/DBI/DBI_810_EN.nro");
f_unlink("sd:/switch/DBI/DBI_810_DE.nro");
f_unlink("sd:/switch/DBI/DBI_845_EN.nro");
f_unlink("sd:/switch/DBI/DBI_845_DE.nro");
f_unlink("sd:/switch/DBI/DBI.nro");
}
}
// Restore Tinfoil if backup exists
if (path_exists("sd:/temp_backup/tinfoil")) {
res = folder_copy("sd:/temp_backup/tinfoil", "sd:/switch");
if (res == FR_OK) {
// Delete old tinfoil.nro
f_unlink("sd:/switch/tinfoil/tinfoil.nro");
}
}
// Restore prod.keys if backup exists
if (path_exists("sd:/temp_backup/prod.keys")) {
res = file_copy("sd:/temp_backup/prod.keys", "sd:/switch/prod.keys");
if (res != FR_OK) {
return res;
}
}
return FR_OK;
}
// Clean up temporary backup directory
int cleanup_backup(void) {
if (path_exists(TEMP_BACKUP_PATH)) {
return folder_delete(TEMP_BACKUP_PATH);
}
return FR_OK;
}

18
source/backup.h Normal file
View File

@@ -0,0 +1,18 @@
/*
* OmniNX Installer - Backup and Restore Operations
* For preserving user data during clean installs
*/
#pragma once
#include <utils/types.h>
#define TEMP_BACKUP_PATH "sd:/temp_backup"
// Backup user data (DBI, Tinfoil, prod.keys) before clean install
int backup_user_data(void);
// Restore user data after clean install
int restore_user_data(void);
// Clean up temporary backup directory
int cleanup_backup(void);

342
source/deletion_lists.h Normal file
View File

@@ -0,0 +1,342 @@
/*
* OmniNX Installer - Deletion Lists for Update Mode
* Arrays of paths to delete during update installation
*/
#pragma once
// Atmosphere subdirectories to delete
static const char* atmosphere_dirs_to_delete[] = {
"sd:/atmosphere/config",
"sd:/atmosphere/crash_reports",
"sd:/atmosphere/erpt_reports",
"sd:/atmosphere/exefs_patches/CrunchPatch",
"sd:/atmosphere/exefs_patches/Crunchyroll Patch 1.10.0",
"sd:/atmosphere/exefs_patches/bluetooth_patches",
"sd:/atmosphere/exefs_patches/bootlogo",
"sd:/atmosphere/exefs_patches/btm_patches",
"sd:/atmosphere/exefs_patches/es_patches",
"sd:/atmosphere/exefs_patches/hid_patches",
"sd:/atmosphere/exefs_patches/logo1",
"sd:/atmosphere/exefs_patches/nfim_ctest",
"sd:/atmosphere/exefs_patches/nim_ctest",
"sd:/atmosphere/exefs_patches/nvnflinger_cmu",
"sd:/atmosphere/extrazz",
"sd:/atmosphere/fatal_errors",
"sd:/atmosphere/fatal_reports",
"sd:/atmosphere/flags",
"sd:/atmosphere/hbl_html",
"sd:/atmosphere/hosts",
"sd:/atmosphere/kips",
"sd:/atmosphere/kip1",
"sd:/atmosphere/kip_patches",
"sd:/atmosphere/logs",
NULL
};
// Atmosphere root directories (title IDs)
static const char* atmosphere_root_dirs_to_delete[] = {
"sd:/atmosphere/0000000000534C56",
"sd:/atmosphere/00FF0000B378D640",
"sd:/atmosphere/00FF0000636C6BFF",
"sd:/atmosphere/00FF0000A53BB665",
"sd:/atmosphere/0100000000000008",
"sd:/atmosphere/010000000000000D",
"sd:/atmosphere/010000000000002B",
"sd:/atmosphere/0100000000000032",
"sd:/atmosphere/0100000000000034",
"sd:/atmosphere/0100000000000036",
"sd:/atmosphere/0100000000000037",
"sd:/atmosphere/010000000000003C",
"sd:/atmosphere/0100000000000042",
"sd:/atmosphere/0100000000000F12",
"sd:/atmosphere/0100000000001000",
"sd:/atmosphere/0100000000001007",
"sd:/atmosphere/0100000000001013",
"sd:/atmosphere/010000000000DA7A",
"sd:/atmosphere/010000000000bd00",
"sd:/atmosphere/01006a800016e000",
"sd:/atmosphere/01009D901BC56000",
"sd:/atmosphere/0100A3900C3E2000",
"sd:/atmosphere/0100F43008C44000",
"sd:/atmosphere/050000BADDAD0000",
"sd:/atmosphere/4200000000000000",
"sd:/atmosphere/420000000000000B",
"sd:/atmosphere/420000000000000E",
"sd:/atmosphere/4200000000000010",
"sd:/atmosphere/4200000000000FFF",
"sd:/atmosphere/420000000007E51A",
"sd:/atmosphere/420000000007E51B",
"sd:/atmosphere/690000000000000D",
NULL
};
// Atmosphere contents directories (title IDs)
static const char* atmosphere_contents_dirs_to_delete[] = {
"sd:/atmosphere/contents/0000000000534C56",
"sd:/atmosphere/contents/00FF0000B378D640",
"sd:/atmosphere/contents/00FF0000636C6BFF",
"sd:/atmosphere/contents/00FF0000A53BB665",
"sd:/atmosphere/contents/0100000000000008",
"sd:/atmosphere/contents/010000000000000D",
"sd:/atmosphere/contents/010000000000002B",
"sd:/atmosphere/contents/0100000000000032",
"sd:/atmosphere/contents/0100000000000034",
"sd:/atmosphere/contents/0100000000000036",
"sd:/atmosphere/contents/0100000000000037",
"sd:/atmosphere/contents/010000000000003C",
"sd:/atmosphere/contents/0100000000000042",
"sd:/atmosphere/contents/0100000000000895",
"sd:/atmosphere/contents/0100000000000F12",
"sd:/atmosphere/contents/0100000000001000",
"sd:/atmosphere/contents/0100000000001007",
"sd:/atmosphere/contents/0100000000001013",
"sd:/atmosphere/contents/010000000000DA7A",
"sd:/atmosphere/contents/010000000000bd00",
"sd:/atmosphere/contents/01006a800016e000",
"sd:/atmosphere/contents/01009D901BC56000",
"sd:/atmosphere/contents/0100A3900C3E2000",
"sd:/atmosphere/contents/0100F43008C44000",
"sd:/atmosphere/contents/050000BADDAD0000",
"sd:/atmosphere/contents/4200000000000000",
"sd:/atmosphere/contents/420000000000000B",
"sd:/atmosphere/contents/420000000000000E",
"sd:/atmosphere/contents/4200000000000010",
"sd:/atmosphere/contents/4200000000000FFF",
"sd:/atmosphere/contents/420000000007E51A",
"sd:/atmosphere/contents/420000000007E51B",
"sd:/atmosphere/contents/690000000000000D",
NULL
};
// Atmosphere files to delete
static const char* atmosphere_files_to_delete[] = {
"sd:/atmosphere/config/exosphere.ini",
"sd:/atmosphere/config/override_config.ini",
"sd:/atmosphere/config/stratosphere.ini",
"sd:/atmosphere/hbl.nsp",
"sd:/atmosphere/package3",
"sd:/atmosphere/reboot_payload.bin",
"sd:/atmosphere/stratosphere.romfs",
NULL
};
// Bootloader directories to delete
static const char* bootloader_dirs_to_delete[] = {
"sd:/bootloader/boot",
"sd:/bootloader/bootlogo",
"sd:/bootloader/ini2",
"sd:/bootloader/payloads",
"sd:/bootloader/reboot",
"sd:/bootloader/res",
"sd:/bootloader/sys",
NULL
};
// Bootloader files to delete
static const char* bootloader_files_to_delete[] = {
"sd:/bootloader/ArgonNX.bin",
"sd:/bootloader/bootlogo.bmp",
"sd:/bootloader/hekate_ipl.ini",
"sd:/bootloader/nyx.ini",
"sd:/bootloader/patches.ini",
"sd:/bootloader/update.bin",
"sd:/bootloader/ini/EmuMMC ohne Mods.ini",
NULL
};
// Config directories to delete
static const char* config_dirs_to_delete[] = {
"sd:/config/aio-switch-updater",
"sd:/config/blue_pack_updater",
"sd:/config/kefir-updater",
"sd:/config/nx-hbmenu",
"sd:/config/quickntp",
"sd:/config/sys-con",
"sd:/config/sys-patch",
"sd:/config/uberhand",
"sd:/config/ultrahand",
NULL
};
// Switch directories to delete
static const char* switch_dirs_to_delete[] = {
"sd:/switch/.overlays",
"sd:/switch/.packages",
"sd:/switch/90DNS_tester",
"sd:/switch/aio-switch-updater",
"sd:/switch/amsPLUS-downloader",
"sd:/switch/appstore",
"sd:/switch/AtmoXL-Titel-Installer",
"sd:/switch/breeze",
"sd:/switch/checkpoint",
"sd:/switch/cheats-updater",
"sd:/switch/chiaki",
"sd:/switch/ChoiDujourNX",
"sd:/switch/crash_ams",
"sd:/switch/Daybreak",
"sd:/switch/DBI_658_EN",
"sd:/switch/DBI_810",
"sd:/switch/DBI_810_DE",
"sd:/switch/DBI_810_EN",
"sd:/switch/DBI_RU",
"sd:/switch/DNS_mitm Tester",
"sd:/switch/EdiZon",
"sd:/switch/Fizeau",
"sd:/switch/FTPD",
"sd:/switch/fw-downloader",
"sd:/switch/gamecard_installer",
"sd:/switch/Goldleaf",
"sd:/switch/haze",
"sd:/switch/JKSV",
"sd:/switch/kefir-updater",
"sd:/switch/ldnmitm_config",
"sd:/switch/Linkalho",
"sd:/switch/Moonlight-Switch",
"sd:/switch/Neumann",
"sd:/switch/NX-Activity-Log",
"sd:/switch/NX-Save-Sync",
"sd:/switch/NX-Shell",
"sd:/switch/NX-Update-Checker ",
"sd:/switch/NXGallery",
"sd:/switch/NXRemoteLauncher",
"sd:/switch/NXThemesInstaller",
"sd:/switch/nxdumptool",
"sd:/switch/nxmtp",
"sd:/switch/Payload_launcher",
"sd:/switch/Reboot",
"sd:/switch/reboot_to_argonNX",
"sd:/switch/reboot_to_hekate",
"sd:/switch/Shutdown_System",
"sd:/switch/SimpleModDownloader",
"sd:/switch/SimpleModManager",
"sd:/switch/sphaira",
"sd:/switch/studious-pancake",
"sd:/switch/Switch-Time",
"sd:/switch/SwitchIdent",
"sd:/switch/Switch_themes_Installer",
"sd:/switch/Switchfin",
"sd:/switch/Sys-Clk Manager",
"sd:/switch/Sys-Con",
"sd:/switch/sys-clk-manager",
"sd:/switch/themezer-nx",
"sd:/switch/themezernx",
"sd:/switch/tinwoo",
NULL
};
// Switch files (NRO) to delete
static const char* switch_files_to_delete[] = {
"sd:/switch/90DNS_tester/90DNS_tester.nro",
"sd:/switch/breeze.nro",
"sd:/switch/cheats-updater.nro",
"sd:/switch/chiaki.nro",
"sd:/switch/ChoiDujourNX.nro",
"sd:/switch/daybreak.nro",
"sd:/switch/DBI.nro",
"sd:/switch/DBI/DBI.nro",
"sd:/switch/DBI/DBI_810_DE.nro",
"sd:/switch/DBI/DBI_810_EN.nro",
"sd:/switch/DBI/DBI_845_DE.nro",
"sd:/switch/DBI/DBI_845_EN.nro",
"sd:/switch/DBI/DBI_849_DE.nro",
"sd:/switch/DBI/DBI_849_EN.nro",
"sd:/switch/DBI_810_DE/DBI_810.nro",
"sd:/switch/DBI_810_DE/DBI_810_DE.nro",
"sd:/switch/DBI_810_EN/DBI_810_EN.nro",
"sd:/switch/DBI_RU/DBI_RU.nro",
"sd:/switch/DNS_mitm Tester.nro",
"sd:/switch/EdiZon.nro",
"sd:/switch/Fizeau.nro",
"sd:/switch/Goldleaf.nro",
"sd:/switch/haze.nro",
"sd:/switch/JKSV.nro",
"sd:/switch/ldnmitm_config.nro",
"sd:/switch/linkalho.nro",
"sd:/switch/Moonlight-Switch.nro",
"sd:/switch/Neumann.nro",
"sd:/switch/NX-Shell.nro",
"sd:/switch/NXGallery.nro",
"sd:/switch/NXThemesInstaller.nro",
"sd:/switch/nxdumptool.nro",
"sd:/switch/nxtc.bin",
"sd:/switch/reboot_to_payload.nro",
"sd:/switch/SimpleModDownloader.nro",
"sd:/switch/SimpleModManager.nro",
"sd:/switch/sphaira.nro",
"sd:/switch/SwitchIdent.nro",
"sd:/switch/Switch_themes_Installer/NXThemesInstaller.nro",
"sd:/switch/Switchfin.nro",
"sd:/switch/Sys-Clk Manager/sys-clk-manager.nro",
"sd:/switch/Sys-Con.nro",
"sd:/switch/sys-clk-manager.nro",
"sd:/switch/tinfoil.nro",
"sd:/switch/tinfoil/tinfoil.nro",
"sd:/switch/tinwoo.nro",
"sd:/switch/tinwoo/tinwoo.nro",
NULL
};
// Root CFW files to delete
static const char* root_files_to_delete[] = {
"sd:/boot.dat",
"sd:/boot.ini",
"sd:/exosphere.bin",
"sd:/exosphere.ini",
"sd:/hbmenu.nro",
"sd:/install.bat",
"sd:/license",
"sd:/loader.bin",
"sd:/mc-mitm.log",
"sd:/payload.bin",
"sd:/update.bin",
"sd:/version",
NULL
};
// Miscellaneous directories to delete
static const char* misc_dirs_to_delete[] = {
"sd:/argon",
"sd:/games",
"sd:/NSPs (Tools)",
"sd:/Patched Apps",
"sd:/SaltySD",
"sd:/scripts",
"sd:/switch/tinfoil/db",
"sd:/tools",
"sd:/warmboot_mariko",
NULL
};
// Miscellaneous files to delete
static const char* misc_files_to_delete[] = {
"sd:/fusee-primary.bin",
"sd:/fusee.bin",
"sd:/SaltySD/exceptions.txt",
"sd:/SaltySD/saltysd_bootstrap.elf",
"sd:/SaltySD/saltysd_bootstrap32_3k.elf",
"sd:/SaltySD/saltysd_bootstrap32_5k.elf",
"sd:/SaltySD/saltysd_core.elf",
"sd:/SaltySD/saltysd_core32.elf",
NULL
};
// Old version marker files to delete
static const char* old_version_files_to_delete[] = {
"sd:/1.0.0l",
"sd:/1.0.0s",
"sd:/1.0.0oc",
"sd:/1.4.0-pre",
"sd:/1.4.0-pre-c",
"sd:/1.4.0-pre-d",
"sd:/1.4.1",
"sd:/1.5.0",
NULL
};
// Helper function to count array elements (excluding NULL terminator)
static inline int count_paths(const char* paths[]) {
int count = 0;
while (paths[count] != NULL) count++;
return count;
}

337
source/fs.c Normal file
View File

@@ -0,0 +1,337 @@
/*
* HATS Installer - Filesystem operations with file logging
*/
#include "fs.h"
#include <libs/fatfs/ff.h>
#include <mem/heap.h>
#include <string.h>
#include <utils/sprintf.h>
#include <stdarg.h>
#include <stdio.h>
#define FS_BUFFER_SIZE 0x100000 // 1MB copy buffer
#define LOG_BUFFER_SIZE 512
// Log file handle
static FIL log_file;
static bool log_enabled = false;
static char log_buf[LOG_BUFFER_SIZE];
// Initialize log file
void log_init(const char *path) {
int res = f_open(&log_file, path, FA_WRITE | FA_CREATE_ALWAYS);
if (res == FR_OK) {
log_enabled = true;
log_write("=== HATS Installer Log ===\n\n");
}
}
// Close log file
void log_close(void) {
if (log_enabled) {
f_sync(&log_file);
f_close(&log_file);
log_enabled = false;
}
}
// Write to log file (variadic version)
void log_write(const char *fmt, ...) {
if (!log_enabled) return;
va_list args;
va_start(args, fmt);
// Format the message
vsnprintf(log_buf, LOG_BUFFER_SIZE, fmt, args);
va_end(args);
// Write to file
UINT bw;
f_write(&log_file, log_buf, strlen(log_buf), &bw);
f_sync(&log_file); // Flush to ensure data is written
}
// Convert FatFS error code to string
const char *fs_error_str(int err) {
switch (err) {
case FR_OK: return "OK";
case FR_DISK_ERR: return "DISK_ERR: Low level disk error";
case FR_INT_ERR: return "INT_ERR: Internal error";
case FR_NOT_READY: return "NOT_READY: Drive not ready";
case FR_NO_FILE: return "NO_FILE: File not found";
case FR_NO_PATH: return "NO_PATH: Path not found";
case FR_INVALID_NAME: return "INVALID_NAME: Invalid path name";
case FR_DENIED: return "DENIED: Access denied";
case FR_EXIST: return "EXIST: Already exists";
case FR_INVALID_OBJECT: return "INVALID_OBJECT: Invalid object";
case FR_WRITE_PROTECTED: return "WRITE_PROTECTED: Write protected";
case FR_INVALID_DRIVE: return "INVALID_DRIVE: Invalid drive";
case FR_NOT_ENABLED: return "NOT_ENABLED: Volume not mounted";
case FR_NO_FILESYSTEM: return "NO_FILESYSTEM: No valid FAT";
case FR_MKFS_ABORTED: return "MKFS_ABORTED: mkfs aborted";
case FR_TIMEOUT: return "TIMEOUT: Timeout";
case FR_LOCKED: return "LOCKED: File locked";
case FR_NOT_ENOUGH_CORE: return "NOT_ENOUGH_CORE: Out of memory";
case FR_TOO_MANY_OPEN_FILES: return "TOO_MANY_OPEN_FILES";
case FR_INVALID_PARAMETER: return "INVALID_PARAMETER";
default: return "UNKNOWN_ERROR";
}
}
// Combine two paths
static char *combine_paths(const char *base, const char *add) {
size_t base_len = strlen(base);
size_t add_len = strlen(add);
char *result = malloc(base_len + add_len + 2);
if (!result) return NULL;
if (base_len > 0 && base[base_len - 1] == '/') {
s_printf(result, "%s%s", base, add);
} else {
s_printf(result, "%s/%s", base, add);
}
return result;
}
// Copy a single file with logging
int file_copy(const char *src, const char *dst) {
FIL fin, fout;
FILINFO fno;
int res;
log_write("COPY: %s -> %s\n", src, dst);
res = f_open(&fin, src, FA_READ | FA_OPEN_EXISTING);
if (res != FR_OK) {
log_write(" ERROR open src: %s\n", fs_error_str(res));
return res;
}
f_stat(src, &fno);
u64 file_size = f_size(&fin);
log_write(" Size: %d bytes\n", (u32)file_size);
res = f_open(&fout, dst, FA_WRITE | FA_CREATE_ALWAYS);
if (res != FR_OK) {
f_close(&fin);
log_write(" ERROR open dst: %s\n", fs_error_str(res));
return res;
}
u8 *buf = malloc(FS_BUFFER_SIZE);
if (!buf) {
f_close(&fin);
f_close(&fout);
log_write(" ERROR: Out of memory for buffer\n");
return FR_NOT_ENOUGH_CORE;
}
u64 remaining = file_size;
UINT br, bw;
while (remaining > 0) {
UINT to_copy = (remaining > FS_BUFFER_SIZE) ? FS_BUFFER_SIZE : (UINT)remaining;
res = f_read(&fin, buf, to_copy, &br);
if (res != FR_OK) {
log_write(" ERROR read: %s\n", fs_error_str(res));
break;
}
if (br != to_copy) {
log_write(" ERROR: Read %d bytes, expected %d\n", br, to_copy);
res = FR_DISK_ERR;
break;
}
res = f_write(&fout, buf, to_copy, &bw);
if (res != FR_OK) {
log_write(" ERROR write: %s\n", fs_error_str(res));
break;
}
if (bw != to_copy) {
log_write(" ERROR: Wrote %d bytes, expected %d\n", bw, to_copy);
res = FR_DISK_ERR;
break;
}
remaining -= to_copy;
}
free(buf);
f_close(&fin);
f_close(&fout);
if (res == FR_OK) {
f_chmod(dst, fno.fattrib, 0x3A);
log_write(" OK\n");
}
return res;
}
// Recursively delete a folder with logging
int folder_delete(const char *path) {
DIR dir;
FILINFO fno;
int res;
log_write("DELETE: %s\n", path);
res = f_opendir(&dir, path);
if (res != FR_OK) {
// Maybe it's a file, try to delete it
log_write(" Not a dir, trying as file...\n");
res = f_unlink(path);
if (res != FR_OK) {
log_write(" ERROR unlink: %s\n", fs_error_str(res));
} else {
log_write(" OK (file deleted)\n");
}
return res;
}
int file_count = 0;
int dir_count = 0;
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK) {
log_write(" ERROR readdir: %s\n", fs_error_str(res));
break;
}
if (fno.fname[0] == 0) break; // End of directory
char *full_path = combine_paths(path, fno.fname);
if (!full_path) {
res = FR_NOT_ENOUGH_CORE;
break;
}
if (fno.fattrib & AM_DIR) {
dir_count++;
res = folder_delete(full_path);
} else {
file_count++;
log_write(" DEL: %s\n", fno.fname);
res = f_unlink(full_path);
if (res != FR_OK) {
log_write(" ERROR: %s\n", fs_error_str(res));
}
}
free(full_path);
if (res != FR_OK) break;
}
f_closedir(&dir);
if (res == FR_OK || res == FR_NO_FILE) {
log_write(" Removing dir: %s (%d files, %d subdirs)\n", path, file_count, dir_count);
res = f_unlink(path);
if (res != FR_OK) {
log_write(" ERROR rmdir: %s\n", fs_error_str(res));
} else {
log_write(" OK\n");
}
}
return res;
}
// Recursively copy a folder with logging
int folder_copy(const char *src, const char *dst) {
DIR dir;
FILINFO fno;
int res;
log_write("FOLDER COPY: %s -> %s\n", src, dst);
res = f_opendir(&dir, src);
if (res != FR_OK) {
log_write(" ERROR opendir src: %s\n", fs_error_str(res));
return res;
}
// Get folder name from src path
const char *folder_name = strrchr(src, '/');
if (folder_name) {
folder_name++;
} else {
folder_name = src;
}
// Create destination folder
char *dst_path = combine_paths(dst, folder_name);
if (!dst_path) {
f_closedir(&dir);
return FR_NOT_ENOUGH_CORE;
}
log_write(" Creating: %s\n", dst_path);
res = f_mkdir(dst_path);
if (res == FR_EXIST) {
log_write(" (already exists)\n");
res = FR_OK;
} else if (res != FR_OK) {
log_write(" ERROR mkdir: %s\n", fs_error_str(res));
f_closedir(&dir);
free(dst_path);
return res;
}
int file_count = 0;
int dir_count = 0;
// Copy contents
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK) {
log_write(" ERROR readdir: %s\n", fs_error_str(res));
break;
}
if (fno.fname[0] == 0) break; // End of directory
char *src_full = combine_paths(src, fno.fname);
char *dst_full = combine_paths(dst_path, fno.fname);
if (!src_full || !dst_full) {
if (src_full) free(src_full);
if (dst_full) free(dst_full);
res = FR_NOT_ENOUGH_CORE;
break;
}
if (fno.fattrib & AM_DIR) {
dir_count++;
res = folder_copy(src_full, dst_path);
} else {
file_count++;
res = file_copy(src_full, dst_full);
}
free(src_full);
free(dst_full);
if (res != FR_OK) break;
}
f_closedir(&dir);
// Copy folder attributes
if (res == FR_OK) {
FILINFO src_info;
if (f_stat(src, &src_info) == FR_OK) {
f_chmod(dst_path, src_info.fattrib, 0x3A);
}
log_write(" Done: %d files, %d subdirs\n", file_count, dir_count);
}
free(dst_path);
return res;
}

19
source/fs.h Normal file
View File

@@ -0,0 +1,19 @@
/*
* HATS Installer - Filesystem operations
*/
#pragma once
#include <utils/types.h>
// Error code to string
const char *fs_error_str(int err);
// File/folder operations - returns 0 on success
int file_copy(const char *src, const char *dst);
int folder_copy(const char *src, const char *dst);
int folder_delete(const char *path);
// File logging
void log_init(const char *path);
void log_close(void);
void log_write(const char *fmt, ...);

705
source/gfx.c Normal file
View File

@@ -0,0 +1,705 @@
/*
* Copyright (c) 2018 naehrwert
* Copyright (c) 2018-2020 CTCaer
* Copyright (c) 2019-2020 shchmue
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <stdarg.h>
#include <string.h>
#include "gfx.h"
gfx_ctxt_t gfx_ctxt;
gfx_con_t gfx_con;
static const u8 _gfx_font[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Char 032 ( )
0x00, 0x30, 0x30, 0x18, 0x18, 0x00, 0x0C, 0x00, // Char 033 (!)
0x00, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, // Char 034 (")
0x00, 0x66, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0x66, // Char 035 (#)
0x00, 0x18, 0x7C, 0x06, 0x3C, 0x60, 0x3E, 0x18, // Char 036 ($)
0x00, 0x46, 0x66, 0x30, 0x18, 0x0C, 0x66, 0x62, // Char 037 (%)
0x00, 0x3C, 0x66, 0x3C, 0x1C, 0xE6, 0x66, 0xFC, // Char 038 (&)
0x00, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, // Char 039 (')
0x00, 0x30, 0x18, 0x0C, 0x0C, 0x18, 0x30, 0x00, // Char 040 (()
0x00, 0x0C, 0x18, 0x30, 0x30, 0x18, 0x0C, 0x00, // Char 041 ())
0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, // Char 042 (*)
0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, // Char 043 (+)
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x0C, 0x00, // Char 044 (,)
0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, // Char 045 (-)
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, // Char 046 (.)
0x00, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x00, // Char 047 (/)
0x00, 0x3C, 0x66, 0x76, 0x6E, 0x66, 0x3C, 0x00, // Char 048 (0)
0x00, 0x18, 0x1C, 0x18, 0x18, 0x18, 0x7E, 0x00, // Char 049 (1)
0x00, 0x3C, 0x62, 0x30, 0x0C, 0x06, 0x7E, 0x00, // Char 050 (2)
0x00, 0x3C, 0x62, 0x38, 0x60, 0x66, 0x3C, 0x00, // Char 051 (3)
0x00, 0x6C, 0x6C, 0x66, 0xFE, 0x60, 0x60, 0x00, // Char 052 (4)
0x00, 0x7E, 0x06, 0x7E, 0x60, 0x66, 0x3C, 0x00, // Char 053 (5)
0x00, 0x3C, 0x06, 0x3E, 0x66, 0x66, 0x3C, 0x00, // Char 054 (6)
0x00, 0x7E, 0x30, 0x30, 0x18, 0x18, 0x18, 0x00, // Char 055 (7)
0x00, 0x3C, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, // Char 056 (8)
0x00, 0x3C, 0x66, 0x7C, 0x60, 0x66, 0x3C, 0x00, // Char 057 (9)
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, // Char 058 (:)
0x00, 0x00, 0x18, 0x00, 0x18, 0x18, 0x0C, 0x00, // Char 059 (;)
0x00, 0x70, 0x1C, 0x06, 0x06, 0x1C, 0x70, 0x00, // Char 060 (<)
0x00, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x00, 0x00, // Char 061 (=)
0x00, 0x0E, 0x38, 0x60, 0x60, 0x38, 0x0E, 0x00, // Char 062 (>)
0x00, 0x3C, 0x66, 0x30, 0x18, 0x00, 0x18, 0x00, // Char 063 (?)
0x00, 0x3C, 0x66, 0x76, 0x76, 0x06, 0x46, 0x3C, // Char 064 (@)
0x00, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, // Char 065 (A)
0x00, 0x3E, 0x66, 0x3E, 0x66, 0x66, 0x3E, 0x00, // Char 066 (B)
0x00, 0x3C, 0x66, 0x06, 0x06, 0x66, 0x3C, 0x00, // Char 067 (C)
0x00, 0x1E, 0x36, 0x66, 0x66, 0x36, 0x1E, 0x00, // Char 068 (D)
0x00, 0x7E, 0x06, 0x1E, 0x06, 0x06, 0x7E, 0x00, // Char 069 (E)
0x00, 0x3E, 0x06, 0x1E, 0x06, 0x06, 0x06, 0x00, // Char 070 (F)
0x00, 0x3C, 0x66, 0x06, 0x76, 0x66, 0x3C, 0x00, // Char 071 (G)
0x00, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, // Char 072 (H)
0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // Char 073 (I)
0x00, 0x78, 0x30, 0x30, 0x30, 0x36, 0x1C, 0x00, // Char 074 (J)
0x00, 0x66, 0x36, 0x1E, 0x1E, 0x36, 0x66, 0x00, // Char 075 (K)
0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x7E, 0x00, // Char 076 (L)
0x00, 0x46, 0x6E, 0x7E, 0x56, 0x46, 0x46, 0x00, // Char 077 (M)
0x00, 0x66, 0x6E, 0x7E, 0x76, 0x66, 0x66, 0x00, // Char 078 (N)
0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // Char 079 (O)
0x00, 0x3E, 0x66, 0x3E, 0x06, 0x06, 0x06, 0x00, // Char 080 (P)
0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x70, 0x00, // Char 081 (Q)
0x00, 0x3E, 0x66, 0x3E, 0x1E, 0x36, 0x66, 0x00, // Char 082 (R)
0x00, 0x3C, 0x66, 0x0C, 0x30, 0x66, 0x3C, 0x00, // Char 083 (S)
0x00, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // Char 084 (T)
0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, // Char 085 (U)
0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, // Char 086 (V)
0x00, 0x46, 0x46, 0x56, 0x7E, 0x6E, 0x46, 0x00, // Char 087 (W)
0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, // Char 088 (X)
0x00, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00, // Char 089 (Y)
0x00, 0x7E, 0x30, 0x18, 0x0C, 0x06, 0x7E, 0x00, // Char 090 (Z)
0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, // Char 091 ([)
0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00, // Char 092 (\)
0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, // Char 093 (])
0x00, 0x18, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, // Char 094 (^)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, // Char 095 (_)
0x00, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, // Char 096 (`)
0x00, 0x00, 0x3C, 0x60, 0x7C, 0x66, 0x7C, 0x00, // Char 097 (a)
0x00, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3E, 0x00, // Char 098 (b)
0x00, 0x00, 0x3C, 0x06, 0x06, 0x06, 0x3C, 0x00, // Char 099 (c)
0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, // Char 100 (d)
0x00, 0x00, 0x3C, 0x66, 0x7E, 0x06, 0x3C, 0x00, // Char 101 (e)
0x00, 0x38, 0x0C, 0x3E, 0x0C, 0x0C, 0x0C, 0x00, // Char 102 (f)
0x00, 0x00, 0x7C, 0x66, 0x7C, 0x40, 0x3C, 0x00, // Char 103 (g)
0x00, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x00, // Char 104 (h)
0x00, 0x18, 0x00, 0x1C, 0x18, 0x18, 0x3C, 0x00, // Char 105 (i)
0x00, 0x30, 0x00, 0x30, 0x30, 0x30, 0x1E, 0x00, // Char 106 (j)
0x00, 0x06, 0x06, 0x36, 0x1E, 0x36, 0x66, 0x00, // Char 107 (k)
0x00, 0x1C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, // Char 108 (l)
0x00, 0x00, 0x66, 0xFE, 0xFE, 0xD6, 0xC6, 0x00, // Char 109 (m)
0x00, 0x00, 0x3E, 0x66, 0x66, 0x66, 0x66, 0x00, // Char 110 (n)
0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, // Char 111 (o)
0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x00, // Char 112 (p)
0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x00, // Char 113 (q)
0x00, 0x00, 0x3E, 0x66, 0x06, 0x06, 0x06, 0x00, // Char 114 (r)
0x00, 0x00, 0x7C, 0x06, 0x3C, 0x60, 0x3E, 0x00, // Char 115 (s)
0x00, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x70, 0x00, // Char 116 (t)
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x00, // Char 117 (u)
0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, // Char 118 (v)
0x00, 0x00, 0xC6, 0xD6, 0xFE, 0x7C, 0x6C, 0x00, // Char 119 (w)
0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, // Char 120 (x)
0x00, 0x00, 0x66, 0x66, 0x7C, 0x60, 0x3C, 0x00, // Char 121 (y)
0x00, 0x00, 0x7E, 0x30, 0x18, 0x0C, 0x7E, 0x00, // Char 122 (z)
0x00, 0x18, 0x08, 0x08, 0x04, 0x08, 0x08, 0x18, // Char 123 ({)
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // Char 124 (|)
0x00, 0x0C, 0x08, 0x08, 0x10, 0x08, 0x08, 0x0C, // Char 125 (})
0x00, 0x00, 0x00, 0x4C, 0x32, 0x00, 0x00, 0x00, // Char 126 (~)
0x00, 0x0C, 0x12, 0x7E, 0x42, 0x42, 0x7E, 0x00, // Char 127 (folder)
0x00, 0x0E, 0x12, 0x22, 0x22, 0x22, 0x3E, 0x00, // Char 128 (file)
0x00, 0x08, 0x0C, 0x0E, 0x7E, 0x70, 0x30, 0x10, // Char 129 (Charging)
};
u32 YLeftConfig = YLEFT;
void gfx_clear_grey(u8 color)
{
memset(gfx_ctxt.fb, color, gfx_ctxt.width * gfx_ctxt.height * 4);
}
void gfx_clear_partial_grey(u8 color, u32 pos_x, u32 height)
{
memset(gfx_ctxt.fb + pos_x * gfx_ctxt.stride, color, height * 4 * gfx_ctxt.stride);
}
void gfx_clear_color(u32 color)
{
for (u32 i = 0; i < gfx_ctxt.width * gfx_ctxt.height; i++)
gfx_ctxt.fb[i] = color;
}
void gfx_init_ctxt(u32 *fb, u32 width, u32 height, u32 stride)
{
gfx_ctxt.fb = fb;
gfx_ctxt.width = width;
gfx_ctxt.height = height;
gfx_ctxt.stride = stride;
}
void gfx_con_init()
{
gfx_con.gfx_ctxt = &gfx_ctxt;
gfx_con.fntsz = 16;
gfx_con.x = 0;
gfx_con.y = 0;
gfx_con.savedx = 0;
gfx_con.savedy = 0;
gfx_con.fgcol = 0xFFCCCCCC;
gfx_con.fillbg = 1;
gfx_con.bgcol = 0xFF1B1B1B;
gfx_con.mute = 0;
}
void gfx_con_setcol(u32 fgcol, int fillbg, u32 bgcol)
{
gfx_con.fgcol = fgcol;
gfx_con.fillbg = fillbg;
gfx_con.bgcol = bgcol;
}
void gfx_con_getpos(u32 *x, u32 *y)
{
*x = YLEFT - gfx_con.y;
*y = gfx_con.x;
}
void gfx_con_setpos(u32 x, u32 y)
{
gfx_con.x = y;
gfx_con.y = YLEFT - x;
}
void gfx_putc(char c)
{
// Duplicate code for performance reasons.
switch (gfx_con.fntsz)
{
case 16:
if (c >= 32 && c <= 129)
{
u8 *cbuf = (u8 *)&_gfx_font[8 * (c - 32)];
u32 *fb = gfx_ctxt.fb + gfx_con.x + gfx_con.y * gfx_ctxt.stride;
for (u32 i = 0; i < 16; i+=2)
{
u8 v = *cbuf;
for (u32 t = 0; t < 8; t++){
if (v & 1 || gfx_con.fillbg){
u32 setColor = (v & 1) ? gfx_con.fgcol : gfx_con.bgcol;
*fb = setColor;
*(fb + 1) = setColor;
*(fb - gfx_ctxt.stride) = setColor;
*(fb - gfx_ctxt.stride + 1) = setColor;
}
v >>= 1;
fb -= gfx_ctxt.stride * 2;
}
fb += gfx_ctxt.stride * 16 + 2;
cbuf++;
/*
for (u32 k = 0; k < 2; k++)
{
for (u32 j = 0; j < 8; j++)
{
if (v & 1)
{
*fb = gfx_con.fgcol;
fb -= gfx_ctxt.stride;
*fb = gfx_con.fgcol;
}
else if (gfx_con.fillbg)
{
*fb = gfx_con.bgcol;
fb -= gfx_ctxt.stride;
*fb = gfx_con.bgcol;
}
else
fb -= gfx_ctxt.stride;
v >>= 1;
fb -= gfx_ctxt.stride;
}
//fb += gfx_ctxt.stride - 16;
//fb = fbtop + 2;
fb += (gfx_ctxt.stride * 16) + 1;
v = *cbuf;
}
cbuf++;
*/
}
gfx_con.y -= 16;
if (gfx_con.y < 16){
gfx_con.y = YLeftConfig;
gfx_con.x += 16;
if (gfx_con.x > 719)
gfx_con.x = 0;
}
}
else if (c == '\n')
{
gfx_con.y = YLeftConfig;
gfx_con.x += 16;
if (gfx_con.x > gfx_ctxt.width - 16)
gfx_con.x = 0;
}
else if (c == '\e')
gfx_con.y = 575;
else if (c == '\a')
gfx_con.y = 639;
else if (c == '\r')
gfx_con.y = YLeftConfig;
break;
case 8:
default:
if (c >= 30 && c <= 129)
{
u8 *cbuf = (u8 *)&_gfx_font[8 * (c - 32)];
u32 *fb = gfx_ctxt.fb + gfx_con.x + gfx_con.y * gfx_ctxt.stride;
for (u32 i = 0; i < 8; i++)
{
u8 v = *cbuf++;
for (u32 j = 0; j < 8; j++)
{
if (v & 1)
*fb = gfx_con.fgcol;
else if (gfx_con.fillbg)
*fb = gfx_con.bgcol;
v >>= 1;
fb -= gfx_ctxt.stride;
}
fb += (gfx_ctxt.stride * 8) + 1;
}
gfx_con.y -= 8;
if (gfx_con.y < 8){
gfx_con.y = YLeftConfig;
gfx_con.x += 8;
}
}
else if (c == '\n')
{
gfx_con.y = YLeftConfig;
gfx_con.x += 8;
if (gfx_con.x > gfx_ctxt.width - 8)
gfx_con.x = 0;
}
else if (c == '\e')
gfx_con.y = 575;
else if (c == '\a')
gfx_con.y = 639;
else if (c == '\r')
gfx_con.y = YLeftConfig;
break;
}
}
void gfx_puts(const char *s)
{
if (!s || gfx_con.mute)
return;
for (; *s; s++)
gfx_putc(*s);
}
void gfx_puts_small(const char *s){
gfx_con.fntsz = 8;
gfx_puts(s);
gfx_con.fntsz = 16;
}
void gfx_puts_limit(const char *s, u32 limit){
if (!s || gfx_con.mute)
return;
u32 len = strlen(s);
if (len > limit)
limit -= 3;
for (int i = 0; i < MIN(len, limit); i++)
gfx_putc(s[i]);
if (len > limit + 3)
gfx_puts("...");
}
static void _gfx_putn(u32 v, int base, char fill, int fcnt)
{
char buf[65];
static const char digits[] = "0123456789ABCDEFghijklmnopqrstuvwxyz";
char *p;
int c = fcnt;
if (base > 36)
return;
bool minus = (base == 10 && v & 0x80000000);
if (minus)
v = (v ^ 0xFFFFFFFF) + 1;
p = buf + 64;
*p = 0;
do
{
c--;
*--p = digits[v % base];
v /= base;
} while (v);
if (minus){
*--p = '-';
c--;
}
if (fill != 0)
{
if (fill == ' ')
gfx_con.y -= c * 16;
else while (c > 0)
{
*--p = fill;
c--;
}
}
gfx_puts(p);
}
void gfx_put_small_sep()
{
u8 prevFontSize = gfx_con.fntsz;
gfx_con.fntsz = 8;
gfx_putc('\n');
gfx_con.fntsz = prevFontSize;
}
void gfx_put_big_sep()
{
u8 prevFontSize = gfx_con.fntsz;
gfx_con.fntsz = 16;
gfx_putc('\n');
gfx_con.fntsz = prevFontSize;
}
void gfx_vprintf(const char *fmt, va_list ap)
{
int fill, fcnt;
while(*fmt)
{
if(*fmt == '%')
{
fmt++;
fill = 0;
fcnt = 0;
if ((*fmt >= '0' && *fmt <= '9') || *fmt == ' ')
{
fcnt = *fmt;
fmt++;
if (*fmt >= '0' && *fmt <= '9')
{
fill = fcnt;
fcnt = *fmt - '0';
fmt++;
}
else
{
fill = ' ';
fcnt -= '0';
}
}
switch(*fmt)
{
case 'c':
gfx_putc(va_arg(ap, u32));
break;
case 's':
gfx_puts(va_arg(ap, char *));
break;
case 'd':
_gfx_putn(va_arg(ap, u32), 10, fill, fcnt);
break;
case 'p':
case 'P':
case 'x':
case 'X':
_gfx_putn(va_arg(ap, u32), 16, fill, fcnt);
break;
case 'k':
gfx_con.fgcol = va_arg(ap, u32);
break;
case 'K':
gfx_con.bgcol = va_arg(ap, u32);
gfx_con.fillbg = 1;
break;
case 'b':;
u32 b = YLEFT - va_arg(ap, u32);
gfx_con.y = b;
YLeftConfig = gfx_con.y;
break;
case '%':
gfx_putc('%');
break;
case '\0':
return;
default:
gfx_putc('%');
gfx_putc(*fmt);
break;
}
}
else
gfx_putc(*fmt);
fmt++;
}
}
void gfx_printf(const char *fmt, ...)
{
if (gfx_con.mute)
return;
va_list ap;
va_start(ap, fmt);
gfx_vprintf(fmt, ap);
va_end(ap);
}
void gfx_putc_small(char c){
gfx_con.fntsz = 8;
gfx_putc(c);
gfx_con.fntsz = 16;
}
#define hexDumpLen 0x20
void gfx_hexdump(u32 base, const u8 *buf, u32 len)
{
if (gfx_con.mute)
return;
u8 prevFontSize = gfx_con.fntsz;
gfx_con.fntsz = 8;
for(u32 i = 0; i < len; i++)
{
if(i % hexDumpLen == 0)
{
if(i != 0)
{
gfx_puts("| ");
for(u32 j = 0; j < hexDumpLen; j++)
{
u8 c = buf[i - hexDumpLen + j];
if(c >= 32 && c <= 126)
gfx_putc(c);
else
gfx_putc('.');
}
gfx_putc('\n');
}
gfx_printf("%08x: ", base + i);
}
gfx_printf("%02x ", buf[i]);
if (i == len - 1)
{
int ln = len % hexDumpLen != 0;
u32 k = hexDumpLen - 1;
if (ln)
{
k = (len & 0xF) - 1;
for (u32 j = 0; j < hexDumpLen - k; j++)
gfx_puts(" ");
}
gfx_puts("| ");
for(u32 j = 0; j < (ln ? k : k + 1); j++)
{
u8 c = buf[i - k + j];
if(c >= 32 && c <= 126)
gfx_putc(c);
else
gfx_putc('.');
}
gfx_putc('\n');
}
}
gfx_putc('\n');
gfx_con.fntsz = prevFontSize;
}
void gfx_hexdiff(u32 base, const u8 *buf1, const u8 *buf2, u32 len)
{
if (gfx_con.mute)
return;
if (memcmp(buf1, buf2, len) == 0)
{
gfx_printf("Diff: No differences found.\n");
return;
}
u8 prevFontSize = gfx_con.fntsz;
gfx_con.fntsz = 8;
for(u32 i = 0; i < len; i+=0x10)
{
u32 bytes_left = len - i < 0x10 ? len - i : 0x10;
if (memcmp(buf1 + i, buf2 + i, bytes_left) == 0)
continue;
gfx_printf("Diff 1: %08x: ", base + i);
for (u32 j = 0; j < bytes_left; j++)
{
if (buf1[i+j] != buf2[i+j])
gfx_con.fgcol = COLOR_ORANGE;
gfx_printf("%02x ", buf1[i+j]);
gfx_con.fgcol = 0xFFCCCCCC;
}
gfx_puts("| ");
gfx_putc('\n');
gfx_printf("Diff 2: %08x: ", base + i);
for (u32 j = 0; j < bytes_left; j++)
{
if (buf1[i+j] != buf2[i+j])
gfx_con.fgcol = COLOR_ORANGE;
gfx_printf("%02x ", buf2[i+j]);
gfx_con.fgcol = 0xFFCCCCCC;
}
gfx_puts("| ");
gfx_putc('\n');
gfx_putc('\n');
}
gfx_putc('\n');
gfx_con.fntsz = prevFontSize;
}
static int abs(int x)
{
if (x < 0)
return -x;
return x;
}
void gfx_set_pixel(u32 x, u32 y, u32 color)
{
gfx_ctxt.fb[x + y * gfx_ctxt.stride] = color;
}
void gfx_set_pixel_horz(int x, int y, u32 color) {
*(gfx_ctxt.fb + (YLEFT - x) * gfx_ctxt.stride + y) = color;
}
void gfx_line(int x0, int y0, int x1, int y1, u32 color)
{
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = (dx > dy ? dx : -dy) / 2, e2;
while (1)
{
gfx_set_pixel(x0, y0, color);
if (x0 == x1 && y0 == y1)
break;
e2 = err;
if (e2 >-dx)
{
err -= dy;
x0 += sx;
}
if (e2 < dy)
{
err += dx;
y0 += sy;
}
}
}
void gfx_set_rect_grey(const u8 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y)
{
u32 pos = 0;
for (u32 y = pos_y; y < (pos_y + size_y); y++)
{
for (u32 x = pos_x; x < (pos_x + size_x); x++)
{
memset(&gfx_ctxt.fb[x + y*gfx_ctxt.stride], buf[pos], 4);
pos++;
}
}
}
void gfx_boxGrey(int x0, int y0, int x1, int y1, u8 shade){
for (int y = (YLEFT - x0); y >= (YLEFT - x1); y--){
memset(gfx_ctxt.fb + y * gfx_ctxt.stride + y0, shade, (y1 - y0 + 1) * 4);
}
}
/*
void gfx_boxGrey_old(int x0, int y0, int x1, int y1, u8 shade){
for (int y = y0; y <= y1; y++){
memset(gfx_ctxt.fb + y * gfx_ctxt.stride + x0, shade, (x1 - x0) * 4);
}
}
*/
void gfx_box(int x0, int y0, int x1, int y1, u32 color){
for (int y = (YLEFT - x0); y >= (YLEFT - x1); y--){
for (int x = y0; x <= y1; x++){
gfx_ctxt.fb[x + y * gfx_ctxt.stride] = color;
}
}
}
/*
void gfx_box_old(int x0, int y0, int x1, int y1, u32 color){
for (int x = x0; x < x1 + 1; x++){
for (int y = y0; y < y1 + 1; y++){
gfx_set_pixel(x, y, color);
}
}
}
*/
void gfx_set_rect_rgb(const u8 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y)
{
u32 pos = 0;
for (u32 y = pos_y; y < (pos_y + size_y); y++)
{
for (u32 x = pos_x; x < (pos_x + size_x); x++)
{
gfx_ctxt.fb[x + y * gfx_ctxt.stride] = buf[pos + 2] | (buf[pos + 1] << 8) | (buf[pos] << 16);
pos+=3;
}
}
}
void gfx_set_rect_argb(const u32 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y)
{
u32 *ptr = (u32 *)buf;
for (u32 y = pos_y; y < (pos_y + size_y); y++)
for (u32 x = pos_x; x < (pos_x + size_x); x++)
gfx_ctxt.fb[x + y * gfx_ctxt.stride] = *ptr++;
}
void gfx_render_bmp_argb(const u32 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y)
{
for (u32 y = pos_y; y < (pos_y + size_y); y++)
{
for (u32 x = pos_x; x < (pos_x + size_x); x++)
gfx_ctxt.fb[x + y * gfx_ctxt.stride] = buf[(size_y + pos_y - 1 - y ) * size_x + x - pos_x];
}
}

96
source/gfx.h Normal file
View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2018 naehrwert
* Copyright (c) 2018-2020 CTCaer
* Copyright (c) 2018 M4xw
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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/>.
*/
#ifndef _GFX_H_
#define _GFX_H_
#include <utils/types.h>
#include <stdarg.h>
#define EPRINTF(text) gfx_printf("%k"text"%k\n", 0xFFFF0000, 0xFFCCCCCC)
#define EPRINTFARGS(text, args...) gfx_printf("%k"text"%k\n", 0xFFFF0000, args, 0xFFCCCCCC)
#define WPRINTF(text) gfx_printf("%k"text"%k\n", 0xFFFFDD00, 0xFFCCCCCC)
#define WPRINTFARGS(text, args...) gfx_printf("%k"text"%k\n", 0xFFFFDD00, args, 0xFFCCCCCC)
typedef struct _gfx_ctxt_t
{
u32 *fb;
u32 width;
u32 height;
u32 stride;
} gfx_ctxt_t;
typedef struct _gfx_con_t
{
gfx_ctxt_t *gfx_ctxt;
u32 fntsz;
u32 x;
u32 y;
u32 savedx;
u32 savedy;
u32 fgcol;
int fillbg;
u32 bgcol;
bool mute;
} gfx_con_t;
extern gfx_ctxt_t gfx_ctxt;
extern gfx_con_t gfx_con;
#define YLEFT 1279
void gfx_init_ctxt(u32 *fb, u32 width, u32 height, u32 stride);
void gfx_clear_grey(u8 color);
void gfx_clear_partial_grey(u8 color, u32 pos_x, u32 height);
void gfx_clear_color(u32 color);
void gfx_con_init();
void gfx_con_setcol(u32 fgcol, int fillbg, u32 bgcol);
void gfx_con_getpos(u32 *x, u32 *y);
void gfx_con_setpos(u32 x, u32 y);
void gfx_putc(char c);
void gfx_puts(const char *s);
void gfx_printf(const char *fmt, ...);
void gfx_vprintf(const char *fmt, va_list ap);
void gfx_hexdump(u32 base, const u8 *buf, u32 len);
void gfx_hexdiff(u32 base, const u8 *buf1, const u8 *buf2, u32 len);
void gfx_puts_limit(const char *s, u32 limit);
void gfx_puts_small(const char *s);
void gfx_putc_small(char c);
void gfx_set_pixel(u32 x, u32 y, u32 color);
void gfx_set_pixel_horz(int x, int y, u32 color);
void gfx_line(int x0, int y0, int x1, int y1, u32 color);
void gfx_put_small_sep();
void gfx_put_big_sep();
void gfx_set_rect_grey(const u8 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y);
void gfx_set_rect_rgb(const u8 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y);
void gfx_set_rect_argb(const u32 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y);
void gfx_render_bmp_argb(const u32 *buf, u32 size_x, u32 size_y, u32 pos_x, u32 pos_y);
void gfx_box(int x0, int y0, int x1, int y1, u32 color);
void gfx_boxGrey(int x0, int y0, int x1, int y1, u8 shade);
/*
#define GFX_SETPOSCORRECTED(x, y) gfx_con_setpos(y - YLEFT, x)
#define GFX_BOXCORRECTED(x0, y0, x1, y1, color) gfx_box_old((y0 - YLEFT), x0, (y1 - YLEFT), x1, color)
#define GFX_BOXGREYCORRECTED(x0, y0, x1, y1, shade) gfx_boxGrey((y0 - YLEFT), x0, (y1 - YLEFT), x1, shade)
*/
// Global gfx console and context.
extern gfx_ctxt_t gfx_ctxt;
extern gfx_con_t gfx_con;
#endif

719
source/install.c Normal file
View File

@@ -0,0 +1,719 @@
/*
* OmniNX Installer - Installation Logic
*/
#include "install.h"
#include "backup.h"
#include "deletion_lists.h"
#include "fs.h"
#include "version.h"
#include "gfx.h"
#include <libs/fatfs/ff.h>
#include <string.h>
#include <utils/sprintf.h>
#ifndef VERSION
#define VERSION "1.0.0"
#endif
// Forward declaration
static void combine_path(char *result, size_t size, const char *base, const char *add);
// Color definitions (some already defined in types.h, but we override for consistency)
#undef COLOR_CYAN
#undef COLOR_WHITE
#undef COLOR_GREEN
#undef COLOR_YELLOW
#undef COLOR_ORANGE
#undef COLOR_RED
#define COLOR_CYAN 0xFF00FFFF
#define COLOR_WHITE 0xFFFFFFFF
#define COLOR_GREEN 0xFF00FF00
#define COLOR_YELLOW 0xFFFFDD00
#define COLOR_ORANGE 0xFF00A5FF
#define COLOR_RED 0xFFFF0000
static void set_color(u32 color) {
gfx_con_setcol(color, gfx_con.fillbg, gfx_con.bgcol);
}
// Check if we need to clear screen (when getting close to bottom)
static void check_and_clear_screen_if_needed(void) {
// In the gfx system:
// - gfx_con.x is the vertical position (line number, increments by 16 per line)
// - gfx_con.y is the horizontal position (pixels from left)
// - Screen is 720px wide, 1280px tall
// - Font is 16px, so we have 720/16 = 45 lines before wrapping
// - When gfx_con.x > 720-16, it wraps to x=0, causing overwrite
// Clear when we're past ~35 lines (35 * 16 = 560 pixels) to leave room
if (gfx_con.x > 35 * 16) {
// Clear screen and reset position
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
// Reprint same header as initial launch
set_color(COLOR_CYAN);
gfx_printf("========================================\n");
gfx_printf(" OmniNX Installer Payload v%s\n", VERSION);
gfx_printf("========================================\n\n");
set_color(COLOR_WHITE);
}
}
// Check if file/directory exists
static bool path_exists(const char *path) {
FILINFO fno;
return (f_stat(path, &fno) == FR_OK);
}
// Count total items (files + directories) in a directory tree recursively
static int count_directory_items(const char *path) {
DIR dir;
FILINFO fno;
int res;
int count = 0;
res = f_opendir(&dir, path);
if (res != FR_OK) {
return 0;
}
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0) break;
// Skip . and ..
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0'))) {
continue;
}
count++; // Count this item
// If it's a directory, recursively count its contents
if (fno.fattrib & AM_DIR) {
char sub_path[256];
s_printf(sub_path, "%s/%s", path, fno.fname);
count += count_directory_items(sub_path);
}
}
f_closedir(&dir);
return count;
}
// Helper to combine paths (handles trailing slashes properly)
static void combine_path(char *result, size_t size, const char *base, const char *add) {
size_t base_len = strlen(base);
if (base_len > 0 && base[base_len - 1] == '/') {
// Base has trailing slash, just append
s_printf(result, "%s%s", base, add);
} else {
// No trailing slash, add one
s_printf(result, "%s/%s", base, add);
}
}
// Recursive folder copy with progress tracking
static int folder_copy_progress_recursive(const char *src, const char *dst, int *copied, int total, u32 start_x, u32 start_y, const char *display_name, int *last_percent) {
DIR dir;
FILINFO fno;
int res;
char src_full[256];
char dst_full[256];
char dst_dir[256];
res = f_opendir(&dir, src);
if (res != FR_OK) {
return res;
}
// Get folder name from src path
const char *folder_name = strrchr(src, '/');
if (folder_name) {
folder_name++;
} else {
folder_name = src;
}
// Create destination folder path (handle trailing slash in dst)
combine_path(dst_dir, sizeof(dst_dir), dst, folder_name);
// Try to create the directory (ignore if it already exists)
res = f_mkdir(dst_dir);
if (res == FR_EXIST) {
res = FR_OK; // Directory already exists, that's fine
} else if (res != FR_OK) {
// If mkdir fails, check if it's actually a file (shouldn't happen, but be safe)
FILINFO fno;
if (f_stat(dst_dir, &fno) == FR_OK && !(fno.fattrib & AM_DIR)) {
// Destination exists but is a file, not a directory - this is an error
f_closedir(&dir);
return FR_DENIED;
}
// If it's a directory, continue (might have been created between check and mkdir)
if (f_stat(dst_dir, &fno) == FR_OK && (fno.fattrib & AM_DIR)) {
res = FR_OK;
} else {
f_closedir(&dir);
return res;
}
}
// Copy contents
while (1) {
res = f_readdir(&dir, &fno);
if (res != FR_OK || fno.fname[0] == 0) break;
// Skip . and ..
if (fno.fname[0] == '.' && (fno.fname[1] == '\0' || (fno.fname[1] == '.' && fno.fname[2] == '\0'))) {
continue;
}
// Build source and destination paths
combine_path(src_full, sizeof(src_full), src, fno.fname);
combine_path(dst_full, sizeof(dst_full), dst_dir, fno.fname);
if (fno.fattrib & AM_DIR) {
res = folder_copy_progress_recursive(src_full, dst_dir, copied, total, start_x, start_y, display_name, last_percent);
// Increment counter for directory (it was counted in total)
(*copied)++;
} else {
res = file_copy(src_full, dst_full);
(*copied)++;
}
// Update progress every 10 items or when percentage changes
if ((*copied % 10 == 0 || total == 0) && res == FR_OK) {
int percent = total > 0 ? (*copied * 100) / total : 0;
if (percent != *last_percent || *copied % 50 == 0) {
gfx_con_setpos(start_x, start_y);
set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [%3d%%] (%d/%d)", display_name, percent, *copied, total);
set_color(COLOR_WHITE);
*last_percent = percent;
}
}
if (res != FR_OK) break;
}
f_closedir(&dir);
return res;
}
// Progress-aware folder copy (improved version)
static int folder_copy_with_progress_v2(const char *src, const char *dst, const char *display_name) {
int copied = 0;
int total = 0;
int last_percent = -1;
u32 start_x, start_y;
int res;
// Check if source exists
if (!path_exists(src)) {
set_color(COLOR_ORANGE);
gfx_printf(" Ueberspringe: %s (nicht gefunden)\n", display_name);
set_color(COLOR_WHITE);
return FR_NO_FILE;
}
// Count total items first
total = count_directory_items(src);
if (total == 0) {
// Empty directory, just create destination
const char *folder_name = strrchr(src, '/');
if (folder_name) folder_name++;
else folder_name = src;
char dst_path[256];
combine_path(dst_path, sizeof(dst_path), dst, folder_name);
res = f_mkdir(dst_path);
if (res == FR_OK || res == FR_EXIST) {
return FR_OK;
}
return res;
}
// Save cursor position
gfx_con_getpos(&start_x, &start_y);
// Show initial status
set_color(COLOR_CYAN);
gfx_printf(" Kopiere: %s [ 0%%] (0/%d)", display_name, total);
set_color(COLOR_WHITE);
// Perform the copy with progress updates
res = folder_copy_progress_recursive(src, dst, &copied, total, start_x, start_y, display_name, &last_percent);
// Final update - overwrite the same line
gfx_con_setpos(start_x, start_y);
if (res == FR_OK) {
set_color(COLOR_GREEN);
gfx_printf(" Kopiere: %s [100%%] (%d/%d) - Fertig!\n", display_name, copied, total);
set_color(COLOR_WHITE);
} else {
set_color(COLOR_RED);
gfx_printf(" Kopiere: %s - Fehlgeschlagen!\n", display_name);
gfx_printf(" Fehler: %s (Code=%d)\n", fs_error_str(res), res);
gfx_printf(" Quelle: %s\n", src);
gfx_printf(" Ziel: %s\n", dst);
set_color(COLOR_WHITE);
// Fallback: try using the original folder_copy function
set_color(COLOR_ORANGE);
gfx_printf(" Versuche alternative Kopiermethode...\n");
set_color(COLOR_WHITE);
int fallback_res = folder_copy(src, dst);
if (fallback_res == FR_OK) {
set_color(COLOR_GREEN);
gfx_printf(" Alternative Kopie erfolgreich!\n");
set_color(COLOR_WHITE);
return FR_OK;
}
}
return res;
}
// Delete a list of paths
int delete_path_list(const char* paths[], const char* description) {
int res;
int deleted = 0;
int failed = 0;
for (int i = 0; paths[i] != NULL; i++) {
if (path_exists(paths[i])) {
FILINFO fno;
f_stat(paths[i], &fno);
if (fno.fattrib & AM_DIR) {
res = folder_delete(paths[i]);
} else {
res = f_unlink(paths[i]);
}
if (res == FR_OK || res == FR_NO_FILE) {
deleted++;
} else {
failed++;
}
}
}
return (failed == 0) ? FR_OK : FR_DISK_ERR;
}
// Delete old version markers (legacy files from old system)
int cleanup_old_version_markers(omninx_variant_t current_variant) {
// Delete all old version files (they're no longer used)
for (int i = 0; old_version_files_to_delete[i] != NULL; i++) {
const char* path = old_version_files_to_delete[i];
if (path_exists(path)) {
f_unlink(path);
}
}
return FR_OK;
}
// Update mode: Cleanup specific directories/files
int update_mode_cleanup(omninx_variant_t variant) {
check_and_clear_screen_if_needed();
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: atmosphere/\n");
set_color(COLOR_WHITE);
// Delete atmosphere subdirectories
delete_path_list(atmosphere_dirs_to_delete, "atmosphere subdirs");
// Delete atmosphere root directories (title IDs)
delete_path_list(atmosphere_root_dirs_to_delete, "atmosphere root dirs");
// Delete atmosphere contents directories (title IDs)
delete_path_list(atmosphere_contents_dirs_to_delete, "atmosphere contents dirs");
// Delete atmosphere files
delete_path_list(atmosphere_files_to_delete, "atmosphere files");
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: bootloader/\n");
set_color(COLOR_WHITE);
// Delete bootloader directories
delete_path_list(bootloader_dirs_to_delete, "bootloader dirs");
// Delete bootloader files
delete_path_list(bootloader_files_to_delete, "bootloader files");
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: config/\n");
set_color(COLOR_WHITE);
// Delete config directories
delete_path_list(config_dirs_to_delete, "config dirs");
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: switch/\n");
set_color(COLOR_WHITE);
// Delete switch directories
delete_path_list(switch_dirs_to_delete, "switch dirs");
// Delete switch files
delete_path_list(switch_files_to_delete, "switch files");
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: Root-Dateien\n");
set_color(COLOR_WHITE);
// Delete root files
delete_path_list(root_files_to_delete, "root files");
// Delete miscellaneous directories
delete_path_list(misc_dirs_to_delete, "misc dirs");
// Delete miscellaneous files
delete_path_list(misc_files_to_delete, "misc files");
set_color(COLOR_GREEN);
gfx_printf(" Bereinigung abgeschlossen!\n");
set_color(COLOR_WHITE);
return FR_OK;
}
// Update mode: Copy files from staging
int update_mode_install(omninx_variant_t variant) {
int res;
const char* staging = get_staging_path(variant);
char src_path[256];
char dst_path[256];
if (!staging) {
return FR_INVALID_PARAMETER;
}
check_and_clear_screen_if_needed();
set_color(COLOR_YELLOW);
gfx_printf("Dateien werden kopiert...\n");
set_color(COLOR_WHITE);
// Copy directories (use progress-aware copy for large directories)
s_printf(src_path, "%s/atmosphere", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "atmosphere/");
if (res != FR_OK && res != FR_NO_FILE) return res;
s_printf(src_path, "%s/bootloader", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "bootloader/");
if (res != FR_OK && res != FR_NO_FILE) return res;
s_printf(src_path, "%s/config", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "config/");
if (res != FR_OK && res != FR_NO_FILE) return res;
s_printf(src_path, "%s/switch", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "switch/");
if (res != FR_OK && res != FR_NO_FILE) return res;
s_printf(src_path, "%s/warmboot_mariko", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "warmboot_mariko/");
if (res != FR_OK && res != FR_NO_FILE) return res;
// OC variant includes SaltySD (this is the large one with ~2500 files)
if (variant == VARIANT_OC) {
s_printf(src_path, "%s/SaltySD", staging);
res = folder_copy_with_progress_v2(src_path, "sd:/", "SaltySD/");
if (res != FR_OK && res != FR_NO_FILE) return res;
}
// Copy root files
set_color(COLOR_CYAN);
gfx_printf(" Kopiere Root-Dateien...\n");
set_color(COLOR_WHITE);
s_printf(src_path, "%s/boot.dat", staging);
s_printf(dst_path, "sd:/boot.dat");
if (path_exists(src_path)) {
file_copy(src_path, dst_path);
}
s_printf(src_path, "%s/boot.ini", staging);
s_printf(dst_path, "sd:/boot.ini");
if (path_exists(src_path)) {
file_copy(src_path, dst_path);
}
s_printf(src_path, "%s/exosphere.ini", staging);
s_printf(dst_path, "sd:/exosphere.ini");
if (path_exists(src_path)) {
file_copy(src_path, dst_path);
}
s_printf(src_path, "%s/hbmenu.nro", staging);
s_printf(dst_path, "sd:/hbmenu.nro");
if (path_exists(src_path)) {
file_copy(src_path, dst_path);
}
s_printf(src_path, "%s/loader.bin", staging);
s_printf(dst_path, "sd:/loader.bin");
if (path_exists(src_path)) {
file_copy(src_path, dst_path);
}
s_printf(src_path, "%s/payload.bin", staging);
s_printf(dst_path, "sd:/payload.bin");
if (path_exists(src_path)) {
file_copy(src_path, dst_path);
}
// Create manifest.ini file
set_color(COLOR_CYAN);
gfx_printf(" Erstelle manifest.ini...\n");
set_color(COLOR_WHITE);
// Ensure config/omninx directory exists
s_printf(dst_path, "sd:/config/omninx");
f_mkdir(dst_path);
// Determine pack name and update channel
const char* pack_name;
int update_channel;
switch (variant) {
case VARIANT_STANDARD:
pack_name = "standard";
update_channel = 2;
break;
case VARIANT_LIGHT:
pack_name = "light";
update_channel = 0;
break;
case VARIANT_OC:
pack_name = "oc";
update_channel = 1;
break;
default:
pack_name = "unknown";
update_channel = 0;
break;
}
// Create manifest.ini
s_printf(dst_path, "sd:/config/omninx/manifest.ini");
FIL fp;
if (f_open(&fp, dst_path, FA_WRITE | FA_CREATE_ALWAYS) == FR_OK) {
f_printf(&fp, "[OmniNX]\n");
f_printf(&fp, "current_pack=%s\n", pack_name);
f_printf(&fp, "version=%s\n", VERSION);
f_printf(&fp, "update_channel=%d\n", update_channel);
f_printf(&fp, "channel_pack=%s\n", pack_name);
f_close(&fp);
set_color(COLOR_GREEN);
gfx_printf(" [OK] manifest.ini erstellt\n");
set_color(COLOR_WHITE);
} else {
set_color(COLOR_ORANGE);
gfx_printf(" [WARN] manifest.ini konnte nicht erstellt werden\n");
set_color(COLOR_WHITE);
}
// Cleanup old version markers (legacy files)
cleanup_old_version_markers(variant);
set_color(COLOR_GREEN);
gfx_printf(" Kopie abgeschlossen!\n");
set_color(COLOR_WHITE);
return FR_OK;
}
// Clean mode: Backup user data
int clean_mode_backup(void) {
set_color(COLOR_CYAN);
gfx_printf(" Sichere: DBI, Tinfoil, prod.keys\n");
set_color(COLOR_WHITE);
int res = backup_user_data();
if (res == FR_OK) {
set_color(COLOR_GREEN);
gfx_printf(" [OK] Sicherung abgeschlossen\n");
set_color(COLOR_WHITE);
}
return res;
}
// Clean mode: Wipe directories
int clean_mode_wipe(void) {
int res;
// Delete entire directories
if (path_exists("sd:/atmosphere")) {
set_color(COLOR_CYAN);
gfx_printf(" Loesche: atmosphere/\n");
set_color(COLOR_WHITE);
res = folder_delete("sd:/atmosphere");
if (res != FR_OK && res != FR_NO_FILE) return res;
}
if (path_exists("sd:/bootloader")) {
set_color(COLOR_CYAN);
gfx_printf(" Loesche: bootloader/\n");
set_color(COLOR_WHITE);
res = folder_delete("sd:/bootloader");
if (res != FR_OK && res != FR_NO_FILE) return res;
}
if (path_exists("sd:/config")) {
set_color(COLOR_CYAN);
gfx_printf(" Loesche: config/\n");
set_color(COLOR_WHITE);
res = folder_delete("sd:/config");
if (res != FR_OK && res != FR_NO_FILE) return res;
}
if (path_exists("sd:/switch")) {
set_color(COLOR_CYAN);
gfx_printf(" Loesche: switch/\n");
set_color(COLOR_WHITE);
res = folder_delete("sd:/switch");
if (res != FR_OK && res != FR_NO_FILE) return res;
}
// Delete root files
set_color(COLOR_CYAN);
gfx_printf(" Bereinige: Root-Dateien\n");
set_color(COLOR_WHITE);
delete_path_list(root_files_to_delete, "root files");
// Delete miscellaneous directories
delete_path_list(misc_dirs_to_delete, "misc dirs");
// Delete miscellaneous files
delete_path_list(misc_files_to_delete, "misc files");
// Recreate switch directory
set_color(COLOR_CYAN);
gfx_printf(" Erstelle: switch/\n");
set_color(COLOR_WHITE);
f_mkdir("sd:/switch");
set_color(COLOR_GREEN);
gfx_printf(" Bereinigung abgeschlossen!\n");
set_color(COLOR_WHITE);
return FR_OK;
}
// Clean mode: Restore user data
int clean_mode_restore(void) {
set_color(COLOR_CYAN);
gfx_printf(" Stelle wieder her: DBI, Tinfoil, prod.keys\n");
set_color(COLOR_WHITE);
int res = restore_user_data();
if (res == FR_OK) {
set_color(COLOR_GREEN);
gfx_printf(" [OK] Wiederherstellung abgeschlossen\n");
set_color(COLOR_WHITE);
}
cleanup_backup(); // Clean up temp backup
return res;
}
// Clean mode: Install files
int clean_mode_install(omninx_variant_t variant) {
// Same as update mode install
return update_mode_install(variant);
}
// Remove staging directory after installation
int cleanup_staging_directory(omninx_variant_t pack_variant) {
const char* staging = get_staging_path(pack_variant);
if (!staging) {
return FR_INVALID_PARAMETER;
}
check_and_clear_screen_if_needed();
if (path_exists(staging)) {
set_color(COLOR_YELLOW);
gfx_printf("\nEntferne Installationsordner...\n");
set_color(COLOR_WHITE);
set_color(COLOR_CYAN);
gfx_printf(" Loesche: %s\n", staging);
set_color(COLOR_WHITE);
int res = folder_delete(staging);
if (res == FR_OK) {
set_color(COLOR_GREEN);
gfx_printf(" [OK] Installationsordner entfernt\n");
set_color(COLOR_WHITE);
} else {
set_color(COLOR_ORANGE);
gfx_printf(" [WARN] Ordner konnte nicht entfernt werden (err=%d)\n", res);
set_color(COLOR_WHITE);
}
return res;
}
return FR_OK;
}
// Main installation function
int perform_installation(omninx_variant_t pack_variant, install_mode_t mode) {
int res;
if (mode == INSTALL_MODE_UPDATE) {
// Update mode: selective cleanup then install
set_color(COLOR_YELLOW);
gfx_printf("Schritt 1: Bereinigung...\n");
set_color(COLOR_WHITE);
res = update_mode_cleanup(pack_variant);
if (res != FR_OK) return res;
check_and_clear_screen_if_needed();
gfx_printf("\n");
set_color(COLOR_YELLOW);
gfx_printf("Schritt 2: Dateien kopieren...\n");
set_color(COLOR_WHITE);
res = update_mode_install(pack_variant);
if (res != FR_OK) return res;
check_and_clear_screen_if_needed();
// Remove staging directory
res = cleanup_staging_directory(pack_variant);
return res;
} else {
// Clean mode: backup, wipe, restore, install
set_color(COLOR_YELLOW);
gfx_printf("Schritt 1: Sichere Benutzerdaten...\n");
set_color(COLOR_WHITE);
res = clean_mode_backup();
if (res != FR_OK) return res;
check_and_clear_screen_if_needed();
gfx_printf("\n");
set_color(COLOR_YELLOW);
gfx_printf("Schritt 2: Bereinige alte Installation...\n");
set_color(COLOR_WHITE);
res = clean_mode_wipe();
if (res != FR_OK) return res;
check_and_clear_screen_if_needed();
gfx_printf("\n");
set_color(COLOR_YELLOW);
gfx_printf("Schritt 3: Stelle Benutzerdaten wieder her...\n");
set_color(COLOR_WHITE);
res = clean_mode_restore();
if (res != FR_OK) return res;
check_and_clear_screen_if_needed();
gfx_printf("\n");
set_color(COLOR_YELLOW);
gfx_printf("Schritt 4: Dateien kopieren...\n");
set_color(COLOR_WHITE);
res = clean_mode_install(pack_variant);
if (res != FR_OK) return res;
check_and_clear_screen_if_needed();
// Remove staging directory
res = cleanup_staging_directory(pack_variant);
return res;
}
}

35
source/install.h Normal file
View File

@@ -0,0 +1,35 @@
/*
* OmniNX Installer - Installation Logic
*/
#pragma once
#include "version.h"
#include <utils/types.h>
// Installation modes
typedef enum {
INSTALL_MODE_UPDATE, // OmniNX detected - selective deletion
INSTALL_MODE_CLEAN // No OmniNX - full wipe
} install_mode_t;
// Main installation function
int perform_installation(omninx_variant_t pack_variant, install_mode_t mode);
// Update mode operations
int update_mode_cleanup(omninx_variant_t variant);
int update_mode_install(omninx_variant_t variant);
// Clean install operations
int clean_mode_backup(void);
int clean_mode_wipe(void);
int clean_mode_restore(void);
int clean_mode_install(omninx_variant_t variant);
// Helper: Delete a list of paths
int delete_path_list(const char* paths[], const char* description);
// Helper: Delete old version markers (except current variant)
int cleanup_old_version_markers(omninx_variant_t current_variant);
// Remove staging directory after installation
int cleanup_staging_directory(omninx_variant_t pack_variant);

110
source/libs/fatfs/diskio.c Normal file
View File

@@ -0,0 +1,110 @@
/*
* HATS Installer - Simplified disk I/O (SD card only)
* Based on TegraExplorer diskio.c by shchmue
*
* This simplified version removes BIS/eMMC support since the
* HATS installer only needs SD card access.
*/
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */
/*-----------------------------------------------------------------------*/
#include <string.h>
#include <libs/fatfs/diskio.h> /* FatFs lower layer API */
#include <memory_map.h>
#include <storage/nx_sd.h>
#include <storage/sdmmc.h>
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive number to identify the drive */
)
{
return 0;
}
/*-----------------------------------------------------------------------*/
/* Initialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive number to identify the drive */
)
{
return 0;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
BYTE pdrv, /* Physical drive number to identify the drive */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
if (pdrv == DRIVE_SD)
return sdmmc_storage_read(&sd_storage, sector, count, buff) ? RES_OK : RES_ERROR;
return RES_ERROR;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_write (
BYTE pdrv, /* Physical drive number to identify the drive */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
if (pdrv == DRIVE_SD)
return sdmmc_storage_write(&sd_storage, sector, count, (void *)buff) ? RES_OK : RES_ERROR;
return RES_ERROR;
}
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
static u32 part_rsvd_size = 0;
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive number (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DWORD *buf = (DWORD *)buff;
if (pdrv == DRIVE_SD)
{
switch (cmd)
{
case GET_SECTOR_COUNT:
*buf = sd_storage.sec_cnt - part_rsvd_size;
break;
case GET_BLOCK_SIZE:
*buf = 32768; // Align to 16MB.
break;
}
}
else if (pdrv == DRIVE_RAM)
{
switch (cmd)
{
case GET_SECTOR_COUNT:
*buf = RAM_DISK_SZ >> 9; // 1GB.
break;
case GET_BLOCK_SIZE:
*buf = 2048; // Align to 1MB.
break;
}
}
return RES_OK;
}

294
source/libs/fatfs/ffconf.h Normal file
View File

@@ -0,0 +1,294 @@
/*---------------------------------------------------------------------------/
/ FatFs Functional Configurations
/---------------------------------------------------------------------------*/
#define FFCONF_DEF 86604 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define FF_FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: Basic functions are fully enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define FF_USE_STRFUNC 2
/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */
#define FF_USE_FIND 1
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define FF_USE_MKFS 1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define FF_FASTFS 0
#if FF_FASTFS
#define FF_USE_FASTSEEK 1
#else
#define FF_USE_FASTSEEK 0
#endif
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define FF_USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define FF_USE_CHMOD 1
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
#define FF_USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define FF_CODE_PAGE 850
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/
#define FF_USE_LFN 3
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() in ffsystem.c, need to be added to the project. */
#define FF_LFN_UNICODE 0
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
/ the file names to read. The maximum possible length of the read file name depends
/ on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_STRF_ENCODE 0
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
/ f_putc(), f_puts and f_printf() convert the character encoding in it.
/ This option selects assumption of character encoding ON THE FILE to be
/ read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
#define FF_FS_RPATH 0
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define FF_VOLUMES 4
/* Number of volumes (logical drives) to be used. (1-10) */
#define FF_STR_VOLUME_ID 1
// Order is important. Any change to order, must also be reflected to diskio drive enum.
#define FF_VOLUME_STRS "sd","ram","emmc","bis"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table needs to be defined as:
/
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/
#define FF_MULTI_PARTITION 0
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ funciton will be available. */
#define FF_MIN_SS 512
#define FF_MAX_SS 512
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk. But a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */
#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
#define FF_FS_NOFSINFO 1
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
#define FF_FS_EXFAT 1
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define FF_FS_NORTC 1
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2020
/* The option FF_FS_NORTC switches timestamp function. If the system does not have
/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable
/ the timestamp function. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect at read-only configuration (FF_FS_READONLY = 1). */
#define FF_FS_LOCK 0
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
/* #include <somertos.h> // O/S definitions */
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
#define FF_SYNC_t HANDLE
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of time tick.
/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. */
/*--- End of configuration options ---*/

View File

@@ -0,0 +1,39 @@
/*------------------------------------------------------------------------*/
/* Sample Code of OS Dependent Functions for FatFs */
/* (C) ChaN, 2018 */
/* (C) CTCaer, 2018 */
/*------------------------------------------------------------------------*/
#include <libs/fatfs/ff.h>
#include <mem/heap.h>
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
/*------------------------------------------------------------------------*/
/* Allocate a memory block */
/*------------------------------------------------------------------------*/
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
UINT msize /* Number of bytes to allocate */
)
{
return malloc(msize); /* Allocate a new memory block with POSIX API */
}
/*------------------------------------------------------------------------*/
/* Free a memory block */
/*------------------------------------------------------------------------*/
void ff_memfree (
void* mblock /* Pointer to the memory block to free (nothing to do if null) */
)
{
free(mblock); /* Free the memory block with POSIX API */
}
#endif

24
source/link.ld Normal file
View File

@@ -0,0 +1,24 @@
ENTRY(_start)
SECTIONS {
PROVIDE(__ipl_start = IPL_LOAD_ADDR);
. = __ipl_start;
.text : {
*(.text._start);
KEEP(*(._boot_cfg));
*(.text*);
}
.data : {
*(.data*);
*(.rodata*);
}
. = ALIGN(0x10);
__ipl_end = .;
.bss : {
__bss_start = .;
*(COMMON)
*(.bss*)
__bss_end = .;
}
__end__ = .;
}

459
source/main.c Normal file
View File

@@ -0,0 +1,459 @@
/*
* OmniNX Installer Payload
* Minimal payload to install OmniNX CFW Pack files outside of Horizon OS
*
* Based on TegraExplorer/hekate by CTCaer, naehrwert, shchmue
* Based on HATS-Installer-Payload by sthetix
*/
#ifndef VERSION
#define VERSION "1.0.0"
#endif
#include <string.h>
#include <display/di.h>
#include "gfx.h"
#include <libs/fatfs/ff.h>
#include <mem/heap.h>
#include <mem/minerva.h>
#include <power/max77620.h>
#include <soc/bpmp.h>
#include <soc/fuse.h>
#include <soc/hw_init.h>
#include <soc/pmc.h>
#include <soc/t210.h>
#include "nx_sd.h"
#include <storage/sdmmc.h>
#include <utils/btn.h>
#include <utils/util.h>
#include <utils/sprintf.h>
#include <input/joycon.h>
#include "fs.h"
#include "version.h"
#include "install.h"
// Configuration
#define PAYLOAD_PATH "sd:/bootloader/update.bin"
// Payload launch defines
#define RELOC_META_OFF 0x7C
#define PATCHED_RELOC_SZ 0x94
#define PATCHED_RELOC_STACK 0x40007000
#define PATCHED_RELOC_ENTRY 0x40010000
#define EXT_PAYLOAD_ADDR 0xC0000000
#define RCM_PAYLOAD_ADDR (EXT_PAYLOAD_ADDR + ALIGN(PATCHED_RELOC_SZ, 0x10))
#define COREBOOT_END_ADDR 0xD0000000
// Hekate config structure (simplified)
typedef struct _hekate_config
{
u32 autoboot;
u32 autoboot_list;
u32 bootwait;
u32 backlight;
u32 autohosoff;
u32 autonogc;
u32 updater2p;
u32 bootprotect;
bool t210b01;
bool se_keygen_done;
bool sept_run;
bool aes_slots_new;
bool emummc_force_disable;
bool rcm_patched;
u32 errors;
} hekate_config;
// Boot config structures (required by bdk)
hekate_config h_cfg;
boot_cfg_t __attribute__((section ("._boot_cfg"))) b_cfg;
volatile nyx_storage_t *nyx_str = (nyx_storage_t *)NYX_STORAGE_ADDR;
static void *coreboot_addr;
static int total_errors = 0;
// Use BDK colors (already defined in types.h)
#define COLOR_CYAN 0xFF00FFFF
#define COLOR_WHITE 0xFFFFFFFF
static void set_default_configuration(void)
{
h_cfg.autoboot = 0;
h_cfg.autoboot_list = 0;
h_cfg.bootwait = 3;
h_cfg.se_keygen_done = 0;
h_cfg.backlight = 100;
h_cfg.autohosoff = 0;
h_cfg.autonogc = 1;
h_cfg.updater2p = 0;
h_cfg.bootprotect = 0;
h_cfg.errors = 0;
h_cfg.sept_run = 0;
h_cfg.aes_slots_new = false;
h_cfg.rcm_patched = fuse_check_patched_rcm();
h_cfg.emummc_force_disable = false;
h_cfg.t210b01 = hw_get_chip_id() == GP_HIDREV_MAJOR_T210B01;
}
static void set_color(u32 color) {
gfx_con_setcol(color, gfx_con.fillbg, gfx_con.bgcol);
}
static void print_header(void) {
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
set_color(COLOR_CYAN);
gfx_printf("========================================\n");
gfx_printf(" OmniNX Installer Payload v%s\n", VERSION);
gfx_printf("========================================\n\n");
set_color(COLOR_WHITE);
}
void reloc_patcher(u32 payload_dst, u32 payload_src, u32 payload_size) {
memcpy((u8 *)payload_src, (u8 *)IPL_LOAD_ADDR, PATCHED_RELOC_SZ);
volatile reloc_meta_t *relocator = (reloc_meta_t *)(payload_src + RELOC_META_OFF);
relocator->start = payload_dst - ALIGN(PATCHED_RELOC_SZ, 0x10);
relocator->stack = PATCHED_RELOC_STACK;
relocator->end = payload_dst + payload_size;
relocator->ep = payload_dst;
if (payload_size == 0x7000) {
memcpy((u8 *)(payload_src + ALIGN(PATCHED_RELOC_SZ, 0x10)), coreboot_addr, 0x7000);
}
}
static int launch_payload(const char *path) {
if (!path)
return 1;
if (sd_mount()) {
FIL fp;
if (f_open(&fp, path, FA_READ)) {
gfx_printf("Payload nicht gefunden: %s\n", path);
return 1;
}
void *buf;
u32 size = f_size(&fp);
if (size < 0x30000)
buf = (void *)RCM_PAYLOAD_ADDR;
else {
coreboot_addr = (void *)(COREBOOT_END_ADDR - size);
buf = coreboot_addr;
}
if (f_read(&fp, buf, size, NULL)) {
f_close(&fp);
return 1;
}
f_close(&fp);
sd_unmount();
if (size < 0x30000) {
reloc_patcher(PATCHED_RELOC_ENTRY, EXT_PAYLOAD_ADDR, ALIGN(size, 0x10));
hw_reinit_workaround(false, byte_swap_32(*(u32 *)(buf + size - sizeof(u32))));
} else {
reloc_patcher(PATCHED_RELOC_ENTRY, EXT_PAYLOAD_ADDR, 0x7000);
hw_reinit_workaround(true, 0);
}
sdmmc_storage_init_wait_sd();
void (*ext_payload_ptr)() = (void *)EXT_PAYLOAD_ADDR;
(*ext_payload_ptr)();
}
return 1;
}
static int file_exists(const char *path) {
FILINFO fno;
return (f_stat(path, &fno) == FR_OK);
}
extern void pivot_stack(u32 stack_top);
void ipl_main(void) {
// Hardware initialization
hw_init();
pivot_stack(IPL_STACK_TOP);
heap_init(IPL_HEAP_START);
// Set bootloader's default configuration
set_default_configuration();
// Mount SD Card
if (!sd_mount()) {
// Can't show error without display, just reboot
power_set_state(POWER_OFF_REBOOT);
}
// Initialize minerva for faster memory
minerva_init();
minerva_change_freq(FREQ_800);
// Initialize display
display_init();
u32 *fb = display_init_framebuffer_pitch();
gfx_init_ctxt(fb, 720, 1280, 720);
gfx_con_init();
display_backlight_pwm_init();
display_backlight_brightness(100, 1000);
// Overclock BPMP
bpmp_clk_rate_set(BPMP_CLK_DEFAULT_BOOST);
print_header();
// Detect current OmniNX installation
omninx_status_t current = detect_omninx_installation();
// Detect which pack variant is on SD card
omninx_variant_t pack_variant = detect_pack_variant();
if (pack_variant == VARIANT_NONE) {
set_color(COLOR_RED);
gfx_printf("FEHLER: Kein OmniNX-Paket auf der SD-Karte gefunden!\n");
gfx_printf("Erwartet wird eines der folgenden:\n");
gfx_printf(" - sd:/OmniNX Standard/\n");
gfx_printf(" - sd:/OmniNX Light/\n");
gfx_printf(" - sd:/OmniNX OC/\n\n");
set_color(COLOR_WHITE);
// Initialize joycons for button input
jc_init_hw();
// Wait for button confirmation
bool button_pressed = false;
// Launch payload if available
if (file_exists(PAYLOAD_PATH)) {
set_color(COLOR_GREEN);
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
gfx_printf("um Hekate zu starten...\n");
set_color(COLOR_WHITE);
} else {
set_color(COLOR_GREEN);
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
gfx_printf("um den Neustart zu starten...\n");
set_color(COLOR_WHITE);
}
// First, wait for power button to be released if it's currently pressed
while (btn_read() & BTN_POWER) {
msleep(50);
}
while (!button_pressed) {
// Check power button
u8 btn_state = btn_read();
if (btn_state & BTN_POWER) {
button_pressed = true;
break;
}
// Check joycon A button
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) {
button_pressed = true;
break;
}
msleep(50); // Small delay to avoid busy-waiting
}
// Launch payload if available, otherwise reboot
if (file_exists(PAYLOAD_PATH)) {
gfx_printf("\n");
set_color(COLOR_CYAN);
gfx_printf("Payload wird gestartet...\n");
set_color(COLOR_WHITE);
msleep(500);
launch_payload(PAYLOAD_PATH);
} else {
power_set_state(POWER_OFF_REBOOT);
}
return;
}
// Determine installation mode
install_mode_t mode = current.is_installed ? INSTALL_MODE_UPDATE : INSTALL_MODE_CLEAN;
// Show information
set_color(COLOR_CYAN);
gfx_printf("Installationsmodus: %s\n", mode == INSTALL_MODE_UPDATE ? "Update" : "Saubere Installation");
gfx_printf("Paket-Variante: %s\n", get_variant_name(pack_variant));
if (current.is_installed) {
gfx_printf("Aktuelle Installation: %s\n", get_variant_name(current.variant));
} else {
gfx_printf("Aktuelle Installation: Keine\n");
}
set_color(COLOR_WHITE);
gfx_printf("\n");
// Initialize joycons for button input
jc_init_hw();
// Intro section - wait for user confirmation
set_color(COLOR_YELLOW);
gfx_printf("Bereit zum Installieren/Aktualisieren.\n");
set_color(COLOR_WHITE);
gfx_printf("\n");
set_color(COLOR_GREEN);
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
gfx_printf("um die Installation zu starten...\n");
set_color(COLOR_WHITE);
// Wait for either A button or Power button
bool button_pressed = false;
// First, wait for power button to be released if it's currently pressed
while (btn_read() & BTN_POWER) {
msleep(50);
}
while (!button_pressed) {
// Check power button - detect press (transition from not pressed to pressed)
u8 btn_state = btn_read();
if (btn_state & BTN_POWER) {
button_pressed = true;
break;
}
// Check joycon A button
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) {
button_pressed = true;
break;
}
msleep(50); // Small delay to avoid busy-waiting
}
// Clear the prompt and start installation
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
print_header();
// Perform the installation
set_color(COLOR_YELLOW);
gfx_printf("Installation wird gestartet...\n\n");
set_color(COLOR_WHITE);
int result = perform_installation(pack_variant, mode);
// Clear screen for final summary to ensure it's visible
gfx_clear_grey(0x1B);
gfx_con_setpos(0, 0);
// Summary
set_color(COLOR_CYAN);
gfx_printf("========================================\n");
gfx_printf(" OmniNX Installer Payload v%s\n", VERSION);
gfx_printf("========================================\n\n");
set_color(COLOR_WHITE);
if (result == FR_OK && total_errors == 0) {
set_color(COLOR_GREEN);
gfx_printf("========================================\n");
gfx_printf(" Installation abgeschlossen!\n");
gfx_printf("========================================\n");
} else {
set_color(COLOR_RED);
gfx_printf("========================================\n");
gfx_printf(" Installation beendet\n");
if (total_errors > 0) {
gfx_printf(" %d Fehler\n", total_errors);
}
gfx_printf("========================================\n");
}
set_color(COLOR_WHITE);
// Wait for user input before launching payload
gfx_printf("\n");
set_color(COLOR_GREEN);
gfx_printf("Druecke A-Taste (rechter Joy-Con) oder Power-Taste,\n");
gfx_printf("um Hekate zu starten...\n");
set_color(COLOR_WHITE);
// Wait for either A button or Power button (reuse button_pressed variable)
button_pressed = false;
// First, wait for power button to be released if it's currently pressed
while (btn_read() & BTN_POWER) {
msleep(50);
}
while (!button_pressed) {
// Check power button - detect press (transition from not pressed to pressed)
u8 btn_state = btn_read();
if (btn_state & BTN_POWER) {
// Power button is pressed
button_pressed = true;
break;
}
// Check joycon A button
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) {
button_pressed = true;
break;
}
msleep(50); // Small delay to avoid busy-waiting
}
gfx_printf("\n");
set_color(COLOR_CYAN);
gfx_printf("Payload wird gestartet...\n");
set_color(COLOR_WHITE);
msleep(500); // Brief delay before launch
if (file_exists(PAYLOAD_PATH)) {
launch_payload(PAYLOAD_PATH);
} else {
// Payload not found, show error
set_color(COLOR_RED);
gfx_printf("\nFEHLER: Payload nicht gefunden!\n");
gfx_printf("Pfad: %s\n", PAYLOAD_PATH);
set_color(COLOR_WHITE);
gfx_printf("\nDruecke A oder Power zum Neustart...\n");
// Wait for button again
button_pressed = false;
// Wait for power button to be released if pressed
while (btn_read() & BTN_POWER) {
msleep(50);
}
while (!button_pressed) {
u8 btn_state = btn_read();
if (btn_state & BTN_POWER) {
button_pressed = true;
break;
}
jc_gamepad_rpt_t *jc = joycon_poll();
if (jc && jc->a) {
button_pressed = true;
break;
}
msleep(50);
}
power_set_state(POWER_OFF_REBOOT);
}
// Should never reach here
while (1)
bpmp_halt();
}

228
source/nx_sd.c Normal file
View File

@@ -0,0 +1,228 @@
/*
* Copyright (c) 2018 naehrwert
* Copyright (c) 2018-2019 CTCaer
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <storage/nx_sd.h>
#include <storage/sdmmc.h>
#include <storage/sdmmc_driver.h>
#include <gfx_utils.h>
#include <libs/fatfs/ff.h>
#include <mem/heap.h>
bool sd_mounted = false;
static u16 sd_errors[3] = { 0 }; // Init and Read/Write errors.
static u32 sd_mode = SD_UHS_SDR82;
sdmmc_t sd_sdmmc;
sdmmc_storage_t sd_storage;
FATFS sd_fs;
void sd_error_count_increment(u8 type)
{
switch (type)
{
case SD_ERROR_INIT_FAIL:
sd_errors[0]++;
break;
case SD_ERROR_RW_FAIL:
sd_errors[1]++;
break;
case SD_ERROR_RW_RETRY:
sd_errors[2]++;
break;
}
}
u16 *sd_get_error_count()
{
return sd_errors;
}
bool sd_get_card_removed()
{
if (!sdmmc_get_sd_inserted())
return true;
return false;
}
u32 sd_get_mode()
{
return sd_mode;
}
int sd_init_retry(bool power_cycle)
{
u32 bus_width = SDMMC_BUS_WIDTH_4;
u32 type = SDHCI_TIMING_UHS_SDR82;
// Power cycle SD card.
if (power_cycle)
{
sd_mode--;
sdmmc_storage_end(&sd_storage);
}
// Get init parameters.
switch (sd_mode)
{
case SD_INIT_FAIL: // Reset to max.
return 0;
case SD_1BIT_HS25:
bus_width = SDMMC_BUS_WIDTH_1;
type = SDHCI_TIMING_SD_HS25;
break;
case SD_4BIT_HS25:
type = SDHCI_TIMING_SD_HS25;
break;
case SD_UHS_SDR82:
type = SDHCI_TIMING_UHS_SDR82;
break;
default:
sd_mode = SD_UHS_SDR82;
}
return sdmmc_storage_init_sd(&sd_storage, &sd_sdmmc, bus_width, type);
}
bool sd_initialize(bool power_cycle)
{
if (power_cycle)
sdmmc_storage_end(&sd_storage);
int res = !sd_init_retry(false);
while (true)
{
if (!res)
return true;
else if (!sdmmc_get_sd_inserted()) // SD Card is not inserted.
{
sd_mode = SD_UHS_SDR82;
break;
}
else
{
sd_errors[SD_ERROR_INIT_FAIL]++;
if (sd_mode == SD_INIT_FAIL)
break;
else
res = !sd_init_retry(true);
}
}
sdmmc_storage_end(&sd_storage);
return false;
}
bool is_sd_inited = false;
bool sd_mount()
{
if (sd_mounted)
return true;
int res = !sd_initialize(false);
is_sd_inited = !res;
if (res)
{
gfx_con.mute = false;
EPRINTF("Failed to init SD card.");
if (!sdmmc_get_sd_inserted())
EPRINTF("Make sure that it is inserted.");
else
EPRINTF("SD Card Reader is not properly seated!");
}
else
{
res = f_mount(&sd_fs, "", 1);
if (res == FR_OK)
{
sd_mounted = true;
return true;
}
else
{
gfx_con.mute = false;
EPRINTFARGS("Failed to mount SD card (FatFS Error %d).\nMake sure that a FAT partition exists..", res);
}
}
return false;
}
static void _sd_deinit()
{
if (sd_mode == SD_INIT_FAIL)
sd_mode = SD_UHS_SDR82;
if (sd_mounted)
{
f_mount(NULL, "", 1);
sdmmc_storage_end(&sd_storage);
sd_mounted = false;
is_sd_inited = false;
}
}
void sd_unmount() { _sd_deinit(); }
void sd_end() { _sd_deinit(); }
void *sd_file_read(const char *path, u32 *fsize)
{
FIL fp;
if (f_open(&fp, path, FA_READ) != FR_OK)
return NULL;
u32 size = f_size(&fp);
if (fsize)
*fsize = size;
char *buf = malloc(size + 1);
buf[size] = '\0';
if (f_read(&fp, buf, size, NULL) != FR_OK)
{
free(buf);
f_close(&fp);
return NULL;
}
f_close(&fp);
return buf;
}
int sd_save_to_file(void *buf, u32 size, const char *filename)
{
FIL fp;
u32 res = 0;
res = f_open(&fp, filename, FA_CREATE_ALWAYS | FA_WRITE);
if (res)
{
EPRINTFARGS("Error (%d) creating file\n%s.\n", res, filename);
return res;
}
f_write(&fp, buf, size, NULL);
f_close(&fp);
return 0;
}

60
source/nx_sd.h Normal file
View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2018 naehrwert
* Copyright (c) 2018-2021 CTCaer
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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/>.
*/
#ifndef NX_SD_H
#define NX_SD_H
#include <storage/sdmmc.h>
#include <storage/sdmmc_driver.h>
#include <libs/fatfs/ff.h>
enum
{
SD_INIT_FAIL = 0,
SD_1BIT_HS25 = 1,
SD_4BIT_HS25 = 2,
SD_UHS_SDR82 = 3,
SD_UHS_SDR104 = 4
};
enum
{
SD_ERROR_INIT_FAIL = 0,
SD_ERROR_RW_FAIL = 1,
SD_ERROR_RW_RETRY = 2
};
extern sdmmc_t sd_sdmmc;
extern sdmmc_storage_t sd_storage;
extern FATFS sd_fs;
void sd_error_count_increment(u8 type);
u16 *sd_get_error_count();
bool sd_get_card_removed();
bool sd_get_card_initialized();
bool sd_get_card_mounted();
u32 sd_get_mode();
int sd_init_retry(bool power_cycle);
bool sd_initialize(bool power_cycle);
bool sd_mount();
void sd_unmount();
void sd_end();
bool sd_is_gpt();
void *sd_file_read(const char *path, u32 *fsize);
int sd_save_to_file(void *buf, u32 size, const char *filename);
#endif

58
source/start.S Normal file
View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2018 naehrwert
*/
.section .text._start
.arm
.extern memset
.type memset, %function
.extern ipl_main
.type ipl_main, %function
.globl _start
.type _start, %function
_start:
ADR R0, _start
LDR R1, =__ipl_start
CMP R0, R1
BEQ _real_start
ADR R2, _reloc_ipl
LDR R3, =0x4003FF00
MOV R4, #(_real_start - _reloc_ipl)
_copy_loop:
LDMIA R2!, {R5}
STMIA R3!, {R5}
SUBS R4, #4
BNE _copy_loop
LDR R2, =__ipl_end
SUB R2, R2, R1
LDR R3, =_real_start
LDR R4, =0x4003FF00
BX R4
_reloc_ipl:
LDMIA R0!, {R4-R7}
STMIA R1!, {R4-R7}
SUBS R2, #0x10
BNE _reloc_ipl
BX R3
_real_start:
LDR SP, =0x4003FF00
LDR R0, =__bss_start
EOR R1, R1, R1
LDR R2, =__bss_end
SUB R2, R2, R0
BL memset
BL ipl_main
B .
.globl pivot_stack
.type pivot_stack, %function
pivot_stack:
MOV SP, R0
BX LR

187
source/version.c Normal file
View File

@@ -0,0 +1,187 @@
/*
* OmniNX Installer - Version and Variant Detection
*/
#include "version.h"
#include <libs/fatfs/ff.h>
#include <string.h>
#include <utils/sprintf.h>
#include <utils/ini.h>
#include <utils/list.h>
#include <mem/heap.h>
// Manifest file path
#define MANIFEST_PATH "sd:/config/omninx/manifest.ini"
// Staging directories
#define STAGING_STANDARD "sd:/OmniNX Standard"
#define STAGING_LIGHT "sd:/OmniNX Light"
#define STAGING_OC "sd:/OmniNX OC"
// Check if file exists
static bool file_exists(const char *path) {
FILINFO fno;
return (f_stat(path, &fno) == FR_OK);
}
// Read manifest.ini and extract pack variant
static omninx_variant_t read_manifest_variant(const char *manifest_path) {
link_t sections;
list_init(&sections);
if (ini_parse(&sections, (char *)manifest_path, false) != 1) {
// Clean up on failure
LIST_FOREACH_ENTRY(ini_sec_t, sec, &sections, link) {
LIST_FOREACH_ENTRY(ini_kv_t, kv, &sec->kvs, link) {
if (kv->key) free(kv->key);
if (kv->val) free(kv->val);
}
if (sec->name) free(sec->name);
}
return VARIANT_NONE;
}
omninx_variant_t variant = VARIANT_NONE;
// Find [OmniNX] section
LIST_FOREACH_ENTRY(ini_sec_t, sec, &sections, link) {
if (sec->type == INI_CHOICE && sec->name && !strcmp(sec->name, "OmniNX")) {
// Look for "current_pack" key
LIST_FOREACH_ENTRY(ini_kv_t, kv, &sec->kvs, link) {
if (kv->key && !strcmp(kv->key, "current_pack")) {
if (kv->val) {
if (!strcmp(kv->val, "standard")) {
variant = VARIANT_STANDARD;
} else if (!strcmp(kv->val, "light")) {
variant = VARIANT_LIGHT;
} else if (!strcmp(kv->val, "oc")) {
variant = VARIANT_OC;
}
}
break;
}
}
break;
}
}
// Clean up
LIST_FOREACH_ENTRY(ini_sec_t, sec, &sections, link) {
LIST_FOREACH_ENTRY(ini_kv_t, kv, &sec->kvs, link) {
if (kv->key) free(kv->key);
if (kv->val) free(kv->val);
}
if (sec->name) free(sec->name);
}
return variant;
}
// Read manifest.ini and extract version string
static void read_manifest_version(const char *manifest_path, char *version_buf, size_t buf_size) {
version_buf[0] = '\0';
link_t sections;
list_init(&sections);
if (ini_parse(&sections, (char *)manifest_path, false) != 1) {
// Clean up on failure
LIST_FOREACH_ENTRY(ini_sec_t, sec, &sections, link) {
LIST_FOREACH_ENTRY(ini_kv_t, kv, &sec->kvs, link) {
if (kv->key) free(kv->key);
if (kv->val) free(kv->val);
}
if (sec->name) free(sec->name);
}
return;
}
// Find [OmniNX] section
LIST_FOREACH_ENTRY(ini_sec_t, sec, &sections, link) {
if (sec->type == INI_CHOICE && sec->name && !strcmp(sec->name, "OmniNX")) {
// Look for "version" key
LIST_FOREACH_ENTRY(ini_kv_t, kv, &sec->kvs, link) {
if (kv->key && !strcmp(kv->key, "version")) {
if (kv->val) {
strncpy(version_buf, kv->val, buf_size - 1);
version_buf[buf_size - 1] = '\0';
}
break;
}
}
break;
}
}
// Clean up
LIST_FOREACH_ENTRY(ini_sec_t, sec, &sections, link) {
LIST_FOREACH_ENTRY(ini_kv_t, kv, &sec->kvs, link) {
if (kv->key) free(kv->key);
if (kv->val) free(kv->val);
}
if (sec->name) free(sec->name);
}
}
// Detect current OmniNX installation status
omninx_status_t detect_omninx_installation(void) {
omninx_status_t status;
status.is_installed = false;
status.variant = VARIANT_NONE;
status.version_file[0] = '\0';
// Check for manifest.ini file
if (file_exists(MANIFEST_PATH)) {
status.variant = read_manifest_variant(MANIFEST_PATH);
if (status.variant != VARIANT_NONE) {
status.is_installed = true;
read_manifest_version(MANIFEST_PATH, status.version_file, sizeof(status.version_file));
}
}
return status;
}
// Detect which pack variant is present on SD card
omninx_variant_t detect_pack_variant(void) {
// Check staging directories (in order of preference)
if (file_exists(STAGING_STANDARD)) {
return VARIANT_STANDARD;
} else if (file_exists(STAGING_LIGHT)) {
return VARIANT_LIGHT;
} else if (file_exists(STAGING_OC)) {
return VARIANT_OC;
}
return VARIANT_NONE;
}
// Get human-readable variant name
const char* get_variant_name(omninx_variant_t variant) {
switch (variant) {
case VARIANT_STANDARD: return "Standard";
case VARIANT_LIGHT: return "Light";
case VARIANT_OC: return "OC";
default: return "None";
}
}
// Get staging/source directory path for variant
const char* get_staging_path(omninx_variant_t variant) {
switch (variant) {
case VARIANT_STANDARD: return STAGING_STANDARD;
case VARIANT_LIGHT: return STAGING_LIGHT;
case VARIANT_OC: return STAGING_OC;
default: return NULL;
}
}
// Get manifest.ini path for variant (in staging directory)
const char* get_manifest_path(omninx_variant_t variant) {
switch (variant) {
case VARIANT_STANDARD: return "sd:/OmniNX Standard/config/omninx/manifest.ini";
case VARIANT_LIGHT: return "sd:/OmniNX Light/config/omninx/manifest.ini";
case VARIANT_OC: return "sd:/OmniNX OC/config/omninx/manifest.ini";
default: return NULL;
}
}

36
source/version.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* OmniNX Installer - Version and Variant Detection
*/
#pragma once
#include <utils/types.h>
// OmniNX variants
typedef enum {
VARIANT_NONE = 0,
VARIANT_STANDARD,
VARIANT_LIGHT,
VARIANT_OC
} omninx_variant_t;
// Installation status
typedef struct {
omninx_variant_t variant;
bool is_installed;
char version_file[32]; // e.g., "1.0.0s"
} omninx_status_t;
// Detect current OmniNX installation status
omninx_status_t detect_omninx_installation(void);
// Detect which pack variant is present on SD card
omninx_variant_t detect_pack_variant(void);
// Get human-readable variant name
const char* get_variant_name(omninx_variant_t variant);
// Get staging/source directory path for variant
const char* get_staging_path(omninx_variant_t variant);
// Get manifest.ini path for variant (in staging directory)
const char* get_manifest_path(omninx_variant_t variant);