mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 05:32:47 +00:00
436 lines
16 KiB
C
436 lines
16 KiB
C
// 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;
|
|
}
|