@sie-js/apoxi-tool 1.0.3 → 1.0.4
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/dist/src/cli/memory.d.ts +16 -0
- package/dist/src/cli/memory.js +164 -0
- package/dist/src/cli/misc.d.ts +4 -0
- package/dist/src/cli/misc.js +48 -0
- package/dist/src/cli/unlock.d.ts +3 -0
- package/dist/src/cli/unlock.js +30 -0
- package/dist/src/cli.d.ts +6 -0
- package/dist/src/cli.js +63 -0
- package/dist/src/data/unlocker.elf.d.ts +2 -0
- package/dist/src/data/unlocker.elf.js +93 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/lib/dwd.d.ts +6 -0
- package/dist/src/lib/dwd.js +104 -0
- package/dist/src/utils/command.d.ts +15 -0
- package/dist/src/utils/command.js +44 -0
- package/dist/src/utils/retry.d.ts +7 -0
- package/dist/src/utils/retry.js +22 -0
- package/dist/src/utils/serial.d.ts +4 -0
- package/dist/src/utils/serial.js +60 -0
- package/dist/src/utils/string.d.ts +3 -0
- package/dist/src/utils/string.js +39 -0
- package/package.json +8 -8
@@ -0,0 +1,16 @@
|
|
1
|
+
import { AppCommand } from "#src/utils/command.js";
|
2
|
+
import { CLIBaseOptions } from "#src/cli.js";
|
3
|
+
export interface CLIReadMemoryOptions extends CLIBaseOptions {
|
4
|
+
addr?: string;
|
5
|
+
size?: string;
|
6
|
+
name?: string;
|
7
|
+
output?: string;
|
8
|
+
}
|
9
|
+
export interface CLIReadAllMemoryOptions extends CLIBaseOptions {
|
10
|
+
output?: string;
|
11
|
+
include?: string[];
|
12
|
+
exclude?: string[];
|
13
|
+
}
|
14
|
+
export declare const cliReadMemory: AppCommand<CLIReadMemoryOptions>;
|
15
|
+
export declare const cliListMemory: AppCommand<CLIBaseOptions>;
|
16
|
+
export declare const cliReadAllMemory: AppCommand<CLIReadAllMemoryOptions>;
|
@@ -0,0 +1,164 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import cliProgress from "cli-progress";
|
3
|
+
import { sprintf } from "sprintf-js";
|
4
|
+
import { AppCommandValidateError, onCleanup } from "#src/utils/command.js";
|
5
|
+
import { formatSize, parseAddr, parseSize } from "#src/utils/string.js";
|
6
|
+
import { connectDWD, disconnectDWD } from "#src/utils/serial.js";
|
7
|
+
import { table as asciiTable } from "table";
|
8
|
+
const MEMORY_REGION_DESCR = {
|
9
|
+
BROM: 'Built-in 1st stage bootloader firmware.',
|
10
|
+
TCM: 'Built-in memory in the CPU, used for IRQ handlers.',
|
11
|
+
SRAM: 'Built-in memory in the CPU.',
|
12
|
+
RAM: 'External RAM.',
|
13
|
+
FLASH: 'NOR flash.',
|
14
|
+
};
|
15
|
+
export const cliReadMemory = async (options) => {
|
16
|
+
let addr = options.addr != null ? parseAddr(options.addr) : 0;
|
17
|
+
let size = options.size != null ? parseSize(options.size) : 0;
|
18
|
+
let name;
|
19
|
+
let outputFile;
|
20
|
+
if (options.name && options.addr)
|
21
|
+
throw new AppCommandValidateError("Can't use both --name and --addr options!");
|
22
|
+
if (options.addr && !options.size)
|
23
|
+
throw new AppCommandValidateError("Can't use --addr option without --size!");
|
24
|
+
if (!options.addr && !options.name)
|
25
|
+
throw new AppCommandValidateError("Need to specify --addr or --name!");
|
26
|
+
const dwd = await connectDWD(options.port, +options.baudrate, options.key);
|
27
|
+
onCleanup(() => disconnectDWD(dwd));
|
28
|
+
const info = await getPhoneInfo(dwd);
|
29
|
+
if (options.name) {
|
30
|
+
const region = getMemoryRegionByName(info.regions, options.name);
|
31
|
+
if (!region)
|
32
|
+
throw new Error(`Memory region "${options.name}" not found!`);
|
33
|
+
addr = region.addr;
|
34
|
+
size = region.size;
|
35
|
+
name = region.name;
|
36
|
+
}
|
37
|
+
else {
|
38
|
+
const region = getMemoryRegionByAddrAndSize(info.regions, addr, size);
|
39
|
+
if (region)
|
40
|
+
name = region.name;
|
41
|
+
}
|
42
|
+
const genericName = `${info.name}${name ? '-' + name : ''}-${sprintf("%08X_%08X", addr, size)}.bin`;
|
43
|
+
if (!options.output) {
|
44
|
+
outputFile = `./${genericName}`;
|
45
|
+
}
|
46
|
+
else if (fs.existsSync(options.output) && fs.lstatSync(options.output).isDirectory()) {
|
47
|
+
outputFile = `${options.output}/${genericName}`;
|
48
|
+
}
|
49
|
+
else {
|
50
|
+
outputFile = options.output;
|
51
|
+
}
|
52
|
+
fs.writeFileSync(outputFile, "");
|
53
|
+
console.log(sprintf("Reading memory %08X ... %08X (%s)", addr, addr + size - 1, formatSize(size)));
|
54
|
+
console.log();
|
55
|
+
const pb = new cliProgress.SingleBar({
|
56
|
+
format: ' [{bar}] {percentage}% | ETA: {eta}s | {speed} kB/s'
|
57
|
+
}, cliProgress.Presets.legacy);
|
58
|
+
pb.start(size, 0);
|
59
|
+
const result = await dwd.readMemory(addr, size, {
|
60
|
+
onProgress: (e) => {
|
61
|
+
pb.update(e.cursor, {
|
62
|
+
speed: e.elapsed ? +((e.cursor / (e.elapsed / 1000)) / 1024).toFixed(2) : 'N/A',
|
63
|
+
});
|
64
|
+
}
|
65
|
+
});
|
66
|
+
pb.stop();
|
67
|
+
fs.writeFileSync(outputFile, result.buffer);
|
68
|
+
console.log();
|
69
|
+
console.log(`File saved to: ${outputFile}`);
|
70
|
+
};
|
71
|
+
export const cliListMemory = async (options) => {
|
72
|
+
const dwd = await connectDWD(options.port, +options.baudrate, options.key);
|
73
|
+
onCleanup(() => disconnectDWD(dwd));
|
74
|
+
const info = await getPhoneInfo(dwd);
|
75
|
+
const table = [
|
76
|
+
['Name', 'Address', 'Size', 'Description'],
|
77
|
+
];
|
78
|
+
for (const r of info.regions) {
|
79
|
+
table.push([
|
80
|
+
r.name,
|
81
|
+
sprintf("0x%08X", r.addr),
|
82
|
+
sprintf("0x%08X (%s)", r.size, formatSize(r.size)),
|
83
|
+
MEMORY_REGION_DESCR[r.name] ?? "Unknown memory region.",
|
84
|
+
]);
|
85
|
+
}
|
86
|
+
console.log(asciiTable(table).trim());
|
87
|
+
};
|
88
|
+
export const cliReadAllMemory = async (options) => {
|
89
|
+
let outputDir = options.output || ".";
|
90
|
+
if (!fs.existsSync(outputDir)) {
|
91
|
+
try {
|
92
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
93
|
+
}
|
94
|
+
catch (e) {
|
95
|
+
console.error(e instanceof Error ? e.message : String(e));
|
96
|
+
console.error(`ERROR: Output dir not found: ${outputDir}`);
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
const dwd = await connectDWD(options.port, +options.baudrate, options.key);
|
101
|
+
onCleanup(() => disconnectDWD(dwd));
|
102
|
+
const info = await getPhoneInfo(dwd);
|
103
|
+
const regions = info.regions.filter((r) => {
|
104
|
+
if (options.include?.length && !options.include.includes(r.name))
|
105
|
+
return false;
|
106
|
+
if (options.exclude?.length && options.exclude.includes(r.name))
|
107
|
+
return false;
|
108
|
+
return true;
|
109
|
+
});
|
110
|
+
regions.sort((a, b) => a.size - b.size);
|
111
|
+
let totalSize = 0;
|
112
|
+
for (const r of regions) {
|
113
|
+
totalSize += r.size;
|
114
|
+
}
|
115
|
+
const pb = new cliProgress.SingleBar({
|
116
|
+
format: ' [{bar}] {percentage}% | ETA: {totalEta}s | {speed} kB/s'
|
117
|
+
}, cliProgress.Presets.legacy);
|
118
|
+
console.log();
|
119
|
+
let i = 1;
|
120
|
+
let totalRead = 0;
|
121
|
+
for (const r of regions) {
|
122
|
+
console.log(sprintf("[%d/%d] Reading %s %08X ... %08X (%s)", i, regions.length, r.name, r.addr, r.addr + r.size - 1, formatSize(r.size)));
|
123
|
+
pb.start(r.size, 0);
|
124
|
+
const response = await dwd.readMemory(r.addr, r.size, {
|
125
|
+
onProgress: (e) => {
|
126
|
+
const speed = e.elapsed ? e.cursor / (e.elapsed / 1000) : 0;
|
127
|
+
pb.update(e.cursor, {
|
128
|
+
speed: speed ? +(speed / 1024).toFixed(2) : 'N/A',
|
129
|
+
filename: r.name,
|
130
|
+
fileIndex: i,
|
131
|
+
totalFiles: regions.length,
|
132
|
+
totalEta: speed ? Math.round((totalSize - (totalRead + e.cursor)) / speed) : 0,
|
133
|
+
});
|
134
|
+
}
|
135
|
+
});
|
136
|
+
totalRead += r.size;
|
137
|
+
i++;
|
138
|
+
const outputFile = `${outputDir}/${info.name}-${r.name}-${sprintf("%08X_%08X", r.addr, r.size)}.bin`;
|
139
|
+
fs.writeFileSync(outputFile, response.buffer);
|
140
|
+
pb.stop();
|
141
|
+
console.log(`File saved to: ${outputFile}`);
|
142
|
+
console.log();
|
143
|
+
}
|
144
|
+
};
|
145
|
+
function getMemoryRegionByName(regions, name) {
|
146
|
+
for (const r of regions) {
|
147
|
+
if (r.name.toLowerCase() == name.toLowerCase())
|
148
|
+
return r;
|
149
|
+
}
|
150
|
+
return undefined;
|
151
|
+
}
|
152
|
+
function getMemoryRegionByAddrAndSize(regions, addr, size) {
|
153
|
+
for (const r of regions) {
|
154
|
+
if (r.addr == addr && r.size == size)
|
155
|
+
return r;
|
156
|
+
}
|
157
|
+
return undefined;
|
158
|
+
}
|
159
|
+
async function getPhoneInfo(dwd) {
|
160
|
+
const regions = await dwd.getMemoryRegions();
|
161
|
+
const swInfo = await dwd.getSWVersion();
|
162
|
+
console.log(`Detected phone: ${swInfo.sw} (${swInfo.cpu})`);
|
163
|
+
return { name: swInfo.sw, regions };
|
164
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { onCleanup } from "#src/utils/command.js";
|
2
|
+
import { SerialPort } from "serialport";
|
3
|
+
import { getUSBDeviceName } from "@sie-js/serial";
|
4
|
+
import { sprintf } from "sprintf-js";
|
5
|
+
import cliProgress from "cli-progress";
|
6
|
+
import { connectDWD, disconnectDWD } from "#src/utils/serial.js";
|
7
|
+
export const cliListSerialPorts = async (options) => {
|
8
|
+
for (let p of await SerialPort.list()) {
|
9
|
+
if (p.productId != null) {
|
10
|
+
const vid = parseInt(p.vendorId, 16);
|
11
|
+
const pid = parseInt(p.productId, 16);
|
12
|
+
const usbName = getUSBDeviceName(vid, pid);
|
13
|
+
const isDefault = p.path === options.port;
|
14
|
+
console.log(sprintf("%s %04x:%04x %s%s", p.path, vid, pid, usbName ?? p.manufacturer, (isDefault ? " <-- selected" : "")));
|
15
|
+
}
|
16
|
+
}
|
17
|
+
};
|
18
|
+
export const cliBruteforceDWDKeys = async (options) => {
|
19
|
+
const dwd = await connectDWD(options.port, +options.baudrate, options.key);
|
20
|
+
onCleanup(() => disconnectDWD(dwd));
|
21
|
+
const pb = new cliProgress.SingleBar({
|
22
|
+
format: ' [{bar}] {percentage}% | ETA: {eta}s | {speed} keys/s'
|
23
|
+
}, cliProgress.Presets.legacy);
|
24
|
+
console.log("Bruteforce key2...");
|
25
|
+
pb.start(0xFFFF, 0);
|
26
|
+
const possibleKeys = await dwd.bruteforceKey2({
|
27
|
+
onProgress: (e) => {
|
28
|
+
pb.update(e.cursor, {
|
29
|
+
speed: e.elapsed ? +((e.cursor / (e.elapsed / 1000))).toFixed(2) : 'N/A',
|
30
|
+
total: e.total,
|
31
|
+
});
|
32
|
+
}
|
33
|
+
});
|
34
|
+
pb.stop();
|
35
|
+
console.log();
|
36
|
+
const foundKeys = [];
|
37
|
+
for (const key2 of possibleKeys) {
|
38
|
+
console.log(sprintf("Bruteforce key1 for key2=%04X", key2));
|
39
|
+
const keys = await dwd.bruteforceKey1(key2);
|
40
|
+
if (keys != null)
|
41
|
+
foundKeys.push(keys);
|
42
|
+
}
|
43
|
+
console.log();
|
44
|
+
console.log("Found keys:");
|
45
|
+
for (const key of foundKeys) {
|
46
|
+
console.log(sprintf(" key1=%s, key2=%04X", key.key1.toString("hex").toUpperCase(), key.key2));
|
47
|
+
}
|
48
|
+
};
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import { onCleanup } from "#src/utils/command.js";
|
2
|
+
import { connectDWD, disconnectDWD } from "#src/utils/serial.js";
|
3
|
+
import { isApoxiBootUnlocked, unlockApoxiBootloader } from "#src/lib/dwd.js";
|
4
|
+
import inquirer from "inquirer";
|
5
|
+
import chalk from "chalk";
|
6
|
+
export const cliUnlockBoot = async (options) => {
|
7
|
+
const dwd = await connectDWD(options.port, +options.baudrate, options.key);
|
8
|
+
onCleanup(() => disconnectDWD(dwd));
|
9
|
+
if (await isApoxiBootUnlocked(dwd)) {
|
10
|
+
console.log("Boot already open! Unlock is not needed.");
|
11
|
+
return;
|
12
|
+
}
|
13
|
+
const { confirm } = await inquirer.prompt([
|
14
|
+
{
|
15
|
+
type: 'confirm',
|
16
|
+
name: 'confirm',
|
17
|
+
message: chalk.red([
|
18
|
+
`WARNING: Unlocking the APOXI bootloader WILL BREAK THE DEVICE and it may never work again.`,
|
19
|
+
'MAKE A FULL BACKUP BEFORE THE OPERATION.',
|
20
|
+
'Continue?'
|
21
|
+
].join('\n')),
|
22
|
+
default: false
|
23
|
+
}
|
24
|
+
]);
|
25
|
+
if (confirm) {
|
26
|
+
await unlockApoxiBootloader(dwd, {
|
27
|
+
debug: (log) => console.log(log),
|
28
|
+
});
|
29
|
+
}
|
30
|
+
};
|
package/dist/src/cli.js
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
import { program } from "commander";
|
3
|
+
import { cliListMemory, cliReadAllMemory, cliReadMemory } from "./cli/memory.js";
|
4
|
+
import { createAppCommand } from "./utils/command.js";
|
5
|
+
import debug from "debug";
|
6
|
+
import { getDefaultPort } from "#src/utils/serial.js";
|
7
|
+
import { cliBruteforceDWDKeys, cliListSerialPorts } from "#src/cli/misc.js";
|
8
|
+
import { cliUnlockBoot } from "#src/cli/unlock.js";
|
9
|
+
import { readPackage } from "read-pkg";
|
10
|
+
const pkg = await readPackage();
|
11
|
+
const DEFAULT_PORT = await getDefaultPort();
|
12
|
+
program
|
13
|
+
.name("apoxi-tool")
|
14
|
+
.version(pkg.version, '-v, --version')
|
15
|
+
.description('CLI tool for APOXI phones.')
|
16
|
+
.option('-p, --port <port>', 'serial port name', DEFAULT_PORT)
|
17
|
+
.option('-b, --baudrate <baudrate>', 'limit maximum baudrate (0 - use maximum)', '0')
|
18
|
+
.option('-k, --key <key>', 'DWD key (auto/lg/panasonic/siemens or KEY1:KEY2)', 'auto')
|
19
|
+
.option('-V, --verbose', 'Increase verbosity', (_, prev) => prev + 1, 0)
|
20
|
+
.hook('preAction', (thisCommand) => {
|
21
|
+
const opts = thisCommand.opts();
|
22
|
+
if (opts.verbose) {
|
23
|
+
console.log(`Verbosity level: ${opts.verbose}`);
|
24
|
+
const filters = ["atc", "dwd"];
|
25
|
+
if (opts.verbose > 1)
|
26
|
+
filters.push("dwd:*", "atc:*");
|
27
|
+
if (opts.verbose > 2)
|
28
|
+
filters.push("*");
|
29
|
+
debug.enable(filters.join(","));
|
30
|
+
}
|
31
|
+
});
|
32
|
+
program
|
33
|
+
.command('unlock-bootloader')
|
34
|
+
.description('Unlock APOXI bootloader (allow using V-Klay)')
|
35
|
+
.action(createAppCommand(cliUnlockBoot));
|
36
|
+
program
|
37
|
+
.command('read-memory')
|
38
|
+
.description('Read and save phone memory')
|
39
|
+
.option('-n, --name <blockName>', 'Read by block name')
|
40
|
+
.option('-a, --addr <address>', 'Read from address (dec or hex)')
|
41
|
+
.option('-s, --size <bytes>', 'Size in bytes (dec, hex, k/m/g allowed)')
|
42
|
+
.option('-o, --output [file]', 'Write output to file or directory')
|
43
|
+
.action(createAppCommand(cliReadMemory));
|
44
|
+
program
|
45
|
+
.command('read-all-memory')
|
46
|
+
.description('Read and save phone memory (ALL available blocks)')
|
47
|
+
.option('-i, --include <blocks>', 'Include blocks (comma separated)', (v) => v.split(','), [])
|
48
|
+
.option('-e, --exclude <blocks>', 'Exclude blocks (comma separated)', (v) => v.split(','), [])
|
49
|
+
.option('-o, --output [dir]', 'Write output to directory')
|
50
|
+
.action(createAppCommand(cliReadAllMemory));
|
51
|
+
program
|
52
|
+
.command('list-memory')
|
53
|
+
.description('List available memory blocks')
|
54
|
+
.action(createAppCommand(cliListMemory));
|
55
|
+
program.command('list-ports')
|
56
|
+
.description('List available serial ports.')
|
57
|
+
.action(createAppCommand(cliListSerialPorts));
|
58
|
+
program.command('bruteforce-dwd-keys')
|
59
|
+
.description('Bruteforce DWD keys')
|
60
|
+
.action(createAppCommand(cliBruteforceDWDKeys));
|
61
|
+
program.showSuggestionAfterError(true);
|
62
|
+
program.showHelpAfterError();
|
63
|
+
program.parse();
|
@@ -0,0 +1,93 @@
|
|
1
|
+
// DO NOT EDIT! AUTO-GENERATED BY tools/importApoxiUnlockerElf.ts
|
2
|
+
export default Buffer.from("7f454c4601010100000000000000000003002800010000007408000034000000e813000000020005340020000700280013001200060000003400000034000000" +
|
3
|
+
"34000000e0000000e000000004000000040000000300000014010000140100001401000011000000110000000400000001000000010000000000000000000000" +
|
4
|
+
"00000000250100002501000004000000010000000100000028010000280100002801000049000000490000000400000004000000010000007401000074010000" +
|
5
|
+
"740100008008000080080000050000000400000001000000f4090000f4090000f40900009400000034010200060000000400000002000000f4090000f4090000" +
|
6
|
+
"f4090000880000008800000006000000040000002f7573722f6c69622f6c642e736f2e3100000000010000000300000000000000000000000000000000000000" +
|
7
|
+
"000000000000000000000000000000000000000014020000000000000300060000000000880a00000000000003000a0000000000400600001700000044060000" +
|
8
|
+
"1700000048060000170000004c06000017000000540600001700000058060000170000005c060000170000006006000017000000740600001700000078060000" +
|
9
|
+
"170000007c060000170000008006000017000000e406000017000000e80600001700000068070000170000006c07000017000000ec07000017000000f0070000" +
|
10
|
+
"17000000f407000017000000700800001700000004e04ee2ff5f2de91c649fe510d04de29b0100eb042096e50f3ae0e3c72f03e50c30e0e3083086e50000e0e3" +
|
11
|
+
"0a32a0e30c0086e50c3093e5000053e10200001a083086e510d08de2ff9ffde80f22a0e3c03092e5000053e3020000aa803092e5010013e30100001a0230e0e3" +
|
12
|
+
"f3ffffea101f12ee103f13ee0c308de50cc09de5a8339fe5021a81e200c083e500c891e59c339fe59c539fe500c083e52e0100eb94339fe594b39fe5003881e5" +
|
13
|
+
"90339fe5c01092e58c439fe5001083e5c03092e50231c3e3c03082e57c339fe5b000cbe1b000c3e174339fe5003085e570339fe5003084e53e0100eb000050e3" +
|
14
|
+
"1200000a0120a0e30010a0e39000a0e3db0000eb0120a0e30210a0e10040a0e1b000cbe19000a0e3d50000eb2c339fe5890054e3200054130140a0130040a003" +
|
15
|
+
"b000c3e13d00000a0a40e0e3070000ea14339fe5003085e510339fe5003084e5240100eb000050e30b40e003e4ffff1ae8329fe5b020dbe1b030d3e1000054e3" +
|
16
|
+
"023883e10c3086e5084086152900001ad50000eb007050e2a600000a0c1097e50a02a0e3890100eb005051e27500001a003097e5b8829fe5f020a0e3b020c3e1" +
|
17
|
+
"ff20a0e3b020c3e10830a0e10c2097e5020054e16600003a0a42a0e36030a0e3b030c4e12020a0e3d030a0e3b030c4e1b020c4e1b030c4e1b030d4e1800013e3" +
|
18
|
+
"fcffff0a0130a0e36090a0e3b090c4e1b030c4e1003097e5f020a0e3b020c3e1ff20a0e3b020c3e1043097e5000053e3560000aa083086e5df0000eb85ffffea" +
|
19
|
+
"002095e598c0a0e30a52a0e30a2282e2b0c0c2e1bee4d5e118329fe50170a0e3ff10a0e3f000a0e317eea0e1004083e5b000c5e1b010c5e1b0c0c2e1b825d5e1" +
|
20
|
+
"00e08de5b000c5e1b010c5e1e8119fe5080052e30940e083002081e5b3ffff8ad8a19fe50221a0e12d2082e22d90a0e304208de504209de5020059e10900001a" +
|
21
|
+
"00309de5040053e10840e013a7ffff1aa0319fe5003093e5020853e30740e0830040a093a1ffffea0910a0e10020a0e39800a0e3620000eb011089e20020a0e3" +
|
22
|
+
"0080a0e19800a0e35d0000eb021089e20020a0e3008488e19800a0e3580000eb0020a0e3031089e2018088e2049089e20070a0e19800a0e3510000eb34319fe5" +
|
23
|
+
"00508ae5007487e10774a0e1970802e00c708ae504018ae9024084e0025085e0002093e510a08ae2020057e100708385cfffffea0a2284e20020d2e5014084e2" +
|
24
|
+
"0120c3e490ffffea0530e0e3a8ffffea0030e0e30c30c8e50d30c8e50e30c8e50f30c8e5500000eb000050e32100000a0c2090e5d030a0e30120c2e34010a0e3" +
|
25
|
+
"70c0a0e3b090c4e1b030c4e1050052e10e00001a0a32a0e36020a0e3b020c3e10120a0e3b020c3e1003090e5f020a0e3b020c3e1ff20a0e3b020c3e10030a0e3" +
|
26
|
+
"083086e56c0000eba20000eb11ffffea0a3285e2b010c3e1b5e098e1b0e0c3e1b0c0c3e1b0e0d3e180001ee3fcffff0a025085e2e4ffffea0630e0e37cffffea" +
|
27
|
+
"74080000180b0200140b0200200b0200120c00a0260b0200100b02001c0b0200240b0200aa0a000054050000aaaa000054550000880a0000880a02008c0a0200" +
|
28
|
+
"900a020058309fe5000052e3003093e50a3283e20600000aaa20a0e3b020c3e140209fe555c0a0e3002092e50a2282e2b0c0c2e10008a0e12008a0e18110a0e1" +
|
29
|
+
"b000c3e10a1281e20a32a0e3f020a0e3b000d1e1b020c3e1ff20a0e3b020c3e11eff2fe1200b02001c0b020074209fe574309fe500c092e50300a0e10020a0e3" +
|
30
|
+
"0c0052e11000001a0000a0e31eff2fe1001093e5160271e30500009a04e093e50e1081e00a0251e30100009a020280e004f09de4012082e20c0052e1103083e2" +
|
31
|
+
"f2ffff1a0000a0e304f09de4001093e5160271e30120829210308392e7ffff9a04e02de5ecffffea8c0a0200900a020010d04de204008de5103f13ee0c308de5" +
|
32
|
+
"04309de5103f03ee0000a0e10000a0e10000a0e10000a0e10000a0e10000a0e10000a0e10000a0e10000a0e10000a0e10c309de510d08de21eff2fe1103f12ee" +
|
33
|
+
"24209fe5023a83e2002092e5002883e518309fe5002093e50f32a0e3c02083e50c309fe5000093e5e0ffffea140b0200100b0200180b020070209fe50a32a0e3" +
|
34
|
+
"002092e510402de9f0c0a0e3ff10a0e3032082e098e0a0e3b0c0c3e1b010c3e1b0e0c2e1b002d3e1b0c0c3e1b010c3e1b0e0c2e1b242d3e1b0c0c3e1b010c3e1" +
|
35
|
+
"b0e0c2e1b422d3e1510050e3520054030100a0030000a013b0c0c3e1590052e30000a01301000002b010c3e11080bde8200b020066feffea0000000000000000" +
|
36
|
+
"0000000000000000000000000000000000000000000000007aff17ee0000a0e10000a0e10000a0e1faffff1a0000a0e39a0f07ee0000a0e10000a0e10000a0e1" +
|
37
|
+
"0000a0e10000a0e10000a0e1150f07ee0000a0e10000a0e11eff2fe1012051e21eff2f013600003a010050e12200009a020011e12300000a0e0211e38111a001" +
|
38
|
+
"0830a0030130a013010251e3000051310112a0310332a031faffff3a020151e3000051318110a0318330a031faffff3a0020a0e3010050e10100402003208221" +
|
39
|
+
"a10050e1a1004020a3208221210150e12101402023218221a10150e1a1014020a3218221000050e32332b0112112a011efffff1a0200a0e11eff2fe10100a003" +
|
40
|
+
"0000a0131eff2fe1010851e32118a0211020a0230020a033010c51e32114a02108208222100051e32112a02104208222040051e303208282a12082903002a0e1" +
|
41
|
+
"1eff2fe1000050e30000e013070000ea000051e3faffff0a03402de9beffffeb0640bde8920003e0031041e01eff2fe11eff2fe1040000002801000005000000" +
|
42
|
+
"7001000006000000400100000a000000010000000b000000100000001500000000000000110000007401000012000000a0000000130000000800000016000000" +
|
43
|
+
"00000000fbffff6f00000008faffff6f1400000000000000000000000000000000000000000000000000000000000000000000000000000000000000f4090000" +
|
44
|
+
"00000000000000004743433a202841726d20474e5520546f6f6c636861696e2031342e322e52656c3120284275696c642061726d2d31342e353229292031342e" +
|
45
|
+
"322e31203230323431313139000000000c000000ffffffff0100017c0e0c0d000c00000000000000dc080000f40000002f646174612f6a656e6b696e732f776f" +
|
46
|
+
"726b73706163652f474e552d746f6f6c636861696e2f61726d2d31342f6275696c642d61726d2d6e6f6e652d656162692f6f626a2f676363322f61726d2d6e6f" +
|
47
|
+
"6e652d656162692f6c6962676363002f646174612f6a656e6b696e732f776f726b73706163652f474e552d746f6f6c636861696e2f61726d2d31342f7372632f" +
|
48
|
+
"6763632f6c69626763632f636f6e6669672f61726d006c69623166756e63732e53004129000000616561626900011f000000053554454a000605080109011204" +
|
49
|
+
"14011501170318011a011e0400000000000000000000000000000000000000001401000000000000030001000000000028010000000000000300020000000000" +
|
50
|
+
"40010000000000000300030000000000700100000000000003000400000000007401000000000000030005000000000014020000000000000300060000000000" +
|
51
|
+
"f40900000000000003000700000000007c0a0000000000000300080000000000880a0000000000000300090000000000880a00000000000003000a0000000000" +
|
52
|
+
"280b02000000000003000b0000000000000000000000000003000c0000000000000000000000000003000d0000000000000000000000000003000e0000000000" +
|
53
|
+
"000000000000000003000f000100000000000000000000000400f1ff080000008406000000000000000006000b00000084060000680000000200060020000000" +
|
54
|
+
"e4060000000000000000060023000000200b02000400000001000a00360000001c0b02000400000001000a0008000000ec060000000000000000060049000000" +
|
55
|
+
"ec060000840000000200060020000000680700000000000000000600670000008c0a02000400000001000a0079000000900a02008000000001000a0008000000" +
|
56
|
+
"70070000000000000000060087000000700700004c0000000200060008000000bc0700000000000000000600a0000000bc0700003c0000000200060020000000" +
|
57
|
+
"ec0700000000000000000600b2000000140b02000400000001000a00c0000000100b02000400000001000a00cb000000180b02000400000001000a0008000000" +
|
58
|
+
"f80700000000000000000600dd000000f80700007c00000002000600200000007008000000000000000006000800000014020000000000000000060020000000" +
|
59
|
+
"400600000000000000000600f3000000260b02000200000001000a00fd000000240b02000200000001000a0007010000880a00000000020001000a0014010000" +
|
60
|
+
"880a02000400000001000a0020000000880a00000000000000000a0020000000880a02000000000000000a00200000008c0a02000000000000000a0020000000" +
|
61
|
+
"900a02000000000000000a0020000000100b02000000000000000a0020000000140b02000000000000000a0020000000180b02000000000000000a0020000000" +
|
62
|
+
"1c0b02000000000000000a0020000000200b02000000000000000a0020000000240b02000000000000000a0020000000260b02000000000000000a0023010000" +
|
63
|
+
"00000000000000000400f1ff0800000074080000000000000000060020000000780800000000000000000600080000009808000000000000000006002e010000" +
|
64
|
+
"00000000000000000400f1ff08000000dc080000000000000000060039010000dc080000000000000000060020000000100000000000000000000d0051010000" +
|
65
|
+
"00000000000000000400f1ff08000000f009000000000000000006000000000000000000000000000400f1ff5d010000f4090000000000000100f1ff66010000" +
|
66
|
+
"d00900002000000002000600770100007c0a0000000000000100f1ff8d010000f00900000400000002000600b4010000280b02000000000010000a009b010000" +
|
67
|
+
"dc080000f400000012020600a5010000880a00000000000010000a00b3010000280b02000000000010000a001e020000740800000000000012000600bf010000" +
|
68
|
+
"f00900000400000022020600cd010000880a00000000000010000a00d9010000140200007004000012000600de010000dc0800000000000012020600ec010000" +
|
69
|
+
"280b02000000000010000a00f401000098080000000000001200060005020000880a000000000000100008000c020000280b02000000000010000a0011020000" +
|
70
|
+
"000008000000000010000b0018020000880a00000000000010000800006d61696e2e6300246100726561645f7265672e636f6e737470726f702e300024640066" +
|
71
|
+
"6c6173685f756e6c6f636b5f616464723100666c6173685f756e6c6f636b5f61646472320066696e645f666c6173685f726567696f6e2e636f6e737470726f70" +
|
72
|
+
"2e300065726173655f726567696f6e735f636e740065726173655f726567696f6e73007365745f646f6d61696e5f6163636573732e697372612e30006c6f636b" +
|
73
|
+
"5f666c6173685f616363657373006f6c645f666c6173685f6d6d75006f6c645f627573636f6e006f6c645f646f6d61696e5f61636365737300636865636b5f71" +
|
74
|
+
"72792e636f6e737470726f702e3000666c6173685f76696400666c6173685f7069640077726974655f627566666572006d61785f65726173655f73697a650069" +
|
75
|
+
"6e69742e532e6f626a005f756469767369332e6f002e756469767369335f736b69705f646976305f74657374005f64766d645f746c732e6f005f44594e414d49" +
|
76
|
+
"43005f5f61656162695f75696469766d6f64005f474c4f42414c5f4f46465345545f5441424c455f005f5f61656162695f6964697630005f5f75646976736933" +
|
77
|
+
"005f5f6273735f73746172745f5f005f5f6273735f656e645f5f005f5f61656162695f6c64697630005f5f6273735f7374617274006d61696e005f5f61656162" +
|
78
|
+
"695f7569646976005f5f656e645f5f00636c65616e5f616c6c5f636163686573005f6564617461005f656e64005f737461636b005f5f646174615f7374617274" +
|
79
|
+
"00002e73796d746162002e737472746162002e7368737472746162002e696e74657270002e68617368002e64796e73796d002e64796e737472002e72656c2e64" +
|
80
|
+
"796e002e74657874002e64796e616d6963002e676f74002e70657273697374656e74002e627373002e6e6f696e6974002e636f6d6d656e74002e64656275675f" +
|
81
|
+
"6672616d65002e64656275675f6c696e655f737472002e41524d2e61747472696275746573000000000000000000000000000000000000000000000000000000" +
|
82
|
+
"000000000000000000000000000000001b0000000100000002000000140100001401000011000000000000000000000001000000000000002300000005000000" +
|
83
|
+
"0200000028010000280100001800000003000000000000000400000004000000290000000b000000020000004001000040010000300000000400000003000000" +
|
84
|
+
"04000000100000003100000003000000020000007001000070010000010000000000000000000000010000000000000039000000090000000200000074010000" +
|
85
|
+
"74010000a0000000030000000000000004000000080000004200000001000000060000001402000014020000e007000000000000000000000400000000000000" +
|
86
|
+
"480000000600000003000000f4090000f409000088000000040000000000000004000000080000005100000001000000030000007c0a00007c0a00000c000000" +
|
87
|
+
"00000000000000000400000004000000560000000100000003000000880a0000880a000000000000000000000000000004000000000000006200000008000000" +
|
88
|
+
"03000000880a0000880a0000a000020000000000000000000400000000000000670000000800000003000000280b020000000000000000000000000000000000" +
|
89
|
+
"04000000000000006f000000010000003000000000000000880a0000450000000000000000000000010000000100000078000000010000000000000000000000" +
|
90
|
+
"d00a0000200000000000000000000000040000000000000085000000010000003000000000000000f00a0000b200000000000000000000000100000001000000" +
|
91
|
+
"95000000030000700000000000000000a20b00002a0000000000000000000000010000000000000001000000020000000000000000000000cc0b000050050000" +
|
92
|
+
"11000000460000000400000010000000090000000300000000000000000000001c11000025020000000000000000000001000000000000001100000003000000" +
|
93
|
+
"000000000000000041130000a500000000000000000000000100000000000000", "hex");
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './lib/dwd.js';
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './lib/dwd.js';
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import { DWD } from "@sie-js/serial";
|
2
|
+
export interface UnlockBootloaderOptions {
|
3
|
+
debug(log: string): void;
|
4
|
+
}
|
5
|
+
export declare function isApoxiBootUnlocked(dwd: DWD): Promise<boolean>;
|
6
|
+
export declare function unlockApoxiBootloader(dwd: DWD, options: UnlockBootloaderOptions): Promise<void>;
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import { sprintf } from "sprintf-js";
|
2
|
+
import unlockerElf from "#src/data/unlocker.elf.js";
|
3
|
+
import { loadELF } from "@sie-js/creampie";
|
4
|
+
import { retryAsyncOnError } from "#src/utils/retry.js";
|
5
|
+
const TCM_START = 0xFFFF0000;
|
6
|
+
const PRAM_IRQ_HANDLER = TCM_START + 0x38;
|
7
|
+
const BOOT_MODE = 0xA000000C;
|
8
|
+
const EBU_ADDRSEL1 = 0xF0000088;
|
9
|
+
var PatchResponseCode;
|
10
|
+
(function (PatchResponseCode) {
|
11
|
+
PatchResponseCode[PatchResponseCode["SUCCESS"] = 0] = "SUCCESS";
|
12
|
+
PatchResponseCode[PatchResponseCode["BOOT_ALREADY_OPEN"] = -1] = "BOOT_ALREADY_OPEN";
|
13
|
+
PatchResponseCode[PatchResponseCode["UNKNOWN_FLASH"] = -2] = "UNKNOWN_FLASH";
|
14
|
+
PatchResponseCode[PatchResponseCode["FLASH_BUSY"] = -3] = "FLASH_BUSY";
|
15
|
+
PatchResponseCode[PatchResponseCode["ERASE_ERROR"] = -4] = "ERASE_ERROR";
|
16
|
+
PatchResponseCode[PatchResponseCode["PROGRAM_ERROR"] = -5] = "PROGRAM_ERROR";
|
17
|
+
PatchResponseCode[PatchResponseCode["ADDR_NOT_ALIGNED"] = -6] = "ADDR_NOT_ALIGNED";
|
18
|
+
PatchResponseCode[PatchResponseCode["FLASH_REGION_NOT_FOUND"] = -7] = "FLASH_REGION_NOT_FOUND";
|
19
|
+
PatchResponseCode[PatchResponseCode["FLASH_REGION_TOO_BIG"] = -8] = "FLASH_REGION_TOO_BIG";
|
20
|
+
PatchResponseCode[PatchResponseCode["INVALID_FLASH_REGIONS"] = -9] = "INVALID_FLASH_REGIONS";
|
21
|
+
PatchResponseCode[PatchResponseCode["INVALID_FLASH_REGION_COUNT"] = -10] = "INVALID_FLASH_REGION_COUNT";
|
22
|
+
PatchResponseCode[PatchResponseCode["UNSUPPORTED_FLASH"] = -11] = "UNSUPPORTED_FLASH";
|
23
|
+
PatchResponseCode[PatchResponseCode["FLASH_NOT_FOUND"] = -12] = "FLASH_NOT_FOUND";
|
24
|
+
PatchResponseCode[PatchResponseCode["UNKNOWN"] = -13] = "UNKNOWN";
|
25
|
+
})(PatchResponseCode || (PatchResponseCode = {}));
|
26
|
+
export async function isApoxiBootUnlocked(dwd) {
|
27
|
+
const bootMode = (await dwd.readMemory(BOOT_MODE, 4)).buffer.readUInt32LE(0);
|
28
|
+
if (bootMode == 0xFFFFFFFF) {
|
29
|
+
return true;
|
30
|
+
}
|
31
|
+
else if (bootMode == 0xFFFF0002) {
|
32
|
+
return false;
|
33
|
+
}
|
34
|
+
else {
|
35
|
+
throw new Error(sprintf("Invalid boot mode: %08X", bootMode));
|
36
|
+
}
|
37
|
+
}
|
38
|
+
export async function unlockApoxiBootloader(dwd, options) {
|
39
|
+
const addrsel = (await dwd.readMemory(EBU_ADDRSEL1, 4)).buffer.readUInt32LE(0);
|
40
|
+
const ramSize = (1 << (27 - ((addrsel & 0xF0) >> 4)));
|
41
|
+
const ramAddr = Number((BigInt(addrsel) & 0xffff0000n));
|
42
|
+
if (await isApoxiBootUnlocked(dwd)) {
|
43
|
+
options.debug("Boot already open! Unlock is not needed.");
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
const bootMode = (await dwd.readMemory(BOOT_MODE, 4)).buffer.readUInt32LE(0);
|
47
|
+
options.debug(sprintf("Boot mode: %08X", bootMode));
|
48
|
+
options.debug(sprintf("Ram: %08X, %dM", ramAddr, ramSize / 1024 / 1024));
|
49
|
+
options.debug("Searching empty ram block....");
|
50
|
+
let emptyRamBlock = 0;
|
51
|
+
for (let i = ramAddr; i < ramAddr + ramSize; i += 256 * 1024) {
|
52
|
+
const blockStart = await dwd.readMemory(i, 230);
|
53
|
+
if (blockStart.buffer.every((v) => v == 0)) {
|
54
|
+
const fullBlock = await dwd.readMemory(i, 256 * 1024);
|
55
|
+
if (fullBlock.buffer.every((v) => v == 0)) {
|
56
|
+
emptyRamBlock = i;
|
57
|
+
break;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
if (!emptyRamBlock)
|
62
|
+
throw new Error("Empty RAM block not found!");
|
63
|
+
options.debug(sprintf("Found empty ram block: %08X", emptyRamBlock));
|
64
|
+
const oldIrqHandler = (await dwd.readMemory(PRAM_IRQ_HANDLER, 4)).buffer.readUInt32LE(0);
|
65
|
+
options.debug(sprintf("Old SWI handler: %08X", oldIrqHandler));
|
66
|
+
const elf = loadELF(emptyRamBlock, unlockerElf);
|
67
|
+
await dwd.writeMemory(emptyRamBlock, elf.image);
|
68
|
+
const check = await dwd.readMemory(emptyRamBlock, elf.image.length);
|
69
|
+
if (check.buffer.toString("hex") != elf.image.toString("hex")) {
|
70
|
+
options.debug(check.buffer.toString("hex"));
|
71
|
+
options.debug(elf.image.toString("hex"));
|
72
|
+
throw new Error("Payload corrupted!!!");
|
73
|
+
}
|
74
|
+
options.debug(sprintf("Patcher entry: %08X", elf.entry));
|
75
|
+
const PATCHER_ADDR = elf.entry;
|
76
|
+
const PARAM_OLD_IRQ_HANDLER = PATCHER_ADDR + 4;
|
77
|
+
const PARAM_RESPONSE_CODE = PATCHER_ADDR + 8;
|
78
|
+
const PARAM_RESPONSE_FLASH_ID = PATCHER_ADDR + 12;
|
79
|
+
for (let i = 0; i < 30; i++) {
|
80
|
+
options.debug("Running patcher...");
|
81
|
+
await dwd.writeMemory(PARAM_OLD_IRQ_HANDLER, uint32(oldIrqHandler));
|
82
|
+
await dwd.writeMemory(PRAM_IRQ_HANDLER, uint32(PATCHER_ADDR));
|
83
|
+
let responseCode = PatchResponseCode.UNKNOWN;
|
84
|
+
let responseFlashId = 0;
|
85
|
+
await retryAsyncOnError(async () => {
|
86
|
+
options.debug("Waiting for done...");
|
87
|
+
responseCode = (await dwd.readMemory(PARAM_RESPONSE_CODE, 4)).buffer.readInt32LE(0);
|
88
|
+
responseFlashId = (await dwd.readMemory(PARAM_RESPONSE_FLASH_ID, 4)).buffer.readUInt32LE(0);
|
89
|
+
options.debug(sprintf("Code: %d (%s)", responseCode, PatchResponseCode[responseCode]));
|
90
|
+
options.debug(sprintf("FlashID: %08X", responseFlashId));
|
91
|
+
if (responseCode == PatchResponseCode.SUCCESS) {
|
92
|
+
options.debug("Success!!! Boot mode patched, now reboot phone.");
|
93
|
+
}
|
94
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
95
|
+
}, { max: 30 });
|
96
|
+
if (!(responseCode == PatchResponseCode.FLASH_NOT_FOUND || responseCode == PatchResponseCode.FLASH_BUSY))
|
97
|
+
break;
|
98
|
+
}
|
99
|
+
}
|
100
|
+
function uint32(value) {
|
101
|
+
const buffer = Buffer.alloc(4);
|
102
|
+
buffer.writeUInt32LE(value);
|
103
|
+
return buffer;
|
104
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { Command } from "commander";
|
2
|
+
export type AppCommand<T, A extends unknown[] = []> = (...args: [...A, options: T, cmd: Command]) => Promise<void>;
|
3
|
+
export interface AppCommandContext<T, A extends unknown[] = []> {
|
4
|
+
options: T;
|
5
|
+
arguments: A;
|
6
|
+
cmd: Command;
|
7
|
+
resource<R>(alloc: () => R | Promise<R>, free: (resource: R) => void | Promise<void>): Promise<R>;
|
8
|
+
onCleanup(handler: AppCommandCleanupHandler): void;
|
9
|
+
cleanupHandlers: AppCommandCleanupHandler[];
|
10
|
+
}
|
11
|
+
export declare class AppCommandValidateError extends Error {
|
12
|
+
}
|
13
|
+
export type AppCommandCleanupHandler = () => Promise<void>;
|
14
|
+
export declare function onCleanup(handler: AppCommandCleanupHandler): void;
|
15
|
+
export declare function createAppCommand<T, A extends unknown[] = []>(handler: AppCommand<T, A>): (...params: unknown[]) => Promise<void>;
|
@@ -0,0 +1,44 @@
|
|
1
|
+
export class AppCommandValidateError extends Error {
|
2
|
+
}
|
3
|
+
const cleanupHandlers = [];
|
4
|
+
export function onCleanup(handler) {
|
5
|
+
cleanupHandlers.push(handler);
|
6
|
+
}
|
7
|
+
async function runCleanupHandlers() {
|
8
|
+
for (const handler of cleanupHandlers) {
|
9
|
+
try {
|
10
|
+
await handler();
|
11
|
+
}
|
12
|
+
catch (e) {
|
13
|
+
console.error(`Error while running cleanup handler`, e);
|
14
|
+
}
|
15
|
+
}
|
16
|
+
}
|
17
|
+
export function createAppCommand(handler) {
|
18
|
+
return async (...params) => {
|
19
|
+
const cmd = params[params.length - 1];
|
20
|
+
const cmdOptions = params[params.length - 2];
|
21
|
+
const cmdArguments = params.slice(0, params.length - 2);
|
22
|
+
await runCommand(cmd, async () => {
|
23
|
+
await handler(...cmdArguments, { ...cmdOptions, ...cmd.optsWithGlobals() }, cmd);
|
24
|
+
});
|
25
|
+
};
|
26
|
+
}
|
27
|
+
async function runCommand(cmd, handler) {
|
28
|
+
try {
|
29
|
+
await handler();
|
30
|
+
}
|
31
|
+
catch (e) {
|
32
|
+
if (e instanceof AppCommandValidateError) {
|
33
|
+
console.error(`ERROR: ${e.message}`);
|
34
|
+
console.error();
|
35
|
+
cmd.help();
|
36
|
+
}
|
37
|
+
else {
|
38
|
+
console.error(e);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
finally {
|
42
|
+
await runCleanupHandlers();
|
43
|
+
}
|
44
|
+
}
|
@@ -0,0 +1,7 @@
|
|
1
|
+
export declare function retryAsync<T>(callback: () => Promise<T>, options: {
|
2
|
+
max: number;
|
3
|
+
until: (lastResult: T) => boolean;
|
4
|
+
}): Promise<NonNullable<T>>;
|
5
|
+
export declare function retryAsyncOnError(callback: () => Promise<void>, options: {
|
6
|
+
max: number;
|
7
|
+
}): Promise<void>;
|
@@ -0,0 +1,22 @@
|
|
1
|
+
export async function retryAsync(callback, options) {
|
2
|
+
let lastResult;
|
3
|
+
for (let i = 0; i < options.max; i++) {
|
4
|
+
lastResult = await callback();
|
5
|
+
if (!options.until(lastResult))
|
6
|
+
break;
|
7
|
+
}
|
8
|
+
return lastResult;
|
9
|
+
}
|
10
|
+
export async function retryAsyncOnError(callback, options) {
|
11
|
+
let lastError;
|
12
|
+
for (let i = 0; i < options.max; i++) {
|
13
|
+
try {
|
14
|
+
await callback();
|
15
|
+
return;
|
16
|
+
}
|
17
|
+
catch (e) {
|
18
|
+
lastError = e;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
throw lastError;
|
22
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import { AsyncSerialPort, DWD } from "@sie-js/serial";
|
2
|
+
import { SerialPort } from "serialport";
|
3
|
+
const USB_DEVICES = [
|
4
|
+
"067B:2303", // PL2303
|
5
|
+
"1A86:7523", // CH340
|
6
|
+
"0403:6001", // FT232
|
7
|
+
"10C4:EA60", // СР2102
|
8
|
+
"11F5:*", // Siemens
|
9
|
+
"04DA:*", // Panasonic/Softbank
|
10
|
+
];
|
11
|
+
export async function connectDWD(path, limitBaudrate, key) {
|
12
|
+
console.info(`Connecting to phone using port ${path}...`);
|
13
|
+
const port = new AsyncSerialPort(new SerialPort({
|
14
|
+
path,
|
15
|
+
baudRate: 112500,
|
16
|
+
autoOpen: false
|
17
|
+
}));
|
18
|
+
await port.open();
|
19
|
+
const dwd = new DWD(port);
|
20
|
+
if (key.indexOf(":") >= 0) {
|
21
|
+
const [key1, key2, key3, key4] = key.split(":");
|
22
|
+
const keys = {
|
23
|
+
key1: Buffer.from(key1, "hex"),
|
24
|
+
key2: parseInt(key2, 16),
|
25
|
+
key3: key3 ? Buffer.from(key3, "hex") : Buffer.from("00000000000000000000000000000000", "hex"),
|
26
|
+
key4: key4 ? parseInt(key4, 16) : 0x0000,
|
27
|
+
};
|
28
|
+
dwd.setKeys(keys);
|
29
|
+
}
|
30
|
+
else {
|
31
|
+
dwd.setKeys(key.toLowerCase());
|
32
|
+
}
|
33
|
+
try {
|
34
|
+
await dwd.connect();
|
35
|
+
}
|
36
|
+
catch (e) {
|
37
|
+
console.error(`Error while connecting to phone!`);
|
38
|
+
await port.close();
|
39
|
+
throw e;
|
40
|
+
}
|
41
|
+
return dwd;
|
42
|
+
}
|
43
|
+
export async function disconnectDWD(dwd) {
|
44
|
+
const port = dwd.getSerialPort();
|
45
|
+
if (port?.isOpen) {
|
46
|
+
await dwd.disconnect();
|
47
|
+
await port.close();
|
48
|
+
}
|
49
|
+
}
|
50
|
+
export async function getDefaultPort() {
|
51
|
+
const availablePorts = (await SerialPort.list()).filter((d) => {
|
52
|
+
if (d.path.startsWith("/dev/ttyUSB"))
|
53
|
+
return false;
|
54
|
+
return USB_DEVICES.includes(`${d.vendorId}:${d.productId}`.toUpperCase());
|
55
|
+
});
|
56
|
+
let defaultPort = availablePorts.length > 0 ? availablePorts[0].path : null;
|
57
|
+
if (!defaultPort)
|
58
|
+
defaultPort = (process.platform === "win32" ? "COM4" : "/dev/ttyACM0");
|
59
|
+
return defaultPort;
|
60
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import chalk from "chalk";
|
2
|
+
export function parseAddr(value) {
|
3
|
+
let m;
|
4
|
+
if ((m = value.match(/^(?:0x)?([a-f0-9]+)$/i))) {
|
5
|
+
return parseInt(m[1], 16);
|
6
|
+
}
|
7
|
+
else {
|
8
|
+
throw new Error(`Invalid address: ${value}`);
|
9
|
+
}
|
10
|
+
}
|
11
|
+
export function parseSize(value) {
|
12
|
+
let m;
|
13
|
+
if ((m = value.match(/^(\d+)M$/i))) {
|
14
|
+
return parseInt(m[1], 10) * 1024 * 1024;
|
15
|
+
}
|
16
|
+
else if ((m = value.match(/^(\d+)k$/i))) {
|
17
|
+
return parseInt(m[1], 10) * 1024;
|
18
|
+
}
|
19
|
+
else if ((m = value.match(/^(?:0x|0)([0-9a-f]+)$/i))) {
|
20
|
+
return parseInt(m[1], 16);
|
21
|
+
}
|
22
|
+
else if ((m = value.match(/^(\d+)$/i))) {
|
23
|
+
return +m[1];
|
24
|
+
}
|
25
|
+
else {
|
26
|
+
throw new Error(`Invalid size: ${value}`);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
export function formatSize(size) {
|
30
|
+
if (size > 1024 * 1024) {
|
31
|
+
return +(size / 1024 / 1024).toFixed(2) + " Mb";
|
32
|
+
}
|
33
|
+
else {
|
34
|
+
return +(size / 1024).toFixed(2) + " kB";
|
35
|
+
}
|
36
|
+
}
|
37
|
+
function showError(message) {
|
38
|
+
console.error(chalk.red(message));
|
39
|
+
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@sie-js/apoxi-tool",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.4",
|
4
4
|
"description": "CLI tools for APOXI phones.",
|
5
5
|
"bin": {
|
6
6
|
"apoxi-tool": "./dist/src/cli.js"
|
@@ -20,6 +20,7 @@
|
|
20
20
|
"author": "kirill.zhumarin@gmail.com",
|
21
21
|
"license": "MIT",
|
22
22
|
"devDependencies": {
|
23
|
+
"@sie-js/serial": "^1.1.10",
|
23
24
|
"@types/cli-progress": "^3.11.6",
|
24
25
|
"@types/node": "^24.2.1",
|
25
26
|
"@types/sprintf-js": "^1.1.4",
|
@@ -31,7 +32,6 @@
|
|
31
32
|
},
|
32
33
|
"dependencies": {
|
33
34
|
"@sie-js/creampie": "^1.0.2",
|
34
|
-
"@sie-js/serial": "^1.1.10",
|
35
35
|
"@types/debug": "^4.1.12",
|
36
36
|
"chalk": "^5.5.0",
|
37
37
|
"cli-progress": "^3.12.0",
|
@@ -41,14 +41,14 @@
|
|
41
41
|
"read-pkg": "^9.0.1",
|
42
42
|
"serialport": "^13.0.0",
|
43
43
|
"sprintf-js": "^1.1.3",
|
44
|
-
"table": "^6.9.0"
|
45
|
-
|
44
|
+
"table": "^6.9.0"
|
45
|
+
},
|
46
|
+
"peerDependencies": {
|
47
|
+
"@sie-js/serial": "^1.1.10"
|
46
48
|
},
|
47
49
|
"scripts": {
|
48
|
-
"build": "tsc
|
49
|
-
"watch": "tsc -w
|
50
|
-
"build:all": "pnpm -r run build",
|
51
|
-
"watch:all": "pnpm -r run watch",
|
50
|
+
"build": "tsc",
|
51
|
+
"watch": "tsc -w",
|
52
52
|
"test": "echo \"Error: no test specified\" && exit 1"
|
53
53
|
}
|
54
54
|
}
|