mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 13:42:47 +00:00
Add support for BPS/BPM/IPS-formatted binary delta patches.
BPS standardization edits. * Clean up headers. * Move crc32.c to /crypto/. * Rename beat.c to bps.c and move it to /system/ with the rest of the non-3DS file formats. Allocate memory for Source and Target files when possible. Last-minute BPS improvements. * BPS fails more gracefully on error, freeing memory and showing a more detailed message. * BPM no longer deletes the folder it patches to - that should be up to the user. * BPM is scanned before patching to figure out the total output file size for a more accurate progress bar. Add IPS support.
This commit is contained in:
parent
c12eb67b55
commit
1a9bf41c9d
75
arm9/source/crypto/crc32.c
Normal file
75
arm9/source/crypto/crc32.c
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// C port of byuu's \nall\crc32.hpp, which was released under GPLv3
|
||||||
|
// https://github.com/eai04191/beat/blob/master/nall/crc32.hpp
|
||||||
|
// Ported by Hyarion for use with VirtualFatFS
|
||||||
|
|
||||||
|
#include "crc32.h"
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
uint32_t crc32_adjust(uint32_t crc32, uint8_t input) {
|
||||||
|
static const uint32_t crc32_table[256] = {
|
||||||
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||||
|
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||||
|
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||||
|
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||||
|
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||||
|
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||||
|
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||||
|
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||||
|
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||||
|
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||||
|
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||||
|
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||||
|
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
||||||
|
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||||
|
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||||
|
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||||
|
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
||||||
|
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||||
|
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
||||||
|
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||||
|
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||||
|
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||||
|
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
||||||
|
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||||
|
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||||
|
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||||
|
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||||
|
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||||
|
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
||||||
|
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||||
|
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||||
|
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||||
|
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||||
|
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||||
|
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||||
|
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||||
|
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
||||||
|
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||||
|
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||||
|
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||||
|
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||||
|
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||||
|
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||||
|
};
|
||||||
|
return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t crc32_calculate(const uint8_t* data, unsigned int length) {
|
||||||
|
uint32_t crc32 = ~0;
|
||||||
|
for(unsigned i = 0; i < length; i++) {
|
||||||
|
crc32 = crc32_adjust(crc32, data[i]);
|
||||||
|
}
|
||||||
|
return ~crc32;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t crc32_calculate_from_file(FIL inputFile, unsigned int length) {
|
||||||
|
uint32_t crc32 = ~0;
|
||||||
|
uint8_t data;
|
||||||
|
unsigned int br;
|
||||||
|
fvx_lseek(&inputFile, 0);
|
||||||
|
for(unsigned i = 0; i < length; i++) {
|
||||||
|
fvx_read(&inputFile, &data, 1, &br);
|
||||||
|
crc32 = crc32_adjust(crc32, data);
|
||||||
|
}
|
||||||
|
return ~crc32;
|
||||||
|
}
|
10
arm9/source/crypto/crc32.h
Normal file
10
arm9/source/crypto/crc32.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// C port of byuu's \nall\crc32.hpp, which was released under GPLv3
|
||||||
|
// https://github.com/eai04191/beat/blob/master/nall/crc32.hpp
|
||||||
|
// Ported by Hyarion for use with VirtualFatFS
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "vff.h"
|
||||||
|
|
||||||
|
uint32_t crc32_adjust(uint32_t crc32, uint8_t input);
|
||||||
|
uint32_t crc32_calculate(const uint8_t* data, unsigned int length);
|
||||||
|
uint32_t crc32_calculate_from_file(FIL inputFile, unsigned int length);
|
435
arm9/source/system/bps.c
Normal file
435
arm9/source/system/bps.c
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
// C port of byuu's \nall\beat\patch.hpp and \multi.hpp, which were released under GPLv3
|
||||||
|
// https://github.com/eai04191/beat/blob/master/nall/beat/patch.hpp
|
||||||
|
// https://github.com/eai04191/beat/blob/master/nall/beat/multi.hpp
|
||||||
|
// Ported by Hyarion for use with VirtualFatFS
|
||||||
|
|
||||||
|
#include "bps.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "crc32.h"
|
||||||
|
#include "fsperm.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "vff.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BEAT_SUCCESS = 0,
|
||||||
|
BEAT_PATCH_TOO_SMALL,
|
||||||
|
BEAT_PATCH_INVALID_HEADER,
|
||||||
|
BEAT_SOURCE_TOO_SMALL,
|
||||||
|
BEAT_TARGET_TOO_SMALL,
|
||||||
|
BEAT_SOURCE_CHECKSUM_INVALID,
|
||||||
|
BEAT_TARGET_CHECKSUM_INVALID,
|
||||||
|
BEAT_PATCH_CHECKSUM_INVALID,
|
||||||
|
BEAT_BPM_CHECKSUM_INVALID,
|
||||||
|
BEAT_INVALID_FILE_PATH,
|
||||||
|
BEAT_CANCELED
|
||||||
|
} BPSRESULT;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BEAT_SOURCEREAD = 0,
|
||||||
|
BEAT_TARGETREAD,
|
||||||
|
BEAT_SOURCECOPY,
|
||||||
|
BEAT_TARGETCOPY
|
||||||
|
} BPSMODE;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BEAT_CREATEPATH = 0,
|
||||||
|
BEAT_CREATEFILE,
|
||||||
|
BEAT_MODIFYFILE,
|
||||||
|
BEAT_MIRRORFILE
|
||||||
|
} BPMACTION;
|
||||||
|
|
||||||
|
size_t patchSize;
|
||||||
|
FIL patchFile;
|
||||||
|
FIL sourceFile;
|
||||||
|
FIL targetFile;
|
||||||
|
|
||||||
|
uint8_t* sourceData;
|
||||||
|
uint8_t* targetData;
|
||||||
|
bool sourceInMemory;
|
||||||
|
bool targetInMemory;
|
||||||
|
|
||||||
|
unsigned int modifyOffset;
|
||||||
|
unsigned int outputOffset;
|
||||||
|
uint32_t modifyChecksum;
|
||||||
|
uint32_t targetChecksum;
|
||||||
|
|
||||||
|
bool bpmIsActive;
|
||||||
|
uint32_t bpmChecksum;
|
||||||
|
|
||||||
|
uint8_t buffer;
|
||||||
|
unsigned int br;
|
||||||
|
|
||||||
|
char progressText[256];
|
||||||
|
unsigned int outputCurrent;
|
||||||
|
unsigned int outputTotal;
|
||||||
|
|
||||||
|
int err(int errcode) {
|
||||||
|
if(sourceInMemory) free(sourceData);
|
||||||
|
if(targetInMemory) free(targetData);
|
||||||
|
fvx_close(&sourceFile);
|
||||||
|
fvx_close(&targetFile);
|
||||||
|
fvx_close(&patchFile);
|
||||||
|
switch(errcode) {
|
||||||
|
case BEAT_PATCH_TOO_SMALL:
|
||||||
|
ShowPrompt(false, "%s\nThe patch is too small to be a valid BPS file.", progressText); break;
|
||||||
|
case BEAT_PATCH_INVALID_HEADER:
|
||||||
|
ShowPrompt(false, "%s\nThe patch is not a valid BPS file.", progressText); break;
|
||||||
|
case BEAT_SOURCE_TOO_SMALL:
|
||||||
|
ShowPrompt(false, "%s\nThe file being patched is smaller than expected.", progressText); break;
|
||||||
|
case BEAT_TARGET_TOO_SMALL:
|
||||||
|
ShowPrompt(false, "%s\nThere is not enough space for the patched file.", progressText); break;
|
||||||
|
case BEAT_SOURCE_CHECKSUM_INVALID:
|
||||||
|
ShowPrompt(false, "%s\nThe file being patched does not match its checksum.", progressText); break;
|
||||||
|
case BEAT_TARGET_CHECKSUM_INVALID:
|
||||||
|
ShowPrompt(false, "%s\nThe patched file does not match its checksum.", progressText); break;
|
||||||
|
case BEAT_PATCH_CHECKSUM_INVALID:
|
||||||
|
ShowPrompt(false, "%s\nThe BPS patch file does not match its checksum.", progressText); break;
|
||||||
|
case BEAT_BPM_CHECKSUM_INVALID:
|
||||||
|
ShowPrompt(false, "%s\nThe BPM patch file does not match its checksum.", progressText); break;
|
||||||
|
case BEAT_INVALID_FILE_PATH:
|
||||||
|
ShowPrompt(false, "%s\nThe requested file path was invalid.", progressText); break;
|
||||||
|
case BEAT_CANCELED:
|
||||||
|
ShowPrompt(false, "%s\nPatching canceled.", progressText); break;
|
||||||
|
}
|
||||||
|
return errcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t BPSread() {
|
||||||
|
fvx_read(&patchFile, &buffer, 1, &br);
|
||||||
|
modifyChecksum = crc32_adjust(modifyChecksum, buffer);
|
||||||
|
if (bpmIsActive) bpmChecksum = crc32_adjust(bpmChecksum, buffer);
|
||||||
|
modifyOffset++;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t BPSdecode() {
|
||||||
|
uint64_t data = 0, shift = 1;
|
||||||
|
while(true) {
|
||||||
|
uint8_t x = BPSread();
|
||||||
|
data += (x & 0x7f) * shift;
|
||||||
|
if(x & 0x80) break;
|
||||||
|
shift <<= 7;
|
||||||
|
data += shift;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BPSwrite(uint8_t data) {
|
||||||
|
if(targetInMemory) targetData[outputOffset] = data;
|
||||||
|
else fvx_write(&targetFile, &data, 1, &br);
|
||||||
|
targetChecksum = crc32_adjust(targetChecksum, data);
|
||||||
|
outputOffset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApplyBeatPatch() {
|
||||||
|
unsigned int sourceRelativeOffset = 0, targetRelativeOffset = 0;
|
||||||
|
sourceInMemory = false, targetInMemory = false;
|
||||||
|
modifyOffset = 0, outputOffset = 0;
|
||||||
|
modifyChecksum = ~0, targetChecksum = ~0;
|
||||||
|
if(patchSize < 19) return err(BEAT_PATCH_TOO_SMALL);
|
||||||
|
|
||||||
|
if(BPSread() != 'B') return err(BEAT_PATCH_INVALID_HEADER);
|
||||||
|
if(BPSread() != 'P') return err(BEAT_PATCH_INVALID_HEADER);
|
||||||
|
if(BPSread() != 'S') return err(BEAT_PATCH_INVALID_HEADER);
|
||||||
|
if(BPSread() != '1') return err(BEAT_PATCH_INVALID_HEADER);
|
||||||
|
|
||||||
|
size_t modifySourceSize = BPSdecode();
|
||||||
|
size_t modifyTargetSize = BPSdecode();
|
||||||
|
size_t modifyMarkupSize = BPSdecode();
|
||||||
|
for(unsigned int n = 0; n < modifyMarkupSize; n++) BPSread(); // metadata, not useful to us
|
||||||
|
|
||||||
|
size_t sourceSize = fvx_size(&sourceFile);
|
||||||
|
fvx_lseek(&sourceFile, 0);
|
||||||
|
fvx_lseek(&targetFile, modifyTargetSize);
|
||||||
|
fvx_lseek(&targetFile, 0);
|
||||||
|
size_t targetSize = fvx_size(&targetFile);
|
||||||
|
if (!bpmIsActive) outputTotal = targetSize;
|
||||||
|
if(modifySourceSize > sourceSize) return err(BEAT_SOURCE_TOO_SMALL);
|
||||||
|
if(modifyTargetSize > targetSize) return err(BEAT_TARGET_TOO_SMALL);
|
||||||
|
|
||||||
|
sourceData = (uint8_t*)malloc(sourceSize);
|
||||||
|
targetData = (uint8_t*)malloc(targetSize);
|
||||||
|
if (sourceData != NULL) {
|
||||||
|
sourceInMemory = true;
|
||||||
|
fvx_read(&sourceFile, sourceData, sourceSize, &br);
|
||||||
|
}
|
||||||
|
if (targetData != NULL) targetInMemory = true;
|
||||||
|
|
||||||
|
while((modifyOffset < patchSize - 12)) {
|
||||||
|
if (!ShowProgress(outputCurrent, outputTotal, progressText) &&
|
||||||
|
ShowPrompt(true, "%s\nB button detected. Cancel?", progressText))
|
||||||
|
return err(BEAT_CANCELED);
|
||||||
|
|
||||||
|
unsigned int length = BPSdecode();
|
||||||
|
unsigned int mode = length & 3;
|
||||||
|
length = (length >> 2) + 1;
|
||||||
|
outputCurrent += length;
|
||||||
|
|
||||||
|
switch(mode) {
|
||||||
|
case BEAT_SOURCEREAD:
|
||||||
|
if (sourceInMemory) {
|
||||||
|
while(length--) BPSwrite(sourceData[outputOffset]);
|
||||||
|
} else {
|
||||||
|
fvx_lseek(&sourceFile, fvx_tell(&targetFile));
|
||||||
|
while(length--) {
|
||||||
|
fvx_read(&sourceFile, &buffer, 1, &br);
|
||||||
|
BPSwrite(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BEAT_TARGETREAD:
|
||||||
|
while(length--) BPSwrite(BPSread());
|
||||||
|
break;
|
||||||
|
case BEAT_SOURCECOPY:
|
||||||
|
case BEAT_TARGETCOPY:
|
||||||
|
; // intentional null statement
|
||||||
|
int offset = BPSdecode();
|
||||||
|
bool negative = offset & 1;
|
||||||
|
offset >>= 1;
|
||||||
|
if(negative) offset = -offset;
|
||||||
|
|
||||||
|
if(mode == BEAT_SOURCECOPY) {
|
||||||
|
sourceRelativeOffset += offset;
|
||||||
|
if(sourceInMemory) {
|
||||||
|
while(length--) BPSwrite(sourceData[sourceRelativeOffset++]);
|
||||||
|
} else {
|
||||||
|
fvx_lseek(&sourceFile, sourceRelativeOffset);
|
||||||
|
while(length--) {
|
||||||
|
fvx_read(&sourceFile, &buffer, 1, &br);
|
||||||
|
BPSwrite(buffer);
|
||||||
|
sourceRelativeOffset++;
|
||||||
|
}
|
||||||
|
fvx_lseek(&sourceFile, fvx_tell(&targetFile));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetRelativeOffset += offset;
|
||||||
|
if(targetInMemory) {
|
||||||
|
while(length--) BPSwrite(targetData[targetRelativeOffset++]);
|
||||||
|
} else {
|
||||||
|
unsigned int targetOffset = fvx_tell(&targetFile);
|
||||||
|
while(length--) {
|
||||||
|
fvx_lseek(&targetFile, targetRelativeOffset);
|
||||||
|
fvx_read(&targetFile, &buffer, 1, &br);
|
||||||
|
fvx_lseek(&targetFile, targetOffset);
|
||||||
|
BPSwrite(buffer);
|
||||||
|
targetRelativeOffset++;
|
||||||
|
targetOffset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t modifySourceChecksum = 0, modifyTargetChecksum = 0, modifyModifyChecksum = 0;
|
||||||
|
for(unsigned int n = 0; n < 32; n += 8) modifySourceChecksum |= BPSread() << n;
|
||||||
|
for(unsigned int n = 0; n < 32; n += 8) modifyTargetChecksum |= BPSread() << n;
|
||||||
|
uint32_t checksum = ~modifyChecksum;
|
||||||
|
for(unsigned int n = 0; n < 32; n += 8) modifyModifyChecksum |= BPSread() << n;
|
||||||
|
|
||||||
|
uint32_t sourceChecksum;
|
||||||
|
if(sourceInMemory) sourceChecksum = crc32_calculate(sourceData, modifySourceSize);
|
||||||
|
else sourceChecksum = crc32_calculate_from_file(sourceFile, modifySourceSize);
|
||||||
|
targetChecksum = ~targetChecksum;
|
||||||
|
|
||||||
|
if (sourceInMemory) free(sourceData);
|
||||||
|
fvx_close(&sourceFile);
|
||||||
|
|
||||||
|
if (targetInMemory) {
|
||||||
|
fvx_write(&targetFile, targetData, targetSize, &br);
|
||||||
|
free(targetData);
|
||||||
|
}
|
||||||
|
fvx_close(&targetFile);
|
||||||
|
|
||||||
|
if (!bpmIsActive) fvx_close(&patchFile);
|
||||||
|
|
||||||
|
if(sourceChecksum != modifySourceChecksum) return err(BEAT_SOURCE_CHECKSUM_INVALID);
|
||||||
|
if(targetChecksum != modifyTargetChecksum) return err(BEAT_TARGET_CHECKSUM_INVALID);
|
||||||
|
if(checksum != modifyModifyChecksum) return err(BEAT_PATCH_CHECKSUM_INVALID);
|
||||||
|
|
||||||
|
return BEAT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApplyBPSPatch(const char* modifyName, const char* sourceName, const char* targetName) {
|
||||||
|
bpmIsActive = false;
|
||||||
|
|
||||||
|
if ((!CheckWritePermissions(targetName)) ||
|
||||||
|
(fvx_open(&patchFile, modifyName, FA_READ) != FR_OK) ||
|
||||||
|
(fvx_open(&sourceFile, sourceName, FA_READ) != FR_OK) ||
|
||||||
|
(fvx_open(&targetFile, targetName, FA_CREATE_ALWAYS | FA_WRITE | FA_READ) != FR_OK))
|
||||||
|
return err(BEAT_INVALID_FILE_PATH);
|
||||||
|
|
||||||
|
patchSize = fvx_size(&patchFile);
|
||||||
|
snprintf(progressText, 256, "%s", targetName);
|
||||||
|
return ApplyBeatPatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t BPMread() {
|
||||||
|
fvx_read(&patchFile, &buffer, 1, &br);
|
||||||
|
if (bpmIsActive) bpmChecksum = crc32_adjust(bpmChecksum, buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t BPMreadNumber() {
|
||||||
|
uint64_t data = 0, shift = 1;
|
||||||
|
while(true) {
|
||||||
|
uint8_t x = BPMread();
|
||||||
|
data += (x & 0x7f) * shift;
|
||||||
|
if(x & 0x80) break;
|
||||||
|
shift <<= 7;
|
||||||
|
data += shift;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* BPMreadString(char text[], unsigned int length) {
|
||||||
|
for(unsigned int n = 0; n < length; n++) text[n] = BPMread();
|
||||||
|
text[length] = '\0';
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t BPMreadChecksum() {
|
||||||
|
uint32_t checksum = 0;
|
||||||
|
checksum |= BPMread() << 0;
|
||||||
|
checksum |= BPMread() << 8;
|
||||||
|
checksum |= BPMread() << 16;
|
||||||
|
checksum |= BPMread() << 24;
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CalculateBPMLength(const char* patchName, const char* sourcePath, const char* targetPath) {
|
||||||
|
bpmIsActive = false;
|
||||||
|
snprintf(progressText, 256, "%s", patchName);
|
||||||
|
fvx_lseek(&patchFile, BPMreadNumber() + fvx_tell(&patchFile));
|
||||||
|
while(fvx_tell(&patchFile) < fvx_size(&patchFile) - 4) {
|
||||||
|
uint64_t encoding = BPMreadNumber();
|
||||||
|
unsigned int action = encoding & 3;
|
||||||
|
char targetName[256];
|
||||||
|
BPMreadString(targetName, (encoding >> 2) + 1);
|
||||||
|
if (!ShowProgress(fvx_tell(&patchFile), fvx_size(&patchFile), progressText) &&
|
||||||
|
ShowPrompt(true, "%s\nB button detected. Cancel?", progressText))
|
||||||
|
return false;
|
||||||
|
if(action == BEAT_CREATEFILE) {
|
||||||
|
uint64_t fileSize = BPMreadNumber();
|
||||||
|
fvx_lseek(&patchFile, fileSize + 4 + fvx_tell(&patchFile));
|
||||||
|
outputTotal += fileSize;
|
||||||
|
} else if(action == BEAT_MODIFYFILE) {
|
||||||
|
fvx_lseek(&patchFile, (BPMreadNumber() >> 1) + fvx_tell(&patchFile));
|
||||||
|
uint64_t patchSize = BPMreadNumber() + fvx_tell(&patchFile);
|
||||||
|
fvx_lseek(&patchFile, 4 + fvx_tell(&patchFile));
|
||||||
|
BPMreadNumber();
|
||||||
|
outputTotal += BPMreadNumber();
|
||||||
|
fvx_lseek(&patchFile, patchSize);
|
||||||
|
} else if(action == BEAT_MIRRORFILE) {
|
||||||
|
encoding = BPMreadNumber();
|
||||||
|
char originPath[256], sourceName[256], oldPath[256];
|
||||||
|
if (encoding & 1) snprintf(originPath, 256, "%s", targetPath);
|
||||||
|
else snprintf(originPath, 256, "%s", sourcePath);
|
||||||
|
if ((encoding >> 1) == 0) snprintf(sourceName, 256, "%s", targetName);
|
||||||
|
else BPMreadString(sourceName, encoding >> 1);
|
||||||
|
snprintf(oldPath, 256, "%s/%s", originPath, sourceName);
|
||||||
|
FIL oldFile;
|
||||||
|
fvx_open(&oldFile, oldPath, FA_READ);
|
||||||
|
outputTotal += fvx_size(&oldFile);
|
||||||
|
fvx_close(&oldFile);
|
||||||
|
fvx_lseek(&patchFile, 4 + fvx_tell(&patchFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fvx_lseek(&patchFile, 4);
|
||||||
|
bpmIsActive = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApplyBPMPatch(const char* patchName, const char* sourcePath, const char* targetPath) {
|
||||||
|
bpmIsActive = true;
|
||||||
|
bpmChecksum = ~0;
|
||||||
|
|
||||||
|
if ((!CheckWritePermissions(targetPath)) ||
|
||||||
|
((fvx_stat(targetPath, NULL) != FR_OK) && (fvx_mkdir(targetPath) != FR_OK)) ||
|
||||||
|
(fvx_open(&patchFile, patchName, FA_READ) != FR_OK))
|
||||||
|
return err(BEAT_INVALID_FILE_PATH);
|
||||||
|
|
||||||
|
if(BPMread() != 'B') return err(BEAT_PATCH_INVALID_HEADER);
|
||||||
|
if(BPMread() != 'P') return err(BEAT_PATCH_INVALID_HEADER);
|
||||||
|
if(BPMread() != 'M') return err(BEAT_PATCH_INVALID_HEADER);
|
||||||
|
if(BPMread() != '1') return err(BEAT_PATCH_INVALID_HEADER);
|
||||||
|
if (!CalculateBPMLength(patchName, sourcePath, targetPath)) return err(BEAT_CANCELED);
|
||||||
|
uint64_t metadataLength = BPMreadNumber();
|
||||||
|
while(metadataLength--) BPMread();
|
||||||
|
|
||||||
|
while(fvx_tell(&patchFile) < fvx_size(&patchFile) - 4) {
|
||||||
|
uint64_t encoding = BPMreadNumber();
|
||||||
|
unsigned int action = encoding & 3;
|
||||||
|
unsigned int targetLength = (encoding >> 2) + 1;
|
||||||
|
char targetName[256];
|
||||||
|
BPMreadString(targetName, targetLength);
|
||||||
|
snprintf(progressText, 256, "%s", targetName);
|
||||||
|
if (!ShowProgress(outputCurrent, outputTotal, progressText) &&
|
||||||
|
ShowPrompt(true, "%s\nB button detected. Cancel?", progressText))
|
||||||
|
return err(BEAT_CANCELED);
|
||||||
|
|
||||||
|
if(action == BEAT_CREATEPATH) {
|
||||||
|
char newPath[256];
|
||||||
|
snprintf(newPath, 256, "%s/%s", targetPath, targetName);
|
||||||
|
if ((fvx_stat(newPath, NULL) != FR_OK) && (fvx_mkdir(newPath) != FR_OK)) return err(BEAT_INVALID_FILE_PATH);
|
||||||
|
} else if(action == BEAT_CREATEFILE) {
|
||||||
|
char newPath[256];
|
||||||
|
snprintf(newPath, 256, "%s/%s", targetPath, targetName);
|
||||||
|
FIL newFile;
|
||||||
|
if (fvx_open(&newFile, newPath, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) return err(BEAT_INVALID_FILE_PATH);
|
||||||
|
uint64_t fileSize = BPMreadNumber();
|
||||||
|
outputCurrent += fileSize;
|
||||||
|
fvx_lseek(&newFile, fileSize);
|
||||||
|
fvx_lseek(&newFile, 0);
|
||||||
|
while(fileSize--) {
|
||||||
|
buffer = BPMread();
|
||||||
|
fvx_write(&newFile, &buffer, 1, &br);
|
||||||
|
}
|
||||||
|
BPMreadChecksum();
|
||||||
|
fvx_close(&newFile);
|
||||||
|
} else {
|
||||||
|
encoding = BPMreadNumber();
|
||||||
|
char originPath[256], sourceName[256], oldPath[256], newPath[256];
|
||||||
|
if (encoding & 1) snprintf(originPath, 256, "%s", targetPath);
|
||||||
|
else snprintf(originPath, 256, "%s", sourcePath);
|
||||||
|
if ((encoding >> 1) == 0) snprintf(sourceName, 256, "%s", targetName);
|
||||||
|
else BPMreadString(sourceName, encoding >> 1);
|
||||||
|
snprintf(oldPath, 256, "%s/%s", originPath, sourceName);
|
||||||
|
snprintf(newPath, 256, "%s/%s", targetPath, targetName);
|
||||||
|
if(action == BEAT_MODIFYFILE) {
|
||||||
|
patchSize = BPMreadNumber();
|
||||||
|
if ((fvx_open(&sourceFile, oldPath, FA_READ) != FR_OK) ||
|
||||||
|
(fvx_open(&targetFile, newPath, FA_CREATE_ALWAYS | FA_WRITE | FA_READ) != FR_OK))
|
||||||
|
return err(BEAT_INVALID_FILE_PATH);
|
||||||
|
int result = ApplyBeatPatch();
|
||||||
|
if (result != BEAT_SUCCESS) return result;
|
||||||
|
} else if(action == BEAT_MIRRORFILE) {
|
||||||
|
FIL oldFile, newFile;
|
||||||
|
if ((fvx_open(&oldFile, oldPath, FA_READ) != FR_OK) ||
|
||||||
|
(fvx_open(&newFile, newPath, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK))
|
||||||
|
return err(BEAT_INVALID_FILE_PATH);
|
||||||
|
uint64_t fileSize = fvx_size(&oldFile);
|
||||||
|
outputCurrent += fileSize;
|
||||||
|
fvx_lseek(&newFile, fileSize);
|
||||||
|
fvx_lseek(&newFile, 0);
|
||||||
|
while(fileSize--) {
|
||||||
|
fvx_read(&oldFile, &buffer, 1, &br);
|
||||||
|
fvx_write(&newFile, &buffer, 1, &br);
|
||||||
|
}
|
||||||
|
BPMreadChecksum();
|
||||||
|
fvx_close(&oldFile);
|
||||||
|
fvx_close(&newFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cksum = ~bpmChecksum;
|
||||||
|
if(BPMread() != (uint8_t)(cksum >> 0)) return err(BEAT_BPM_CHECKSUM_INVALID);
|
||||||
|
if(BPMread() != (uint8_t)(cksum >> 8)) return err(BEAT_BPM_CHECKSUM_INVALID);
|
||||||
|
if(BPMread() != (uint8_t)(cksum >> 16)) return err(BEAT_BPM_CHECKSUM_INVALID);
|
||||||
|
if(BPMread() != (uint8_t)(cksum >> 24)) return err(BEAT_BPM_CHECKSUM_INVALID);
|
||||||
|
|
||||||
|
fvx_close(&patchFile);
|
||||||
|
return BEAT_SUCCESS;
|
||||||
|
}
|
9
arm9/source/system/bps.h
Normal file
9
arm9/source/system/bps.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// C port of byuu's \nall\beat\patch.hpp and \multi.hpp, which were released under GPLv3
|
||||||
|
// https://github.com/eai04191/beat/blob/master/nall/beat/patch.hpp
|
||||||
|
// https://github.com/eai04191/beat/blob/master/nall/beat/multi.hpp
|
||||||
|
// Ported by Hyarion for use with VirtualFatFS
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
int ApplyBPSPatch(const char* modifyName, const char* sourceName, const char* targetName);
|
||||||
|
int ApplyBPMPatch(const char* patchName, const char* sourcePath, const char* targetPath);
|
199
arm9/source/system/ips.c
Normal file
199
arm9/source/system/ips.c
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// 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 "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
|
||||||
|
} IPSERROR;
|
||||||
|
|
||||||
|
FIL patchFile, inFile, outFile;
|
||||||
|
size_t patchSize;
|
||||||
|
char errName[256];
|
||||||
|
|
||||||
|
uint8_t buf[3];
|
||||||
|
unsigned int br;
|
||||||
|
|
||||||
|
int ret(int errcode) {
|
||||||
|
fvx_close(&patchFile);
|
||||||
|
fvx_close(&inFile);
|
||||||
|
fvx_close(&outFile);
|
||||||
|
switch(errcode) {
|
||||||
|
case IPS_NOTTHIS:
|
||||||
|
ShowPrompt(false, "%s\nThe patch is most likely not intended for this file.", errName); break;
|
||||||
|
case IPS_THISOUT:
|
||||||
|
ShowPrompt(false, "%s\nYou most likely applied the patch on the output file.", errName); break;
|
||||||
|
case IPS_SCRAMBLED:
|
||||||
|
ShowPrompt(false, "%s\nThe patch is technically valid,\nbut seems scrambled or malformed.", errName); break;
|
||||||
|
case IPS_INVALID:
|
||||||
|
ShowPrompt(false, "%s\nThe patch is invalid.", errName); break;
|
||||||
|
case IPS_16MB:
|
||||||
|
ShowPrompt(false, "%s\nOne or both files is bigger than 16MB.\nThe IPS format doesn't support that.", errName); break;
|
||||||
|
case IPS_INVALID_FILE_PATH:
|
||||||
|
ShowPrompt(false, "%s\nThe requested file path was invalid.", errName); break;
|
||||||
|
case IPS_CANCELED:
|
||||||
|
ShowPrompt(false, "%s\nPatching canceled.", errName); break;
|
||||||
|
}
|
||||||
|
return errcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t read8() {
|
||||||
|
if (fvx_tell(&patchFile) >= patchSize) return 0;
|
||||||
|
fvx_read(&patchFile, &buf, 1, &br);
|
||||||
|
return buf[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int read16() {
|
||||||
|
if (fvx_tell(&patchFile)+1 >= patchSize) return 0;
|
||||||
|
fvx_read(&patchFile, &buf, 2, &br);
|
||||||
|
return (buf[0] << 8) | buf[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int read24() {
|
||||||
|
if (fvx_tell(&patchFile)+2 >= patchSize) return 0;
|
||||||
|
fvx_read(&patchFile, &buf, 3, &br);
|
||||||
|
return (buf[0] << 16) | (buf[1] << 8) | buf[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
int ApplyIPSPatch(const char* patchName, const char* inName, const char* outName) {
|
||||||
|
int error = IPS_INVALID;
|
||||||
|
unsigned int outlen_min, outlen_max, outlen_min_mem;
|
||||||
|
snprintf(errName, 256, "%s", patchName);
|
||||||
|
|
||||||
|
if (fvx_open(&patchFile, patchName, FA_READ) != FR_OK) return ret(IPS_INVALID_FILE_PATH);
|
||||||
|
patchSize = fvx_size(&patchFile);
|
||||||
|
ShowProgress(0, patchSize, patchName);
|
||||||
|
|
||||||
|
// Check validity of patch
|
||||||
|
if (patchSize < 8) return ret(IPS_INVALID);
|
||||||
|
if (read8() != 'P' ||
|
||||||
|
read8() != 'A' ||
|
||||||
|
read8() != 'T' ||
|
||||||
|
read8() != 'C' ||
|
||||||
|
read8() != 'H')
|
||||||
|
{
|
||||||
|
return ret(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(fvx_tell(&patchFile), patchSize, patchName) &&
|
||||||
|
ShowPrompt(true, "%s\nB button detected. Cancel?", patchName))
|
||||||
|
return ret(IPS_CANCELED);
|
||||||
|
unsigned int size = read16();
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
size = read16();
|
||||||
|
if (!size) return ret(IPS_INVALID);
|
||||||
|
thisout = offset + size;
|
||||||
|
read8();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
thisout = offset + size;
|
||||||
|
fvx_lseek(&patchFile, fvx_tell(&patchFile) + size);
|
||||||
|
}
|
||||||
|
if (offset < lastoffset) w_scrambled = true;
|
||||||
|
lastoffset = offset;
|
||||||
|
if (thisout > outlen) outlen = thisout;
|
||||||
|
if (fvx_tell(&patchFile) >= patchSize) return ret(IPS_INVALID);
|
||||||
|
offset = read24();
|
||||||
|
}
|
||||||
|
outlen_min_mem = outlen;
|
||||||
|
outlen_max = 0xFFFFFFFF;
|
||||||
|
if (fvx_tell(&patchFile)+3 == patchSize)
|
||||||
|
{
|
||||||
|
unsigned int truncate = read24();
|
||||||
|
outlen_max = truncate;
|
||||||
|
if (outlen > truncate)
|
||||||
|
{
|
||||||
|
outlen = truncate;
|
||||||
|
w_scrambled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fvx_tell(&patchFile) != patchSize) return ret(IPS_INVALID);
|
||||||
|
outlen_min = outlen;
|
||||||
|
error = IPS_OK;
|
||||||
|
if (w_scrambled) error = IPS_SCRAMBLED;
|
||||||
|
|
||||||
|
// start applying patch
|
||||||
|
bool inPlace = false;
|
||||||
|
if (!CheckWritePermissions(outName)) return ret(IPS_INVALID_FILE_PATH);
|
||||||
|
if (strncasecmp(inName, outName, 256) == 0)
|
||||||
|
{
|
||||||
|
if (fvx_open(&outFile, outName, FA_WRITE | FA_READ) != FR_OK) return ret(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 ret(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);
|
||||||
|
|
||||||
|
if (!inPlace) {
|
||||||
|
fvx_lseek(&inFile, 0);
|
||||||
|
uint8_t buffer;
|
||||||
|
for(size_t n = 0; n < min(inSize, outlen); n++) {
|
||||||
|
fvx_read(&inFile, &buffer, 1, &br);
|
||||||
|
fvx_write(&outFile, &buffer, 1, &br);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (outSize > inSize) {
|
||||||
|
fvx_lseek(&outFile, inSize);
|
||||||
|
for(size_t n = inSize; n < outSize; n++) fvx_write(&outFile, 0, 1, &br);
|
||||||
|
}
|
||||||
|
|
||||||
|
fvx_lseek(&patchFile, 5);
|
||||||
|
offset = read24();
|
||||||
|
while (offset != 0x454F46)
|
||||||
|
{
|
||||||
|
if (!ShowProgress(offset, outSize, outName) &&
|
||||||
|
ShowPrompt(true, "%s\nB button detected. Cancel?", patchName))
|
||||||
|
return ret(IPS_CANCELED);
|
||||||
|
|
||||||
|
fvx_lseek(&outFile, offset);
|
||||||
|
unsigned int size = read16();
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
size = read16();
|
||||||
|
uint8_t b = read8();
|
||||||
|
for(size_t n = 0; n < size; n++) fvx_write(&outFile, &b, 1, &br);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint8_t buffer;
|
||||||
|
for(size_t n = 0; n < size; n++) {
|
||||||
|
fvx_read(&patchFile, &buffer, 1, &br);
|
||||||
|
fvx_write(&outFile, &buffer, 1, &br);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset = read24();
|
||||||
|
}
|
||||||
|
|
||||||
|
fvx_lseek(&outFile, outSize);
|
||||||
|
f_truncate(&outFile);
|
||||||
|
return ret(error);
|
||||||
|
}
|
7
arm9/source/system/ips.h
Normal file
7
arm9/source/system/ips.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
int ApplyIPSPatch(const char* patchName, const char* sourceName, const char* targetName);
|
@ -13,6 +13,8 @@
|
|||||||
#include "hid.h"
|
#include "hid.h"
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
#include "png.h"
|
#include "png.h"
|
||||||
|
#include "ips.h"
|
||||||
|
#include "bps.h"
|
||||||
|
|
||||||
|
|
||||||
#define _MAX_ARGS 4
|
#define _MAX_ARGS 4
|
||||||
@ -105,6 +107,9 @@ typedef enum {
|
|||||||
CMD_ID_ENCRYPT,
|
CMD_ID_ENCRYPT,
|
||||||
CMD_ID_BUILDCIA,
|
CMD_ID_BUILDCIA,
|
||||||
CMD_ID_EXTRCODE,
|
CMD_ID_EXTRCODE,
|
||||||
|
CMD_ID_APPLYIPS,
|
||||||
|
CMD_ID_APPLYBPS,
|
||||||
|
CMD_ID_APPLYBPM,
|
||||||
CMD_ID_ISDIR,
|
CMD_ID_ISDIR,
|
||||||
CMD_ID_EXIST,
|
CMD_ID_EXIST,
|
||||||
CMD_ID_BOOT,
|
CMD_ID_BOOT,
|
||||||
@ -169,6 +174,9 @@ Gm9ScriptCmd cmd_list[] = {
|
|||||||
{ CMD_ID_ENCRYPT , "encrypt" , 1, 0 },
|
{ CMD_ID_ENCRYPT , "encrypt" , 1, 0 },
|
||||||
{ CMD_ID_BUILDCIA, "buildcia", 1, _FLG('l') },
|
{ CMD_ID_BUILDCIA, "buildcia", 1, _FLG('l') },
|
||||||
{ CMD_ID_EXTRCODE, "extrcode", 2, 0 },
|
{ CMD_ID_EXTRCODE, "extrcode", 2, 0 },
|
||||||
|
{ CMD_ID_APPLYIPS, "applyips", 3, 0 },
|
||||||
|
{ CMD_ID_APPLYBPS, "applybps", 3, 0 },
|
||||||
|
{ CMD_ID_APPLYBPM, "applybpm", 3, 0 },
|
||||||
{ CMD_ID_ISDIR, "isdir" , 1, 0 },
|
{ CMD_ID_ISDIR, "isdir" , 1, 0 },
|
||||||
{ CMD_ID_EXIST, "exist" , 1, 0 },
|
{ CMD_ID_EXIST, "exist" , 1, 0 },
|
||||||
{ CMD_ID_BOOT , "boot" , 1, 0 },
|
{ CMD_ID_BOOT , "boot" , 1, 0 },
|
||||||
@ -1260,6 +1268,18 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) {
|
|||||||
if (err_str) snprintf(err_str, _ERR_STR_LEN, "extract .code failed");
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "extract .code failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (id == CMD_ID_APPLYIPS) {
|
||||||
|
ret = (ApplyIPSPatch(argv[0], argv[1], argv[2]) == 0);
|
||||||
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "apply IPS failed");
|
||||||
|
}
|
||||||
|
else if (id == CMD_ID_APPLYBPS) {
|
||||||
|
ret = (ApplyBPSPatch(argv[0], argv[1], argv[2]) == 0);
|
||||||
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "apply BPS failed");
|
||||||
|
}
|
||||||
|
else if (id == CMD_ID_APPLYBPM) {
|
||||||
|
ret = (ApplyBPMPatch(argv[0], argv[1], argv[2]) == 0);
|
||||||
|
if (err_str) snprintf(err_str, _ERR_STR_LEN, "apply BPM failed");
|
||||||
|
}
|
||||||
else if (id == CMD_ID_ISDIR) {
|
else if (id == CMD_ID_ISDIR) {
|
||||||
DIR fdir;
|
DIR fdir;
|
||||||
if (fvx_opendir(&fdir, argv[0]) == FR_OK) {
|
if (fvx_opendir(&fdir, argv[0]) == FR_OK) {
|
||||||
|
@ -268,6 +268,21 @@ verify S:/firm1.bin
|
|||||||
# -l / --legit force CIA to be legit (only works for legit system installed titles)
|
# -l / --legit force CIA to be legit (only works for legit system installed titles)
|
||||||
# buildcia 0:/x.ncch
|
# buildcia 0:/x.ncch
|
||||||
|
|
||||||
|
# 'applyips' COMMAND
|
||||||
|
# This will apply the given IPS-formatted delta patch (argument 1) to the specified file (argument 2)
|
||||||
|
# to produce the patched file (argument 3). 2 and 3 may be the same to perform an in-place patch.
|
||||||
|
# applyips 0:/example/patch.ips 0:/data/original.bin 0:/game/modded.bin
|
||||||
|
|
||||||
|
# 'applybps' COMMAND
|
||||||
|
# This will apply the given BPS-formatted delta patch (argument 1) to the specified file (argument 2)
|
||||||
|
# to produce the patched file (argument 3).
|
||||||
|
# applybps 0:/example/patch.bps 0:/data/original.bin 0:/game/modded.bin
|
||||||
|
|
||||||
|
# 'applybpm' COMMAND
|
||||||
|
# This will apply the given BPM-formatted delta patch (argument 1) to the specified directory (argument 2)
|
||||||
|
# to produce a directory containing patched files (argument 3).
|
||||||
|
# applybpm 0:/example/patch.bpm 0:/data/originalfolder 0:/game/moddedfolder
|
||||||
|
|
||||||
# 'boot' COMMAND
|
# 'boot' COMMAND
|
||||||
# Use this command to chainload a compatible FIRM
|
# Use this command to chainload a compatible FIRM
|
||||||
# boot 0:/boot.firm
|
# boot 0:/boot.firm
|
||||||
|
Loading…
x
Reference in New Issue
Block a user