Skip to content

Instantly share code, notes, and snippets.

@castdrian
Forked from rdmrocha/nsp_version_patcher.cpp
Created August 1, 2018 15:23
Show Gist options
  • Save castdrian/e3462c4ce705b8296e438ad94d4ce970 to your computer and use it in GitHub Desktop.
Save castdrian/e3462c4ce705b8296e438ad94d4ce970 to your computer and use it in GitHub Desktop.
NSP Version patcher
#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