// C port of Alcaro's libips.cpp, which was released under GPLv3 // https://github.com/Alcaro/Flips/blob/master/libips.cpp // Ported by Hyarion for use with VirtualFatFS #include "ips.h" #include "common.h" #include "fsperm.h" #include "language.h" #include "ui.h" #include "vff.h" typedef enum { IPS_OK, IPS_NOTTHIS, IPS_THISOUT, IPS_SCRAMBLED, IPS_INVALID, IPS_16MB, IPS_INVALID_FILE_PATH, IPS_CANCELED, IPS_MEMORY } IPSERROR; static FIL patchFile, inFile, outFile; static size_t patchSize; static u8 *patch; static u32 patchOffset; char errName[256]; int displayError(int errcode) { switch(errcode) { case IPS_NOTTHIS: ShowPrompt(false, "%s\n%s", errName, STR_PATCH_MOST_LIKELY_NOT_FOR_THIS_FILE); break; case IPS_THISOUT: ShowPrompt(false, "%s\n%s", errName, STR_YOU_MOST_LIKELY_APPLIED_PATCH_ON_OUTPUT); break; case IPS_SCRAMBLED: ShowPrompt(false, "%s\n%s", errName, STR_PATCH_TECHNICALLY_VALID_BUT_SEEMS_SCRAMBLED); break; case IPS_INVALID: ShowPrompt(false, "%s\n%s", errName, STR_PATCH_IS_INVALID); break; case IPS_16MB: ShowPrompt(false, "%s\n%s", errName, STR_FILES_BIGGER_THAN_16MB_IPS_DOESNT_SUPPORT_THAT); break; case IPS_INVALID_FILE_PATH: ShowPrompt(false, "%s\n%s", errName, STR_REQUESTED_FILE_PATH_WAS_INVALID); break; case IPS_CANCELED: ShowPrompt(false, "%s\n%s", errName, STR_PATCHING_CANCELED); break; case IPS_MEMORY: ShowPrompt(false, "%s\n%s", errName, STR_NOT_ENOUGH_MEMORY); break; } fvx_close(&patchFile); fvx_close(&inFile); fvx_close(&outFile); return errcode; } typedef enum { COPY_IN, COPY_PATCH, COPY_RLE } COPYMODE; bool IPScopy(u8 mode, u32 size, u8 rle) { bool ret = true; if (mode == COPY_PATCH) { UINT bytes_written = size; if ((fvx_write(&outFile, &patch[patchOffset], size, &bytes_written) != FR_OK) || (size != bytes_written)) ret = false; patchOffset += size; } else { u32 bufsiz = min(STD_BUFFER_SIZE, size); u8* buffer = malloc(bufsiz); if (!buffer) return false; if (mode == COPY_RLE) memset(buffer, rle, bufsiz); for (u64 pos = 0; (pos < size) && ret; pos += bufsiz) { UINT read_bytes = min(bufsiz, size - pos); UINT bytes_written = read_bytes; if (((mode == COPY_IN) && (fvx_read(&inFile, buffer, read_bytes, &bytes_written) != FR_OK)) || ((mode == COPY_PATCH) && (fvx_read(&patchFile, buffer, read_bytes, &bytes_written) != FR_OK)) || (read_bytes != bytes_written)) ret = false; if ((ret && (fvx_write(&outFile, buffer, read_bytes, &bytes_written) != FR_OK)) || (read_bytes != bytes_written)) ret = false; } free(buffer); } return ret; } u8 read8() { if (patchOffset >= patchSize) return 0; return patch[patchOffset++]; } UINT read16() { if (patchOffset+1 >= patchSize) return 0; UINT buf = patch[patchOffset++] << 8; buf |= patch[patchOffset++]; return buf; } UINT read24() { if (patchOffset+2 >= patchSize) return 0; UINT buf = patch[patchOffset++] << 16; buf |= patch[patchOffset++] << 8; buf |= patch[patchOffset++]; return buf; } int ApplyIPSPatch(const char* patchName, const char* inName, const char* outName) { int error = IPS_INVALID; UINT outlen_min, outlen_max, outlen_min_mem; snprintf(errName, 256, "%s", patchName); if (fvx_open(&patchFile, patchName, FA_READ) != FR_OK) return displayError(IPS_INVALID_FILE_PATH); patchSize = fvx_size(&patchFile); ShowProgress(0, patchSize, patchName); patch = malloc(patchSize); if (!patch || fvx_read(&patchFile, patch, patchSize, NULL) != FR_OK) return displayError(IPS_MEMORY); // Check validity of patch if (patchSize < 8) return displayError(IPS_INVALID); if (read8() != 'P' || read8() != 'A' || read8() != 'T' || read8() != 'C' || read8() != 'H') { return displayError(IPS_INVALID); } unsigned int offset = read24(); unsigned int outlen = 0; unsigned int thisout = 0; unsigned int lastoffset = 0; bool w_scrambled = false; while (offset != 0x454F46) // 454F46=EOF { if (!ShowProgress(patchOffset, patchSize, patchName)) { if (ShowPrompt(true, "%s\n%s", patchName, STR_B_DETECTED_CANCEL)) return displayError(IPS_CANCELED); ShowProgress(0, patchSize, patchName); ShowProgress(patchOffset, patchSize, patchName); } unsigned int size = read16(); if (size == 0) { size = read16(); if (!size) return displayError(IPS_INVALID); thisout = offset + size; read8(); } else { thisout = offset + size; patchOffset += size; } if (offset < lastoffset) w_scrambled = true; lastoffset = offset; if (thisout > outlen) outlen = thisout; if (patchOffset >= patchSize) return displayError(IPS_INVALID); offset = read24(); } outlen_min_mem = outlen; outlen_max = 0xFFFFFFFF; if (patchOffset+3 == patchSize) { unsigned int truncate = read24(); outlen_max = truncate; if (outlen > truncate) { outlen = truncate; w_scrambled = true; } } if (patchOffset != patchSize) return displayError(IPS_INVALID); outlen_min = outlen; error = IPS_OK; if (w_scrambled) error = IPS_SCRAMBLED; // start applying patch bool inPlace = false; if (!CheckWritePermissions(outName)) return displayError(IPS_INVALID_FILE_PATH); if (strncasecmp(inName, outName, 256) == 0) { if (fvx_open(&outFile, outName, FA_WRITE | FA_READ) != FR_OK) return displayError(IPS_INVALID_FILE_PATH); inFile = outFile; inPlace = true; } else if ((fvx_open(&inFile, inName, FA_READ) != FR_OK) || (fvx_open(&outFile, outName, FA_CREATE_ALWAYS | FA_WRITE | FA_READ) != FR_OK)) return displayError(IPS_INVALID_FILE_PATH); size_t inSize = fvx_size(&inFile); outlen = max(outlen_min, min(inSize, outlen_max)); fvx_lseek(&outFile, max(outlen, outlen_min_mem)); fvx_lseek(&outFile, 0); size_t outSize = outlen; ShowProgress(0, outSize, outName); fvx_lseek(&inFile, 0); if (!inPlace && !IPScopy(COPY_IN, min(inSize, outlen), 0)) return displayError(IPS_MEMORY); fvx_lseek(&outFile, inSize); if (outSize > inSize && !IPScopy(COPY_RLE, outSize - inSize, 0)) return displayError(IPS_MEMORY); fvx_lseek(&patchFile, 5); offset = read24(); while (offset != 0x454F46) { if (!ShowProgress(offset, outSize, outName)) { if (ShowPrompt(true, "%s\n%s", outName, STR_B_DETECTED_CANCEL)) return displayError(IPS_CANCELED); ShowProgress(0, outSize, outName); ShowProgress(offset, outSize, outName); } fvx_lseek(&outFile, offset); unsigned int size = read16(); if (size == 0 && !IPScopy(COPY_RLE, read16(), read8())) return displayError(IPS_MEMORY); else if (size != 0 && !IPScopy(COPY_PATCH, size, 0)) return displayError(IPS_MEMORY); offset = read24(); } fvx_lseek(&outFile, outSize); f_truncate(&outFile); return displayError(error); }