Repository navigation
Expand file tree
/
Copy pathneuronvs_patch.c
More file actions
222 lines (194 loc) · 8.25 KB
/
Copy pathneuronvs_patch.c
File metadata and controls
222 lines (194 loc) · 8.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/*
* neuronvs_patch - stability patch for NeuronVS.vst (i386 Mach-O, 982580
* bytes), toggled in place.
*
* Fixes a SIGSEGV in QuickDraw CopyBits (see README.md for the full writeup
* and disassembly). Two complementary fixes:
*
* 1. Thread-affinity fix (P3, P4): the USB hardware-remote thread used to
* call into CDisplayView's blit path directly from CDisplay::SetText,
* racing the main thread's own QuickDraw calls. SetText now only raises
* a dirty flag; draw() services it on the UI thread instead.
*
* 2. Defensive null-check (P1, P2, P5): CBitmap::draw and
* CBitmap::drawTransparent (the exact frame in the crash report) call
* CopyBits with the CBitmap's own pixel buffer as srcBits even when its
* GWorld has no allocated pixels (baseAddr NULL) - e.g. after a purge
* or during teardown. Both call sites are redirected to a guard
* trampoline (P5), squeezed into 12 bytes of dead alignment padding
* right after _NASEngineProcess, which skips the blit when
* srcBits->baseAddr is invalid and otherwise tail-jumps into the real
* CopyBits.
*
* Usage: neuronvs_patch [VST_PATH_OR_BINARY]
*
* VST_PATH_OR_BINARY defaults to "./NeuronVS.vst". If it is a directory, the
* binary is looked up at <path>/Contents/MacOS/NeuronVS; if it is a regular
* file, it is used directly. The binary is validated, then patched in place
* if original, or reverted in place if already patched.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
typedef struct {
const char *name;
long offset;
size_t len;
const unsigned char *orig;
const unsigned char *patched;
} Patch;
static const unsigned char P1_ORIG[3] = { 0xe1, 0x2a, 0x35 };
static const unsigned char P1_NEW[3] = { 0xad, 0xc8, 0x07 };
static const unsigned char P2_ORIG[3] = { 0x2c, 0x24, 0x35 };
static const unsigned char P2_NEW[3] = { 0xf8, 0xc1, 0x07 };
static const unsigned char P3_ORIG[24] = {
0x8b, 0x45, 0xf4, 0x8b, 0x80, 0xb8, 0x01, 0x00, 0x00, 0x85, 0xc0, 0x74,
0x0b, 0x8b, 0x45, 0xf4, 0x89, 0x04, 0x24, 0xe8, 0xd6, 0xf9, 0xff, 0xff
};
static const unsigned char P3_NEW[24] = {
0x8b, 0x45, 0xf4, 0x8b, 0x10, 0x8b, 0x52, 0x3c, 0xc7, 0x44, 0x24, 0x04,
0x01, 0x00, 0x00, 0x00, 0x89, 0x04, 0x24, 0xff, 0xd2, 0x90, 0x90, 0x90
};
static const unsigned char P4_ORIG[70] = {
0xc7, 0x44, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x44, 0x24, 0x04,
0x00, 0x00, 0x00, 0x00, 0x8d, 0x45, 0xc8, 0x89, 0x04, 0x24, 0xe8, 0x73,
0xe7, 0xfb, 0xff, 0xc7, 0x44, 0x24, 0x10, 0x00, 0x00, 0x00, 0x00, 0xc7,
0x44, 0x24, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x44, 0x24, 0x08, 0x00,
0x00, 0x00, 0x00, 0xc7, 0x44, 0x24, 0x04, 0x00, 0x00, 0x00, 0x00, 0x8d,
0x45, 0xb8, 0x89, 0x04, 0x24, 0xe8, 0xc2, 0xe1, 0xfc, 0xff
};
static const unsigned char P4_NEW[70] = {
0x8b, 0x45, 0x08, 0x80, 0xb8, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x74, 0x08,
0x89, 0x04, 0x24, 0xe8, 0x26, 0xf8, 0xff, 0xff, 0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90
};
static const unsigned char P5_ORIG[12] = {
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90
};
static const unsigned char P5_NEW[12] = {
0x8b, 0x02, 0x85, 0xc0, 0x7e, 0x05, 0xe9, 0x29, 0x62, 0x2d, 0x00, 0xc3
};
static const Patch PATCHES[] = {
{ "CBitmap::draw: redirect CopyBits through baseAddr-null guard", 0x179B2, 3, P1_ORIG, P1_NEW },
{ "CBitmap::drawTransparent: redirect CopyBits through baseAddr-null guard", 0x18067, 3, P2_ORIG, P2_NEW },
{ "CDisplay::SetText: drop synchronous drawofl, setDirty(true) instead", 0x44B3A, 24, P3_ORIG, P3_NEW },
{ "CDisplayView::draw: render offscreen on UI thread when dirty", 0x44CEE, 70, P4_ORIG, P4_NEW },
{ "code cave after _NASEngineProcess: CopyBits baseAddr-null guard", 0x94263, 12, P5_ORIG, P5_NEW },
};
#define NUM_PATCHES (sizeof(PATCHES) / sizeof(PATCHES[0]))
#define EXPECT_SIZE 982580
static void hexprint(const unsigned char *buf, size_t len) {
size_t i;
for (i = 0; i < len; i++) printf("%02x", buf[i]);
}
/* Resolve a user-supplied path (bundle directory or raw binary) to the
* actual Mach-O file to read/write. Result written into out (size outsz). */
static int resolve_binary_path(const char *arg, char *out, size_t outsz) {
struct stat st;
if (stat(arg, &st) != 0) {
fprintf(stderr, "error: %s does not exist\n", arg);
return -1;
}
if (S_ISDIR(st.st_mode)) {
int n = snprintf(out, outsz, "%s/Contents/MacOS/NeuronVS", arg);
if (n < 0 || (size_t)n >= outsz) {
fprintf(stderr, "error: path too long\n");
return -1;
}
if (stat(out, &st) != 0 || !S_ISREG(st.st_mode)) {
fprintf(stderr, "error: no binary found at %s\n", out);
return -1;
}
return 0;
}
if (S_ISREG(st.st_mode)) {
int n = snprintf(out, outsz, "%s", arg);
if (n < 0 || (size_t)n >= outsz) {
fprintf(stderr, "error: path too long\n");
return -1;
}
return 0;
}
fprintf(stderr, "error: %s is neither a directory nor a regular file\n", arg);
return -1;
}
int main(int argc, char **argv) {
const char *arg_path = (argc > 1) ? argv[1] : "NeuronVS.vst";
char path[4096];
FILE *f;
struct stat st;
unsigned char *buf;
size_t len, i;
int all_orig = 1, all_patched = 1;
if (resolve_binary_path(arg_path, path, sizeof(path)) != 0) return 1;
if (stat(path, &st) != 0) {
fprintf(stderr, "error: cannot stat %s\n", path);
return 1;
}
len = (size_t)st.st_size;
printf("file: %s\n", path);
printf("size: %zu%s\n", len, len == EXPECT_SIZE ? "" : " (WARNING: unexpected)");
buf = (unsigned char *)malloc(len);
if (!buf) { fprintf(stderr, "error: out of memory\n"); return 1; }
f = fopen(path, "rb");
if (!f || fread(buf, 1, len, f) != len) {
fprintf(stderr, "error: could not read %s\n", path);
free(buf);
return 1;
}
fclose(f);
for (i = 0; i < NUM_PATCHES; i++) {
const Patch *p = &PATCHES[i];
const unsigned char *got;
if (p->offset < 0 || (size_t)p->offset + p->len > len) {
printf(" [FAIL] 0x%05lX out of bounds for file of size %zu - %s\n",
p->offset, len, p->name);
free(buf);
return 1;
}
got = buf + p->offset;
if (memcmp(got, p->patched, p->len) == 0) {
printf(" [ ok ] 0x%05lX already patched - %s\n", p->offset, p->name);
all_orig = 0;
} else if (memcmp(got, p->orig, p->len) == 0) {
printf(" [ ok ] 0x%05lX original bytes verified - %s\n", p->offset, p->name);
all_patched = 0;
} else {
printf(" [FAIL] 0x%05lX byte mismatch - %s\n", p->offset, p->name);
printf(" expected (orig) "); hexprint(p->orig, p->len); printf("\n");
printf(" expected (new) "); hexprint(p->patched, p->len); printf("\n");
printf(" found "); hexprint(got, p->len); printf("\n");
printf("Aborting; nothing written.\n");
free(buf);
return 1;
}
}
if (!all_orig && !all_patched) {
fprintf(stderr, "error: file has a mix of patched and unpatched regions - refusing to touch it\n");
free(buf);
return 1;
}
if (all_patched) {
printf("\nAlready patched; reverting in place...\n");
for (i = 0; i < NUM_PATCHES; i++)
memcpy(buf + PATCHES[i].offset, PATCHES[i].orig, PATCHES[i].len);
} else {
printf("\nApplying patch in place...\n");
for (i = 0; i < NUM_PATCHES; i++)
memcpy(buf + PATCHES[i].offset, PATCHES[i].patched, PATCHES[i].len);
}
f = fopen(path, "wb");
if (!f || fwrite(buf, 1, len, f) != len) {
fprintf(stderr, "error: could not write %s\n", path);
free(buf);
return 1;
}
fclose(f);
printf("done: %s (%zu bytes)\n", path, len);
free(buf);
return 0;
}