diff options
author | neonloop | 2021-09-06 23:44:53 +0000 |
---|---|---|
committer | neonloop | 2021-09-06 23:44:53 +0000 |
commit | 7032dd1ce78e03f1f50475be76ef8b5c642c9d01 (patch) | |
tree | a232c4674a942da0bcfceaaddb5e60356a0ab183 | |
parent | 05c5d66301f14bd8ae50490ebdc7e32e371f851b (diff) | |
download | picoarch-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-- | Makefile | 2 | ||||
-rw-r--r-- | cheat.c | 2 | ||||
-rw-r--r-- | content.c | 284 | ||||
-rw-r--r-- | content.h | 23 | ||||
-rw-r--r-- | core.c | 152 | ||||
-rw-r--r-- | core.h | 8 | ||||
-rw-r--r-- | main.c | 25 | ||||
-rw-r--r-- | menu.c | 32 | ||||
-rw-r--r-- | menu.h | 2 | ||||
-rw-r--r-- | patch.c | 290 | ||||
-rw-r--r-- | patch.h | 7 |
11 files changed, 680 insertions, 147 deletions
@@ -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 @@ -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 @@ -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) { @@ -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); @@ -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); } @@ -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); } @@ -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); @@ -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; +} @@ -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 |