mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-22 20:31:14 +00:00
fusee_cpp: implement ips patching of kips
This commit is contained in:
parent
07779b787a
commit
c5d021c172
7 changed files with 253 additions and 42 deletions
|
@ -17,18 +17,6 @@ SECTIONS
|
||||||
BYTE(00);
|
BYTE(00);
|
||||||
} >main AT>glob
|
} >main AT>glob
|
||||||
|
|
||||||
.ovl_data :
|
|
||||||
{
|
|
||||||
FILL(0x00000000)
|
|
||||||
fusee_mtc_erista.o(SORT(.data*));
|
|
||||||
fusee_mtc_erista.o(SORT(.bss*));
|
|
||||||
fusee_mtc_mariko.o(SORT(.data*));
|
|
||||||
fusee_mtc_mariko.o(SORT(.bss*));
|
|
||||||
. = ALIGN(16);
|
|
||||||
. = . + 15;
|
|
||||||
BYTE(00);
|
|
||||||
} >main AT>glob
|
|
||||||
|
|
||||||
.text :
|
.text :
|
||||||
{
|
{
|
||||||
FILL(0x00000000)
|
FILL(0x00000000)
|
||||||
|
|
|
@ -26,6 +26,8 @@ SECTIONS {
|
||||||
KEEP(*(.text._ZN3ams6nxboot22DoMemoryTrainingEristaEiPv))
|
KEEP(*(.text._ZN3ams6nxboot22DoMemoryTrainingEristaEiPv))
|
||||||
fusee_mtc_erista.o(.text*);
|
fusee_mtc_erista.o(.text*);
|
||||||
fusee_mtc_erista.o(SORT(.rodata*));
|
fusee_mtc_erista.o(SORT(.rodata*));
|
||||||
|
fusee_mtc_erista.o(SORT(.data*));
|
||||||
|
fusee_mtc_erista.o(SORT(.bss*));
|
||||||
FILL(0x00000000)
|
FILL(0x00000000)
|
||||||
. = ORIGIN(ovl) + LENGTH(ovl) - 1;
|
. = ORIGIN(ovl) + LENGTH(ovl) - 1;
|
||||||
BYTE(0x00);
|
BYTE(0x00);
|
||||||
|
@ -34,6 +36,8 @@ SECTIONS {
|
||||||
KEEP(*(.text._ZN3ams6nxboot22DoMemoryTrainingMarikoEPbiPv))
|
KEEP(*(.text._ZN3ams6nxboot22DoMemoryTrainingMarikoEPbiPv))
|
||||||
fusee_mtc_mariko.o(.text*);
|
fusee_mtc_mariko.o(.text*);
|
||||||
fusee_mtc_mariko.o(SORT(.rodata*));
|
fusee_mtc_mariko.o(SORT(.rodata*));
|
||||||
|
fusee_mtc_mariko.o(SORT(.data*));
|
||||||
|
fusee_mtc_mariko.o(SORT(.bss*));
|
||||||
FILL(0x00000000)
|
FILL(0x00000000)
|
||||||
. = ORIGIN(ovl) + LENGTH(ovl) - 1;
|
. = ORIGIN(ovl) + LENGTH(ovl) - 1;
|
||||||
BYTE(0x00);
|
BYTE(0x00);
|
||||||
|
|
|
@ -110,6 +110,9 @@ namespace ams::nxboot {
|
||||||
/* Read the rest of the archive file. */
|
/* Read the rest of the archive file. */
|
||||||
ReadFullSecondaryArchive();
|
ReadFullSecondaryArchive();
|
||||||
|
|
||||||
|
/* Save the memory training overlay. */
|
||||||
|
SaveMemoryTrainingOverlay();
|
||||||
|
|
||||||
/* Initialize display (splash screen will be visible from this point onwards). */
|
/* Initialize display (splash screen will be visible from this point onwards). */
|
||||||
InitializeDisplay();
|
InitializeDisplay();
|
||||||
ShowDisplay();
|
ShowDisplay();
|
||||||
|
@ -120,6 +123,12 @@ namespace ams::nxboot {
|
||||||
/* Perform rest of the boot process. */
|
/* Perform rest of the boot process. */
|
||||||
SetupAndStartHorizon();
|
SetupAndStartHorizon();
|
||||||
|
|
||||||
|
/* Restore the memory training overlay. */
|
||||||
|
RestoreMemoryTrainingOverlay();
|
||||||
|
|
||||||
|
/* Restore memory clock rate. */
|
||||||
|
RestoreMemoryClockRate();
|
||||||
|
|
||||||
/* Finalize display. */
|
/* Finalize display. */
|
||||||
FinalizeDisplay();
|
FinalizeDisplay();
|
||||||
|
|
||||||
|
|
|
@ -49,4 +49,22 @@ namespace ams::nxboot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SaveMemoryTrainingOverlay() {
|
||||||
|
if (fuse::GetSocType() == fuse::SocType_Erista) {
|
||||||
|
/* NOTE: Erista does not do memory clock restoration. */
|
||||||
|
/* std::memcpy(const_cast<u8 *>(GetSecondaryArchive().ovl_mtc_erista), GetOverlayDestination(), sizeof(SecondaryArchive{}.ovl_mtc_erista)); */
|
||||||
|
} else {
|
||||||
|
std::memcpy(const_cast<u8 *>(GetSecondaryArchive().ovl_mtc_mariko), GetOverlayDestination(), sizeof(SecondaryArchive{}.ovl_mtc_mariko));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RestoreMemoryTrainingOverlay() {
|
||||||
|
if (fuse::GetSocType() == fuse::SocType_Erista) {
|
||||||
|
/* NOTE: Erista does not do memory clock restoration. */
|
||||||
|
/* std::memcpy(GetOverlayDestination(), GetSecondaryArchive().ovl_mtc_erista, sizeof(SecondaryArchive{}.ovl_mtc_erista)); */
|
||||||
|
} else {
|
||||||
|
std::memcpy(GetOverlayDestination(), GetSecondaryArchive().ovl_mtc_mariko, sizeof(SecondaryArchive{}.ovl_mtc_mariko));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,4 +26,7 @@ namespace ams::nxboot {
|
||||||
|
|
||||||
void LoadOverlay(fs::FileHandle archive_file, OverlayId ovl);
|
void LoadOverlay(fs::FileHandle archive_file, OverlayId ovl);
|
||||||
|
|
||||||
|
void SaveMemoryTrainingOverlay();
|
||||||
|
void RestoreMemoryTrainingOverlay();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,11 +85,11 @@ namespace ams::nxboot {
|
||||||
x <<= 4;
|
x <<= 4;
|
||||||
|
|
||||||
if ('0' <= c && c <= '9') {
|
if ('0' <= c && c <= '9') {
|
||||||
x |= c - '0';
|
x |= (c - '0');
|
||||||
} else if ('a' <= c && c <= 'f') {
|
} else if ('a' <= c && c <= 'f') {
|
||||||
x |= c - 'a';
|
x |= (c - 'a') + 10;
|
||||||
} else if ('A' <= c && c <= 'F') {
|
} else if ('A' <= c && c <= 'F') {
|
||||||
x |= c - 'A';
|
x |= (c - 'A') + 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ namespace ams::nxboot {
|
||||||
|
|
||||||
struct PatchMeta {
|
struct PatchMeta {
|
||||||
PatchMeta *next;
|
PatchMeta *next;
|
||||||
|
bool is_memset;
|
||||||
u32 start_segment;
|
u32 start_segment;
|
||||||
u32 rel_offset;
|
u32 rel_offset;
|
||||||
const void *data;
|
const void *data;
|
||||||
|
@ -321,6 +322,15 @@ namespace ams::nxboot {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InitialProcessMeta *FindInitialProcess(const se::Sha256Hash &hash) {
|
||||||
|
for (InitialProcessMeta *cur = std::addressof(g_initial_process_meta); cur != nullptr; cur = cur->next) {
|
||||||
|
if (std::memcmp(std::addressof(cur->kip_hash), std::addressof(hash), sizeof(hash)) == 0) {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
u32 GetPatchSegments(const InitialProcessHeader *kip, u32 offset, size_t size) {
|
u32 GetPatchSegments(const InitialProcessHeader *kip, u32 offset, size_t size) {
|
||||||
/* Create segment mask. */
|
/* Create segment mask. */
|
||||||
u32 segments = 0;
|
u32 segments = 0;
|
||||||
|
@ -360,7 +370,7 @@ namespace ams::nxboot {
|
||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddPatch(InitialProcessMeta *meta, u32 offset, const void *data, size_t data_size) {
|
void AddPatch(InitialProcessMeta *meta, u32 offset, const void *data, size_t data_size, bool is_memset = false) {
|
||||||
/* Determine the segment. */
|
/* Determine the segment. */
|
||||||
const u32 segments = GetPatchSegments(meta->kip, offset, data_size);
|
const u32 segments = GetPatchSegments(meta->kip, offset, data_size);
|
||||||
|
|
||||||
|
@ -385,6 +395,7 @@ namespace ams::nxboot {
|
||||||
auto *new_patch = static_cast<PatchMeta *>(AllocateAligned(sizeof(PatchMeta), alignof(PatchMeta)));
|
auto *new_patch = static_cast<PatchMeta *>(AllocateAligned(sizeof(PatchMeta), alignof(PatchMeta)));
|
||||||
|
|
||||||
new_patch->next = nullptr;
|
new_patch->next = nullptr;
|
||||||
|
new_patch->is_memset = is_memset;
|
||||||
new_patch->start_segment = start_segment;
|
new_patch->start_segment = start_segment;
|
||||||
new_patch->rel_offset = offset;
|
new_patch->rel_offset = offset;
|
||||||
new_patch->data = data;
|
new_patch->data = data;
|
||||||
|
@ -400,6 +411,78 @@ namespace ams::nxboot {
|
||||||
meta->patches_tail = new_patch;
|
meta->patches_tail = new_patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddIps24PatchToKip(InitialProcessMeta *meta, const u8 *ips, s32 size) {
|
||||||
|
while (size > 0) {
|
||||||
|
/* Read offset, stopping at EOF */
|
||||||
|
const u32 offset = (static_cast<u32>(ips[0]) << 16) | (static_cast<u32>(ips[1]) << 8) | (static_cast<u32>(ips[2]) << 0);
|
||||||
|
if (offset == 0x454F46) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read size. */
|
||||||
|
const u16 cur_size = (static_cast<u32>(ips[3]) << 8) | (static_cast<u32>(ips[4]) << 0);
|
||||||
|
|
||||||
|
if (cur_size > 0) {
|
||||||
|
/* Add patch. */
|
||||||
|
AddPatch(meta, offset, ips + 5, cur_size, false);
|
||||||
|
|
||||||
|
/* Advance. */
|
||||||
|
ips += (5 + cur_size);
|
||||||
|
size -= (5 + cur_size);
|
||||||
|
} else {
|
||||||
|
/* Read RLE size */
|
||||||
|
const u16 rle_size = (static_cast<u32>(ips[5]) << 8) | (static_cast<u32>(ips[6]) << 0);
|
||||||
|
|
||||||
|
/* Add patch. */
|
||||||
|
AddPatch(meta, offset, ips + 7, rle_size, true);
|
||||||
|
|
||||||
|
/* Advance. */
|
||||||
|
ips += 8;
|
||||||
|
size -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddIps32PatchToKip(InitialProcessMeta *meta, const u8 *ips, s32 size) {
|
||||||
|
while (size > 0) {
|
||||||
|
/* Read offset, stopping at EOF */
|
||||||
|
const u32 offset = (static_cast<u32>(ips[0]) << 24) | (static_cast<u32>(ips[1]) << 16) | (static_cast<u32>(ips[2]) << 8) | (static_cast<u32>(ips[3]) << 0);
|
||||||
|
if (offset == 0x45454F46) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read size. */
|
||||||
|
const u16 cur_size = (static_cast<u32>(ips[4]) << 8) | (static_cast<u32>(ips[5]) << 0);
|
||||||
|
|
||||||
|
if (cur_size > 0) {
|
||||||
|
/* Add patch. */
|
||||||
|
AddPatch(meta, offset, ips + 6, cur_size, false);
|
||||||
|
|
||||||
|
/* Advance. */
|
||||||
|
ips += (6 + cur_size);
|
||||||
|
size -= (6 + cur_size);
|
||||||
|
} else {
|
||||||
|
/* Read RLE size */
|
||||||
|
const u16 rle_size = (static_cast<u32>(ips[6]) << 8) | (static_cast<u32>(ips[7]) << 0);
|
||||||
|
|
||||||
|
/* Add patch. */
|
||||||
|
AddPatch(meta, offset, ips + 8, rle_size, true);
|
||||||
|
|
||||||
|
/* Advance. */
|
||||||
|
ips += 9;
|
||||||
|
size -= 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddIpsPatchToKip(InitialProcessMeta *meta, const u8 *ips, s32 size) {
|
||||||
|
if (std::memcmp(ips, "PATCH", 5) == 0) {
|
||||||
|
AddIps24PatchToKip(meta, ips + 5, size - 5);
|
||||||
|
} else if (std::memcmp(ips, "IPS32", 5) == 0) {
|
||||||
|
AddIps32PatchToKip(meta, ips + 5, size - 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constexpr const u8 NogcPatch0[] = {
|
constexpr const u8 NogcPatch0[] = {
|
||||||
0x80
|
0x80
|
||||||
};
|
};
|
||||||
|
@ -508,6 +591,32 @@ namespace ams::nxboot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void *ReadFile(s64 *out_size, const char *path, size_t align = 0x10) {
|
||||||
|
fs::FileHandle file;
|
||||||
|
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read))) {
|
||||||
|
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||||
|
|
||||||
|
Result result;
|
||||||
|
|
||||||
|
/* Get the kip size. */
|
||||||
|
if (R_FAILED((result = fs::GetFileSize(out_size, file)))) {
|
||||||
|
ShowFatalError("Failed to get size (0x%08" PRIx32 ") of %s!\n", result.GetValue(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate file. */
|
||||||
|
void *data = AllocateAligned(*out_size, std::max<size_t>(align, 0x10));
|
||||||
|
|
||||||
|
/* Read the file. */
|
||||||
|
if (R_FAILED((result = fs::ReadFile(file, 0, data, *out_size)))) {
|
||||||
|
ShowFatalError("Failed to read (0x%08" PRIx32 ") %s!\n", result.GetValue(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 ConfigureStratosphere(const u8 *nn_package2, ams::TargetFirmware target_firmware, bool emummc_enabled, bool nogc_enabled) {
|
u32 ConfigureStratosphere(const u8 *nn_package2, ams::TargetFirmware target_firmware, bool emummc_enabled, bool nogc_enabled) {
|
||||||
|
@ -524,6 +633,11 @@ namespace ams::nxboot {
|
||||||
s64 count;
|
s64 count;
|
||||||
fs::DirectoryEntry entries[1];
|
fs::DirectoryEntry entries[1];
|
||||||
while (R_SUCCEEDED(fs::ReadDirectory(std::addressof(count), entries, kip_dir, util::size(entries))) && count > 0) {
|
while (R_SUCCEEDED(fs::ReadDirectory(std::addressof(count), entries, kip_dir, util::size(entries))) && count > 0) {
|
||||||
|
/* Check that file is a file. */
|
||||||
|
if (fs::GetEntryType(entries[0]) != fs::DirectoryEntryType_File) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* Get filename length. */
|
/* Get filename length. */
|
||||||
const int name_len = std::strlen(entries[0].file_name);
|
const int name_len = std::strlen(entries[0].file_name);
|
||||||
|
|
||||||
|
@ -537,32 +651,9 @@ namespace ams::nxboot {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check that file is a file. */
|
|
||||||
if (fs::GetEntryType(entries[0]) != fs::DirectoryEntryType_File) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Open the kip. */
|
|
||||||
fs::FileHandle kip_file;
|
|
||||||
if (R_SUCCEEDED(fs::OpenFile(std::addressof(kip_file), kip_path, fs::OpenMode_Read))) {
|
|
||||||
ON_SCOPE_EXIT { fs::CloseFile(kip_file); };
|
|
||||||
|
|
||||||
Result result;
|
|
||||||
|
|
||||||
/* Get the kip size. */
|
|
||||||
s64 file_size;
|
|
||||||
if (R_FAILED((result = fs::GetFileSize(std::addressof(file_size), kip_file)))) {
|
|
||||||
ShowFatalError("Failed to get size (0x%08" PRIx32 ") of %s!\n", result.GetValue(), kip_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate kip. */
|
|
||||||
InitialProcessHeader *kip = static_cast<InitialProcessHeader *>(AllocateAligned(file_size, alignof(InitialProcessHeader)));
|
|
||||||
|
|
||||||
/* Read the kip. */
|
/* Read the kip. */
|
||||||
if (R_FAILED((result = fs::ReadFile(kip_file, 0, kip, file_size)))) {
|
s64 file_size;
|
||||||
ShowFatalError("Failed to read (0x%08" PRIx32 ") %s!\n", result.GetValue(), kip_path);
|
if (InitialProcessHeader *kip = static_cast<InitialProcessHeader *>(ReadFile(std::addressof(file_size), kip_path, alignof(InitialProcessHeader))); kip != nullptr) {
|
||||||
}
|
|
||||||
|
|
||||||
/* If the kip is valid, add it. */
|
/* If the kip is valid, add it. */
|
||||||
if (kip->magic == InitialProcessHeader::Magic && file_size == GetInitialProcessSize(kip)) {
|
if (kip->magic == InitialProcessHeader::Magic && file_size == GetInitialProcessSize(kip)) {
|
||||||
AddInitialProcess(kip);
|
AddInitialProcess(kip);
|
||||||
|
@ -630,6 +721,104 @@ namespace ams::nxboot {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add generic patches. */
|
/* Add generic patches. */
|
||||||
|
{
|
||||||
|
/* Create patch path. */
|
||||||
|
char patch_path[0x220];
|
||||||
|
std::memcpy(patch_path, "sdmc:/atmosphere/kip_patches", 0x1D);
|
||||||
|
|
||||||
|
fs::DirectoryHandle patch_root_dir;
|
||||||
|
if (R_SUCCEEDED(fs::OpenDirectory(std::addressof(patch_root_dir), patch_path))) {
|
||||||
|
ON_SCOPE_EXIT { fs::CloseDirectory(patch_root_dir); };
|
||||||
|
|
||||||
|
s64 count;
|
||||||
|
fs::DirectoryEntry entries[1];
|
||||||
|
while (R_SUCCEEDED(fs::ReadDirectory(std::addressof(count), entries, patch_root_dir, util::size(entries))) && count > 0) {
|
||||||
|
/* Check that dir is a dir. */
|
||||||
|
if (fs::GetEntryType(entries[0]) != fs::DirectoryEntryType_Directory) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For compatibility, ignore the old "default_nogc" patches. */
|
||||||
|
if (std::strcmp(entries[0].file_name, "default_nogc") == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get filename length. */
|
||||||
|
const int dir_len = std::strlen(entries[0].file_name);
|
||||||
|
|
||||||
|
/* Adjust patch path. */
|
||||||
|
patch_path[0x1C] = '/';
|
||||||
|
std::memcpy(patch_path + 0x1D, entries[0].file_name, dir_len + 1);
|
||||||
|
|
||||||
|
/* Try to open the patch subdirectory. */
|
||||||
|
fs::DirectoryHandle patch_dir;
|
||||||
|
if (R_SUCCEEDED(fs::OpenDirectory(std::addressof(patch_dir), patch_path))) {
|
||||||
|
ON_SCOPE_EXIT { fs::CloseDirectory(patch_dir); };
|
||||||
|
|
||||||
|
/* Read patches. */
|
||||||
|
while (R_SUCCEEDED(fs::ReadDirectory(std::addressof(count), entries, patch_dir, util::size(entries))) && count > 0) {
|
||||||
|
/* Check that file is a file. */
|
||||||
|
if (fs::GetEntryType(entries[0]) != fs::DirectoryEntryType_File) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get filename length. */
|
||||||
|
const int name_len = std::strlen(entries[0].file_name);
|
||||||
|
|
||||||
|
/* Adjust patch path. */
|
||||||
|
patch_path[0x1D + dir_len] = '/';
|
||||||
|
std::memcpy(patch_path + 0x1D + dir_len + 1, entries[0].file_name, name_len + 1);
|
||||||
|
|
||||||
|
/* Check that file is "{hex}.ips" file. */
|
||||||
|
const int path_len = 0x1D + dir_len + 1 + name_len;
|
||||||
|
if (name_len != 0x44 || std::memcmp(patch_path + path_len - 4, ".ips", 5) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that the filename is hex. */
|
||||||
|
bool valid_name = true;
|
||||||
|
se::Sha256Hash patch_name = {};
|
||||||
|
u32 shift = 4;
|
||||||
|
for (int i = 0; i < name_len - 4; ++i) {
|
||||||
|
const char c = entries[0].file_name[i];
|
||||||
|
|
||||||
|
u8 val;
|
||||||
|
if ('0' <= c && c <= '9') {
|
||||||
|
val = (c - '0');
|
||||||
|
} else if ('a' <= c && c <= 'f') {
|
||||||
|
val = (c - 'a') + 10;
|
||||||
|
} else if ('A' <= c && c <= 'F') {
|
||||||
|
val = (c - 'A') + 10;
|
||||||
|
} else {
|
||||||
|
valid_name = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch_name.bytes[i >> 1] |= val << shift;
|
||||||
|
shift ^= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ignore invalid patches. */
|
||||||
|
if (!valid_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find kip for the patch. */
|
||||||
|
auto *kip_meta = FindInitialProcess(patch_name);
|
||||||
|
if (kip_meta == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read the ips patch. */
|
||||||
|
s64 file_size;
|
||||||
|
if (u8 *ips = static_cast<u8 *>(ReadFile(std::addressof(file_size), patch_path)); ips != nullptr) {
|
||||||
|
AddIpsPatchToKip(kip_meta, ips, static_cast<s32>(file_size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return the fs version we're using. */
|
/* Return the fs version we're using. */
|
||||||
|
|
Loading…
Reference in a new issue