@sie-js/apoxi-tool 1.0.0

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/README.md ADDED
@@ -0,0 +1,119 @@
1
+ [![NPM Version](https://img.shields.io/npm/v/%40sie-js%2Fsiemens-memory-dumper)](https://www.npmjs.com/package/@sie-js/siemens-memory-dumper)
2
+
3
+ # SUMMARY
4
+
5
+ A console utility for working with APOXI-based phones (SGold/SGold2). [Read more about APOXI.](https://siemens-mobile-hacks.github.io/docs/panasonic/)
6
+
7
+ Works on all major operating systems: Linux, macOS, and Windows.
8
+
9
+ > [!NOTE]
10
+ > All these functions are also available from the browser: [Web Tools](https://siemens-mobile-hacks.github.io/web-tools/).
11
+
12
+ # INSTALL
13
+
14
+ ### OSX & Linux
15
+ 1. Install the latest version of [Node.js](https://nodejs.org/en/download/).
16
+ 2. Install the package:
17
+
18
+ ```bash
19
+ npm install -g @sie-js/apoxi-tool@latest
20
+ ```
21
+
22
+ ### Windows
23
+ 1. Find and install USB drivers for your phone.
24
+ 2. Install scoop: https://scoop.sh/
25
+ 3. Run in PowerShell:
26
+ ```
27
+ scoop bucket add main
28
+ scoop install main/nodejs
29
+ npm install -g @sie-js/apoxi-tool@latest
30
+ ```
31
+
32
+ # TIPS & TRICKS
33
+
34
+ 1. Only phones with NOR flash are supported. NAND support is coming soon.
35
+ 2. For maximum speed, use a USB cable.
36
+ 3. With a USB cable, you can read memory in both P-Test and Normal modes. A serial cable works only in P-Test mode.
37
+ 4. To enter P-Test mode: press the \* and # keys simultaneously, then turn on the phone using the power key. You should see a rainbow screen.
38
+ 5. Usually, you don’t need to specify the `PORT` option, because it is detected automatically (by USB ID).
39
+
40
+ # USAGE
41
+
42
+ ```
43
+ Usage: apoxi-tool [options] [command]
44
+
45
+ CLI tool for APOXI phones.
46
+
47
+ Options:
48
+ -v, --version output the version number
49
+ -p, --port <port> serial port name (default: "/dev/ttyACM0")
50
+ -b, --baudrate <baudrate> limit maximum baudrate (0 - use maximum) (default: "0")
51
+ -k, --key <key> DWD key (auto/lg/panasonic/siemens or KEY1:KEY2) (default: "auto")
52
+ -V, --verbose increase verbosity
53
+ -h, --help display help for command
54
+
55
+ Commands:
56
+ unlock-bootloader Unlock APOXI bootloader (allows using V-Klay)
57
+ read-memory [options] Read and save phone memory
58
+ read-all-memory [options] Read and save all available phone memory blocks
59
+ list-memory List available memory blocks
60
+ list-ports List available serial ports
61
+ bruteforce-dwd-keys Brute-force DWD keys
62
+ help [command] Display help for a command
63
+ ```
64
+
65
+ ### Unlock bootloader
66
+
67
+ Used for patching the boot mode to allow connections with flashers (e.g., V-Klay).
68
+
69
+ Replaces the two bytes `FF02` with `FFFF` at address `0xA000000C`.
70
+
71
+ ```bash
72
+ apoxi-tool -p PORT unlock-bootloader
73
+ ```
74
+
75
+ ### List all available memory blocks
76
+
77
+ ```bash
78
+ apoxi-tool -p PORT list-memory
79
+ ```
80
+
81
+ ### Dump a memory block
82
+
83
+ ```bash
84
+ # Save dump in the current directory
85
+ apoxi-tool -p PORT read-memory -n SRAM
86
+
87
+ # Save dump as a specific file
88
+ apoxi-tool -p PORT read-memory -n SRAM -o ./SRAM.bin
89
+
90
+ # Dump a custom memory region by address and size
91
+ apoxi-tool -p PORT read-memory -a 0xA0000000 -s 128k -o ./bootcore.bin
92
+ apoxi-tool -p PORT read-memory -a 0xA0000000 -s 0x20000 -o ./bootcore.bin
93
+ ```
94
+
95
+ ### Dump all available memory blocks
96
+
97
+ ```bash
98
+ # Save dump in the current directory
99
+ apoxi-tool -p PORT read-all-memory
100
+
101
+ # Save dump in a specified directory
102
+ apoxi-tool -p PORT read-all-memory -o OUTPUT_DIR
103
+
104
+ # Dump all except FLASH
105
+ apoxi-tool -p PORT read-all-memory --exclude FLASH
106
+
107
+ # Dump only SRAM, RAM, and TCM
108
+ apoxi-tool -p PORT read-all-memory --include SRAM,RAM,TCM
109
+ ```
110
+
111
+ ### Brute-force DWD keys
112
+
113
+ Used for brute-forcing DWD service keys.
114
+
115
+ This is useful for new, unknown APOXI-based phones. Keys for Siemens, Panasonic, and LG are already included in the program.
116
+
117
+ ```bash
118
+ apoxi-tool -p PORT bruteforce-dwd-keys
119
+ ```
@@ -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,4 @@
1
+ import { CLIBaseOptions } from "#src/cli.js";
2
+ import { AppCommand } from "#src/utils/command.js";
3
+ export declare const cliListSerialPorts: AppCommand<CLIBaseOptions>;
4
+ export declare const cliBruteforceDWDKeys: AppCommand<CLIBaseOptions>;
@@ -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,3 @@
1
+ import { AppCommand } from "#src/utils/command.js";
2
+ import { CLIBaseOptions } from "#src/cli.js";
3
+ export declare const cliUnlockBoot: AppCommand<CLIBaseOptions>;
@@ -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
+ };
@@ -0,0 +1,5 @@
1
+ export interface CLIBaseOptions {
2
+ port: string;
3
+ baudrate: string;
4
+ key: string;
5
+ }
@@ -0,0 +1,62 @@
1
+ import { program } from "commander";
2
+ import { cliListMemory, cliReadAllMemory, cliReadMemory } from "./cli/memory.js";
3
+ import { createAppCommand } from "./utils/command.js";
4
+ import debug from "debug";
5
+ import { getDefaultPort } from "#src/utils/serial.js";
6
+ import { cliBruteforceDWDKeys, cliListSerialPorts } from "#src/cli/misc.js";
7
+ import { cliUnlockBoot } from "#src/cli/unlock.js";
8
+ import { readPackage } from "read-pkg";
9
+ const pkg = await readPackage();
10
+ const DEFAULT_PORT = await getDefaultPort();
11
+ program
12
+ .name("apoxi-tool")
13
+ .version(pkg.version, '-v, --version')
14
+ .description('CLI tool for APOXI phones.')
15
+ .option('-p, --port <port>', 'serial port name', DEFAULT_PORT)
16
+ .option('-b, --baudrate <baudrate>', 'limit maximum baudrate (0 - use maximum)', '0')
17
+ .option('-k, --key <key>', 'DWD key (auto/lg/panasonic/siemens or KEY1:KEY2)', 'auto')
18
+ .option('-V, --verbose', 'Increase verbosity', (_, prev) => prev + 1, 0)
19
+ .hook('preAction', (thisCommand) => {
20
+ const opts = thisCommand.opts();
21
+ if (opts.verbose) {
22
+ console.log(`Verbosity level: ${opts.verbose}`);
23
+ const filters = ["atc", "dwd"];
24
+ if (opts.verbose > 1)
25
+ filters.push("dwd:*", "atc:*");
26
+ if (opts.verbose > 2)
27
+ filters.push("*");
28
+ debug.enable(filters.join(","));
29
+ }
30
+ });
31
+ program
32
+ .command('unlock-bootloader')
33
+ .description('Unlock APOXI bootloader (allow using V-Klay)')
34
+ .action(createAppCommand(cliUnlockBoot));
35
+ program
36
+ .command('read-memory')
37
+ .description('Read and save phone memory')
38
+ .option('-n, --name <blockName>', 'Read by block name')
39
+ .option('-a, --addr <address>', 'Read from address (dec or hex)')
40
+ .option('-s, --size <bytes>', 'Size in bytes (dec, hex, k/m/g allowed)')
41
+ .option('-o, --output [file]', 'Write output to file or directory')
42
+ .action(createAppCommand(cliReadMemory));
43
+ program
44
+ .command('read-all-memory')
45
+ .description('Read and save phone memory (ALL available blocks)')
46
+ .option('-i, --include <blocks>', 'Include blocks (comma separated)', (v) => v.split(','), [])
47
+ .option('-e, --exclude <blocks>', 'Exclude blocks (comma separated)', (v) => v.split(','), [])
48
+ .option('-o, --output [dir]', 'Write output to directory')
49
+ .action(createAppCommand(cliReadAllMemory));
50
+ program
51
+ .command('list-memory')
52
+ .description('List available memory blocks')
53
+ .action(createAppCommand(cliListMemory));
54
+ program.command('list-ports')
55
+ .description('List available serial ports.')
56
+ .action(createAppCommand(cliListSerialPorts));
57
+ program.command('bruteforce-dwd-keys')
58
+ .description('Bruteforce DWD keys')
59
+ .action(createAppCommand(cliBruteforceDWDKeys));
60
+ program.showSuggestionAfterError(true);
61
+ program.showHelpAfterError();
62
+ program.parse();
@@ -0,0 +1,2 @@
1
+ declare const _default: Buffer<ArrayBuffer>;
2
+ export default _default;
@@ -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,4 @@
1
+ import { DWD } from "@sie-js/serial";
2
+ export declare function connectDWD(path: string, limitBaudrate: number, key: string): Promise<DWD>;
3
+ export declare function disconnectDWD(dwd: DWD): Promise<void>;
4
+ export declare function getDefaultPort(): Promise<string>;
@@ -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,3 @@
1
+ export declare function parseAddr(value: string): number;
2
+ export declare function parseSize(value: string): number;
3
+ export declare function formatSize(size: number): string;
@@ -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 ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@sie-js/apoxi-tool",
3
+ "version": "1.0.0",
4
+ "description": "CLI tools for APOXI phones.",
5
+ "bin": {
6
+ "apoxi-tool": "./dist/src/cli.js"
7
+ },
8
+ "type": "module",
9
+ "main": "dist/src/index.js",
10
+ "types": "dist/src/index.d.ts",
11
+ "files": [
12
+ "./dist/src/*"
13
+ ],
14
+ "keywords": [
15
+ "apoxi"
16
+ ],
17
+ "author": "kirill.zhumarin@gmail.com",
18
+ "license": "MIT",
19
+ "devDependencies": {
20
+ "@types/cli-progress": "^3.11.6",
21
+ "@types/node": "^24.2.1",
22
+ "@types/sprintf-js": "^1.1.4",
23
+ "tsx": "^4.20.3",
24
+ "typescript": "^5.9.2"
25
+ },
26
+ "imports": {
27
+ "#src/*": "./src/*"
28
+ },
29
+ "dependencies": {
30
+ "@sie-js/creampie": "^1.0.2",
31
+ "@sie-js/serial": "^1.1.10",
32
+ "@types/debug": "^4.1.12",
33
+ "chalk": "^5.5.0",
34
+ "cli-progress": "^3.12.0",
35
+ "commander": "^14.0.0",
36
+ "debug": "^4.4.1",
37
+ "inquirer": "^12.9.1",
38
+ "read-pkg": "^9.0.1",
39
+ "serialport": "^13.0.0",
40
+ "sprintf-js": "^1.1.3",
41
+ "table": "^6.9.0"
42
+ },
43
+ "scripts": {
44
+ "build": "tsc",
45
+ "watch": "tsc -w",
46
+ "test": "echo \"Error: no test specified\" && exit 1"
47
+ }
48
+ }