micetools/src/micetools/micepatch/main.c

304 lines
10 KiB
C

#include "../lib/json/json.h"
#include "../lib/util/hex.h"
#include "stdbool.h"
#include "stdio.h"
#include "string.h"
typedef struct {
size_t offset;
size_t count;
unsigned char* from;
unsigned char* to;
} patch_t;
typedef struct {
char* name;
char* description;
bool apply;
size_t nopatches;
patch_t patches[];
} patchset_t;
typedef struct {
size_t nopatchsets;
patchset_t** patchsets;
} patches_t;
bool fetch(json_value* object, char* name, json_value** value) {
if (object->type != json_object) return false;
for (size_t j = 0; j < object->u.object.length; j++) {
json_object_entry* entry = &(object->u.object.values[j]);
if (strncmp(name, entry->name, entry->name_length) == 0) {
*value = entry->value;
return true;
}
}
return false;
}
bool fetch_string(json_value* object, char* name, char** value) {
json_value* fetched;
if (!fetch(object, name, &fetched)) return false;
if (fetched->type != json_string) return false;
*value = fetched->u.string.ptr;
return true;
}
bool fetch_int(json_value* object, char* name, size_t* value) {
json_value* fetched;
if (!fetch(object, name, &fetched)) return false;
if (fetched->type != json_integer) return false;
*value = fetched->u.integer;
return true;
}
bool fetch_bool(json_value* object, char* name, bool* value) {
json_value* fetched;
if (!fetch(object, name, &fetched)) return false;
if (fetched->type != json_boolean) return false;
*value = fetched->u.boolean;
return true;
}
void free_patches(patches_t* patches) {
if (patches->nopatchsets == 0) return;
for (int i = patches->nopatchsets - 1; i >= 0; i--) {
if (patches->patchsets[i] != NULL) {
for (int j = 0; j < patches->patchsets[i]->nopatches; j++) {
if (patches->patchsets[i]->patches[j].from != NULL) free(patches->patchsets[i]->patches[j].from);
if (patches->patchsets[i]->patches[j].to != NULL) free(patches->patchsets[i]->patches[j].to);
}
free(patches->patchsets[i]);
}
}
patches->nopatchsets = 0;
}
bool parse_patches(patches_t* patches, json_value** set_json, int set_count, char* error) {
patches->nopatchsets = set_count;
patches->patchsets = (patchset_t**)malloc(set_count * sizeof(patchset_t*));
for (int i = 0; i < set_count; i++) patches->patchsets[i] = NULL;
error[0] = '\0';
for (int i = 0; i < set_count; i++) {
char *name, *description;
if (!fetch_string(set_json[i], "name", &name)) {
snprintf(error, json_error_max, "'name' missing for patch %d", i);
goto failed;
}
if (!fetch_string(set_json[i], "description", &description)) {
snprintf(error, json_error_max, "'description' missing for patch %d (%s)", i, name);
goto failed;
}
bool apply;
if (!fetch_bool(set_json[i], "apply", &apply)) {
snprintf(error, json_error_max, "'apply' missing for patch %d (%s)", i, name);
goto failed;
}
json_value* set_patches;
if (!fetch(set_json[i], "patches", &set_patches) || set_patches->type != json_array) {
snprintf(error, json_error_max, "'patches' missing for patch %d (%s)", i, name);
goto failed;
}
int count = set_patches->u.array.length;
patchset_t* patchset = (patchset_t*)malloc(sizeof(patchset_t) + (sizeof(patch_t) * count));
patches->patchsets[i] = patchset;
patchset->name = name;
patchset->apply = apply;
patchset->description = description;
patchset->nopatches = count;
for (int j = 0; j < count; j++) {
json_value* this_patch = set_patches->u.array.values[j];
size_t at;
char *from, *to;
if (!fetch_int(this_patch, "at", &at)) {
snprintf(error, json_error_max, "'at' missing for patch %s[%d]", name, j);
goto failed;
}
if (!fetch_string(this_patch, "from", &from)) {
snprintf(error, json_error_max, "'from' missing for patch %s[%d]", name, j);
goto failed;
}
if (!fetch_string(this_patch, "to", &to)) {
snprintf(error, json_error_max, "'to' missing for patch %s[%d]", name, j);
goto failed;
}
patchset->patches[j].offset = at;
size_t size = strlen(from);
if (size != strlen(to)) {
snprintf(error, json_error_max, "'from' and 'to' lengths differ in patch %s[%d]", name, j);
goto failed;
}
if (size % 2 != 0) {
snprintf(error, json_error_max, "invalid hex string in patch %s[%d]", name, j);
goto failed;
}
char* bin_from = patchset->patches[j].from = (char*)malloc(size / 2);
char* bin_to = patchset->patches[j].to = (char*)malloc(size / 2);
if (!hex_to_bin(from, bin_from, size)) {
snprintf(error, json_error_max, "invalid hex string in patch %s[%d].from", name, j);
goto failed;
};
if (!hex_to_bin(to, bin_to, size)) {
snprintf(error, json_error_max, "invalid hex string in patch %s[%d].to", name, j);
goto failed;
};
patchset->patches[j].count = size / 2;
}
}
return true;
failed:
free_patches(patches);
return false;
}
void print_patches(patches_t* patches) {
for (int i = 0; i < patches->nopatchsets; i++) {
patchset_t* patchset = patches->patchsets[i];
printf("Patch: %s (%s)\n", patchset->name, patchset->apply ? "applied" : "unapplied");
printf("- %s\n", patchset->description);
for (int i = 0; i < patchset->nopatches; i++) {
printf(":: %d bytes at %08x\n", patchset->patches[i].count, patchset->patches[i].offset);
}
puts("======================");
}
}
void apply_patches(patches_t* patches, char* filename) {
FILE* fp = fopen(filename, "r+b");
if (fp == NULL) {
fprintf(stderr, "Failed to open %s for modification\n", filename);
return;
}
fseek(fp, 0L, SEEK_END);
size_t sz = ftell(fp);
for (int i = 0; i < patches->nopatchsets; i++) {
patchset_t* patchset = patches->patchsets[i];
for (int j = 0; j < patchset->nopatches; j++) {
patch_t patch = patchset->patches[j];
if (patch.offset + patch.count > sz) {
fprintf(stderr, "E: Patch %s[%d] outside file bounds\n", patchset->name, j);
goto done;
}
fseek(fp, patch.offset, SEEK_SET);
bool matches_from = true;
bool matches_to = true;
for (int i = 0; i < patch.count; i++) {
unsigned char seen;
if (!fread(&seen, 1, 1, fp)) {
matches_from = false;
matches_to = false;
break;
}
if (seen != patch.from[i]) matches_from = false;
if (seen != patch.to[i]) matches_to = false;
if (!(matches_from || matches_to)) break;
}
printf("%s[%d]: ", patchset->name, j);
if (patchset->apply) {
if (matches_to) {
puts("Already applied");
continue;
}
if (!matches_from) {
puts("From value missmatch! Ignoring");
continue;
}
fseek(fp, patch.offset, SEEK_SET);
fwrite(patch.to, 1, patch.count, fp);
puts("Patch applied");
} else {
if (matches_from) {
puts("Not applied");
continue;
}
if (!matches_to) {
puts("We didn't perform this patch. Leaving patched");
continue;
}
fseek(fp, patch.offset, SEEK_SET);
fwrite(patch.from, 1, patch.count, fp);
puts("Patch removed");
}
}
}
done:
fclose(fp);
}
int main(int argc, char** argv) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <patch file> <exe>\n", argc > 0 ? argv[0] : "micepatch.exe");
return -1;
}
FILE* fp = fopen(argv[1], "r");
if (fp == NULL) {
fprintf(stderr, "Failed to open '%s' (%d)\n", argv[1], errno);
return -1;
}
fseek(fp, 0L, SEEK_END);
size_t sz = ftell(fp);
rewind(fp);
char* json_buf = (char*)malloc(sz);
if (json_buf == NULL) {
fprintf(stderr, "Failed to allocate %d bytes\n", sz, errno);
fclose(fp);
return -1;
}
if (!(sz = fread(json_buf, 1, sz, fp))) {
fprintf(stderr, "Failed to read from '%s' (%d)\n", argv[1], errno);
fclose(fp);
return -1;
};
fclose(fp);
json_settings settings = {0};
char error[json_error_max];
json_value* parsed = json_parse_ex(&settings, json_buf, sz, error);
if (parsed == NULL) {
fprintf(stderr, "Parse error: %s\n", error);
return -1;
}
int loaded_patches = 0;
json_value** patches_json;
if (parsed->type == json_array) {
loaded_patches = parsed->u.array.length;
patches_json = parsed->u.array.values;
} else if (parsed->type == json_object) {
loaded_patches = 1;
patches_json = &parsed;
} else {
fprintf(stderr, "Patch file format error\n");
json_value_free(parsed);
return;
}
patches_t all_patches;
if (!parse_patches(&all_patches, patches_json, loaded_patches, error)) {
fprintf(stderr, "Patch file format error: %s\n", error);
return;
}
puts("");
puts("Loaded patches:");
puts("---------------");
print_patches(&all_patches);
apply_patches(&all_patches, argv[2]);
free_patches(&all_patches);
return 0;
}