435 lines
16 KiB
C
Raw Normal View History

// 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;
}