Last active
August 1, 2018 15:23
-
-
Save rdmrocha/6c74d8c238751f3e5a0aac3119dfc586 to your computer and use it in GitHub Desktop.
NSP Version patcher
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <boost/iostreams/device/mapped_file.hpp> | |
#include <boost/algorithm/string.hpp> | |
#include <algorithm> | |
#include <iostream> | |
#include <cstring> | |
#include <inttypes.h> | |
/***** TAKEN AND ADAPTED FROM HACTOOL SOURCE CODE - BEGIN *****/ | |
#define MAGIC_PFS0 0x30534650 | |
#ifdef _MSC_VER | |
inline int fseeko64(FILE *__stream, long long __off, int __whence) | |
{ | |
return _fseeki64(__stream, __off, __whence); | |
} | |
#elif __MINGW32__ | |
/* MINGW32 does not have 64-bit offsets even with large file support. */ | |
extern int fseeko64 (FILE *__stream, _off64_t __off, int __whence); | |
#else | |
/* off_t is 64-bit with large file support */ | |
#define fseeko64 fseek | |
#endif | |
typedef struct { | |
uint32_t magic; | |
uint32_t num_files; | |
uint32_t string_table_size; | |
uint32_t reserved; | |
} pfs0_header_t; | |
typedef struct { | |
uint64_t offset; | |
uint64_t size; | |
uint32_t string_table_offset; | |
uint32_t reserved; | |
} pfs0_file_entry_t; | |
typedef struct { | |
FILE *file; | |
pfs0_header_t *header; | |
} pfs0_ctx_t; | |
static inline pfs0_file_entry_t *pfs0_get_file_entry(pfs0_header_t *hdr, uint32_t i) { | |
if (i >= hdr->num_files) return NULL; | |
return (pfs0_file_entry_t *)((char *)(hdr) + sizeof(*hdr) + i * sizeof(pfs0_file_entry_t)); | |
} | |
static inline char *pfs0_get_string_table(pfs0_header_t *hdr) { | |
return (char *)(hdr) + sizeof(*hdr) + hdr->num_files * sizeof(pfs0_file_entry_t); | |
} | |
static inline char *pfs0_get_file_name(pfs0_header_t *hdr, uint32_t i) { | |
return pfs0_get_string_table(hdr) + pfs0_get_file_entry(hdr, i)->string_table_offset; | |
} | |
static inline uint64_t pfs0_get_header_size(pfs0_header_t *hdr) { | |
return sizeof(*hdr) + hdr->num_files * sizeof(pfs0_file_entry_t) + hdr->string_table_size; | |
} | |
uint64_t get_xml_offset(pfs0_ctx_t *ctx) { | |
if (ctx->header->num_files > 0 && ctx->header->num_files < 15) { /* Arbitrary. */ | |
for (unsigned int i = 0; i < ctx->header->num_files; i++) { | |
pfs0_file_entry_t *cur_file = pfs0_get_file_entry(ctx->header, i); | |
char* filename = pfs0_get_file_name(ctx->header, i); | |
if(strlen(filename) > 4 && !strcmp(filename + strlen(filename) - 4, ".xml")) { | |
return cur_file->offset; | |
} | |
} | |
} | |
return 0; | |
} | |
uint64_t pfs0_process(pfs0_ctx_t *ctx) { | |
/* Read *just* safe amount. */ | |
pfs0_header_t raw_header; | |
fseeko64(ctx->file, 0, SEEK_SET); | |
if (fread(&raw_header, 1, sizeof(raw_header), ctx->file) != sizeof(raw_header)) { | |
fprintf(stderr, "Failed to read PFS0 header!\n"); | |
exit(EXIT_FAILURE); | |
} | |
if (raw_header.magic != MAGIC_PFS0) { | |
printf("Error: PFS0 is corrupt!\n"); | |
exit(EXIT_FAILURE); | |
} | |
uint64_t header_size = pfs0_get_header_size(&raw_header); | |
ctx->header = (pfs0_header_t*)malloc(header_size); | |
if (ctx->header == NULL) { | |
fprintf(stderr, "Failed to allocate PFS0 header!\n"); | |
exit(EXIT_FAILURE); | |
} | |
fseeko64(ctx->file, 0, SEEK_SET); | |
if (fread(ctx->header, 1, header_size, ctx->file) != header_size) { | |
fprintf(stderr, "Failed to read PFS0 header!\n"); | |
exit(EXIT_FAILURE); | |
} | |
return get_xml_offset(ctx); | |
} | |
/***** TAKEN AND ADAPTED FROM HACTOOL SOURCE CODE - END *****/ | |
static const char* keyGenerationBegin = "<KeyGenerationMin>"; | |
const char* requiredBegin = "<RequiredSystemVersion>"; | |
const char* requiredEnd = "</RequiredSystemVersion>"; | |
int main(int argc, const char * argv[]) { | |
if (argc == 1) { | |
std::cout << "Usage: " << argv[0] << " your_nsp_file"; | |
return -1; | |
} | |
pfs0_ctx_t pfs0_ctx; | |
memset(&pfs0_ctx, 0, sizeof(pfs0_ctx)); | |
if ((pfs0_ctx.file = fopen(argv[1], "rb")) == NULL) { | |
fprintf(stderr, "unable to open %s: %s\n", argv[1], strerror(errno)); | |
return EXIT_FAILURE; | |
} | |
uint64_t offset = pfs0_process(&pfs0_ctx); | |
if (pfs0_ctx.header) { | |
free(pfs0_ctx.header); | |
} | |
fclose(pfs0_ctx.file); | |
boost::iostreams::mapped_file mmap; | |
try { | |
mmap.open(argv[1], boost::iostreams::mapped_file::readwrite); | |
} catch(std::exception &ex) { | |
std::cout << "Error opening '" << argv[1] << "'" << std::endl; | |
return -1; | |
} | |
char* f = mmap.data() + offset; | |
const char* l = f + mmap.size(); | |
long buffer_size = 196L; | |
char buffer[buffer_size]; | |
while (f && f!=l) { | |
if ((f = static_cast<char*>(memchr(f, keyGenerationBegin[0], l-f)))) { | |
// found the first char? lets check if we can find the rest | |
long len = std::min(buffer_size, l-f); | |
strncpy(buffer, f, len); | |
buffer[buffer_size-1] = '\0'; | |
const char * matchPtr = strstr(buffer, keyGenerationBegin); | |
if (matchPtr) { | |
// got it!! we have the key generation | |
f+= matchPtr-buffer; //alread adjusting next | |
// copy to our buffer | |
len = std::min(buffer_size, l-f); | |
memset(buffer, 0, buffer_size); | |
strncpy(buffer, f, len); | |
buffer[len-1] = '\0'; | |
const char gen = buffer[strlen(keyGenerationBegin)]; | |
std::string genText = ""; | |
switch(gen) { | |
case '0': genText = "all"; break; | |
case '1': genText = "1.0.0"; break; | |
case '2': genText = "3.0.0"; break; | |
case '3': genText = "3.0.1"; break; | |
case '4': genText = "4.0.0"; break; | |
case '5': genText = "5.0.0"; break; | |
default: std::cout << "Unknown generation. Aborting" << std::endl; return -1; | |
} | |
const char * reqBeginPtr = strstr(buffer, requiredBegin); | |
if (!reqBeginPtr) { | |
std::cout << "Unable to find the necessary info." << std::endl; | |
return -1; | |
} | |
const char * reqEndPtr = strstr(buffer, requiredEnd); | |
if (!reqEndPtr) { | |
std::cout << "Unable to find the necessary info." << std::endl; | |
return -1; | |
} | |
// ask confirmation and proceed | |
std::cout << "This title will be playable on " << genText << " firmares"; | |
if (gen != '0') { | |
std::cout << " (and above)."; | |
} | |
std::cout << "\n\nAre you sure you want to proceed? Type YES to continue: "; | |
std::string answer = ""; | |
std::cin >> answer; | |
if (!boost::iequals(answer, "yes")) { | |
return -1; | |
} | |
size_t beginSize = strlen(requiredBegin); | |
char* patchStart = f + (reqBeginPtr-buffer+beginSize); | |
long patchLen = reqEndPtr-reqBeginPtr-beginSize; | |
memset(buffer, 0, buffer_size); | |
strncpy(buffer, patchStart, patchLen); | |
memset(patchStart, '0', patchLen); | |
std::cout << "Patching complete!" << std::endl; | |
return 0; | |
} | |
++f; | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment