buddy-reroll 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +85 -19
- package/lib/estimator.js +1 -1
- package/lib/finder.js +13 -5
- package/package.json +1 -1
- package/scripts/worker.js +7 -1
- package/ui-fallback.js +17 -4
- package/ui.jsx +25 -23
package/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { readFileSync, writeFileSync, existsSync, copyFileSync, renameSync, unlinkSync } from "fs";
|
|
4
4
|
import { platform } from "os";
|
|
5
|
-
import { execFileSync } from "child_process";
|
|
5
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
6
6
|
import { parseArgs } from "util";
|
|
7
7
|
import chalk from "chalk";
|
|
8
8
|
import { renderSprite, colorizeSprite, RARITY_STARS, RARITY_COLORS } from "./sprites.js";
|
|
@@ -27,8 +27,16 @@ import { installHook, removeHook, storeSalt, readStoredSalt } from "./lib/hooks.
|
|
|
27
27
|
|
|
28
28
|
const IS_BUN = typeof Bun !== "undefined";
|
|
29
29
|
const IS_APPLY_HOOK = process.argv.includes("--apply-hook");
|
|
30
|
+
|
|
30
31
|
if (!IS_BUN && !IS_APPLY_HOOK) {
|
|
31
|
-
|
|
32
|
+
try {
|
|
33
|
+
const cmd = platform() === "win32" ? "where.exe" : "which";
|
|
34
|
+
const bunPath = execFileSync(cmd, ["bun"], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim().split("\n")[0];
|
|
35
|
+
if (bunPath) {
|
|
36
|
+
const result = spawnSync(bunPath, process.argv.slice(1), { stdio: "inherit" });
|
|
37
|
+
process.exit(result.status ?? 0);
|
|
38
|
+
}
|
|
39
|
+
} catch {}
|
|
32
40
|
}
|
|
33
41
|
|
|
34
42
|
function getUserId(configPath) {
|
|
@@ -89,7 +97,19 @@ function patchBinary(binaryPath, oldSalt, newSalt) {
|
|
|
89
97
|
try {
|
|
90
98
|
const tmpPath = binaryPath + ".tmp";
|
|
91
99
|
writeFileSync(tmpPath, data);
|
|
92
|
-
|
|
100
|
+
try {
|
|
101
|
+
renameSync(tmpPath, binaryPath);
|
|
102
|
+
} catch {
|
|
103
|
+
if (existsSync(binaryPath + ".backup")) {
|
|
104
|
+
try { unlinkSync(binaryPath); } catch {}
|
|
105
|
+
}
|
|
106
|
+
renameSync(tmpPath, binaryPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const verify = readFileSync(binaryPath);
|
|
110
|
+
const found = verify.indexOf(Buffer.from(newSalt));
|
|
111
|
+
if (found === -1) throw new Error("Patch verification failed — new salt not found after write");
|
|
112
|
+
|
|
93
113
|
return count;
|
|
94
114
|
} catch (err) {
|
|
95
115
|
try { unlinkSync(binaryPath + ".tmp"); } catch {}
|
|
@@ -97,7 +117,10 @@ function patchBinary(binaryPath, oldSalt, newSalt) {
|
|
|
97
117
|
sleepMs(2000);
|
|
98
118
|
continue;
|
|
99
119
|
}
|
|
100
|
-
|
|
120
|
+
if (isWin && (err.code === "EPERM" || err.code === "EBUSY")) {
|
|
121
|
+
throw new Error("Can't write — Claude Code might still be running. Close it and try again.");
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`Failed to write: ${err.message}`);
|
|
101
124
|
}
|
|
102
125
|
}
|
|
103
126
|
}
|
|
@@ -106,10 +129,12 @@ function resignBinary(binaryPath) {
|
|
|
106
129
|
if (platform() !== "darwin") return false;
|
|
107
130
|
try {
|
|
108
131
|
execFileSync("codesign", ["-s", "-", "--force", binaryPath], {
|
|
109
|
-
stdio: "ignore",
|
|
132
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
133
|
+
timeout: 30000,
|
|
110
134
|
});
|
|
111
135
|
return true;
|
|
112
|
-
} catch {
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.warn(` ⚠ Code signing failed: ${err.message}\n Try manually: codesign --force --sign - "${binaryPath}"`);
|
|
113
138
|
return false;
|
|
114
139
|
}
|
|
115
140
|
}
|
|
@@ -130,7 +155,32 @@ function fail(message) {
|
|
|
130
155
|
|
|
131
156
|
function readCurrentCompanion(binaryPath, userId) {
|
|
132
157
|
const binaryData = readFileSync(binaryPath);
|
|
133
|
-
|
|
158
|
+
let currentSalt = findCurrentSalt(binaryData);
|
|
159
|
+
|
|
160
|
+
if (!currentSalt) {
|
|
161
|
+
const stored = readStoredSalt();
|
|
162
|
+
if (stored) {
|
|
163
|
+
const storedBuf = Buffer.from(stored.salt);
|
|
164
|
+
if (binaryData.includes(storedBuf)) {
|
|
165
|
+
currentSalt = stored.salt;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!currentSalt) {
|
|
171
|
+
const backupPath = binaryPath + ".backup";
|
|
172
|
+
if (existsSync(backupPath)) {
|
|
173
|
+
console.log(" ⚠ Can't find salt in binary — restoring from backup...");
|
|
174
|
+
try {
|
|
175
|
+
copyFileSync(backupPath, binaryPath);
|
|
176
|
+
resignBinary(binaryPath);
|
|
177
|
+
const restored = readFileSync(binaryPath);
|
|
178
|
+
currentSalt = findCurrentSalt(restored);
|
|
179
|
+
if (currentSalt) console.log(" ✓ Restored successfully.");
|
|
180
|
+
} catch {}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
134
184
|
if (!currentSalt) fail(" ✗ Couldn't read your current buddy from the Claude binary.");
|
|
135
185
|
return { currentSalt, currentRoll: rollFrom(currentSalt, userId) };
|
|
136
186
|
}
|
|
@@ -216,7 +266,7 @@ async function interactiveMode(binaryPath, configPath, userId) {
|
|
|
216
266
|
binaryPath,
|
|
217
267
|
configPath,
|
|
218
268
|
userId,
|
|
219
|
-
bruteForce,
|
|
269
|
+
bruteForce: parallelBruteForce,
|
|
220
270
|
patchBinary,
|
|
221
271
|
resignBinary,
|
|
222
272
|
clearCompanion,
|
|
@@ -230,6 +280,8 @@ async function interactiveMode(binaryPath, configPath, userId) {
|
|
|
230
280
|
EYES,
|
|
231
281
|
HATS,
|
|
232
282
|
STAT_NAMES,
|
|
283
|
+
storeSalt,
|
|
284
|
+
installHook,
|
|
233
285
|
};
|
|
234
286
|
|
|
235
287
|
try {
|
|
@@ -277,25 +329,30 @@ async function nonInteractiveMode(args, binaryPath, configPath, userId) {
|
|
|
277
329
|
const target = buildTargetFromArgs(args);
|
|
278
330
|
if (Object.keys(target).length === 0) fail(" ✗ Tell me what kind of buddy you want! Use --help to see options.");
|
|
279
331
|
|
|
280
|
-
const
|
|
281
|
-
console.log(` Target: ${Object.entries(target).map(([k, v]) => `${k}=${v}`).join(" ")}`);
|
|
282
|
-
console.log(` This might take ~${expected.toLocaleString()} tries\n`);
|
|
332
|
+
const patchability = assertPatchable(binaryPath);
|
|
283
333
|
|
|
284
334
|
if (matches(currentRoll, target)) {
|
|
285
335
|
console.log(" ✓ Your buddy already looks like that!\n" + formatCompanionCard(currentRoll));
|
|
286
336
|
return;
|
|
287
337
|
}
|
|
288
338
|
|
|
289
|
-
const
|
|
339
|
+
const expected = estimateAttempts(target);
|
|
340
|
+
console.log(` Target: ${Object.entries(target).map(([k, v]) => `${k}=${v}`).join(" ")}`);
|
|
341
|
+
console.log(` This might take ~${expected.toLocaleString()} tries\n`);
|
|
290
342
|
|
|
291
343
|
if (isClaudeRunning()) {
|
|
292
344
|
console.warn(" ⚠ Claude Code is still running — close it first so the changes stick.");
|
|
293
345
|
}
|
|
294
346
|
|
|
295
347
|
console.log(" Looking for your buddy...");
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
348
|
+
let found;
|
|
349
|
+
try {
|
|
350
|
+
found = await parallelBruteForce(userId, target, (attempts, elapsed, est, workers) => {
|
|
351
|
+
process.stdout.write(`\r ${formatProgress(attempts, elapsed, est, workers)}`);
|
|
352
|
+
});
|
|
353
|
+
} catch (err) {
|
|
354
|
+
fail(`\n ✗ ${err.message}`);
|
|
355
|
+
}
|
|
299
356
|
if (!found) fail("\n ✗ Couldn't find a match. Try being less picky!");
|
|
300
357
|
console.log(`\n ✓ Found it! (${found.checked.toLocaleString()} tries, ${(found.elapsed / 1000).toFixed(1)}s)`);
|
|
301
358
|
console.log(formatCompanionCard(found.result));
|
|
@@ -316,8 +373,9 @@ async function nonInteractiveMode(args, binaryPath, configPath, userId) {
|
|
|
316
373
|
if (resignBinary(binaryPath)) console.log(" Re-signed for macOS ✓");
|
|
317
374
|
clearCompanion(configPath);
|
|
318
375
|
storeSalt(found.salt);
|
|
376
|
+
try { installHook(); } catch {}
|
|
319
377
|
console.log(" Cleaned up old buddy data ✓");
|
|
320
|
-
console.log("\n All set! Restart Claude Code and say /buddy to meet your new friend.\n");
|
|
378
|
+
console.log("\n All set! Your buddy will stick around even after Claude updates.\n Restart Claude Code and say /buddy to meet your new friend.\n");
|
|
321
379
|
} catch (err) {
|
|
322
380
|
fail(` ✗ ${err.message}`);
|
|
323
381
|
}
|
|
@@ -365,8 +423,7 @@ async function main() {
|
|
|
365
423
|
buddy-reroll --current Show current buddy
|
|
366
424
|
buddy-reroll --doctor Check setup
|
|
367
425
|
buddy-reroll --restore Undo changes
|
|
368
|
-
buddy-reroll --
|
|
369
|
-
buddy-reroll --unhook Stop keeping after updates
|
|
426
|
+
buddy-reroll --unhook Stop auto-keeping after updates
|
|
370
427
|
|
|
371
428
|
Appearance (all optional — skip to leave random):
|
|
372
429
|
--species <name> ${SPECIES.join(", ")}
|
|
@@ -413,8 +470,17 @@ async function main() {
|
|
|
413
470
|
if (currentSalt === stored.salt) process.exit(0);
|
|
414
471
|
const patchability = getPatchability(bp);
|
|
415
472
|
if (!patchability.ok) process.exit(0);
|
|
473
|
+
const backupPath = patchability.backupPath;
|
|
474
|
+
if (!existsSync(backupPath)) copyFileSync(bp, backupPath);
|
|
416
475
|
patchBinary(bp, currentSalt, stored.salt);
|
|
417
|
-
|
|
476
|
+
if (platform() === "darwin") {
|
|
477
|
+
try {
|
|
478
|
+
execFileSync("codesign", ["-s", "-", "--force", bp], { stdio: "ignore", timeout: 30000 });
|
|
479
|
+
} catch {
|
|
480
|
+
copyFileSync(backupPath, bp);
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
418
484
|
clearCompanion(cp);
|
|
419
485
|
} catch {}
|
|
420
486
|
process.exit(0);
|
package/lib/estimator.js
CHANGED
|
@@ -8,7 +8,7 @@ export function estimateAttempts(target) {
|
|
|
8
8
|
if (target.species) probability *= 1 / 18;
|
|
9
9
|
if (target.rarity) probability *= RARITY_WEIGHTS[target.rarity] / 100;
|
|
10
10
|
if (target.eye) probability *= 1 / 6;
|
|
11
|
-
if (target.hat && target.rarity !== "common") probability *= 1 / 8;
|
|
11
|
+
if (target.hat && target.hat !== "none" && target.rarity !== "common") probability *= 1 / 8;
|
|
12
12
|
if (target.shiny === true) probability *= 0.01;
|
|
13
13
|
if (target.peak) probability *= 1 / 5;
|
|
14
14
|
if (target.dump) probability *= 1 / 4;
|
package/lib/finder.js
CHANGED
|
@@ -14,6 +14,7 @@ export async function parallelBruteForce(userId, target, onProgress) {
|
|
|
14
14
|
return new Promise((resolve, reject) => {
|
|
15
15
|
const children = [];
|
|
16
16
|
const workerStdout = [];
|
|
17
|
+
const workerStderr = [];
|
|
17
18
|
const workerAttempts = [];
|
|
18
19
|
let resolved = false;
|
|
19
20
|
let exited = 0;
|
|
@@ -26,6 +27,7 @@ export async function parallelBruteForce(userId, target, onProgress) {
|
|
|
26
27
|
|
|
27
28
|
for (let i = 0; i < numWorkers; i++) {
|
|
28
29
|
workerStdout[i] = "";
|
|
30
|
+
workerStderr[i] = "";
|
|
29
31
|
workerAttempts[i] = 0;
|
|
30
32
|
|
|
31
33
|
const child = spawn(process.execPath, [WORKER_SCRIPT, userId, JSON.stringify(target)], {
|
|
@@ -38,7 +40,9 @@ export async function parallelBruteForce(userId, target, onProgress) {
|
|
|
38
40
|
});
|
|
39
41
|
|
|
40
42
|
child.stderr.on("data", (chunk) => {
|
|
41
|
-
const
|
|
43
|
+
const text = chunk.toString();
|
|
44
|
+
workerStderr[i] += text;
|
|
45
|
+
const lines = text.split("\n").filter(Boolean);
|
|
42
46
|
for (const line of lines) {
|
|
43
47
|
try {
|
|
44
48
|
const progress = JSON.parse(line);
|
|
@@ -80,14 +84,17 @@ export async function parallelBruteForce(userId, target, onProgress) {
|
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
if (exited === numWorkers && !resolved) {
|
|
83
|
-
|
|
87
|
+
const totalAttempts = workerAttempts.reduce((a, b) => a + b, 0);
|
|
88
|
+
const stderr = workerStderr.filter(Boolean).join("\n").trim();
|
|
89
|
+
const detail = stderr ? `\n Worker output: ${stderr.slice(0, 200)}` : "";
|
|
90
|
+
reject(new Error(`All ${numWorkers} workers exited without finding a match (${totalAttempts.toLocaleString()} tries).${detail}`));
|
|
84
91
|
}
|
|
85
92
|
});
|
|
86
93
|
|
|
87
|
-
child.on("error", () => {
|
|
94
|
+
child.on("error", (err) => {
|
|
88
95
|
exited++;
|
|
89
96
|
if (exited === numWorkers && !resolved) {
|
|
90
|
-
|
|
97
|
+
reject(new Error(`Worker failed to start: ${err.message}`));
|
|
91
98
|
}
|
|
92
99
|
});
|
|
93
100
|
}
|
|
@@ -97,7 +104,8 @@ export async function parallelBruteForce(userId, target, onProgress) {
|
|
|
97
104
|
if (!resolved) {
|
|
98
105
|
resolved = true;
|
|
99
106
|
killAll();
|
|
100
|
-
|
|
107
|
+
const totalAttempts = workerAttempts.reduce((a, b) => a + b, 0);
|
|
108
|
+
reject(new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s (${totalAttempts.toLocaleString()} tries). This combination might be extremely rare — try fewer constraints.`));
|
|
101
109
|
}
|
|
102
110
|
}, timeoutMs);
|
|
103
111
|
});
|
package/package.json
CHANGED
package/scripts/worker.js
CHANGED
|
@@ -14,7 +14,13 @@ function randomSalt() {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
const userId = process.argv[2];
|
|
17
|
-
|
|
17
|
+
let target;
|
|
18
|
+
try {
|
|
19
|
+
target = JSON.parse(process.argv[3]);
|
|
20
|
+
} catch {
|
|
21
|
+
process.stderr.write("Invalid target JSON\n");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
18
24
|
|
|
19
25
|
if (!userId || !target) {
|
|
20
26
|
process.stderr.write("Usage: worker.js <userId> '<targetJSON>'\n");
|
package/ui-fallback.js
CHANGED
|
@@ -30,6 +30,7 @@ export async function runInteractiveUI(opts) {
|
|
|
30
30
|
currentRoll, currentSalt, binaryPath, configPath, userId,
|
|
31
31
|
bruteForce, patchBinary, resignBinary, clearCompanion, getPatchability, isClaudeRunning,
|
|
32
32
|
rollFrom, matches, SPECIES, RARITIES, RARITY_LABELS, EYES, HATS, STAT_NAMES,
|
|
33
|
+
storeSalt, installHook,
|
|
33
34
|
} = opts;
|
|
34
35
|
|
|
35
36
|
console.log(chalk.bold.dim("\n buddy-reroll\n"));
|
|
@@ -151,9 +152,19 @@ export async function runInteractiveUI(opts) {
|
|
|
151
152
|
if (!proceed) return;
|
|
152
153
|
|
|
153
154
|
console.log(" Looking for your buddy...");
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
let found;
|
|
156
|
+
try {
|
|
157
|
+
found = await bruteForce(userId, target, (attempts, elapsed, expected, workers) => {
|
|
158
|
+
const pct = Math.min(100, Math.round((attempts / expected) * 100));
|
|
159
|
+
const rate = attempts / (elapsed / 1000);
|
|
160
|
+
const rateStr = rate >= 1e6 ? `${(rate / 1e6).toFixed(1)}M` : `${(rate / 1e3).toFixed(1)}k`;
|
|
161
|
+
const eta = Math.max(0, (expected - attempts) / rate);
|
|
162
|
+
process.stdout.write(`\r ${pct}% | ${rateStr} tries/s | ~${Math.round(eta)}s left | ${workers} cores`);
|
|
163
|
+
});
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.log(chalk.red(`\n✗ ${err.message}`));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
157
168
|
|
|
158
169
|
if (!found) {
|
|
159
170
|
console.log(chalk.red("\n✗ Couldn't find a match. Try being less picky!"));
|
|
@@ -174,7 +185,9 @@ export async function runInteractiveUI(opts) {
|
|
|
174
185
|
if (resignBinary(binaryPath)) console.log(" Re-signed for macOS ✓");
|
|
175
186
|
clearCompanion(configPath);
|
|
176
187
|
console.log(" Cleaned up old buddy data ✓");
|
|
177
|
-
|
|
188
|
+
if (storeSalt) storeSalt(found.salt);
|
|
189
|
+
if (installHook) installHook();
|
|
190
|
+
console.log(chalk.bold("\n All set! Your buddy will stick around even after Claude updates.\n Restart Claude Code and say /buddy to meet your new friend.\n"));
|
|
178
191
|
} catch (err) {
|
|
179
192
|
console.log(chalk.red(`\n✗ ${err.message}`));
|
|
180
193
|
}
|
package/ui.jsx
CHANGED
|
@@ -183,11 +183,21 @@ function SearchStep({ userId, target, bruteForce, onFound, onFail, isActive }) {
|
|
|
183
183
|
if (hasStarted.current) return;
|
|
184
184
|
hasStarted.current = true;
|
|
185
185
|
(async () => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
186
|
+
let found;
|
|
187
|
+
try {
|
|
188
|
+
found = await bruteForce(userId, target, (attempts, elapsed, expected, workers) => {
|
|
189
|
+
if (!cancelRef.current) {
|
|
190
|
+
const pct = Math.min(100, Math.round((attempts / expected) * 100));
|
|
191
|
+
const rate = attempts / (elapsed / 1000);
|
|
192
|
+
const rateStr = rate >= 1e6 ? `${(rate / 1e6).toFixed(1)}M` : `${(rate / 1e3).toFixed(1)}k`;
|
|
193
|
+
const eta = Math.max(0, (expected - attempts) / rate);
|
|
194
|
+
setProgress(`${pct}% | ${rateStr} tries/s | ~${Math.round(eta)}s left | ${workers} cores`);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
} catch {
|
|
198
|
+
if (!cancelRef.current) onFail();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
191
201
|
if (cancelRef.current) return;
|
|
192
202
|
if (found) onFound(found);
|
|
193
203
|
else onFail();
|
|
@@ -221,7 +231,7 @@ function DoneStep({ messages, isActive }) {
|
|
|
221
231
|
<Text bold>
|
|
222
232
|
{hasErrors
|
|
223
233
|
? "Something went wrong — check the issue above and try again."
|
|
224
|
-
: "All set! Restart Claude Code and say /buddy
|
|
234
|
+
: "All set! Your buddy will stick around even after updates. Restart Claude Code and say /buddy!"}
|
|
225
235
|
</Text>
|
|
226
236
|
</Box>
|
|
227
237
|
<KeyHint>Press any key to exit</KeyHint>
|
|
@@ -236,8 +246,7 @@ function getPrevStep(current, rarity, peak) {
|
|
|
236
246
|
if (idx <= 0) return null;
|
|
237
247
|
let prev = STEP_ORDER[idx - 1];
|
|
238
248
|
if (prev === "hat" && rarity === "common") prev = "eye";
|
|
239
|
-
if (prev === "dump" && peak
|
|
240
|
-
if (prev === "peak") prev = "shiny";
|
|
249
|
+
if (prev === "dump" && !peak) prev = "peak";
|
|
241
250
|
return prev;
|
|
242
251
|
}
|
|
243
252
|
|
|
@@ -247,6 +256,7 @@ function App({ opts }) {
|
|
|
247
256
|
currentRoll, currentSalt, binaryPath, configPath, userId,
|
|
248
257
|
bruteForce, patchBinary, resignBinary, clearCompanion, getPatchability, isClaudeRunning,
|
|
249
258
|
rollFrom, matches, SPECIES, RARITIES, RARITY_LABELS, EYES, HATS, STAT_NAMES,
|
|
259
|
+
storeSalt, installHook,
|
|
250
260
|
} = opts;
|
|
251
261
|
|
|
252
262
|
const [step, setStep] = useState("action");
|
|
@@ -394,21 +404,11 @@ function App({ opts }) {
|
|
|
394
404
|
isActive={step === "shiny"}
|
|
395
405
|
onConfirm={() => {
|
|
396
406
|
setShiny(true);
|
|
397
|
-
|
|
398
|
-
setDoneMessages([{ type: "success", text: "Your buddy already looks like that!" }]);
|
|
399
|
-
setStep("done");
|
|
400
|
-
} else {
|
|
401
|
-
setStep("peak");
|
|
402
|
-
}
|
|
407
|
+
setStep("peak");
|
|
403
408
|
}}
|
|
404
409
|
onCancel={() => {
|
|
405
410
|
setShiny(false);
|
|
406
|
-
|
|
407
|
-
setDoneMessages([{ type: "success", text: "Your buddy already looks like that!" }]);
|
|
408
|
-
setStep("done");
|
|
409
|
-
} else {
|
|
410
|
-
setStep("peak");
|
|
411
|
-
}
|
|
411
|
+
setStep("peak");
|
|
412
412
|
}}
|
|
413
413
|
onBack={() => goBack()}
|
|
414
414
|
/>
|
|
@@ -416,7 +416,7 @@ function App({ opts }) {
|
|
|
416
416
|
|
|
417
417
|
{step === "peak" && (
|
|
418
418
|
<ListSelect
|
|
419
|
-
label="
|
|
419
|
+
label="Best at"
|
|
420
420
|
options={[
|
|
421
421
|
{ label: "Any (random)", value: "any" },
|
|
422
422
|
...(STAT_NAMES || []).map(s => ({ label: s, value: s })),
|
|
@@ -437,7 +437,7 @@ function App({ opts }) {
|
|
|
437
437
|
|
|
438
438
|
{step === "dump" && (
|
|
439
439
|
<ListSelect
|
|
440
|
-
label="
|
|
440
|
+
label="Worst at"
|
|
441
441
|
options={[
|
|
442
442
|
{ label: "Any (random)", value: "any" },
|
|
443
443
|
...(STAT_NAMES || []).filter(s => s !== peak).map(s => ({ label: s, value: s })),
|
|
@@ -490,7 +490,7 @@ function App({ opts }) {
|
|
|
490
490
|
|
|
491
491
|
{step === "result" && (
|
|
492
492
|
<Box flexDirection="column">
|
|
493
|
-
<Text bold color="green">✓ Found
|
|
493
|
+
<Text bold color="green">✓ Found your buddy! ({found.checked.toLocaleString()} tries, {(found.elapsed / 1000).toFixed(1)}s)</Text>
|
|
494
494
|
<ConfirmSelect
|
|
495
495
|
label="Apply patch?"
|
|
496
496
|
isActive={step === "result"}
|
|
@@ -514,6 +514,8 @@ function App({ opts }) {
|
|
|
514
514
|
msgs.push({ type: "success", text: "Applied!" });
|
|
515
515
|
if (resignBinary(binaryPath)) msgs.push({ type: "success", text: "Re-signed for macOS" });
|
|
516
516
|
clearCompanion(configPath);
|
|
517
|
+
if (storeSalt) storeSalt(found.salt);
|
|
518
|
+
if (installHook) installHook();
|
|
517
519
|
msgs.push({ type: "success", text: "Cleaned up old buddy data" });
|
|
518
520
|
} catch (err) {
|
|
519
521
|
msgs.push({ type: "error", text: err.message });
|