Last active
December 27, 2019 03:37
-
-
Save Wolfvak/6f372ed67f106ef64d527f5afc44d521 to your computer and use it in GitHub Desktop.
simple and (hopefully) not too broken BPS patcher
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
compile with gcc beat.c -o beat | |
run as ./beat patch.bps input.bin output.bin | |
*/ | |
#include <stdio.h> | |
#include <assert.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <sys/stat.h> | |
#define BEAT_VLIBUFSZ (16) | |
#define BEAT_COPYBUFSZ (1024 * 1024) | |
#define BPS_EOAL_OFF (12) | |
#define BPM_EOAL_OFF (4) | |
#define BEAT_RANGESZ(c, i) ((c)->ranges[1][i] - (c)->ranges[0][i]) | |
#define BPM_MAXPATH (256) | |
#define min(x, y) ((x) < (y) ? (x) : (y)) | |
#define DISPLAY_STATUS(x, ...) printf(x, __VA_ARGS__) | |
#include <stdbool.h> | |
#define CRC32_POLY 0xEDB88320 | |
static uint32_t crc32_table[0x100]; | |
static void crc32_init(void) { | |
for (int i = 0; i < 0x100; i++) { | |
uint32_t crc = i; | |
for (unsigned j = 0; j < 8; j++) | |
crc = (crc >> 1) ^ (-(int)(crc & 1) & CRC32_POLY); | |
crc32_table[i] = crc; | |
} | |
} | |
static uint32_t crc32_adjust(const void *data, size_t len, uint32_t crc) { | |
const uint8_t *dat = data; | |
crc = ~crc; | |
while(len--) | |
crc = (crc >> 8) ^ crc32_table[(crc & 0xFF) ^ *(dat++)]; | |
return ~crc; | |
} | |
static bool crc32_file(const char *path, size_t off, size_t len, uint32_t *crc) | |
{ | |
int res; | |
FILE *fp; | |
uint8_t *data; | |
uint32_t calc_crc = 0; | |
fp = fopen(path, "rb"); | |
if (fp == NULL) | |
return false; | |
data = malloc(BEAT_COPYBUFSZ); | |
if (data == NULL) { | |
fclose(fp); | |
return false; | |
} | |
fseek(fp, off, SEEK_SET); | |
while(len > 0) { | |
size_t blksz = min(len, BEAT_COPYBUFSZ); | |
res = fread(data, blksz, 1, fp); | |
if (res != 1) break; | |
calc_crc = crc32_adjust(data, blksz, calc_crc); | |
len -= blksz; | |
} | |
fclose(fp); | |
free(data); | |
*crc = calc_crc; | |
return (len == 0); | |
} | |
static size_t fs_size(const char *p) | |
{ | |
size_t ret = 0; | |
FILE *f = fopen(p, "rb"); | |
if (f != NULL) { | |
fseek(f, 0L, SEEK_END); | |
ret = ftell(f); | |
fclose(f); | |
} | |
return ret; | |
} | |
/* Possible error codes */ | |
enum { | |
BEAT_OK = 0, | |
BEAT_EOAL, | |
BEAT_ABORTED, | |
BEAT_IO_ERROR, | |
BEAT_OVERFLOW, | |
BEAT_BADPATCH, | |
BEAT_BADINPUT, | |
BEAT_BADOUTPUT, | |
BEAT_BADCHKSUM, | |
BEAT_PATCH_EXPECT, | |
BEAT_OUT_OF_MEMORY, | |
}; | |
/* State machine actions */ | |
enum { | |
BPS_SOURCEREAD = 0, | |
BPS_TARGETREAD = 1, | |
BPS_SOURCECOPY = 2, | |
BPS_TARGETCOPY = 3 | |
}; | |
enum { | |
BPM_CREATEPATH = 0, | |
BPM_CREATEFILE = 1, | |
BPM_MODIFYFILE = 2, | |
BPM_MIRRORFILE = 3 | |
}; | |
/* File handles used within the Beat state */ | |
enum { | |
BEAT_PATCHFILE = 0, | |
BEAT_SRCFILE, | |
BEAT_DSTFILE, | |
}; | |
#define BEAT_FILECOUNT 3 | |
static const uint8_t bps_signature[] = { 'B', 'P', 'S', '1' }; | |
static const uint8_t bps_chksumoffs[BEAT_FILECOUNT] = { | |
[BEAT_PATCHFILE] = 4, [BEAT_DSTFILE] = 8, [BEAT_SRCFILE] = 12, | |
}; | |
static const uint8_t bpm_signature[] = { 'B', 'P', 'M', '1' }; | |
/** BEAT STATE STORAGE */ | |
typedef struct { | |
uint8_t *copybuf; | |
FILE *file[BEAT_FILECOUNT]; | |
ssize_t foffset[BEAT_FILECOUNT], eoal_offset; | |
size_t ranges[2][BEAT_FILECOUNT]; | |
uint32_t ocrc; // output crc | |
union { | |
struct { // BPS exclusive fields | |
uint32_t xocrc; // expected output crc | |
size_t source_relative, target_relative; | |
}; | |
struct { // BPM exclusive fields | |
const char *bpm_path, *source_dir, *target_dir; | |
}; | |
}; | |
} BEAT_Context; | |
typedef int (*BEAT_Action)(BEAT_Context*, uint64_t); | |
/** BEAT STATE MANAGEMENT */ | |
static const char *BEAT_ErrString(int error) | |
{ // Get an error description string | |
switch(error) { | |
case BEAT_OK: return "No error"; | |
case BEAT_EOAL: return "End of action list"; | |
case BEAT_ABORTED: return "Aborted by user"; | |
case BEAT_IO_ERROR: return "Failed to read/write file"; | |
case BEAT_OVERFLOW: return "Attempted to write beyond end of file"; | |
case BEAT_BADPATCH: return "Invalid patch file"; | |
case BEAT_BADINPUT: return "Invalid input file"; | |
case BEAT_BADOUTPUT: return "Output file checksum mismatch"; | |
case BEAT_BADCHKSUM: return "File checksum failed"; | |
case BEAT_PATCH_EXPECT: return "Expected more patch data"; | |
case BEAT_OUT_OF_MEMORY: return "Out of memory"; | |
default: return "Unknown error"; | |
} | |
} | |
static int BEAT_Read(BEAT_Context *ctx, int id, void *out, size_t len, bool advance) | |
{ // Read up to `len` bytes from the context file `id` to the `out` buffer | |
if (len == 0) return BEAT_OK; | |
len = min(len, BEAT_RANGESZ(ctx, id) - ctx->foffset[id]); | |
fseek(ctx->file[id], ctx->ranges[0][id] + ctx->foffset[id], SEEK_SET); // ALWAYS use the state offset + start range | |
if (advance) ctx->foffset[id] += len; | |
return (fread(out, len, 1, ctx->file[id]) == 1) ? BEAT_OK : BEAT_IO_ERROR; | |
} | |
static int BEAT_WriteOut(BEAT_Context *ctx, const uint8_t *in, size_t len, bool advance) | |
{ // Write `len` bytes from `in` to BEAT_DSTFILE, updates the output CRC | |
if (len == 0) return BEAT_OK; | |
if (len + ctx->foffset[BEAT_DSTFILE] > BEAT_RANGESZ(ctx, BEAT_DSTFILE)) | |
return BEAT_OVERFLOW; | |
// Blindly assume all writes will be done linearly, as it should be | |
// Updates the output CRC before writing to stream | |
ctx->ocrc = crc32_adjust(in, len, ctx->ocrc); | |
fseek(ctx->file[BEAT_DSTFILE], ctx->ranges[0][BEAT_DSTFILE] + ctx->foffset[BEAT_DSTFILE], SEEK_SET); | |
if (advance) ctx->foffset[BEAT_DSTFILE] += len; | |
return (fwrite(in, len, 1, ctx->file[BEAT_DSTFILE]) == 1) ? BEAT_OK : BEAT_IO_ERROR; | |
} | |
static void BEAT_SeekOff(BEAT_Context *ctx, int id, ssize_t offset) | |
{ ctx->foffset[id] += offset; } // Seek `offset` bytes forward | |
static void BEAT_SeekAbs(BEAT_Context *ctx, int id, size_t pos) | |
{ ctx->foffset[id] = pos; } // Seek to absolute position `pos` | |
static int BEAT_NextVLI(BEAT_Context *ctx, uint64_t *vli) | |
{ // Read the next VLI in the file, update the seek position | |
uint8_t vli_rdbuf[BEAT_VLIBUFSZ], *scan = vli_rdbuf; | |
uint32_t iter = 0; | |
uint64_t ret = 0; | |
int res; | |
memset(vli_rdbuf, 0, sizeof(vli_rdbuf)); // ALWAYS clear the stack buffer | |
res = BEAT_Read(ctx, BEAT_PATCHFILE, vli_rdbuf, sizeof(vli_rdbuf), false); | |
if (res != BEAT_OK) return res; | |
while(scan < &vli_rdbuf[sizeof(vli_rdbuf)]) { | |
uint64_t val = *(scan++); | |
ret += (val & 0x7F) << iter; | |
if (val & 0x80) break; | |
iter += 7; | |
ret += (uint64_t)(1ULL << iter); | |
} | |
// Seek forward only by the amount of used bytes | |
BEAT_SeekOff(ctx, BEAT_PATCHFILE, scan - vli_rdbuf); | |
*vli = ret; | |
return res; | |
} | |
static int64_t BEAT_DecodeSigned(uint64_t val) // Extract the signed number | |
{ return (int64_t)(val >> 1) * ((val&1) ? -1 : 1); } | |
static int BEAT_NextAction(int *act, uint64_t *len, BEAT_Context *ctx) | |
{ // Decode next action word, retrieves state and length parameters | |
int res; | |
ssize_t end; | |
uint64_t val; | |
end = BEAT_RANGESZ(ctx, BEAT_PATCHFILE) - ctx->foffset[BEAT_PATCHFILE]; | |
if (end == ctx->eoal_offset) return BEAT_EOAL; | |
if (end < ctx->eoal_offset) return BEAT_PATCH_EXPECT; | |
res = BEAT_NextVLI(ctx, &val); | |
*act = val & 3; | |
*len = (val >> 2) + 1; | |
return res; | |
} | |
static int BEAT_RunActions(BEAT_Context *ctx, const BEAT_Action *acts) | |
{ // Parses an action list and runs commands specified in `acts` | |
int cmd; | |
uint64_t len; | |
while(1) { | |
int res = BEAT_NextAction(&cmd, &len, ctx); | |
if (res == BEAT_EOAL) return BEAT_EOAL; // End of patch | |
if (res != BEAT_OK) return res; // Failed to get next action | |
res = (acts[cmd])(ctx, len); // Execute next action | |
if (res == BEAT_ABORTED) return BEAT_ABORTED; // Return on user abort | |
if (res != BEAT_OK) return res; // Break on error | |
} | |
} | |
static void BEAT_ReleaseCTX(BEAT_Context *ctx) | |
{ // Release any resources associated to the context | |
free(ctx->copybuf); | |
for (int i = 0; i < BEAT_FILECOUNT; i++) { | |
if (ctx->file[i]) fclose(ctx->file[i]); | |
} | |
} | |
// BPS Specific functions | |
/** | |
Initialize the Beat File Structure | |
- verifies checksums | |
- performs further sanity checks | |
- extracts initial info | |
- leaves the file ready to begin state machine execution | |
*/ | |
static int BPS_InitCTX_Advanced(BEAT_Context *ctx, const char *bps_path, const char *in_path, const char *out_path, size_t start, size_t end) | |
{ | |
int res; | |
uint8_t read_magic[4]; | |
uint64_t vli, in_sz, metaend_off; | |
uint32_t chksum[BEAT_FILECOUNT], expected_chksum[BEAT_FILECOUNT]; | |
// Clear stackbuf | |
memset(ctx, 0, sizeof(*ctx)); | |
ctx->eoal_offset = BPS_EOAL_OFF; | |
if (end == 0) { | |
start = 0; | |
end = fs_size(bps_path); | |
} | |
// Get checksums of BPS and input files | |
DISPLAY_STATUS("Checking %s\n", bps_path); | |
if (!crc32_file(bps_path, start, end - start - 4, &chksum[BEAT_PATCHFILE])) return BEAT_BADCHKSUM; | |
DISPLAY_STATUS("Checking %s\n", in_path); | |
if (!crc32_file(in_path, 0, fs_size(in_path), &chksum[BEAT_SRCFILE])) return BEAT_BADCHKSUM; | |
// open all files | |
ctx->file[BEAT_PATCHFILE] = fopen(bps_path, "rb"); | |
ctx->ranges[0][BEAT_PATCHFILE] = start; | |
ctx->ranges[1][BEAT_PATCHFILE] = end; | |
ctx->file[BEAT_SRCFILE] = fopen(in_path, "rb"); | |
ctx->ranges[0][BEAT_SRCFILE] = 0; | |
ctx->ranges[1][BEAT_SRCFILE] = fs_size(in_path); | |
ctx->file[BEAT_DSTFILE] = fopen(out_path, "wb+"); | |
if (!ctx->file[BEAT_DSTFILE]) return BEAT_IO_ERROR; | |
// Verify BPS1 header magic | |
res = BEAT_Read(ctx, BEAT_PATCHFILE, read_magic, sizeof(read_magic), true); | |
if (res != BEAT_OK) return BEAT_IO_ERROR; | |
res = memcmp(read_magic, bps_signature, sizeof(bps_signature)); | |
if (res != 0) return BEAT_BADPATCH; | |
// Check input size | |
res = BEAT_NextVLI(ctx, &in_sz); | |
if (res != BEAT_OK) return res; | |
if (ctx->ranges[1][BEAT_SRCFILE] != in_sz) return BEAT_BADINPUT; | |
// Get expected output size | |
res = BEAT_NextVLI(ctx, &vli); | |
if (res != BEAT_OK) return res; | |
ctx->ranges[0][BEAT_DSTFILE] = 0; | |
ctx->ranges[1][BEAT_DSTFILE] = vli; | |
// Get end of metadata offset | |
res = BEAT_NextVLI(ctx, &metaend_off); | |
if (res != BEAT_OK) return res; | |
metaend_off += ctx->foffset[BEAT_PATCHFILE]; | |
// Read checksums from BPS file | |
for (int i = 0; i < BEAT_FILECOUNT; i++) { | |
BEAT_SeekAbs(ctx, BEAT_PATCHFILE, ctx->ranges[1][BEAT_PATCHFILE] - ctx->ranges[0][BEAT_PATCHFILE] - bps_chksumoffs[i]); | |
BEAT_Read(ctx, BEAT_PATCHFILE, &expected_chksum[i], sizeof(uint32_t), false); | |
} | |
// Verify patch and input checksums | |
if (chksum[BEAT_PATCHFILE] != expected_chksum[BEAT_PATCHFILE]) return BEAT_BADCHKSUM; | |
if (chksum[BEAT_SRCFILE] != expected_chksum[BEAT_SRCFILE]) return BEAT_BADCHKSUM; | |
// Initialize output checksums | |
ctx->ocrc = 0; | |
ctx->xocrc = expected_chksum[BEAT_DSTFILE]; | |
// Allocate temporary block copy buffer | |
ctx->copybuf = malloc(BEAT_COPYBUFSZ); | |
if (ctx->copybuf == NULL) return BEAT_OUT_OF_MEMORY; | |
// Seek back to the start of action stream / end of metadata | |
BEAT_SeekAbs(ctx, BEAT_PATCHFILE, metaend_off); | |
return BEAT_OK; | |
} | |
static int BPS_InitCTX(BEAT_Context *ctx, const char *bps_path, const char *in_path, const char *out_path) | |
{ | |
return BPS_InitCTX_Advanced(ctx, bps_path, in_path, out_path, 0, 0); | |
} | |
/* | |
Generic helper function to copy from `src_id` to BEAT_DSTFILE | |
Used by SourceRead, TargetRead and CreateFile | |
*/ | |
static int BEAT_BlkCopy(BEAT_Context *ctx, int src_id, uint64_t len) | |
{ | |
while(len > 0) { | |
ssize_t blksz = min(len, BEAT_COPYBUFSZ); | |
int res = BEAT_Read(ctx, src_id, ctx->copybuf, blksz, true); | |
if (res != BEAT_OK) return res; | |
res = BEAT_WriteOut(ctx, ctx->copybuf, blksz, true); | |
if (res != BEAT_OK) return res; | |
len -= blksz; | |
} | |
return BEAT_OK; | |
} | |
static int BPS_SourceRead(BEAT_Context *ctx, uint64_t len) | |
{ // This command copies bytes from the source file to the target file | |
BEAT_SeekAbs(ctx, BEAT_SRCFILE, ctx->foffset[BEAT_DSTFILE]); | |
return BEAT_BlkCopy(ctx, BEAT_SRCFILE, len); | |
} | |
/* | |
[...] the actual data is not available to the patch applier, | |
so it is stored directly inside the patch. | |
*/ | |
static int BPS_TargetRead(BEAT_Context *ctx, uint64_t len) | |
{ | |
return BEAT_BlkCopy(ctx, BEAT_PATCHFILE, len); | |
} | |
/* | |
An offset is supplied to seek the sourceRelativeOffset to the desired | |
location, and then data is copied from said offset to the target file | |
*/ | |
static int BPS_SourceCopy(BEAT_Context *ctx, uint64_t len) | |
{ | |
int res; | |
uint64_t vli; | |
int64_t offset; | |
res = BEAT_NextVLI(ctx, &vli); | |
if (res != BEAT_OK) return res; | |
offset = BEAT_DecodeSigned(vli); | |
BEAT_SeekAbs(ctx, BEAT_SRCFILE, ctx->source_relative + offset); | |
ctx->source_relative += offset + len; | |
return BEAT_BlkCopy(ctx, BEAT_SRCFILE, len); | |
} | |
/* This command treats all of the data that has already been written to the target file as a dictionary */ | |
static int BPS_TargetCopy(BEAT_Context *ctx, uint64_t len) | |
{ // the black sheep of the family, needs special care | |
int res; | |
int64_t offset; | |
uint64_t out_off, rel_off, vli; | |
res = BEAT_NextVLI(ctx, &vli); | |
if (res != BEAT_OK) return res; | |
offset = BEAT_DecodeSigned(vli); | |
out_off = ctx->foffset[BEAT_DSTFILE]; | |
rel_off = ctx->target_relative + offset; | |
if (rel_off > out_off) { | |
//printf("%d %d %d\n", offset, ctx->foffset[BEAT_DSTFILE], ctx->target_relative); | |
__builtin_trap(); | |
return BEAT_BADPATCH; // Illegal | |
} | |
while(len != 0) { | |
uint8_t *remfill; | |
ssize_t blksz, distance, remainder; | |
blksz = min(len, BEAT_COPYBUFSZ); | |
distance = min((ssize_t)(out_off - rel_off), blksz); | |
BEAT_SeekAbs(ctx, BEAT_DSTFILE, rel_off); | |
res = BEAT_Read(ctx, BEAT_DSTFILE, ctx->copybuf, distance, false); | |
if (res != BEAT_OK) return res; | |
// Fill the buffer with repeats if necessary | |
remfill = ctx->copybuf + distance; | |
remainder = blksz - distance; | |
while(remainder > 0) { | |
ssize_t remblk = min(distance, remainder); | |
memcpy(remfill, ctx->copybuf, remblk); // TODO: Inline memcpy | |
remfill += remblk; | |
remainder -= remblk; | |
} | |
BEAT_SeekAbs(ctx, BEAT_DSTFILE, out_off); | |
res = BEAT_WriteOut(ctx, ctx->copybuf, blksz, false); | |
if (res != BEAT_OK) return res; | |
rel_off += blksz; | |
out_off += blksz; | |
len -= blksz; | |
} | |
BEAT_SeekAbs(ctx, BEAT_DSTFILE, out_off); | |
ctx->target_relative = rel_off; | |
return BEAT_OK; | |
} | |
static int BPS_RunActions(BEAT_Context *ctx) | |
{ | |
static const BEAT_Action BPS_Actions[] = { // BPS action handlers | |
[BPS_SOURCEREAD] = BPS_SourceRead, | |
[BPS_TARGETREAD] = BPS_TargetRead, | |
[BPS_SOURCECOPY] = BPS_SourceCopy, | |
[BPS_TARGETCOPY] = BPS_TargetCopy | |
}; | |
int res = BEAT_RunActions(ctx, BPS_Actions); | |
if (res == BEAT_ABORTED) return BEAT_ABORTED; | |
if (res == BEAT_EOAL) // Verify hashes | |
return (ctx->ocrc == ctx->xocrc) ? BEAT_OK : BEAT_BADOUTPUT; | |
return res; // some kind of error | |
} | |
/*********************** | |
BPM Specific functions | |
***********************/ | |
static int BPM_OpenFile(BEAT_Context *ctx, int id, const char *path, size_t max_sz) | |
{ | |
if (ctx->file[id]) fclose(ctx->file[id]); | |
const char *mode = max_sz ? "wb+" : "rb"; | |
ctx->file[id] = fopen(path, mode); | |
if (!ctx->file[id]) return BEAT_IO_ERROR; | |
ctx->ranges[0][id] = 0; | |
if (max_sz > 0) { | |
ctx->ranges[1][id] = max_sz; | |
} else { | |
fseek(ctx->file[id], 0L, SEEK_END); | |
ctx->ranges[1][id] = ftell(ctx->file[id]); | |
} | |
//printf("OPENED FILE %s MODE %s ID %d RANGES %d %d\n", path, mode, id, ctx->ranges[0][id], ctx->ranges[1][id]); | |
// if a new file is opened it makes no sense | |
// to keep the old CRC | |
// a single dest file will never be created | |
// from more than a single source (and patch) | |
ctx->ocrc = 0; | |
ctx->foffset[id] = 0; | |
return BEAT_OK; | |
} | |
static int BPM_InitCTX(BEAT_Context *ctx, const char *bpm_path, const char *src_dir, const char *dst_dir) | |
{ | |
int res; | |
uint64_t metaend_off; | |
uint8_t read_magic[4]; | |
uint32_t chksum, expected_chksum; | |
memset(ctx, 0, sizeof(*ctx)); | |
ctx->bpm_path = bpm_path; | |
ctx->source_dir = src_dir; | |
ctx->target_dir = dst_dir; | |
ctx->eoal_offset = BPM_EOAL_OFF; | |
DISPLAY_STATUS("Checking %s, size %d\n", bpm_path, (int)fs_size(bpm_path)); | |
if (!crc32_file(bpm_path, 0, fs_size(bpm_path) - 4, &chksum)) return BEAT_BADCHKSUM; | |
res = BPM_OpenFile(ctx, BEAT_PATCHFILE, bpm_path, 0); | |
res = BEAT_Read(ctx, BEAT_PATCHFILE, read_magic, sizeof(read_magic), true); | |
if (res != BEAT_OK) return res; | |
res = memcmp(read_magic, bpm_signature, sizeof(bpm_signature)); | |
if (res != 0) return BEAT_BADPATCH; | |
// Get end of metadata offset | |
res = BEAT_NextVLI(ctx, &metaend_off); | |
if (res != BEAT_OK) return res; | |
metaend_off += ctx->foffset[BEAT_PATCHFILE]; | |
// Read checksums from BPS file | |
BEAT_SeekAbs(ctx, BEAT_PATCHFILE, BEAT_RANGESZ(ctx, BEAT_PATCHFILE) - 4); | |
res = BEAT_Read(ctx, BEAT_PATCHFILE, &expected_chksum, sizeof(uint32_t), false); | |
if (res != BEAT_OK) return res; | |
if (expected_chksum != chksum) return BEAT_BADCHKSUM; | |
// Allocate temporary block copy buffer | |
ctx->copybuf = malloc(BEAT_COPYBUFSZ); | |
if (ctx->copybuf == NULL) return BEAT_OUT_OF_MEMORY; | |
// Seek back to the start of action stream / end of metadata | |
BEAT_SeekAbs(ctx, BEAT_PATCHFILE, metaend_off); | |
return BEAT_OK; | |
} | |
static int BPM_NextPath(BEAT_Context *ctx, char *out, int name_len) | |
{ | |
if (name_len >= BPM_MAXPATH) return BEAT_BADPATCH; | |
int res = BEAT_Read(ctx, BEAT_PATCHFILE, out, name_len, true); | |
out[name_len] = '\0'; | |
return res; | |
} | |
static int BPM_MakeRelativePaths(BEAT_Context *ctx, char *src, char *dst, int name_len) | |
{ | |
char name[BPM_MAXPATH]; | |
int res = BPM_NextPath(ctx, name, name_len); | |
if (res != BEAT_OK) return res; | |
if (src != NULL) | |
if (snprintf(src, BPM_MAXPATH, "%s/%s", ctx->source_dir, name) < 0) return BEAT_BADPATCH; | |
if (dst != NULL) | |
if (snprintf(dst, BPM_MAXPATH, "%s/%s", ctx->target_dir, name) < 0) return BEAT_BADPATCH; | |
return res; | |
} | |
#include <errno.h> | |
static int BPM_CreatePath(BEAT_Context *ctx, uint64_t name_len) | |
{ // Create a directory | |
char path[BPM_MAXPATH]; | |
int res = BPM_MakeRelativePaths(ctx, NULL, path, name_len); | |
if (res != BEAT_OK) return res; | |
printf("CREATE PATH \"%s\"\n", path); | |
res = mkdir(path, S_IRWXU); | |
if (res < 0 && errno != EEXIST) return BEAT_IO_ERROR; | |
return BEAT_OK; | |
} | |
static int BPM_CreateFile(BEAT_Context *ctx, uint64_t name_len) | |
{ // Create a file and fill it with data provided in the BPM | |
uint64_t file_sz; | |
uint32_t checksum; | |
char path[BPM_MAXPATH]; | |
int res = BPM_MakeRelativePaths(ctx, NULL, path, name_len); | |
if (res != BEAT_OK) return res; | |
printf("CREATE FILE \"%s\"\n", path); | |
res = BEAT_NextVLI(ctx, &file_sz); // get new file size | |
if (res != BEAT_OK) return res; | |
res = BPM_OpenFile(ctx, BEAT_DSTFILE, path, file_sz); // open file as RW | |
if (res != BEAT_OK) return res; | |
res = BEAT_BlkCopy(ctx, BEAT_PATCHFILE, file_sz); // copy data to new file | |
if (res != BEAT_OK) return res; | |
res = BEAT_Read(ctx, BEAT_PATCHFILE, &checksum, sizeof(uint32_t), true); | |
if (res != BEAT_OK) return res; | |
if (ctx->ocrc != checksum) return BEAT_BADOUTPUT; // get and check CRC32 | |
return BEAT_OK; | |
} | |
static int BPM_ModifyFile(BEAT_Context *ctx, uint64_t name_len) | |
{ // Apply a BPS patch | |
uint64_t origin, bps_sz; | |
BEAT_Context bps_context; | |
char src[BPM_MAXPATH], dst[BPM_MAXPATH]; | |
int res = BPM_MakeRelativePaths(ctx, src, dst, name_len); | |
if (res != BEAT_OK) return res; | |
printf("MODIFY FILE: \"%s\"\n", dst); | |
res = BEAT_NextVLI(ctx, &origin); // get dummy(?) origin value | |
if (res != BEAT_OK) return res; | |
res = BEAT_NextVLI(ctx, &bps_sz); // get embedded BPS size | |
if (res != BEAT_OK) return res; | |
res = BPS_InitCTX_Advanced( | |
&bps_context, ctx->bpm_path, src, dst, | |
ctx->foffset[BEAT_PATCHFILE], ctx->foffset[BEAT_PATCHFILE] + bps_sz | |
); // create a BPS context using the current ranges | |
if (res == BEAT_OK) res = BPS_RunActions(&bps_context); // run if OK | |
if (res != BEAT_OK) return res; // break off if there was an error | |
BEAT_SeekOff(ctx, BEAT_PATCHFILE, bps_sz); // advance beyond the BPS | |
return BEAT_OK; | |
} | |
static int BPM_MirrorFile(BEAT_Context *ctx, uint64_t name_len) | |
{ // Copy a file from source to target without any modifications | |
uint64_t origin; | |
uint32_t checksum; | |
char src[BPM_MAXPATH], dst[BPM_MAXPATH]; | |
int res = BPM_MakeRelativePaths(ctx, src, dst, name_len); | |
if (res != BEAT_OK) return res; | |
printf("MIRROR FILE: \"%s\"\n", dst); | |
// open source and destination files, read the origin dummy | |
res = BPM_OpenFile(ctx, BEAT_SRCFILE, src, 0); | |
if (res != BEAT_OK) return res; | |
res = BPM_OpenFile(ctx, BEAT_DSTFILE, dst, ctx->ranges[1][BEAT_SRCFILE]); | |
if (res != BEAT_OK) return res; | |
res = BEAT_NextVLI(ctx, &origin); | |
if (res != BEAT_OK) return res; | |
// copy straight from source to destination | |
res = BEAT_BlkCopy(ctx, BEAT_SRCFILE, ctx->ranges[1][BEAT_SRCFILE]); | |
if (res != BEAT_OK) return res; | |
res = BEAT_Read(ctx, BEAT_PATCHFILE, &checksum, sizeof(uint32_t), true); | |
if (res != BEAT_OK) return res; | |
if (ctx->ocrc != checksum) return BEAT_BADOUTPUT; // verify checksum | |
return BEAT_OK; | |
} | |
static int BPM_RunActions(BEAT_Context *ctx) | |
{ | |
static const BEAT_Action BPM_Actions[] = { // BPM Action handlers | |
[BPM_CREATEPATH] = BPM_CreatePath, | |
[BPM_CREATEFILE] = BPM_CreateFile, | |
[BPM_MODIFYFILE] = BPM_ModifyFile, | |
[BPM_MIRRORFILE] = BPM_MirrorFile | |
}; | |
int res = BEAT_RunActions(ctx, BPM_Actions); | |
if (res == BEAT_ABORTED) return BEAT_ABORTED; | |
if (res == BEAT_EOAL) return BEAT_OK; | |
return res; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
int res; | |
BEAT_Context ctx; | |
if (argc < 4) { | |
printf("Usage: %s patch.bps input.bin output.bin\n", argv[0]); | |
return 0; | |
} | |
crc32_init(); | |
/*res = BPS_InitCTX(&ctx, argv[1], argv[2], argv[3]); | |
if (res == BEAT_OK) res = BPS_RunActions(&ctx);*/ | |
res = BPM_InitCTX(&ctx, argv[1], argv[2], argv[3]); | |
if (res == BEAT_OK) res = BPM_RunActions(&ctx); | |
if (res != BEAT_OK && res != BEAT_ABORTED) | |
printf("An error occurred while patching: %s\n", BEAT_ErrString(res)); | |
BEAT_ReleaseCTX(&ctx); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment