aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorneonloop2021-09-06 23:44:53 +0000
committerneonloop2021-09-06 23:44:53 +0000
commit7032dd1ce78e03f1f50475be76ef8b5c642c9d01 (patch)
treea232c4674a942da0bcfceaaddb5e60356a0ab183
parent05c5d66301f14bd8ae50490ebdc7e32e371f851b (diff)
downloadpicoarch-7032dd1ce78e03f1f50475be76ef8b5c642c9d01.tar.gz
picoarch-7032dd1ce78e03f1f50475be76ef8b5c642c9d01.tar.bz2
picoarch-7032dd1ce78e03f1f50475be76ef8b5c642c9d01.zip
Adds ips/bps softpatching
Patches named like the content will be auto-applied in memory when the game is loaded. Example: /roms/game.gba will apply patches named /roms/game.ips, /roms/game.ips1, /roms/game.IPS2, /roms/game.bps, etc. This only happens in memory, original files are unmodified. Patches are loaded in case-insensitive alphabetical order. Note that .ips12 loads before .ips2, but after .ips02. Only supports cores that load from memory (see libretro page at https://docs.libretro.com/guides/softpatching/ for compatibility list)
-rw-r--r--Makefile2
-rw-r--r--cheat.c2
-rw-r--r--content.c284
-rw-r--r--content.h23
-rw-r--r--core.c152
-rw-r--r--core.h8
-rw-r--r--main.c25
-rw-r--r--menu.c32
-rw-r--r--menu.h2
-rw-r--r--patch.c290
-rw-r--r--patch.h7
11 files changed, 680 insertions, 147 deletions
diff --git a/Makefile b/Makefile
index e67cb70..19d2e38 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ SYSROOT = $(shell $(CC) --print-sysroot)
PROCS = -j4
-OBJS = libpicofe/input.o libpicofe/in_sdl.o libpicofe/linux/in_evdev.o libpicofe/linux/plat.o libpicofe/fonts.o libpicofe/readpng.o libpicofe/config_file.o cheat.o config.o core.o menu.o main.o options.o overrides.o scale.o unzip.o util.o
+OBJS = libpicofe/input.o libpicofe/in_sdl.o libpicofe/linux/in_evdev.o libpicofe/linux/plat.o libpicofe/fonts.o libpicofe/readpng.o libpicofe/config_file.o cheat.o config.o content.o core.o menu.o main.o options.o overrides.o patch.o scale.o unzip.o util.o
BIN = picoarch
diff --git a/cheat.c b/cheat.c
index 6b57993..4304593 100644
--- a/cheat.c
+++ b/cheat.c
@@ -12,7 +12,7 @@
static size_t parse_count(FILE *file) {
size_t count = 0;
- fscanf(file, " cheats = %d\n", &count);
+ fscanf(file, " cheats = %ld\n", &count);
return count;
}
diff --git a/content.c b/content.c
new file mode 100644
index 0000000..b9a3c4a
--- /dev/null
+++ b/content.c
@@ -0,0 +1,284 @@
+#include <dirent.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <string.h>
+#include "core.h"
+#include "content.h"
+#include "patch.h"
+#include "unzip.h"
+#include "util.h"
+
+static int alloc_readfile(const char *path, void **buf, size_t *size) {
+ int ret = -1;
+ FILE *file = fopen(path, "r");
+
+ if (!file) {
+ goto finish;
+ }
+
+ fseek(file, 0, SEEK_END);
+ *size = ftell(file);
+ rewind(file);
+
+ if (!*size) {
+ ret = 0;
+ goto finish;
+ }
+
+ *buf = malloc(*size);
+ if (!buf) {
+ PA_ERROR("Couldn't allocate memory for file: %s\n", path);
+ goto finish;
+ }
+
+ if (*size != fread(*buf, sizeof(uint8_t), *size, file)) {
+ PA_ERROR("Error reading file: %s\n", path);
+ goto finish;
+ }
+ ret = 0;
+
+finish:
+ if (ret) {
+ free(*buf);
+ *buf = NULL;
+ *size = 0;
+ }
+
+ if (file)
+ fclose(file);
+
+ return ret;
+}
+
+static int content_load_zip(struct content *content) {
+ const char *ext = NULL;
+ int i = 0;
+ bool haszip = false;
+ int ret = -1;
+ FILE *f = NULL;
+ const char **extensions = core_extensions();
+
+ if (extensions && has_suffix_i(content->path, ".zip")) {
+ while((ext = extensions[i++])) {
+ if (!strcmp(ext, "zip")) {
+ haszip = true;
+ break;
+ }
+ }
+
+ if (!haszip) {
+ f = fopen(content->path, "r");
+ if (!f) {
+ goto finish;
+ }
+
+ free(content->tmpfile);
+
+ content->tmpfile = calloc(MAX_PATH, sizeof(*content->tmpfile));
+ if (!content->tmpfile) {
+ PA_ERROR("Couldn't allocate memory for unzipped path\n");
+ goto finish;
+ }
+
+ if (unzip_tmp(f, extensions, content->tmpfile, MAX_PATH)) {
+ free(content->tmpfile);
+ content->tmpfile = NULL;
+ goto finish;
+ }
+
+ ret = 0;
+ }
+ }
+
+finish:
+ if (f)
+ fclose(f);
+
+ return ret;
+}
+
+static char *content_patch_pattern;
+
+static int content_patch_filter(const struct dirent *ent) {
+ const char *p;
+
+ if (content_patch_pattern &&
+ !strncmp(ent->d_name, content_patch_pattern, strlen(content_patch_pattern))) {
+ p = ent->d_name + strlen(content_patch_pattern);
+
+ return !strncasecmp(p, ".ips", sizeof(".ips") - 1) ||
+ !strncasecmp(p, ".bps", sizeof(".bps") - 1);
+ }
+
+ return 0;
+}
+
+static int content_patch_compare(const struct dirent **d1, const struct dirent **d2) {
+ return strcasecmp((*d1)->d_name, (*d2)->d_name);
+}
+
+static int content_patch(const struct content *content, void **out, size_t *out_size) {
+ struct dirent **namelist;
+ char pattern[MAX_PATH];
+ char patch_path[MAX_PATH];
+ int n = 0;
+ char *path = strdup(content->path);
+ char *dir;
+
+ const void *in = content->data;
+ size_t in_size = content->size;
+ void *patch_data = NULL;
+ size_t patch_size = 0;
+ void *patched = NULL;
+ size_t patched_size = 0;
+
+ int i = 0;
+ int ret = -1;
+
+ dir = dirname(path);
+ content_based_name(content, pattern, sizeof(pattern), NULL, NULL, "");
+
+ content_patch_pattern = basename(pattern);
+ n = scandir(dir, &namelist, content_patch_filter, content_patch_compare);
+ content_patch_pattern = NULL;
+
+ if (n < 0) {
+ PA_ERROR("Error reading directory: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ if (n == 0) {
+ goto finish;
+ }
+
+ for (i = 0; i < n; i++) {
+ free(patch_data);
+ snprintf(patch_path, sizeof(patch_path), "%s%s%s", dir, "/", namelist[i]->d_name);
+
+ if (alloc_readfile(patch_path, &patch_data, &patch_size)) {
+ goto finish;
+ }
+
+ if (patched) {
+ if (in != content->data)
+ free((void *)in);
+
+ in = patched;
+ in_size = patched_size;
+ patched = NULL;
+ }
+
+ if (patch(in, in_size, patch_data, patch_size, &patched, &patched_size))
+ goto finish;
+
+ PA_INFO("Applied %s\n", patch_path);
+ }
+
+ *out = patched;
+ *out_size = patched_size;
+
+ ret = 0;
+finish:
+
+ while (n--) {
+ free(namelist[n]);
+ }
+ free(namelist);
+
+ if (in != content->data)
+ free((void *)in);
+
+ free(patch_data);
+ free(path);
+ return ret;
+}
+
+struct content *content_init(const char *path) {
+ struct content* content = calloc(1, sizeof(struct content));
+
+ if (content) {
+ strncpy((char *)content->path, path, sizeof(content->path) - 1);
+ }
+ return content;
+}
+
+void content_based_name(const struct content *content,
+ char *buf, size_t len,
+ const char *basedir, const char *subdir,
+ const char *new_extension) {
+ char filename[MAX_PATH];
+ char *path = strdup(content->path);
+ char *dot;
+
+ if (basedir) {
+ if (!subdir)
+ subdir = "";
+
+ strncpy(filename, basename(path), sizeof(filename));
+ } else {
+ basedir = "";
+ subdir = "";
+ strncpy(filename, path, sizeof(filename));
+ }
+
+ filename[sizeof(filename) - 1] = 0;
+
+ dot = strrchr(filename, '.');
+ if (dot)
+ *dot = 0;
+
+ snprintf(buf, len, "%s%s%s%s", basedir, subdir, filename, new_extension);
+
+ free(path);
+}
+
+int content_load_game_info(struct content *content, struct retro_game_info *info, bool needs_fullpath) {
+ const char *path;
+ int ret = -1;
+ PA_INFO("Loading %s\n", content->path);
+
+ content_load_zip(content);
+ path = content->tmpfile ? content->tmpfile : content->path;
+
+ if (needs_fullpath) {
+ info->path = path;
+ } else {
+ void *patched_data = NULL;
+ size_t patched_size = 0;
+
+ free(content->data);
+
+ if (alloc_readfile(path, &content->data, &content->size)) {
+ PA_ERROR("Error reading content file: %s\n", path);
+ goto finish;
+ }
+
+ if (!content_patch(content, &patched_data, &patched_size) && patched_data) {
+ free(content->data);
+ content->data = patched_data;
+ content->size = patched_size;
+ }
+
+ info->path = path;
+ info->data = content->data;
+ info->size = content->size;
+ }
+ ret = 0;
+
+finish:
+ return ret;
+}
+
+void content_free(struct content *content) {
+ if (!content)
+ return;
+
+ if (content->tmpfile) {
+ remove(content->tmpfile);
+ }
+
+ free(content->tmpfile);
+ free(content->data);
+ free(content);
+}
diff --git a/content.h b/content.h
new file mode 100644
index 0000000..a57c039
--- /dev/null
+++ b/content.h
@@ -0,0 +1,23 @@
+#ifndef CONTENT_H
+#define CONTENT_H
+
+#include "libretro.h"
+#include "main.h"
+
+struct content {
+ const char path[MAX_PATH];
+
+ char *tmpfile;
+ void *data;
+ size_t size;
+};
+
+struct content *content_init(const char *path);
+void content_based_name(const struct content *content,
+ char *buf, size_t len,
+ const char *basedir, const char *subdir,
+ const char *new_extension);
+int content_load_game_info(struct content *content, struct retro_game_info *info, bool needs_fullpath);
+void content_free(struct content *content);
+
+#endif
diff --git a/core.c b/core.c
index 3a801e5..6be799f 100644
--- a/core.c
+++ b/core.c
@@ -13,12 +13,11 @@
#include "options.h"
#include "overrides.h"
#include "plat.h"
-#include "unzip.h"
#include "util.h"
struct core_cbs current_core;
char core_path[MAX_PATH];
-char content_path[MAX_PATH];
+struct content *content;
static struct string_list *extensions;
struct cheats *cheats;
@@ -32,110 +31,21 @@ int resume_slot = -1;
static char config_dir[MAX_PATH];
static char save_dir[MAX_PATH];
static char system_dir[MAX_PATH];
-static char temp_rom[MAX_PATH];
static struct retro_disk_control_ext_callback disk_control_ext;
static uint32_t buttons = 0;
-static void core_handle_zip(const char *path, struct retro_game_info *game_info, FILE** file) {
- const char *ext = NULL;
- int index = 0;
- bool haszip = false;
- FILE *dest = NULL;
-
- if (extensions && has_suffix_i(path, ".zip")) {
- while((ext = extensions->list[index++])) {
- if (!strcmp(ext, "zip")) {
- haszip = true;
- break;
- }
- }
-
- if (!haszip) {
- if (!unzip_tmp(*file, extensions->list, temp_rom, MAX_PATH)) {
- game_info->path = temp_rom;
- dest = fopen(temp_rom, "r");
- if (dest) {
- fclose(*file);
- *file = dest;
- }
- }
- }
- }
-}
-
-static int core_load_game_info(const char *path, struct retro_game_info *game_info) {
- struct retro_system_info info = {0};
- FILE *file = fopen(path, "rb");
- int ret = -1;
-
- if (!file) {
- PA_ERROR("Couldn't load content: %s\n", strerror(errno));
- goto finish;
- }
-
- PA_INFO("Loading %s\n", path);
- game_info->path = path;
-
+static int core_load_game_info(struct content *content, struct retro_game_info *game_info) {
+ struct retro_system_info info = {};
current_core.retro_get_system_info(&info);
- core_handle_zip(path, game_info, &file);
-
- fseek(file, 0, SEEK_END);
- game_info->size = ftell(file);
- rewind(file);
-
- if (!info.need_fullpath) {
- void *game_data = malloc(game_info->size);
-
- if (!game_data) {
- PA_ERROR("Couldn't allocate memory for content\n");
- goto finish;
- }
-
- if (fread(game_data, 1, game_info->size, file) != game_info->size) {
- PA_ERROR("Couldn't read file: %s\n", strerror(errno));
- goto finish;
- }
-
- game_info->data = game_data;
- }
-
- ret = 0;
-finish:
- if (file)
- fclose(file);
-
- return ret;
-}
-
-static void core_free_game_info(struct retro_game_info *game_info) {
- if (game_info->data) {
- free((void *)game_info->data);
- game_info->data = NULL;
- game_info->size = 0;
- }
-}
-
-static void gamepak_related_name(char *buf, size_t len, const char *subdir, const char *new_extension)
-{
- char filename[MAX_PATH];
- char *dot;
-
- strncpy(filename, basename(content_path), sizeof(filename));
- filename[sizeof(filename) - 1] = 0;
-
- dot = strrchr(filename, '.');
- if (dot)
- *dot = 0;
-
- snprintf(buf, len, "%s%s%s%s", save_dir, subdir, filename, new_extension);
+ return content_load_game_info(content, game_info, info.need_fullpath);
}
void config_file_name(char *buf, size_t len, int is_game)
{
- if (is_game && content_path[0]) {
- gamepak_related_name(buf, len, "", ".cfg");
+ if (is_game && content) {
+ content_based_name(content, buf, len, save_dir, NULL, ".cfg");
} else {
snprintf(buf, len, "%s%s", config_dir, "picoarch.cfg");
}
@@ -155,7 +65,7 @@ void sram_write(void) {
return;
}
- gamepak_related_name(filename, MAX_PATH, "", ".sav");
+ content_based_name(content, filename, MAX_PATH, save_dir, NULL, ".sav");
sram_file = fopen(filename, "w");
if (!sram_file) {
@@ -184,7 +94,7 @@ void sram_read(void) {
return;
}
- gamepak_related_name(filename, MAX_PATH, "", ".sav");
+ content_based_name(content, filename, MAX_PATH, save_dir, NULL, ".sav");
sram_file = fopen(filename, "r");
if (!sram_file) {
@@ -208,7 +118,7 @@ void state_file_name(char *name, size_t size, int slot) {
char extension[5] = {0};
snprintf(extension, 5, ".st%d", slot);
- gamepak_related_name(name, MAX_PATH, "", extension);
+ content_based_name(content, name, MAX_PATH, save_dir, NULL, extension);
}
int state_read(void) {
@@ -341,18 +251,22 @@ bool disc_switch_index(unsigned index) {
bool disc_replace_index(unsigned index, const char *content_path) {
bool ret = false;
- struct retro_game_info info = {0};
+ struct retro_game_info info = {};
+ struct content *content;
if (!disk_control_ext.replace_image_index)
return false;
- if (core_load_game_info(content_path, &info)) {
+ content = content_init(content_path);
+ if (!content)
+ goto finish;
+
+ if (core_load_game_info(content, &info))
goto finish;
- }
ret = disk_control_ext.replace_image_index(index, &info);
finish:
- core_free_game_info(&info);
+ content_free(content);
return ret;
}
@@ -607,8 +521,8 @@ void core_extract_name(const char* core_file, char *buf, size_t len) {
}
}
-int core_load(const char *corefile) {
- struct retro_system_info info = {0};
+int core_open(const char *corefile) {
+ struct retro_system_info info = {};
void (*set_environment)(retro_environment_t) = NULL;
void (*set_video_refresh)(retro_video_refresh_t) = NULL;
@@ -663,24 +577,26 @@ int core_load(const char *corefile) {
set_input_poll(pa_input_poll);
set_input_state(pa_input_state);
- current_core.retro_init();
-
current_core.retro_get_system_info(&info);
if (info.valid_extensions)
extensions = string_split(info.valid_extensions, '|');
+ return 0;
+}
+
+void core_load(void) {
+ current_core.retro_init();
current_core.initialized = true;
PA_INFO("Finished loading core\n");
- return 0;
}
-int core_load_content(const char *path) {
- struct retro_game_info game_info = {0};
- struct retro_system_av_info av_info = {0};
+int core_load_content(struct content *content) {
+ struct retro_game_info game_info = {};
+ struct retro_system_av_info av_info = {};
int ret = -1;
char cheats_path[MAX_PATH] = {0};
- if (core_load_game_info(path, &game_info)) {
+ if (core_load_game_info(content, &game_info)) {
goto finish;
}
@@ -689,7 +605,7 @@ int core_load_content(const char *path) {
goto finish;
}
- gamepak_related_name(cheats_path, sizeof(cheats_path), "cheats/", ".cht");
+ content_based_name(content, cheats_path, sizeof(cheats_path), save_dir, "cheats/", ".cht");
if (cheats_path[0] != '\0') {
cheats = cheats_load(cheats_path);
core_apply_cheats(cheats);
@@ -709,12 +625,11 @@ int core_load_content(const char *path) {
aspect_ratio = av_info.geometry.aspect_ratio;
#ifdef MMENU
- gamepak_related_name(save_template_path, MAX_PATH, "", ".st%i");
+ content_based_name(content, save_template_path, MAX_PATH, save_dir, NULL, ".st%i");
#endif
ret = 0;
finish:
- core_free_game_info(&game_info);
return ret;
}
@@ -740,11 +655,8 @@ void core_unload_content(void) {
cheats = NULL;
current_core.retro_unload_game();
- if (temp_rom[0]) {
- remove(temp_rom);
- temp_rom[0] = '\0';
- }
- content_path[0] = '\0';
+ content_free(content);
+ content = NULL;
}
const char **core_extensions(void) {
diff --git a/core.h b/core.h
index 8958e9e..847510b 100644
--- a/core.h
+++ b/core.h
@@ -2,6 +2,7 @@
#define _CORE_H__
#include "cheat.h"
+#include "content.h"
#include "libretro.h"
#include "main.h"
@@ -32,7 +33,7 @@ struct core_cbs {
extern struct core_cbs current_core;
extern char core_path[MAX_PATH];
-extern char content_path[MAX_PATH];
+extern struct content *content;
extern struct cheats *cheats;
extern double sample_rate;
@@ -60,8 +61,9 @@ bool disc_switch_index(unsigned index);
bool disc_replace_index(unsigned index, const char *content_path);
void core_extract_name(const char* core_file, char *buf, size_t len);
-int core_load(const char *corefile);
-int core_load_content(const char *path);
+int core_open(const char *corefile);
+void core_load(void);
+int core_load_content(struct content *content);
void core_apply_cheats(struct cheats *cheats);
void core_unload_content(void);
const char **core_extensions(void);
diff --git a/main.c b/main.c
index 08f83d1..7bdcd07 100644
--- a/main.c
+++ b/main.c
@@ -5,6 +5,7 @@
#include <unistd.h>
#include "core.h"
#include "config.h"
+#include "content.h"
#include "libpicofe/config_file.h"
#include "libpicofe/input.h"
#include "main.h"
@@ -326,10 +327,10 @@ void handle_emu_action(emu_action action)
toggle_fast_forward(1); /* Force FF off */
sram_write();
#ifdef MMENU
- if (mmenu) {
+ if (mmenu && content && content->path) {
ShowMenu_t ShowMenu = (ShowMenu_t)dlsym(mmenu, "ShowMenu");
SDL_Surface *screen = SDL_GetVideoSurface();
- MenuReturnStatus status = ShowMenu(content_path, state_allowed() ? save_template_path : NULL, screen, kMenuEventKeyDown);
+ MenuReturnStatus status = ShowMenu(content->path, state_allowed() ? save_template_path : NULL, screen, kMenuEventKeyDown);
char disc_path[256];
ChangeDisc_t ChangeDisc = (ChangeDisc_t)dlsym(mmenu, "ChangeDisc");
@@ -501,6 +502,8 @@ static void adjust_audio(void) {
}
int main(int argc, char **argv) {
+ char content_path[MAX_PATH];
+
if (argc > 1) {
if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
printf("Usage: picoarch [libretro_core [content]]\n");
@@ -525,20 +528,28 @@ int main(int argc, char **argv) {
core_extract_name(core_path, core_name, sizeof(core_name));
- set_defaults();
-
- if (core_load(core_path)) {
+ if (core_open(core_path)) {
quit(-1);
}
if (argc > 2 && argv[2]) {
strncpy(content_path, argv[2], sizeof(content_path) - 1);
} else {
- if (menu_select_content())
+ if (menu_select_content(content_path, sizeof(content_path)))
quit(-1);
}
- if (core_load_content(content_path)) {
+ content = content_init(content_path);
+ if (!content) {
+ PA_ERROR("Couldn't allocate memory for content path\n");
+ quit(-1);
+ }
+
+ set_defaults();
+ load_config();
+ core_load();
+
+ if (core_load_content(content)) {
quit(-1);
}
diff --git a/menu.c b/menu.c
index 2674007..1dac041 100644
--- a/menu.c
+++ b/menu.c
@@ -245,21 +245,22 @@ int hidden_file_filter(struct dirent **namelist, int count, const char *basedir)
const char *select_content(void) {
const char *fname = NULL;
+ char content_path[MAX_PATH];
const char **extensions = core_extensions();
const char **exts_with_zip = NULL;
int i = 0, size = 0;
- if (content_path[0] == '\0') {
- if (getenv("CONTENT_DIR")) {
- strncpy(content_path, getenv("CONTENT_DIR"), sizeof(content_path) - 1);
+ if (content && content->path) {
+ strncpy(content_path, content->path, sizeof(content_path) - 1);
+ } else if (getenv("CONTENT_DIR")) {
+ strncpy(content_path, getenv("CONTENT_DIR"), sizeof(content_path) - 1);
#ifdef CONTENT_DIR
- } else {
- strncpy(content_path, CONTENT_DIR, sizeof(content_path) - 1);
+ } else {
+ strncpy(content_path, CONTENT_DIR, sizeof(content_path) - 1);
#else
- } else if (getenv("HOME")) {
- strncpy(content_path, getenv("HOME"), sizeof(content_path) - 1);
+ } else if (getenv("HOME")) {
+ strncpy(content_path, getenv("HOME"), sizeof(content_path) - 1);
#endif
- }
}
if (extensions) {
@@ -286,7 +287,7 @@ const char *select_content(void) {
return fname;
}
-int menu_select_content(void) {
+int menu_select_content(char *filename, size_t len) {
const char *fname = NULL;
int ret = -1;
@@ -295,9 +296,7 @@ int menu_select_content(void) {
if (!fname)
goto finish;
- strncpy(content_path, fname, sizeof(content_path) - 1);
- set_defaults();
- load_config();
+ strncpy(filename, fname, len - 1);
if (g_autostateld_opt)
resume_slot = 0;
ret = 0;
@@ -318,11 +317,16 @@ static int menu_loop_select_content(int id, int keys) {
return -1;
core_unload_content();
- strncpy(content_path, fname, sizeof(content_path) - 1);
+
+ content = content_init(fname);
+ if (!content) {
+ PA_ERROR("Couldn't allocate memory for content\n");
+ quit(-1);
+ }
set_defaults();
- if (core_load_content(fname)) {
+ if (core_load_content(content)) {
quit(-1);
}
diff --git a/menu.h b/menu.h
index 33950cc..d3821a2 100644
--- a/menu.h
+++ b/menu.h
@@ -7,7 +7,7 @@
int menu_init(void);
void menu_loop(void);
int menu_select_core(void);
-int menu_select_content(void);
+int menu_select_content(char *filename, size_t len);
void menu_begin(void);
void menu_end(void);
void menu_finish(void);
diff --git a/patch.c b/patch.c
new file mode 100644
index 0000000..e594444
--- /dev/null
+++ b/patch.c
@@ -0,0 +1,290 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "main.h"
+#include "patch.h"
+#include "util.h"
+
+typedef int (*patch_func)(const uint8_t *in, size_t in_size,
+ const uint8_t *patch, size_t patch_size,
+ uint8_t *out, size_t *out_size);
+
+static uint32_t crc32_table[256];
+
+static void crc32_build_table(void) {
+ uint32_t i, j;
+
+ for (i = 0; i < array_size(crc32_table); i++) {
+ crc32_table[i] = i;
+
+ for (j = 8; j; j--) {
+ crc32_table[i] = crc32_table[i] & 1
+ ? (crc32_table[i] >> 1) ^ 0xEDB88320
+ : crc32_table[i] >> 1;
+ }
+ }
+}
+
+static uint32_t crc32(const uint8_t *buf, size_t len) {
+ uint32_t hash = -1;
+
+ if (crc32_table[1] == 0)
+ crc32_build_table();
+
+ while (len--) {
+ int index = (uint8_t)(hash ^ *buf);
+ hash = crc32_table[index] ^ (hash >> 8);
+ buf++;
+ }
+
+ return ~hash;
+}
+
+enum bps_mode {
+ BPS_MODE_SRC_READ,
+ BPS_MODE_DST_READ,
+ BPS_MODE_SRC_COPY,
+ BPS_MODE_DST_COPY,
+};
+
+static uint64_t bps_decode(const uint8_t *patch, size_t *index) {
+ uint64_t data = 0, shift = 1;
+
+ while (1) {
+ uint8_t x = patch[(*index)++];
+ data += (x & 0x7f) * shift;
+ if (x & 0x80) break;
+ shift <<= 7;
+ data += shift;
+ }
+
+ return data;
+}
+
+static int patch_bps(const uint8_t *in, size_t in_size,
+ const uint8_t *patch, size_t patch_size,
+ uint8_t *out, size_t *out_size) {
+ size_t src_size, target_size, metadata_size;
+ size_t written = 0;
+ size_t src_rel_offset = 0, dst_rel_offset = 0;
+ uint32_t source_hash = 0, patch_hash = 0, target_hash = 0;
+ size_t i = 0;
+ int j = 0;
+
+ if (patch_size < 19) {
+ return -1;
+ }
+
+ i = 4; /* skip BPS1 header */
+
+ src_size = bps_decode(patch, &i);
+ if (src_size != in_size) {
+ PA_ERROR("Input size does not match expected size: %d != %d\n", in_size, src_size);
+ return -1;
+ }
+
+ target_size = bps_decode(patch, &i);
+
+ if (!out) {
+ *out_size = target_size;
+ return 0;
+ } else if (*out_size != target_size) {
+ PA_ERROR("Output size does not match expected size: %d != %d\n", in_size, target_size);
+ return -1;
+ }
+
+ metadata_size = bps_decode(patch, &i);
+ i += metadata_size;
+
+ while(i < patch_size - 12) {
+ size_t length = bps_decode(patch, &i);
+ enum bps_mode mode = length & 3;
+ int offset = 0;
+ length = (length >> 2) + 1;
+
+ switch(mode) {
+ case BPS_MODE_SRC_READ:
+ while (length--) {
+ out[written] = in[written];
+ written++;
+ }
+ break;
+ case BPS_MODE_DST_READ:
+ while (length--)
+ out[written++] = patch[i++];
+ break;
+ case BPS_MODE_SRC_COPY:
+ case BPS_MODE_DST_COPY:
+ offset = bps_decode(patch, &i);
+ offset = offset & 1 ? -(offset >> 1) : (offset >> 1);
+
+ if (mode == BPS_MODE_SRC_COPY) {
+ src_rel_offset += offset;
+ while (length--)
+ out[written++] = in[src_rel_offset++];
+ } else {
+ dst_rel_offset += offset;
+ while (length--)
+ out[written++] = out[dst_rel_offset++];
+ }
+ break;
+ }
+ }
+
+ if (written != target_size) {
+ PA_ERROR("Did not write expected number of bytes: %d != %d\n", written, target_size);
+ return -1;
+ }
+
+ for (j = 0; j < 32; j += 8) {
+ source_hash |= patch[i++] << j;
+ }
+
+ for (j = 0; j < 32; j += 8) {
+ target_hash |= patch[i++] << j;
+ }
+
+ for (j = 0; j < 32; j += 8) {
+ patch_hash |= patch[i++] << j;
+ }
+
+ if (source_hash != crc32(in, in_size)) {
+ PA_ERROR("Input hash did not match: 0x%X != 0x%X\n", source_hash, crc32(in, in_size));
+ return -1;
+ }
+
+ if (target_hash != crc32(out, *out_size)) {
+ PA_ERROR("Output hash did not match: 0x%X != 0x%X\n", target_hash, crc32(out, *out_size));
+ return -1;
+ }
+
+ if (patch_hash != crc32(patch, patch_size - 4)) {
+ PA_ERROR("Patch hash did not match: 0x%X != 0x%X\n", patch_hash, crc32(patch, patch_size));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int patch_ips(const uint8_t *in, size_t in_size,
+ const uint8_t *patch, size_t patch_size,
+ uint8_t *out, size_t *out_size) {
+ int i = 0;
+ int ret = -1;
+ uint32_t offset = 0;
+ uint16_t len = 0;
+
+ /* Needs at last PATCH and EOF */
+ if (patch_size < 8)
+ return -1;
+
+ if (out == NULL && !strncmp((const char *)&patch[patch_size - 6], "EOF", 3)) {
+ i = patch_size - 3;
+ *out_size = patch[i++] << 16;
+ *out_size |= patch[i++] << 8;
+ *out_size |= patch[i++];
+ return 0;
+ }
+
+ i = 5; /* Skip PATCH header */
+
+ while (i <= patch_size - 3) {
+ offset = patch[i++] << 16;
+ offset |= patch[i++] << 8;
+ offset |= patch[i++];
+
+ if (offset == 0x454f46) {
+ ret = 0;
+ break;
+ } else if (i <= patch_size - 2) {
+ len = patch[i++] << 8;
+ len |= patch[i++];
+
+ if (len) {
+ if (i > patch_size - len)
+ break;
+
+ if (out) {
+ while (len-- && offset < *out_size) {
+ out[offset++] = patch[i++];
+ }
+ } else {
+ i += len;
+ }
+ } else if (i <= patch_size - 3) { /* RLE */
+ len = patch[i++] << 8;
+ len |= patch[i++];
+
+ if (out) {
+ while (len-- && offset < *out_size) {
+ out[offset++] = patch[i];
+ }
+ }
+ i++;
+ } else {
+ break;
+ }
+
+ if (!out && *out_size < offset + len)
+ *out_size = offset + len;
+ }
+ }
+
+ if (!out) {
+ if (ret) {
+ *out_size = 0;
+ } else if (*out_size < in_size) {
+ *out_size = in_size;
+ }
+ }
+
+ return ret;
+}
+
+int patch(const void *in, size_t in_size,
+ const uint8_t *patch, size_t patch_size,
+ void **out, size_t *out_size) {
+ int ret = -1;
+ patch_func patch_apply;
+
+ if (patch_size >= 8 && !strncmp((const char *)patch, "PATCH", sizeof("PATCH") - 1)) {
+ patch_apply = patch_ips;
+ } else if (patch_size >= 19 && !strncmp((const char *)patch, "BPS1", sizeof("BPS1") - 1)) {
+ patch_apply = patch_bps;
+ } else {
+ PA_ERROR("Couldn't detect format of patch\n");
+ goto finish;
+ }
+
+ if (patch_apply(in, in_size, patch, patch_size, NULL, out_size)) {
+ PA_ERROR("Couldn't calculate output file size\n");
+ goto finish;
+ }
+
+ if (*out_size == 0) {
+ goto finish;
+ }
+
+ *out = (uint8_t *)calloc(*out_size, sizeof(uint8_t));
+ if (!*out) {
+ PA_ERROR("Couldn't allocate memory for patched output\n");
+ goto finish;
+ }
+
+ memcpy(*out, in, MIN(in_size, *out_size));
+
+ if (patch_apply(in, in_size, patch, patch_size, *out, out_size)) {
+ PA_ERROR("Error patching output\n");
+ goto finish;
+ }
+ ret = 0;
+
+finish:
+ if (ret) {
+ free(*out);
+ *out = NULL;
+ *out_size = 0;
+ }
+
+ return ret;
+}
diff --git a/patch.h b/patch.h
new file mode 100644
index 0000000..47d2aad
--- /dev/null
+++ b/patch.h
@@ -0,0 +1,7 @@
+#ifndef PATCH_H
+#define PATCH_H
+
+int patch(const void *in, size_t in_size,
+ const uint8_t *patch, size_t patch_size,
+ void **out, size_t *out_size);
+#endif