Initial Commit
This commit is contained in:
103
source/backup.c
Normal file
103
source/backup.c
Normal 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
18
source/backup.h
Normal 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
342
source/deletion_lists.h
Normal 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
337
source/fs.c
Normal 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
19
source/fs.h
Normal 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
705
source/gfx.c
Normal 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
96
source/gfx.h
Normal 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
719
source/install.c
Normal 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
35
source/install.h
Normal 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
110
source/libs/fatfs/diskio.c
Normal 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
294
source/libs/fatfs/ffconf.h
Normal 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 ---*/
|
||||
39
source/libs/fatfs/ffsystem.c
Normal file
39
source/libs/fatfs/ffsystem.c
Normal 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
24
source/link.ld
Normal 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
459
source/main.c
Normal 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
228
source/nx_sd.c
Normal 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
60
source/nx_sd.h
Normal 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
58
source/start.S
Normal 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
187
source/version.c
Normal 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(§ions);
|
||||
|
||||
if (ini_parse(§ions, (char *)manifest_path, false) != 1) {
|
||||
// Clean up on failure
|
||||
LIST_FOREACH_ENTRY(ini_sec_t, sec, §ions, 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, §ions, 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, §ions, 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(§ions);
|
||||
|
||||
if (ini_parse(§ions, (char *)manifest_path, false) != 1) {
|
||||
// Clean up on failure
|
||||
LIST_FOREACH_ENTRY(ini_sec_t, sec, §ions, 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, §ions, 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, §ions, 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
36
source/version.h
Normal 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);
|
||||
Reference in New Issue
Block a user