diff --git a/README.md b/README.md
index a3864240..00594540 100644
--- a/README.md
+++ b/README.md
@@ -154,6 +154,29 @@ If you are to install nro forwarders, remove `R_TRY(ValidateAcidSignature(std::a
Uncompress the kip to make it work with config editor: `hactool -t kip1 Atmosphere/stratosphere/loader/loader.kip --uncompress=Atmosphere/stratosphere/loader/loader.kip`
+### [DEPRECATED] Patching sysmodules manually
+
+This method is NOT RECOMMENDED and NOT SUPPORTED ANY MORE, only served as reference.
+
+
+
+ Tools:
+ - Lockpick_RCM
+ - TegraExplorer
+ - [hactool](https://github.com/SciresM/hactool)
+ - [nx2elf](https://github.com/shuffle2/nx2elf)
+ - elf2nso from [switch-tools](https://github.com/switchbrew/switch-tools/)
+ - [hacpack](https://github.com/The-4n/hacPack)
+
+ 1. Dump `prod.keys` with Lockpick_RCM
+ 2. Dump HOS firmware with TegraExplorer
+ 3. Configure and run `test.sh` in `/Source/Atmosphere/stratosphere/loader/source/oc/` to generate patched pcv & ptm
+ 4. Replace nca in `SYSTEM:/Contents/registered/` with TegraExplorer
+ 5. `ValidateAcidSignature()` should be stubbed to allow unsigned sysmodules to load (a.k.a. `loader_patch`)
+
+
+
+
## Acknowledgement
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/Makefile b/Source/Atmosphere/stratosphere/loader/source/oc/Makefile
index 798740d2..754342a6 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/Makefile
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/Makefile
@@ -3,7 +3,7 @@ export CC := g++-11
all: test
test:
- $(CC) ldr_oc_suite.cpp test.cpp -o ./test -O2 -std=c++20
+ $(CC) ldr_oc_suite.cpp test.cpp -o ./test -O2 -std=c++20 -Wall
clean:
rm ./test
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp
index 8864947d..396059a6 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/ldr_oc_suite.cpp
@@ -1122,17 +1122,8 @@ namespace ams::ldr::oc {
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
+ #ifdef ATMOSPHERE_IS_STRATOSPHERE
SafetyCheck();
-
- #ifndef ATMOSPHERE_IS_STRATOSPHERE
- void* buf = malloc(nso_size);
- uintptr_t mapped_exe = reinterpret_cast(buf);
- std::memcpy(buf, reinterpret_cast(mapped_nso), nso_size);
- Erista::Patch(mapped_exe, nso_size);
- std::memcpy(buf, reinterpret_cast(mapped_nso), nso_size);
- Mariko::Patch(mapped_exe, nso_size);
- free(buf);
- #else
bool isMariko = (spl::GetSocType() == spl::SocType_Mariko);
if (isMariko)
Mariko::Patch(mapped_nso, nso_size);
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/test.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/test.cpp
index 3c97a951..354243b2 100644
--- a/Source/Atmosphere/stratosphere/loader/source/oc/test.cpp
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/test.cpp
@@ -17,10 +17,19 @@
#include
#include
#include
+#include
namespace ams::ldr::oc {
namespace pcv {
- void Patch(uintptr_t mapped_nso, size_t nso_size);
+ namespace Erista {
+ void Patch(uintptr_t mapped_exe, size_t nso_size);
+ }
+
+ namespace Mariko {
+ void Patch(uintptr_t mapped_exe, size_t nso_size);
+ }
+
+ void SafetyCheck();
}
namespace ptm {
@@ -28,25 +37,24 @@ namespace ams::ldr::oc {
}
}
-static void* ReadFile(const char* file_loc, long* out_size) {
- FILE* fp;
- void* buf;
- long size;
-
- fp = fopen(file_loc, "r");
+void* loadExec(const char* file_loc, size_t* out_size) {
+ FILE* fp = fopen(file_loc, "rb");
if (!fp) {
- fprintf(stderr, "Cannot open file: %s\n", file_loc);
+ fprintf(stderr, "Cannot open file: \"%s\"\n", file_loc);
exit(-1);
}
fseek(fp, 0, SEEK_END);
- size = ftell(fp);
+ size_t size = ftell(fp);
+
fseek(fp, 0, SEEK_SET);
- buf = malloc((size + 1) * sizeof(char));
- fread(buf, sizeof(char), size, fp);
+ void* buf = malloc(size);
+
+ fread(buf, size, 1, fp);
fclose(fp);
+
if (size < 8192) {
- fprintf(stderr, "File is too small to process: %u Bytes\n", size);
+ fprintf(stderr, "File is too small to process: \"%s\" (%lu B)\n", file_loc, size);
exit(-1);
}
@@ -54,41 +62,110 @@ static void* ReadFile(const char* file_loc, long* out_size) {
return buf;
}
+void saveExec(const char* file_loc, const void* buf, size_t size) {
+ FILE* fp = fopen(file_loc, "wb");
+ if (!fp) {
+ fprintf(stderr, "Cannot write to \"%s\"\n", file_loc);
+ exit(-1);
+ }
+ printf("Saving to \"%s\"...\n", file_loc);
+ fwrite(buf, size, 1, fp);
+ fclose(fp);
+}
+
int main(int argc, char** argv) {
- const char* pcv_opt = "pcv";
- const char* ptm_opt = "ptm";
- enum {
+ const char* pcv_opt = "pcv";
+ const char* ptm_opt = "ptm";
+ const char* save_opt = "-s";
+ const char* mariko_ext = ".mariko";
+ const char* erista_ext = ".erista";
+ enum EXE_OPTION {
EXE_PCV,
EXE_PTM,
UNKNOWN
};
- int option = UNKNOWN;
- if (argc == 3) {
+ EXE_OPTION exe_opt = UNKNOWN;
+ if (argc > 2) {
if (!strcmp(argv[1], pcv_opt))
- option = EXE_PCV;
+ exe_opt = EXE_PCV;
if (!strcmp(argv[1], ptm_opt))
- option = EXE_PTM;
+ exe_opt = EXE_PTM;
}
- if (option == UNKNOWN) {
- fprintf(stderr, "Usage: %s %s/%s \n", argv[0], pcv_opt, ptm_opt);
+ if ((argc != 3 && argc != 4) || exe_opt == UNKNOWN) {
+ fprintf(stderr, "Usage:\n"\
+ " %s %s | %s [%s] \n\n"\
+ " %s : Save patched executable with extension \"%s\" / \"%s\"\n"
+ , argv[0], pcv_opt, ptm_opt, save_opt
+ , save_opt, mariko_ext, erista_ext);
return -1;
}
- long file_size;
- void* file_buffer = ReadFile(argv[2], &file_size);
- uintptr_t mapped_exe = reinterpret_cast(file_buffer);
- size_t exe_size = reinterpret_cast(file_size * sizeof(char));
- switch (option) {
- case EXE_PCV:
- printf("Patching %s...\n", pcv_opt);
- ams::ldr::oc::pcv::Patch(mapped_exe, exe_size);
- break;
- case EXE_PTM:
- printf("Patching %s...\n", ptm_opt);
- ams::ldr::oc::ptm::Patch(mapped_exe, exe_size);
- break;
+ bool save_patched = false;
+ char* exec_path = argv[2];
+ if (argc == 4 && !strcmp(argv[2], save_opt)) {
+ save_patched = true;
+ exec_path = argv[3];
}
+
+ size_t file_size;
+ void* file_buffer = loadExec(exec_path, &file_size);
+
+ size_t exec_path_len = strlen(reinterpret_cast(exec_path));
+ size_t exec_path_patched_len = exec_path_len + std::max(strlen(mariko_ext), strlen(erista_ext)) + 1;
+
+ if (exe_opt == EXE_PCV) {
+ ams::ldr::oc::pcv::SafetyCheck();
+
+ {
+ void* erista_buf = malloc(file_size);
+ std::memcpy(erista_buf, file_buffer, file_size);
+
+ printf("Patching %s for Erista...\n", pcv_opt);
+ ams::ldr::oc::pcv::Erista::Patch(reinterpret_cast(erista_buf), file_size);
+ if (save_patched) {
+ char* exec_path_erista = reinterpret_cast(malloc(exec_path_patched_len));
+ strlcpy(exec_path_erista, exec_path, exec_path_patched_len);
+ strlcat(exec_path_erista, erista_ext, exec_path_patched_len);
+ saveExec(exec_path_erista, erista_buf, file_size);
+ free(exec_path_erista);
+ }
+ free(erista_buf);
+ }
+
+ {
+ void* mariko_buf = malloc(file_size);
+ std::memcpy(mariko_buf, file_buffer, file_size);
+
+ printf("Patching %s for Mariko...\n", pcv_opt);
+ ams::ldr::oc::pcv::Mariko::Patch(reinterpret_cast(mariko_buf), file_size);
+ if (save_patched) {
+ char* exec_path_mariko = reinterpret_cast(malloc(exec_path_patched_len));
+ strlcpy(exec_path_mariko, exec_path, exec_path_patched_len);
+ strlcat(exec_path_mariko, mariko_ext, exec_path_patched_len);
+ saveExec(exec_path_mariko, mariko_buf, file_size);
+ free(exec_path_mariko);
+ }
+ free(mariko_buf);
+ }
+ }
+
+ if (exe_opt == EXE_PTM) {
+ void* mariko_buf = malloc(file_size);
+ std::memcpy(mariko_buf, file_buffer, file_size);
+
+ printf("Patching %s (Mariko Only)...", ptm_opt);
+ ams::ldr::oc::ptm::Patch(reinterpret_cast(mariko_buf), file_size);
+ if (save_patched) {
+ char* exec_path_mariko = reinterpret_cast(malloc(exec_path_patched_len));
+ strlcpy(exec_path_mariko, exec_path, exec_path_patched_len);
+ strlcat(exec_path_mariko, mariko_ext, exec_path_patched_len);
+ saveExec(exec_path_mariko, mariko_buf, file_size);
+ free(exec_path_mariko);
+ }
+ free(mariko_buf);
+ }
+
free(file_buffer);
printf("Passed!\n\n");
return 0;
diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/test.sh b/Source/Atmosphere/stratosphere/loader/source/oc/test.sh
index 908b4672..b9fc7c4a 100755
--- a/Source/Atmosphere/stratosphere/loader/source/oc/test.sh
+++ b/Source/Atmosphere/stratosphere/loader/source/oc/test.sh
@@ -2,15 +2,36 @@
fw_dir="/Volumes/RAM/NX-14.0.0/"
tmp_dir="/Volumes/RAM/"
+repack_out_dir="/Volumes/RAM/out/"
oc_test_dir="$HOME/Source/Switch-OC-Suite/Source/Atmosphere/stratosphere/loader/source/oc"
prodkeys="$HOME/.switch/prod.keys"
hactool_exe="$HOME/Source/hactool/hactool"
nx2elf_exe="$HOME/Source/nx2elf/nx2elf"
+elf2nso_exe="$HOME/Source/switch-tools/elf2nso"
+hacpack_exe="$HOME/Source/hacPack/hacpack"
+
+should_remove_tmp="Y"
+should_save_repack="Y"
+option_Mariko_Erista="M"
echo -e "\nExtracting nca..."
out_pcv="${tmp_dir}pcv/"
out_ptm="${tmp_dir}ptm/"
+mkdir -p "${out_pcv}"
+mkdir -p "${out_ptm}"
cd $fw_dir
+
+pcv_nca_name=""
+ptm_nca_name=""
+for file_00 in ./*.nca/00
+do
+ if [ -e "${file_00}" ]; then
+ echo "Processing \"*.nca/00\" files..."
+ find "${fw_dir}" -type f -name "00" -exec sh -c 'DIR=$(dirname "{}"); FW_DIR=$(dirname "${DIR}"); mv "{}" "${FW_DIR}/00"; rm -r "${DIR}"; mv "${FW_DIR}/00" "${DIR}"' \;
+ fi
+ break
+done
+
for nca_file in ./*.nca
do
file_size=`wc -c "$nca_file" | awk '{print $1}'`
@@ -21,12 +42,14 @@ do
titleid=`$hactool_exe -k $prodkeys --disablekeywarns -t nca $nca_file | grep 'Title ID'`
if [[ $titleid == *"010000000000001a"* ]]; then
- echo "$nca_file (pcv) -> $out_pcv"
+ pcv_nca_name="$(basename $nca_file)"
+ echo "$pcv_nca_name (pcv) -> $out_pcv"
$hactool_exe -k $prodkeys --disablekeywarns -t nca $nca_file --exefsdir "$out_pcv" 1> /dev/null
fi
if [[ $titleid == *"0100000000000010"* ]]; then
- echo "$nca_file (ptm) -> $out_ptm"
+ ptm_nca_name="$(basename $nca_file)"
+ echo "$ptm_nca_name (ptm) -> $out_ptm"
$hactool_exe -k $prodkeys --disablekeywarns -t nca $nca_file --exefsdir "$out_ptm" 1> /dev/null
fi
done
@@ -35,12 +58,61 @@ echo -e "\nConverting nca to elf..."
$nx2elf_exe "${out_pcv}main" 1> /dev/null
$nx2elf_exe "${out_ptm}main" 1> /dev/null
-echo -e "\nBuilding and testing..."
+echo -e "\nBuilding..."
cd $oc_test_dir
make test 1> /dev/null
-./test pcv "${out_pcv}main.elf"
-./test ptm "${out_ptm}main.elf"
+
+echo -e "\nPatching..."
+
+[ -z "$should_save_repack" ] && read -p "Save and repack to nca (y/N)? " should_save_repack
+SAVE_OPT=" "
+case $should_save_repack in
+ Y|y ) SAVE_OPT="-s ";;
+esac
+
+./test pcv $SAVE_OPT "${out_pcv}main.elf"
+./test ptm $SAVE_OPT "${out_ptm}main.elf"
make clean 1> /dev/null
+if [ ! -z $SAVE_OPT ]; then
+ case $should_save_repack in
+ Y|y )
+ patched_ext=".mariko"
+ [ -z "$option_Mariko_Erista" ] && read -p "[M]ariko (Default) | [E]rista ? " option_Mariko_Erista
+ case $option_Mariko_Erista in
+ E|e ) patched_ext=".erista";;
+ esac
+ mkdir -p "${repack_out_dir}"
+ cd "${tmp_dir}"
+ echo -e "\nRepacking pcv to ${repack_out_dir}${pcv_nca_name}..."
+ TMP="${out_pcv}temp/"
+ mkdir -p "${TMP}"
+ $elf2nso_exe "${out_pcv}main.elf${patched_ext}" "${TMP}main" 1> /dev/null
+ cp "${out_pcv}main.npdm" "${TMP}main.npdm"
+ $hacpack_exe -k $prodkeys -o "${TMP}nca" --type nca --ncatype program --titleid 010000000000001A --exefsdir "${TMP}" 1> /dev/null
+ find "${TMP}nca" -name "*.nca" -exec mv {} "${repack_out_dir}${pcv_nca_name}" \;
+
+ if [[ $patched_ext == ".mariko" ]]; then
+ echo -e "\nRepacking ptm (Mariko Only) to ${repack_out_dir}${ptm_nca_name}..."
+ TMP="${out_ptm}temp/"
+ mkdir -p "${TMP}"
+ $elf2nso_exe "${out_ptm}main.elf${patched_ext}" "${TMP}main" 1> /dev/null
+ cp "${out_ptm}main.npdm" "${TMP}main.npdm"
+ $hacpack_exe -k $prodkeys -o "${TMP}nca" --type nca --ncatype program --titleid 0100000000000010 --exefsdir "${TMP}" 1> /dev/null
+ find "${TMP}nca" -name "*.nca" -exec mv {} "${repack_out_dir}${ptm_nca_name}" \;
+ fi
+ ;;
+ esac
+fi
+
+[ -z "$should_remove_tmp" ] && read -p "Remove temp files (Y/n)? " should_remove_tmp
+case $should_remove_tmp in
+ N|n )
+ exit;;
+esac
+
rm -fr $out_pcv
rm -fr $out_ptm
+rm -fr "${tmp_dir}hacpack_backup"
+
+echo -e "\nDone!"