Skip to content

Instantly share code, notes, and snippets.

@Wolfvak
Last active December 27, 2019 03:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Wolfvak/6f372ed67f106ef64d527f5afc44d521 to your computer and use it in GitHub Desktop.
Save Wolfvak/6f372ed67f106ef64d527f5afc44d521 to your computer and use it in GitHub Desktop.
simple and (hopefully) not too broken BPS patcher
/**
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