-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
629 lines (558 loc) · 26.3 KB
/
main.cpp
File metadata and controls
629 lines (558 loc) · 26.3 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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
/*
* RCM Payload Emulator - Main Entry Point
*
* Loads an RCM binary payload (.bin), initializes ARM32 emulation
* via Unicorn Engine, sets up T210 memory map and MMIO hooks,
* and runs the payload with SDL2 display output.
*
* Usage: ./rcm_emu <payload.bin>
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <thread>
#include <chrono>
#include <vector>
#include <string>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <unicorn/unicorn.h>
#include "emu_state.h"
#include "t210/memory_map.h"
#include "t210/mmio.h"
#include "display/sdl_display.h"
#include "display/config_window.h"
#include "display/console_window.h"
// ==================== Payload Loading ====================
static uint8_t *load_payload(const char *path, size_t *out_size) {
FILE *f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "[error] Cannot open payload: %s\n", path);
return nullptr;
}
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *buf = (uint8_t *)malloc(size);
if (!buf) {
fclose(f);
fprintf(stderr, "[error] Failed to allocate %zu bytes\n", size);
return nullptr;
}
size_t nread = fread(buf, 1, size, f);
if (nread != size) {
fprintf(stderr, "[warning] Short read: %zu/%zu bytes\n", nread, size);
size = nread;
}
fclose(f);
*out_size = size;
printf("[loader] Loaded payload: %s (%zu bytes / %.1f KB)\n", path, size, size / 1024.0);
// Try to identify the payload
if (size >= 0x120) {
uint32_t magic = *(uint32_t *)(buf + 0x118);
if (magic == 0x43544349) { // "ICTC" - hekate
uint32_t ver = *(uint32_t *)(buf + 0x11C);
int major = (ver & 0xFF) - '0';
int minor = ((ver >> 8) & 0xFF) - '0';
int hotfix = ((ver >> 16) & 0xFF) - '0';
printf("[loader] Detected: Hekate v%d.%d.%d\n", major, minor, hotfix);
} else if (magic == 0x4E595849) { // "IXYN" - Nyx
printf("[loader] Detected: Nyx GUI\n");
} else {
printf("[loader] Unknown payload type (magic: 0x%08X)\n", magic);
}
}
return buf;
}
// ==================== Emulation Setup ====================
// Global for instruction tracing
static uint32_t pc_trace[2000];
static size_t pc_trace_idx = 0;
static uint32_t last_valid_block = 0;
static uc_hook trace_h, block_h;
static void trace_callback(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
static uint32_t last_pc = 0;
if (address == last_pc) return;
last_pc = (uint32_t)address;
pc_trace[pc_trace_idx] = (uint32_t)address;
pc_trace_idx = (pc_trace_idx + 1) % 2000;
// NOP-slide detection
if (address >= 0x40030000 && address < 0x41000000) {
uint16_t insn = 0;
if (uc_mem_read(uc, address, &insn, 2) == UC_ERR_OK && insn == 0) {
static int nop_count = 0;
if (++nop_count > 10) {
printf("\n[emu] NOP-slide detected at 0x%08llX! Last valid block: 0x%08X\n",
(unsigned long long)address, last_valid_block);
uc_emu_stop(uc);
}
} else {
// nop_count = 0; // would need a persistent counter
}
}
}
static void block_callback(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
if (address >= 0x40000000 && address < 0x40030000) {
last_valid_block = (uint32_t)address;
}
}
static uc_engine *setup_emulation(EmuState *state, uint8_t *payload, size_t payload_size) {
uc_engine *uc;
uc_err err;
// Open Unicorn in ARM32 mode with Thumb support
err = uc_open(UC_ARCH_ARM, UC_MODE_ARM, &uc);
if (err != UC_ERR_OK) {
fprintf(stderr, "[error] uc_open failed: %s\n", uc_strerror(err));
return nullptr;
}
state->uc = uc;
// Setup hooks
uc_hook_add(uc, &trace_h, UC_HOOK_CODE, (void*)trace_callback, nullptr, 1, 0);
uc_hook_add(uc, &block_h, UC_HOOK_BLOCK, (void*)block_callback, nullptr, 1, 0);
// Enable VFP/NEON
uint32_t cpacr = 0x00F00000;
uc_reg_write(uc, UC_ARM_REG_C1_C0_2, &cpacr);
// Map IRAM (16MB)
state->iram_ptr = (uint8_t *)calloc(1, IRAM_SIZE);
err = uc_mem_map_ptr(uc, IRAM_BASE, IRAM_SIZE, UC_PROT_ALL, state->iram_ptr);
if (err != UC_ERR_OK) {
fprintf(stderr, "[error] Failed to map IRAM: %s\n", uc_strerror(err));
uc_close(uc);
return nullptr;
}
printf("[emu] Mapped IRAM: 0x%08llX - 0x%08llX (%u KB)\n",
(unsigned long long)IRAM_BASE,
(unsigned long long)(IRAM_BASE + IRAM_SIZE),
(unsigned)(IRAM_SIZE / 1024));
uc_mem_write(uc, IPL_LOAD_ADDR, payload, payload_size);
// Pre-set Hekate's "watchdog fired" magic at IRAM 0x4003FF18 (cookie "WDT")
// so its early boot does `goto skip_lp0_minerva_config`, skipping both
// libsys_lp0.bso and the Minerva DRAM-training path. The matching
// EXCP_EN_ADDR (0x4003FF1C) is intentionally left zeroed so ERR_EXCEPTION
// is *not* set and the user doesn't see a "hang detected" warning screen.
// We can't model EMC/MC well enough for real Minerva training, so this is
// the cleanest opt-out (the same path Hekate uses on hardware after a
// legitimate WDT reset).
{
uint32_t wdt_magic = 0x544457; // "WDT"
uc_mem_write(uc, 0x4003FF18, &wdt_magic, sizeof(wdt_magic));
}
// Setup instruction tracing
uc_hook_add(uc, &trace_h, UC_HOOK_CODE, (void*)trace_callback, nullptr, 1, 0);
// ---- Map DRAM Low (256MB @ 0x80000000) ----
// Allocated host-side so soft reboot can memset() it (Nyx loads here and
// its file-static SD/eMMC caches would otherwise survive across reboots).
state->dram_low_ptr = (uint8_t *)calloc(1, 256 * 1024 * 1024);
err = uc_mem_map_ptr(uc, DRAM_BASE, 256 * 1024 * 1024, UC_PROT_ALL, state->dram_low_ptr);
if (err != UC_ERR_OK) {
fprintf(stderr, "[error] Failed to map low DRAM: %s\n", uc_strerror(err));
uc_close(uc);
return nullptr;
}
printf("[emu] Mapped DRAM Low: 0x%08llX - 0x%08llX (256 MB)\n",
(unsigned long long)DRAM_BASE,
(unsigned long long)(DRAM_BASE + 256 * 1024 * 1024));
// ---- Map DRAM High + FB (1GB @ 0xC0000000) ----
// This covers 0xE5000000 and 0xF5A00000
size_t high_dram_size = 1024 * 1024 * 1024;
state->dram_ptr = (uint8_t *)calloc(1, high_dram_size);
if (!state->dram_ptr) {
fprintf(stderr, "[error] Failed to allocate high DRAM host buffer\n");
return nullptr;
}
err = uc_mem_map_ptr(uc, 0xC0000000, high_dram_size, UC_PROT_ALL, state->dram_ptr);
if (err != UC_ERR_OK) {
fprintf(stderr, "[error] Failed to map high DRAM: %s\n", uc_strerror(err));
return nullptr;
}
printf("[emu] Mapped DRAM High: 0xC0000000 - 0x%08llX (1024 MB)\n",
(unsigned long long)(0xC0000000 + high_dram_size));
// ---- Map Framebuffer pointer ----
// FB_BASE is 0xF5A00000. Offset in 1GB block starting at 0xC0000000 is 0x35A00000.
state->fb_ptr = state->dram_ptr + (FB_BASE - 0xC0000000);
// Fill with hekate background color (0x1B1B1B)
for (size_t i = 0; i < FB_SIZE; i += 4) {
state->fb_ptr[i + 0] = 0x1B; // B
state->fb_ptr[i + 1] = 0x1B; // G
state->fb_ptr[i + 2] = 0x1B; // R
state->fb_ptr[i + 3] = 0xFF; // A
}
state->fb_addr = FB_BASE;
printf("[emu] Defined FB: 0x%08llX - 0x%08llX (%u MB)\n",
(unsigned long long)FB_BASE,
(unsigned long long)(FB_BASE + FB_SIZE),
(unsigned)(FB_SIZE / (1024 * 1024)));
// ---- Map low memory (16MB @ 0x0) ----
// hekate seems to do a memset(0, ...) for clear screen if some ptr is NULL.
uint8_t *low_ptr = (uint8_t *)calloc(1, 0x01000000);
uc_mem_map_ptr(uc, 0, 0x01000000, UC_PROT_ALL, low_ptr);
// ---- Nyx Storage (16MB @ 0xED000000) ----
// Already mapped as part of 2GB DRAM chunk
// ---- Map Peripherals (PWM, SDMMC, etc.) ----
// Map individual pages to allow MMIO hooks
// The mappings previously declared manually were moved here:
// Some are mapped earlier in setup_emulation properly using FB_BASE e.g.
uc_mem_map(uc, 0x7000A000, 0x1000, UC_PROT_ALL); // PWM
uc_mem_map(uc, 0x700B0000, 0x1000, UC_PROT_ALL); // SDMMC1
uc_mem_map(uc, 0x7000E000, 0x1000, UC_PROT_ALL); // RTC/PMC
uc_mem_map(uc, 0x6000C000, 0x2000, UC_PROT_ALL); // SYSREG/APB_SEMAPH
// BootROM (iROM) at 0x100000, 96 KB. We don't have the real BootROM
// contents, but mapping the region as zero-filled lets Hekate's "Bootrom
// Info" / "Dump Bootrom" features read it without faulting. The IPATCH
// CAM at 0x6001DC00 is also zero-mapped so the ipatches table renders
// empty (which matches an unpatched SoC).
uc_mem_map(uc, 0x00100000, 0x18000, UC_PROT_READ | UC_PROT_EXEC); // iROM
uc_mem_map(uc, 0x6001D000, 0x1000, UC_PROT_ALL); // IPATCH CAM page
// ---- heap region (32MB @ 0x90000000) ----
// Already mapped as part of 2GB DRAM chunk
// ---- Copy payload to IRAM at IPL_LOAD_ADDR ----
size_t copy_size = payload_size;
if (copy_size > IRAM_SIZE - (IPL_LOAD_ADDR - IRAM_BASE)) {
copy_size = IRAM_SIZE - (IPL_LOAD_ADDR - IRAM_BASE);
printf("[loader] Warning: payload truncated to %zu bytes\n", copy_size);
}
memcpy(state->iram_ptr + (IPL_LOAD_ADDR - IRAM_BASE), payload, copy_size);
printf("[emu] Payload copied to IRAM @ 0x%08llX\n", (unsigned long long)IPL_LOAD_ADDR);
// ---- Set initial register state ----
uint32_t sp = IPL_STACK_ADDR;
uint32_t pc = IPL_LOAD_ADDR;
uc_reg_write(uc, UC_ARM_REG_SP, &sp);
uc_reg_write(uc, UC_ARM_REG_PC, &pc);
printf("[emu] Initial PC=0x%08X SP=0x%08X\n", pc, sp);
// ---- Setup MMIO hooks ----
mmio_init(uc, state);
printf("[emu] MMIO hooks registered\n");
return uc;
}
// ==================== Main ====================
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <payload.bin> [--sd <sd.img>] [--boot0 <boot0.bin>] [--rawnand <rawnand_prefix>] [--prod-keys <prod.keys>] [--oem erista|mariko]\n", argv[0]);
fprintf(stderr, "\nControls:\n");
fprintf(stderr, " Arrow Up/Down = VOL+/VOL- buttons\n");
fprintf(stderr, " Enter = POWER button\n");
fprintf(stderr, " Escape = Quit\n");
fprintf(stderr, " F8 = Save raw guest FB to fb_dump_NNNN.raw\n");
return 1;
}
printf("=== RCM Payload Emulator ===\n");
printf("Using Unicorn Engine for ARM32 emulation\n\n");
// Initialize emulator state
EmuState state = {};
state.init_fuse_defaults();
// Overlay any saved hardware tweaks from rcm_emu.ini (cwd-relative).
// The load is additive — missing keys keep the defaults set above.
if (config_window_load_ini(&state, "rcm_emu.ini")) {
printf("[config] Loaded rcm_emu.ini\n");
}
// Load payload binary
size_t payload_size = 0;
uint8_t *payload = load_payload(argv[1], &payload_size);
if (!payload) return 1;
// Parse additional arguments for storage. Configuration state
// (is_mariko, fuses, panel ID, battery values, ...) lives in
// rcm_emu.ini and is loaded by config_window_load_ini() above.
// CLI flags only point at on-disk images and one explicit override
// (--oem) that lets you re-run the same payload+ini with a different
// SoC family without editing the ini. Any auto-derivation from
// BOOT0 was deliberately removed: the emulator must not silently
// contradict the ini, otherwise "patch the ini and re-run" stops
// being a deterministic loop.
const char *sd_path = nullptr;
const char *boot0_path = nullptr;
const char *rawnand_prefix = nullptr;
const char *prod_keys_path = nullptr;
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "--sd") == 0 && i + 1 < argc) {
sd_path = argv[++i];
} else if (strcmp(argv[i], "--boot0") == 0 && i + 1 < argc) {
boot0_path = argv[++i];
} else if (strcmp(argv[i], "--rawnand") == 0 && i + 1 < argc) {
rawnand_prefix = argv[++i];
} else if (strcmp(argv[i], "--prod-keys") == 0 && i + 1 < argc) {
prod_keys_path = argv[++i];
} else if (strcmp(argv[i], "--oem") == 0 && i + 1 < argc) {
const char *oem = argv[++i];
if (strcmp(oem, "mariko") == 0 || strcmp(oem, "t210b01") == 0) {
state.is_mariko = true;
state.pmic_otp = 0x53;
printf("[emu] OEM: Mariko (T210B01) [overrides ini]\n");
} else if (strcmp(oem, "erista") == 0 || strcmp(oem, "t210") == 0) {
state.is_mariko = false;
state.pmic_otp = 0x35;
printf("[emu] OEM: Erista (T210) [overrides ini]\n");
} else {
fprintf(stderr, "[emu] Unknown --oem value '%s'; expected 'erista' or 'mariko'\n", oem);
}
}
}
if (prod_keys_path) {
extern int se_engine_load_prod_keys(const char *);
se_engine_load_prod_keys(prod_keys_path);
}
// Auto-script flag: feed a deterministic button sequence after the menu
// has settled. Useful for capturing the PIN recovery flow non-interactively
// (e.g. when verifying a fix without manually clicking through the SDL window).
bool auto_pin_recovery = false;
bool auto_te_script = false;
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "--auto-pin-recovery") == 0) auto_pin_recovery = true;
else if (strcmp(argv[i], "--auto-te-script") == 0) auto_te_script = true;
}
if (sd_path) {
state.sd_fd = open(sd_path, O_RDWR);
if (state.sd_fd < 0) perror("[emu] Failed to open SD image");
else printf("[emu] SD image opened: %s\n", sd_path);
}
if (boot0_path) {
state.emmc_boot0_fd = open(boot0_path, O_RDWR);
if (state.emmc_boot0_fd < 0) perror("[emu] Failed to open BOOT0 image");
else printf("[emu] BOOT0 image opened: %s\n", boot0_path);
}
if (rawnand_prefix) {
for (int i = 0; i < 16; i++) {
char path[512];
snprintf(path, sizeof(path), "%s.%02d", rawnand_prefix, i);
int fd = open(path, O_RDWR);
if (fd >= 0) {
state.emmc_gpp_fds.push_back(fd);
// printf("[emu] rawnand part %02d opened: %s\n", i, path);
} else {
break;
}
}
if (!state.emmc_gpp_fds.empty()) {
printf("[emu] GPP rawnand opened (%zu parts starting with %s.00)\n",
state.emmc_gpp_fds.size(), rawnand_prefix);
}
}
// Setup ARM emulation. Hold on to the payload buffer in EmuState so the
// soft-reboot path (config window "Reboot" button) can re-write it into
// IRAM without re-reading from disk.
uc_engine *uc = setup_emulation(&state, payload, payload_size);
state.payload_ptr = payload;
state.payload_len = payload_size;
if (!uc) { free(payload); return 1; }
// Initialize SDL2 display
if (!sdl_display_init()) {
fprintf(stderr, "[error] Failed to initialize display\n");
uc_close(uc);
return 1;
}
// Initialize the hardware-tweak config window (hidden until 'M' is pressed).
if (!config_window_init()) {
fprintf(stderr, "[warn] Config window init failed; M-key menu disabled\n");
}
// Initialize the UART console window (hidden until 'C' is pressed).
if (!console_window_init()) {
fprintf(stderr, "[warn] Console window init failed; C-key console disabled\n");
}
printf("\n[emu] Starting emulation...\n");
printf("[emu] Esc quit | M hardware config | C UART console\n\n");
// ---- Emulation loop ----
// We run emulation in batches, interleaving with SDL event handling
// and display updates to keep the UI responsive.
const uint64_t BATCH_INSTRUCTIONS = 100000; // Instructions per batch
const int DISPLAY_UPDATE_MS = 16; // ~60 FPS
auto last_display_update = std::chrono::steady_clock::now();
while (state.running) {
// Process SDL events (keyboard input, window close)
if (!sdl_display_poll_events(&state, uc)) break;
// Soft reboot: re-write the payload to IRAM, wipe DRAM (so Nyx and
// the bootloader's file-static caches reset), reset PC/SP/clock and
// re-prime the WDT cookie so Hekate's early boot skips Minerva again.
if (state.reboot_requested.exchange(false)) {
uc_emu_stop(uc);
memset(state.dram_low_ptr, 0, 256 * 1024 * 1024);
memset(state.dram_ptr, 0, 1024 * 1024 * 1024);
uc_mem_write(uc, IPL_LOAD_ADDR, state.payload_ptr, state.payload_len);
uint32_t wdt_magic = 0x544457;
uc_mem_write(uc, 0x4003FF18, &wdt_magic, sizeof(wdt_magic));
uint32_t reset_pc = IPL_LOAD_ADDR;
uint32_t reset_sp = IPL_STACK_ADDR;
uint32_t reset_cpsr = 0; // ARM mode, all flags clear
uc_reg_write(uc, UC_ARM_REG_PC, &reset_pc);
uc_reg_write(uc, UC_ARM_REG_SP, &reset_sp);
uc_reg_write(uc, UC_ARM_REG_CPSR, &reset_cpsr);
state.fb_addr = FB_BASE; // re-point display at the FB base
state.emu_usec = 0;
state.insn_count = 0;
state.touch_phase = 0;
state.paused = false;
printf("[emu] Soft reboot complete (DRAM wiped)\n");
}
if (!state.paused) {
uint32_t pc, cpsr;
uc_reg_read(uc, UC_ARM_REG_PC, &pc);
uc_reg_read(uc, UC_ARM_REG_CPSR, &cpsr);
// ---- PIN recovery scripted navigation (Lockpick patch menu) ----
// With EmuNAND grayed out (no emummc.ini), Lockpick's main menu
// layout effectively becomes:
// 0: Dump from SysNAND
// 1: --- (was EmuNAND, grayed)
// 2: --- (caption)
// 3: Recover Parental PIN ← target (single VOL_DOWN reaches it)
// Stage 1: VOL_DOWN to navigate. Stage 2: POWER press-then-release
// (just long enough for the menu to dispatch recover_pin), then
// idle so recover_pin's final btn_wait blocks on us — keeps the
// result text visible in the framebuffer when the run times out.
if (auto_pin_recovery) {
static int pin_stage = 0;
static uint64_t pin_t = 0;
auto press_release = [&](std::atomic<bool> *btn,
uint64_t hold_us,
uint64_t cooldown_us) {
if (state.emu_usec - pin_t < hold_us) {
btn->store(true);
} else if (state.emu_usec - pin_t < hold_us + cooldown_us) {
btn->store(false);
} else {
pin_t = state.emu_usec;
++pin_stage;
}
};
if (pin_t == 0 && state.emu_usec > 6000000) {
pin_t = state.emu_usec;
pin_stage = 1;
printf("[emu] Auto PIN recovery armed at emu_usec=%llu\n",
(unsigned long long)state.emu_usec);
}
static int last_logged_stage = -1;
if (pin_stage != last_logged_stage) {
printf("[emu] Auto PIN stage %d at emu_usec=%llu\n", pin_stage,
(unsigned long long)state.emu_usec);
last_logged_stage = pin_stage;
}
switch (pin_stage) {
case 1: press_release(&state.btn_vol_down, 1500000, 1500000); break;
case 2:
if (state.emu_usec - pin_t < 1500000) {
state.btn_power.store(true);
} else if (state.emu_usec - pin_t < 3000000) {
state.btn_power.store(false);
} else {
printf("[emu] Auto PIN recovery sequence done\n");
pin_t = state.emu_usec;
pin_stage = 3;
}
break;
case 3: break; // Idle — let recover_pin run to completion.
default: break;
}
}
// ---- TegraExplorer scripted nav (recover_pin.te) ----
// Sequence captured from a manual interactive run on the same
// testcase (sw051/13.2.1 + TegraExplorer.bin) and replayed
// verbatim — emu_usec is deterministic, so identical timestamps
// hit the UI in the same state.
//
// Flow: POWER (skip "Grabbing keys... done") → 13×VOL_DOWN
// (reach recover_pin.te in main menu) → POWER (enter script)
// → 1×VOL_DOWN (select "Recover from sysmmc") → POWER (run).
// After the last POWER we idle: returning to TE's main menu
// means the script crashed/errored; otherwise the PIN stays
// painted in the framebuffer.
if (auto_te_script) {
struct InputEv { uint64_t at_us; char btn; bool down; };
static const InputEv te_events[] = {
// Skip "Grabbing keys... done"
{3480000, 'P', true}, {3640000, 'P', false},
// 13× VOL_DOWN to recover_pin.te
{10080000, 'D', true}, {10460000, 'D', false},
{10830000, 'D', true}, {11000000, 'D', false},
{11340000, 'D', true}, {11540000, 'D', false},
{11850000, 'D', true}, {12010000, 'D', false},
{12330000, 'D', true}, {12540000, 'D', false},
{12830000, 'D', true}, {13040000, 'D', false},
{13360000, 'D', true}, {13580000, 'D', false},
{13900000, 'D', true}, {14110000, 'D', false},
{14420000, 'D', true}, {14650000, 'D', false},
{14980000, 'D', true}, {15210000, 'D', false},
{15550000, 'D', true}, {15760000, 'D', false},
{16200000, 'D', true}, {16340000, 'D', false},
// POWER → enter recover_pin.te
{18110000, 'P', true}, {18310000, 'P', false},
// VOL_DOWN → select "Recover from sysmmc"
{21670000, 'D', true}, {21900000, 'D', false},
// POWER → run script
{22850000, 'P', true}, {23020000, 'P', false},
};
static const size_t te_n = sizeof(te_events)/sizeof(te_events[0]);
static size_t te_idx = 0;
while (te_idx < te_n && state.emu_usec >= te_events[te_idx].at_us) {
const InputEv &ev = te_events[te_idx];
std::atomic<bool> *btn = (ev.btn == 'P') ? &state.btn_power
: (ev.btn == 'D') ? &state.btn_vol_down
: &state.btn_vol_up;
btn->store(ev.down);
printf("[auto-te] %s %s @emu_usec=%llu (event %zu/%zu)\n",
ev.down ? "DOWN" : "UP ",
ev.btn == 'P' ? "POWER " :
ev.btn == 'D' ? "VOL_DOWN" : "VOL_UP ",
(unsigned long long)state.emu_usec, te_idx + 1, te_n);
te_idx++;
if (te_idx == te_n)
printf("[auto-te] sequence complete; idling for output\n");
}
}
// Touch injection for the Nyx GUI popup (eMMC Issues Warning).
// Nyx initializes after the IPL stage; we inject a tap once enough
// emulated time has passed for Nyx to load and render its dialog.
// touch_x/y are portrait coordinates (720x1280 space); Nyx maps them
// to landscape (1280x720) internally.
// Default: (360, 640) = portrait center. Tune if the OK button
// is not hit (e.g. try touch_x=490, touch_y=640 for the actual button).
if (state.emu_usec > 3000000 && state.touch_phase == 0) {
state.touch_phase = 1;
printf("[emu] Touch injection armed: portrait (%d,%d)\n",
state.touch_x, state.touch_y);
}
// Run a batch of ARM instructions
uint32_t start_addr = pc;
if (cpsr & (1 << 5)) start_addr |= 1;
uc_err err = uc_emu_start(uc, start_addr, 0, 0, BATCH_INSTRUCTIONS);
if (err != UC_ERR_OK) {
uint32_t error_pc;
uc_reg_read(uc, UC_ARM_REG_PC, &error_pc);
fprintf(stderr, "\n[emu] FATAL: Emulation error at PC=0x%08X: %s\n", error_pc, uc_strerror(err));
state.running = false;
break;
}
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// Update display periodically
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_display_update);
if (elapsed.count() >= DISPLAY_UPDATE_MS) {
sdl_display_update(&state, uc);
config_window_render(&state);
console_window_render(&state);
last_display_update = now;
}
}
// Cleanup
printf("\n[emu] Shutting down...\n");
uint32_t final_pc;
uc_reg_read(uc, UC_ARM_REG_PC, &final_pc);
printf("[emu] Final PC: 0x%08X\n", final_pc);
// Print the instruction that it halted on as well as the previous 8 bytes
uint16_t final_insn[8];
if (uc_mem_read(uc, (final_pc & ~1) - 8, final_insn, 16) == UC_ERR_OK) {
printf("[emu] Final INSNS (-8): %04X %04X %04X %04X %04X %04X %04X %04X\n",
final_insn[0], final_insn[1], final_insn[2], final_insn[3],
final_insn[4], final_insn[5], final_insn[6], final_insn[7]);
}
console_window_shutdown();
config_window_shutdown();
sdl_display_shutdown();
uc_close(uc);
free(state.iram_ptr);
free(state.dram_ptr);
free(state.dram_low_ptr);
free(state.payload_ptr);
// fb_ptr points inside dram_ptr; do not free separately.
printf("[emu] Done.\n");
return 0;
}