Compare commits
89
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23f765371b | ||
|
|
e77a3c27fe | ||
|
|
04baa7ad9a | ||
|
|
639a78419e | ||
|
|
1c6312e0c9 | ||
|
|
1dea1373e3 | ||
|
|
783e61cba2 | ||
|
|
69a8b2e172 | ||
|
|
cf45216693 | ||
|
|
737f0248b2 | ||
|
|
75f559a3ce | ||
|
|
2db1e8d9f3 | ||
|
|
2a55a7048f | ||
|
|
25ae4fa72e | ||
|
|
2f2b6d302a | ||
|
|
92dbd8354c | ||
|
|
cfbdeaa0d0 | ||
|
|
94070b6e33 | ||
|
|
85a4b71e46 | ||
|
|
2215cf5b5c | ||
|
|
dcbf6bf925 | ||
|
|
edd4f5d6f5 | ||
|
|
bc2856eb07 | ||
|
|
599488f0bb | ||
|
|
89435cd4ec | ||
|
|
9a37f97960 | ||
|
|
7d2160f07f | ||
|
|
5379266fc8 | ||
|
|
a93c0badff | ||
|
|
9685f8008e | ||
|
|
f7eb287d5b | ||
|
|
33e7aeaacc | ||
|
|
c12c432e71 | ||
|
|
2924eaab79 | ||
|
|
5f6d5fefe3 | ||
|
|
89534822ab | ||
|
|
2d2e56231a | ||
|
|
98800d97bf | ||
|
|
eecae5c9bf | ||
|
|
0b8c3a1cc5 | ||
|
|
d70bef918f | ||
|
|
7bdcc54bc1 | ||
|
|
9d671f18c9 | ||
|
|
0b2ac1a118 | ||
|
|
3045c7663a | ||
|
|
7229f5781e | ||
|
|
5204020e7b | ||
|
|
51ec333ad2 | ||
|
|
a5734e47cc | ||
|
|
7530664fc9 | ||
|
|
2beb5e97e5 | ||
|
|
baef683b66 | ||
|
|
30df465edf | ||
|
|
369ffc8461 | ||
|
|
e789491f49 | ||
|
|
971f741d35 | ||
|
|
550f805c62 | ||
|
|
c5764b71a6 | ||
|
|
9b78eb6c27 | ||
|
|
fb8fae8e96 | ||
|
|
7e3fbbfa47 | ||
|
|
b9b37c803d | ||
|
|
c5b9fdfa6c | ||
|
|
01245454c5 | ||
|
|
795db8a2a7 | ||
|
|
4559b44cc2 | ||
|
|
ebb9d8b081 | ||
|
|
c8259c95fa | ||
|
|
3d5d250807 | ||
|
|
8671b32475 | ||
|
|
a8bf3a09f1 | ||
|
|
3f217d5c32 | ||
|
|
419e5fcc82 | ||
|
|
a049029734 | ||
|
|
8963125bcc | ||
|
|
0857d220d5 | ||
|
|
1cfe82ee7a | ||
|
|
38256ae465 | ||
|
|
058e3776c8 | ||
|
|
936257c562 | ||
|
|
323d82eaaf | ||
|
|
3c0f56fa8a | ||
|
|
07342d6629 | ||
|
|
3ca36ef542 | ||
|
|
3a34b60734 | ||
|
|
4fd3cb9125 | ||
|
|
95671dff0f | ||
|
|
7d0b9260eb | ||
|
|
affeb2967e |
@@ -0,0 +1,36 @@
|
||||
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: [16.x, 18.x, 20.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm test
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundle-${{ matrix.node-version }}
|
||||
path: dist/bin.cjs
|
||||
@@ -1,3 +1,5 @@
|
||||
bundle.js
|
||||
venv
|
||||
node_modules
|
||||
build
|
||||
dist/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: run hook install-wireshark-dissector test
|
||||
.PHONY: run hook install-wireshark-dissector test build
|
||||
|
||||
bundle.js: frida-hooks.js func_replacements.js
|
||||
~/node_modules/.bin/frida-compile -o $@ frida-hooks.js
|
||||
@@ -8,8 +8,15 @@ venv: requirements.txt
|
||||
./venv/bin/pip install -r requirements.txt
|
||||
touch venv
|
||||
|
||||
build: node_modules
|
||||
npm run tsc
|
||||
npm run build
|
||||
|
||||
run: node_modules
|
||||
./node_modules/.bin/ts-node --esm server.ts
|
||||
./node_modules/.bin/ts-node --esm cmd/bin http_server --port=1234
|
||||
|
||||
pair: node_modules
|
||||
./node_modules/.bin/ts-node --esm cmd/bin pair
|
||||
|
||||
hook: bundle.js venv
|
||||
./venv/bin/python3 -u loader3.py
|
||||
|
||||
@@ -1,26 +1,59 @@
|
||||
Re-implementation of the "ilnk" protocol used on some cheap (\<$5) IP cameras (sometimes branded as 'X5' or 'A9').
|
||||
Re-implementation of the "iLnk"/"iLnkP2P"/"PPPP" protocol used on some cheap (\<$5) IP cameras (sometimes branded as 'X5' or 'A9').
|
||||
|
||||
* Bought [here](https://www.aliexpress.com/item/1005006287788979.html).
|
||||
* Waiting for [this A9 camera](https://www.aliexpress.com/item/1005006117593880.html) to validate support.
|
||||
* Bought [this X5](https://www.aliexpress.com/item/1005006287788979.html) and [this A9](https://www.aliexpress.com/item/1005006117593880.html).
|
||||
* App is [YsxLite](https://play.google.com/store/apps/details?id=com.ysxlite.cam&hl=en&gl=US)
|
||||
|
||||
|
||||
Per [pictures](https://github.com/DavidVentura/cam-reverse/blob/master/pics/pcb.jpg?raw=true) the main chip is TXW817 ([chinese](https://www.taixin-semi.com/Product/ProductDetail?productId=306), [eng, google translate](https://www-taixin--semi-com.translate.goog/Product/ProductDetail?productId=306&_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=wapp))
|
||||
Per pictures of the [X5](https://github.com/DavidVentura/cam-reverse/blob/master/pics/pcb.jpg?raw=true), [A9](https://github.com/DavidVentura/cam-reverse/blob/master/pics/pcb_a9.jpg?raw=true) the main chip is TXW817 ([chinese](https://www.taixin-semi.com/Product/ProductDetail?productId=306), [eng, google translate](https://www-taixin--semi-com.translate.goog/Product/ProductDetail?productId=306&_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=wapp))
|
||||
|
||||
The interesting implementation is in `libvdp.so`, part of the apk bundle.
|
||||
|
||||
Protocol reversing was done with a combination of static analysis of the shared object with [Ghidra](https://ghidra-sre.org/) and dynamic analysis with [Frida](https://frida.re/docs/javascript-api/).
|
||||
|
||||
The headers reversed with Ghidra are at `types/all.h`. They are almost not used by this minimal implementation though.
|
||||
|
||||
The hooks used with frida are at `frida-hooks.js`, but it's mostly a playground - some useful functions got deleted once I understood the protocol.
|
||||
|
||||
There's also a pretty crappy Wireshark dissector at `dissector.lua`. You can install it with `make install-wireshark-dissector`.
|
||||
|
||||
## Running
|
||||
To execute the server, run `make run`; JPEG files and `audio.pcm` will be created in a folder named `captures`.
|
||||
To execute the HTTP server, run `node bin.cjs`; you can access the JPEG stream at http://localhost:5000/ and (optionally) a file `audio.pcm` will be created.
|
||||
|
||||
There's no live-stream server built into this project yet.
|
||||
```bash
|
||||
$ node dist/bin.cjs http_server --help
|
||||
|
||||
start http server
|
||||
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
--debug [boolean] [default: false]
|
||||
--ansi [boolean] [default: false]
|
||||
--audio [boolean] [default: false]
|
||||
--discovery_ip [default: "192.168.1.255"]
|
||||
--attempt_to_fix_packet_loss [default: false]
|
||||
--port HTTP Port to listen on [number] [default: 5000]
|
||||
```
|
||||
|
||||
The roundtrip delay when using MJPEG is [~350ms](pics/delay.jpg?raw=true).
|
||||
|
||||
There's a basic UI which can display multiple cameras:
|
||||
|
||||

|
||||
|
||||
The server will send a broadcast packet every few seconds to discover all the cameras available; this means that it *must* run in the same broadcast domain (VLAN) as your cameras.
|
||||
|
||||
Clicking on the image will take you to a page that has audio streaming enabled.
|
||||
|
||||
## Pairing a new camera
|
||||
|
||||
Connect to the device's access point and run `node bin.cjs pair --ssid <SSID> --password <PASSWORD>`.
|
||||
```bash
|
||||
$ node dist/bin.cjs pair --help
|
||||
|
||||
configure a camera
|
||||
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
--debug [boolean] [default: false]
|
||||
--ansi [boolean] [default: false]
|
||||
--discovery_ip [default: "192.168.1.255"]
|
||||
--attempt_to_fix_packet_loss [default: false]
|
||||
--ssid [string] [required]
|
||||
--password [string] [required]
|
||||
```
|
||||
|
||||
## Protocol
|
||||
|
||||
@@ -89,6 +122,93 @@ sequenceDiagram
|
||||
end
|
||||
```
|
||||
|
||||
### Serial
|
||||
|
||||
The A9 cameras have a TX/RX test points - connecting with UART at 921600 8N1 gives _read only_ access to some debug logs.
|
||||
|
||||
### Discrepancies between cameras
|
||||
|
||||
1. Wifi Strength
|
||||
- A9 reports '100%' strength
|
||||
- X5 reports different strength values
|
||||
|
||||
I bricked two cameras by patching out part of the WiFi setup - unclear yet which commands.
|
||||
|
||||
After bricking itself, it reports very broken configuration via serial:
|
||||
|
||||
```
|
||||
network interface: ƀ (Default)
|
||||
MTU: 51050
|
||||
MAC: 06 18 40 06 3e 51 b4 e2 c6 80 06 3f 77 30 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 74 00 00 00 01 00 00 00 9c ea 01 20 00 00 00 00 00 00 00 00 00 28 60 00 00 00 00 00 00 00 00 00 06 4e 00 20 2a 00 2a 00 80 00 00 00 00 00 ff ff ff ff ff ff 3e 51 b4 e2 c6 80 08 06 00 01 08 00 06 04 00 01 3e 51 b4 e2 c6 80 01 01 01 01 00 00 00 00 00 00 01 01 01 01 00 28 74 00 00 00 00 00 00 00 00 00 58 4e 00 20 48 00 48 00 80 00 00 00 00 00 ff ff ff ff ff ff 3e 51 b4 e2 c6 80 08 06 45 00 00 48 00 51 00 00 ff 11 c8 be 01 01 01 01 23 9c cc f7 7d 6c
|
||||
FLAGS: DOWN LINK_DOWN IGMP
|
||||
ip address: 1.1.1.1
|
||||
gw address: 1.1.1.1
|
||||
net mask : 1.1.1.1
|
||||
|
||||
network i
|
||||
nterface: ^@^@
|
||||
MTU: 0
|
||||
MAC:
|
||||
FLAGS: DOWN LINK_DOWN
|
||||
ip address: 127.0.0.1
|
||||
gw address: 127.0.0.1
|
||||
net mask : 255.0.0.0
|
||||
```
|
||||
|
||||
## Spyware
|
||||
|
||||
When connecting the camera to a network, it tries to send a HELLO (?) to 4 IP addresses:
|
||||
```
|
||||
139.155.68.77 - Shenzhen Tencent Computer Systems Company Limited
|
||||
119.45.114.92 - Shenzhen Tencent Computer Systems Company Limited
|
||||
162.62.63.154 - Tencent Building, Kejizhongyi Avenue
|
||||
3.132.215.40 - ec2-3-132-215-40.us-east-2.compute.amazonaws.com
|
||||
```
|
||||
|
||||
With the payload
|
||||
```
|
||||
0000 f1 10 00 28 42 41 54 43 00 00 00 00 00 09 4d 2c ...(BATC......M,
|
||||
0010 48 56 44 43 53 00 00 00 08 00 02 01 00 00 6c 7d HVDCS.........l}
|
||||
0020 65 28 a8 c0 00 00 00 00 00 00 00 00 e(..........
|
||||
```
|
||||
|
||||
which is `DevLogin`
|
||||
|
||||
|
||||
These addresses are decoded (script at `scripts/dec_svr.py`) from the string `SWPNPDPFLVAOLNSXPHSQPIEOPAIDENLXHXEHIFLKPGLRHUARSTLQEEEPSUIHPDLSPEAOICLOSQEMLPPALNIBIAERHZLKHXEJHYHUEIEHELEEEKEG`.
|
||||
|
||||
Every 8-10s
|
||||
|
||||
There are some other strings in the APK ending in `-$$` which decode to other ips/hostnames.
|
||||
|
||||
|
||||
## Other stuff
|
||||
|
||||
These little cameras have quite some packet loss - I _tried_ to deal with it by splicing around it on the JPEG payloads, but it's probably wrong, I expected artifacts like this:
|
||||
|
||||

|
||||
|
||||
but most of the time got:
|
||||
|
||||

|
||||
|
||||
which _moves_ the rest of the image, causing more visual noise.
|
||||
|
||||
For now, images on which there was packet loss get skipped. The algorithm to "fix" packet loss can be enabled as an option.
|
||||
|
||||
|
||||
## Reversing
|
||||
|
||||
The interesting implementation is in `libvdp.so`, part of the apk bundle.
|
||||
|
||||
Protocol reversing was done with a combination of static analysis of the shared object with [Ghidra](https://ghidra-sre.org/) and dynamic analysis with [Frida](https://frida.re/docs/javascript-api/).
|
||||
|
||||
The headers reversed with Ghidra are at `types/all.h`. They are almost not used by this minimal implementation though.
|
||||
|
||||
The hooks used with frida are at `frida-hooks.js`, but it's mostly a playground - some useful functions got deleted once I understood the protocol.
|
||||
|
||||
There's also a partial Wireshark dissector at `dissector.lua`. You can install it with `make install-wireshark-dissector`.
|
||||
|
||||
### Take APK from emulator/sacrificial device
|
||||
```
|
||||
adb shell pm list packages | grep ysx
|
||||
@@ -104,3 +224,8 @@ adb install-multiple *apk
|
||||
|
||||
[docs](https://frida.re/docs/android/)
|
||||
|
||||
### Start frida server
|
||||
|
||||
```
|
||||
adb shell 'su -c nohup /data/local/tmp/frida-server-16.1.11-android-arm64 &'
|
||||
```
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import beamcoder from "beamcoder";
|
||||
import EventEmitter from "node:events";
|
||||
import fs from "node:fs";
|
||||
import Stream from "node:stream";
|
||||
|
||||
const sleep = (waitTimeInMs) => new Promise((resolve) => setTimeout(resolve, waitTimeInMs));
|
||||
let c = 10;
|
||||
const readableStream = new Stream.Readable({
|
||||
async read() {
|
||||
await sleep(10);
|
||||
if (c < 810) {
|
||||
const data = fs.readFileSync(
|
||||
`captures/${parseInt(c / 10)
|
||||
.toString()
|
||||
.padStart(4, "0")}.jpg`,
|
||||
);
|
||||
this.push(data);
|
||||
} else {
|
||||
this.push(null);
|
||||
}
|
||||
c++;
|
||||
},
|
||||
});
|
||||
|
||||
const ee = new EventEmitter();
|
||||
|
||||
let demuxers = beamcoder.demuxers();
|
||||
// console.log(demuxers);
|
||||
|
||||
ee.on("inputFrame", (f) => {});
|
||||
|
||||
const makeEncoder = (frame, frameRate) => {
|
||||
return beamcoder.encoder({
|
||||
name: "libx264",
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
bit_rate: 400000,
|
||||
// qmin: 22,
|
||||
time_base: [1, frameRate],
|
||||
framerate: [frameRate, 1],
|
||||
pix_fmt: "yuv420p",
|
||||
preset: "faster",
|
||||
gop_size: 10,
|
||||
max_b_frames: 1,
|
||||
});
|
||||
};
|
||||
async function imageToVideo(imagePath, duration, frameRate = 20) {
|
||||
const muxTimeBase = 90000;
|
||||
let demuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 });
|
||||
|
||||
console.log("piping");
|
||||
readableStream.pipe(demuxerStream);
|
||||
console.log("creating demuxer");
|
||||
// Create a demuxer for the JPEG image
|
||||
// let demuxer = await beamcoder.demuxer(imagePath);
|
||||
let demuxer = await demuxerStream.demuxer({ name: "jpeg_pipe" });
|
||||
// Create a decoder for the image
|
||||
let decoder = beamcoder.decoder({ demuxer: demuxer, name: "mjpeg" });
|
||||
|
||||
// Read the image packet
|
||||
console.log("wait demuxer");
|
||||
// let packet = await demuxer.read();
|
||||
let encoder = null;
|
||||
let muxer = null;
|
||||
let vstr = null;
|
||||
let i = 0;
|
||||
while (true) {
|
||||
let packet = await demuxer.read();
|
||||
if (packet == null) break;
|
||||
let frames = await decoder.decode(packet);
|
||||
let frame = frames.frames[0];
|
||||
if (encoder == null) {
|
||||
encoder = makeEncoder(frame, frameRate);
|
||||
// Create an H.264 encoder
|
||||
// https://stackoverflow.com/a/13646293/3530257
|
||||
// > the codec unit of measurement is commonly set to the interval between
|
||||
// each frame and the next, > so that frame times are successive integers.
|
||||
|
||||
// TODO a muxer per client?
|
||||
let stream = beamcoder.muxerStream({});
|
||||
stream.pipe(fs.createWriteStream("test.mp4"));
|
||||
// Create a muxer for the output video
|
||||
muxer = stream.muxer({ format_name: "mp4" });
|
||||
|
||||
// console.log(demuxer.streams[0].codecpar.extradata); // null
|
||||
vstr = muxer.newStream({
|
||||
name: "h264",
|
||||
time_base: [1, muxTimeBase], // frameRate],
|
||||
interleaved: true,
|
||||
});
|
||||
|
||||
// the Object.assign is structural (!!)
|
||||
Object.assign(vstr.codecpar, {
|
||||
width: encoder.width,
|
||||
height: encoder.height,
|
||||
format: encoder.pix_fmt,
|
||||
});
|
||||
|
||||
await muxer.openIO();
|
||||
// adding "empty_moov" crashes mpv/ffmpeg
|
||||
// await muxer.initOutput({ movflags:
|
||||
// "frag_keyframe+default_base_moof+faststart" });
|
||||
await muxer.initOutput({ movflags: "frag_keyframe" });
|
||||
console.log("inited");
|
||||
// Add a video stream to the muxer
|
||||
await muxer.writeHeader();
|
||||
console.log("header written");
|
||||
}
|
||||
if (frame) {
|
||||
frame.pts = i; // << the successive integers
|
||||
frame.dts = i; // << the successive integers
|
||||
let encodedPackets = await encoder.encode(frame);
|
||||
|
||||
// Write the encoded packets to the output file
|
||||
for (let packet of encodedPackets.packets) {
|
||||
packet.duration = 1;
|
||||
packet.stream_index = vstr.index;
|
||||
packet.pts = (packet.pts * muxTimeBase) / frameRate;
|
||||
packet.dts = (packet.dts * muxTimeBase) / frameRate;
|
||||
// packet.pts = i;
|
||||
await muxer.writeFrame(packet);
|
||||
// outFile.write(packet.data);
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
// Finalize the encoder and muxer
|
||||
let encodedPackets = await encoder.flush();
|
||||
// after flushing the encoder, we may hve some more packets
|
||||
|
||||
// Write the encoded packets to the output file
|
||||
for (let packet of encodedPackets.packets) {
|
||||
packet.duration = 1;
|
||||
packet.stream_index = vstr.index;
|
||||
packet.pts = (packet.pts * muxTimeBase) / frameRate;
|
||||
packet.dts = (packet.dts * muxTimeBase) / frameRate;
|
||||
await muxer.writeFrame(packet);
|
||||
}
|
||||
await muxer.writeTrailer();
|
||||
}
|
||||
|
||||
// Usage example
|
||||
imageToVideo("captures/0010.jpg", 20)
|
||||
.then(() => {
|
||||
console.log("Video created successfully");
|
||||
})
|
||||
.catch(console.error);
|
||||
@@ -0,0 +1,62 @@
|
||||
<html>
|
||||
<h2>${id}</h2><a href="/camera/${id}"><img src="/camera/${id}"/></a><hr/>
|
||||
<script>
|
||||
const alaw_to_s16_table = [
|
||||
-5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752,
|
||||
-2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016,
|
||||
-20992, -24064, -23040, -17920, -16896, -19968, -18944, -30208, -29184, -32256, -31232, -26112, -25088, -28160,
|
||||
-27136, -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, -15104, -14592, -16128, -15616, -13056, -12544,
|
||||
-14080, -13568, -344, -328, -376, -360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88,
|
||||
-72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376, -1312, -1504, -1440, -1120,
|
||||
-1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688, -656, -752, -720, -560, -528, -624,
|
||||
-592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296,
|
||||
8064, 7808, 6528, 6272, 7040, 6784, 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264,
|
||||
3136, 3520, 3392, 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088,
|
||||
28160, 27136, 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080,
|
||||
13568, 344, 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88, 72, 120, 104, 24, 8, 56,
|
||||
40, 216, 200, 248, 232, 152, 136, 184, 168, 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, 1888, 1824, 2016, 1952,
|
||||
1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848,
|
||||
];
|
||||
|
||||
const alaw_to_s16 = (a_val) => {
|
||||
return alaw_to_s16_table[a_val];
|
||||
};
|
||||
|
||||
const audio_context = new AudioContext();
|
||||
const gain_node = audio_context.createGain(); // Declare gain node
|
||||
const channels =1;
|
||||
const sample_rate = 8000;
|
||||
const audioBuffer = audio_context.createBuffer(channels, 960, sample_rate); // 960??
|
||||
//const audioBuffer = audio_context.createBuffer(channels, decoded.length, sample_rate);
|
||||
|
||||
gain_node.connect(audio_context.destination); // Connect gain node to speakers
|
||||
audio_context.resume();
|
||||
|
||||
const evtSource = new EventSource('/audio/${id}');
|
||||
evtSource.onopen = (e) => {
|
||||
console.log("evtsource open");
|
||||
}
|
||||
let endsAt = 0;
|
||||
let startAt = 0;
|
||||
evtSource.onmessage = (e) => {
|
||||
const nowBuffering = audioBuffer.getChannelData(0);
|
||||
const u8 = Uint8Array.from(atob(e.data), c => c.charCodeAt(0));
|
||||
new Int16Array(u8).map(alaw_to_s16).forEach((el, i) => nowBuffering[i] = el / 0x8000 );
|
||||
|
||||
const source_node = audio_context.createBufferSource();
|
||||
source_node.buffer = audioBuffer;
|
||||
source_node.connect(gain_node);
|
||||
const now = Date.now();
|
||||
if(now > endsAt) { // lost packets
|
||||
startAt = 0;
|
||||
} else {
|
||||
startAt += audioBuffer.duration;
|
||||
}
|
||||
source_node.start(startAt);
|
||||
endsAt = now + audioBuffer.duration * 1000;
|
||||
};
|
||||
evtSource.onerror = (e) => {
|
||||
console.log("evtsource error", e);
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import yargs from "yargs/yargs";
|
||||
|
||||
import { serveHttp } from "../http_server.js";
|
||||
import { opt } from "../options.js";
|
||||
import { pair } from "../pair.js";
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.command(
|
||||
"http_server",
|
||||
"start http server",
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.option("debug", { default: false })
|
||||
.option("ansi", { default: false })
|
||||
.option("audio", { default: false })
|
||||
.boolean(["debug", "ansi", "audio"])
|
||||
.option("discovery_ip", { default: "192.168.1.255" })
|
||||
.option("attempt_to_fix_packet_loss", { default: false })
|
||||
.option("port", { describe: "HTTP Port to listen on", default: 5000 })
|
||||
.number(["port"])
|
||||
.strict();
|
||||
},
|
||||
(argv) => {
|
||||
const opts: opt = argv as opt;
|
||||
serveHttp(opts, argv.port, argv.audio || false);
|
||||
},
|
||||
)
|
||||
.command(
|
||||
"pair",
|
||||
"configure a camera",
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.option("debug", { default: false })
|
||||
.option("ansi", { default: false })
|
||||
.boolean(["debug", "ansi"])
|
||||
.option("discovery_ip", { default: "192.168.1.255" })
|
||||
.option("attempt_to_fix_packet_loss", { default: false })
|
||||
.demandOption("ssid")
|
||||
.demandOption("password")
|
||||
.string(["ssid", "password"]);
|
||||
},
|
||||
(argv) => {
|
||||
const opts: opt = argv as unknown as opt;
|
||||
pair({ opts, ssid: argv.ssid, password: argv.password });
|
||||
},
|
||||
)
|
||||
.demandCommand()
|
||||
.parseSync();
|
||||
Binary file not shown.
+34
-2
@@ -24,12 +24,44 @@ export const Commands = {
|
||||
};
|
||||
|
||||
// Record<keyof typeof Commands,
|
||||
export const CommandsByValue = Object.keys(Commands).reduce((acc, cur) => {
|
||||
acc[Commands[cur]] = cur;
|
||||
type t = Record<number, keyof typeof Commands>;
|
||||
export const CommandsByValue: t = Object.keys(Commands).reduce((acc: t, cur) => {
|
||||
let key: keyof typeof Commands = cur as keyof typeof Commands;
|
||||
acc[Commands[key]] = key;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
export const DrwStart = 0x0a11;
|
||||
export const ControlCommands = {
|
||||
// TODO: flip these..
|
||||
ConnectUser: 0x2010,
|
||||
ConnectUserAck: 0x2011,
|
||||
// CloseSession: 0x3110,
|
||||
// CloseSessionAck: 0x3111,
|
||||
DevStatus: 0x0810, // CMD_SYSTEM_STATUS_GET
|
||||
DevStatusAck: 0x0811,
|
||||
WifiSettingsSet: 0x0160, // CMD_NET_WIFISETTING_SET
|
||||
WifiSettings: 0x0260, // CMD_NET_WIFISETTING_GET
|
||||
WifiSettingsAck: 0x0261,
|
||||
ListWifi: 0x0360, // CMD_NET_WIFI_SCAN
|
||||
ListWifiAck: 0x0361,
|
||||
StartVideo: 0x1030, // CMD_PEER_LIVEVIDEO_START
|
||||
StopVideo: 0x1130, // CMD_PEER_LIVEVIDEO_STOP
|
||||
Shutdown: 0x1010, //CMD_SYSTEM_SHUTDOWN,
|
||||
Reboot: 0x1110, //CMD_SYSTEM_REBOOT,
|
||||
VideoParamSet: 0x1830, // CMD_PEER_VIDEOPARAM_SET
|
||||
VideoParamGet: 0x1930, // CMD_PEER_VIDEOPARAM_GET
|
||||
IRToggle: 0x0a30, // CMD_PEER_IRCUT_ONOFF
|
||||
};
|
||||
|
||||
export const ccDest: Record<number, number> = {
|
||||
[ControlCommands.ConnectUser]: 0xff00,
|
||||
[ControlCommands.DevStatus]: 0x0000,
|
||||
[ControlCommands.StartVideo]: 0x0000,
|
||||
[ControlCommands.ListWifi]: 0x0000,
|
||||
[ControlCommands.WifiSettings]: 0x0000,
|
||||
|
||||
[ControlCommands.ListWifiAck]: 0xaa55,
|
||||
[ControlCommands.ConnectUserAck]: 0xaa55,
|
||||
[ControlCommands.DevStatusAck]: 0xaa55,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import EventEmitter from "node:events";
|
||||
import { create_LanSearch, parse_PunchPkt } from "./impl.js";
|
||||
import { createSocket, RemoteInfo } from "node:dgram";
|
||||
import { Commands } from "./datatypes.js";
|
||||
|
||||
const handleIncomingPunch = (msg: Buffer, ee: EventEmitter, rinfo: RemoteInfo, debug: boolean) => {
|
||||
const ab = new Uint8Array(msg).buffer;
|
||||
const dv = new DataView(ab);
|
||||
const cmd_id = dv.readU16();
|
||||
if (cmd_id != Commands.PunchPkt) {
|
||||
return;
|
||||
}
|
||||
if (debug) {
|
||||
console.log("Discovery got a PunchPkt");
|
||||
}
|
||||
ee.emit("discover", rinfo, parse_PunchPkt(dv));
|
||||
};
|
||||
|
||||
export const discoverDevices = (debug: boolean, discovery_ip: string): EventEmitter => {
|
||||
const sock = createSocket("udp4");
|
||||
const SEND_PORT = 32108;
|
||||
const ee = new EventEmitter();
|
||||
|
||||
sock.on("error", (err) => {
|
||||
console.error(`sock error:\n${err.stack}`);
|
||||
sock.close();
|
||||
});
|
||||
|
||||
sock.on("message", (msg, rinfo) => handleIncomingPunch(msg, ee, rinfo, debug));
|
||||
|
||||
sock.on("listening", () => {
|
||||
sock.setBroadcast(true);
|
||||
console.log(`Searching for devices on ${discovery_ip}`);
|
||||
|
||||
let buf = create_LanSearch();
|
||||
setInterval(() => {
|
||||
console.log(".");
|
||||
sock.send(new Uint8Array(buf.buffer), SEND_PORT, discovery_ip);
|
||||
}, 2000);
|
||||
sock.send(new Uint8Array(buf.buffer), SEND_PORT, discovery_ip);
|
||||
});
|
||||
|
||||
sock.bind();
|
||||
|
||||
return ee;
|
||||
};
|
||||
+84
-41
@@ -1,24 +1,29 @@
|
||||
-- Create a new protocol for your custom packets
|
||||
my_protocol = Proto("myprotocol", "My Custom Protocol")
|
||||
ilnk_proto = Proto("iLnkP2P", "iLnk")
|
||||
|
||||
-- Define the fields you want to display in Wireshark
|
||||
my_protocol.fields = {}
|
||||
my_protocol.fields.type = ProtoField.string("myprotocol.type", "Type")
|
||||
my_protocol.fields.payload = ProtoField.bytes("myprotocol.payload", "Data")
|
||||
my_protocol.fields.len = ProtoField.uint16("myprotocol.len", "Len", base.HEX)
|
||||
ilnk_proto.fields = {}
|
||||
ilnk_proto.fields.type = ProtoField.string("iLnkP2P.type", "Type")
|
||||
-- ilnk_proto.fields.payload = ProtoField.bytes("iLnkP2P.payload", "Payload")
|
||||
ilnk_proto.fields.len = ProtoField.uint16("iLnkP2P.len", "Packet length", base.HEX)
|
||||
|
||||
my_protocol.fields.m_type = ProtoField.uint8("myprotocol.m_type", "Stream Type", base.HEX)
|
||||
my_protocol.fields.m_stream_id = ProtoField.uint8("myprotocol.m_stream_id", "Stream ID", base.HEX)
|
||||
my_protocol.fields.elem_count = ProtoField.uint16("myprotocol.elem_count", "Elem count", base.DEC)
|
||||
ilnk_proto.fields.m_type = ProtoField.uint8("iLnkP2P.m_type", "Stream type", base.HEX)
|
||||
ilnk_proto.fields.m_stream_id = ProtoField.uint8("iLnkP2P.m_stream_id", "Stream ID", base.HEX)
|
||||
ilnk_proto.fields.pkt_seq = ProtoField.uint16("iLnkP2P.pkt_seq", "Packet ID", base.HEX)
|
||||
ilnk_proto.fields.elem_count = ProtoField.uint16("iLnkP2P.elem_count", "Elem count", base.DEC)
|
||||
|
||||
my_protocol.fields.cmd_payload_len = ProtoField.uint16("myprotocol.cmd_payload_len", "CMD Payload Len", base.HEX)
|
||||
my_protocol.fields.cmd = ProtoField.uint16("myprotocol.cmd", "CMD", base.HEX)
|
||||
my_protocol.fields.start = ProtoField.uint16("myprotocol.start", "Start", base.HEX)
|
||||
my_protocol.fields.cmd_dest = ProtoField.uint16("myprotocol.cmd_dest", "Dest", base.HEX)
|
||||
my_protocol.fields.cmd_payload = ProtoField.bytes("myprotocol.payload", "Payload", base.DASH)
|
||||
ilnk_proto.fields.cmd_payload_len = ProtoField.uint16("iLnkP2P.cmd_payload_len", "CMD Payload Len", base.HEX)
|
||||
ilnk_proto.fields.cmd = ProtoField.uint16("iLnkP2P.cmd", "CMD", base.HEX)
|
||||
ilnk_proto.fields.start = ProtoField.uint16("iLnkP2P.start", "Start", base.HEX)
|
||||
ilnk_proto.fields.cmd_dest = ProtoField.uint16("iLnkP2P.cmd_dest", "Dest", base.HEX)
|
||||
ilnk_proto.fields.auth_token = ProtoField.bytes("iLnkP2P.auth_token", "CMD auth token", base.DASH)
|
||||
ilnk_proto.fields.cmd_payload = ProtoField.bytes("iLnkP2P.payload", "CMD Payload", base.DASH)
|
||||
-- jpeg | audio | continuation type?
|
||||
ilnk_proto.fields.data_payload = ProtoField.bytes("iLnkP2P.data_payload", "Data Payload", base.DASH)
|
||||
|
||||
my_protocol.fields.encrypted = ProtoField.bool("myprotocol.encrypted", "Encrypted")
|
||||
my_protocol.fields.cmd_type = ProtoField.string("myprotocol.payload", "Cmd Pkt Type")
|
||||
ilnk_proto.fields.encrypted = ProtoField.bool("iLnkP2P.encrypted", "Encrypted")
|
||||
ilnk_proto.fields.cmd_type = ProtoField.string("iLnkP2P.cmd_type", "Cmd Pkt Type")
|
||||
ilnk_proto.fields.decrypted_data = ProtoField.bytes("iLnkP2P.decrypted_data", "Decrypted data")
|
||||
|
||||
lut = {
|
||||
[0xf1f0] = "Close",
|
||||
@@ -48,45 +53,83 @@ lut = {
|
||||
setmetatable(lut, lut)
|
||||
|
||||
-- Define a function to dissect the packets
|
||||
function my_protocol.dissector(buffer, pinfo, tree)
|
||||
function ilnk_proto.dissector(buffer, pinfo, tree)
|
||||
local packet_length = buffer:len()
|
||||
|
||||
local subtree = tree:add(my_protocol, buffer(), "My Custom Protocol Data")
|
||||
local subtree = tree:add(ilnk_proto, buffer(), "iLnkP2P")
|
||||
|
||||
-- Add the entire packet as a field
|
||||
local packetname = lut[buffer(0, 2):uint()]
|
||||
subtree:add(my_protocol.fields.type, packetname)
|
||||
subtree:add(ilnk_proto.fields.type, packetname)
|
||||
|
||||
-- Set the protocol description in the packet list
|
||||
pinfo.cols.protocol:set("myprotocol")
|
||||
pinfo.cols.protocol:set("iLnkP2P")
|
||||
if packetname == "DrwAck" then
|
||||
subtree:add(my_protocol.fields.len, buffer(2, 2))
|
||||
subtree:add(my_protocol.fields.m_type, buffer(4, 1))
|
||||
subtree:add(my_protocol.fields.m_stream_id, buffer(5, 1))
|
||||
subtree:add(my_protocol.fields.elem_count, buffer(6, 2))
|
||||
subtree:add(ilnk_proto.fields.len, buffer(2, 2))
|
||||
subtree:add(ilnk_proto.fields.m_type, buffer(4, 1))
|
||||
subtree:add(ilnk_proto.fields.m_stream_id, buffer(5, 1))
|
||||
subtree:add(ilnk_proto.fields.elem_count, buffer(6, 2))
|
||||
|
||||
end
|
||||
if packetname == "Drw" then
|
||||
subtree:add(my_protocol.fields.len, buffer(2, 2))
|
||||
subtree:add(my_protocol.fields.m_type, buffer(4, 1))
|
||||
subtree:add(my_protocol.fields.m_stream_id, buffer(5, 1))
|
||||
subtree:add_le(my_protocol.fields.start, buffer(8, 2))
|
||||
subtree:add_le(my_protocol.fields.cmd, buffer(0xa, 2))
|
||||
local b_pkt_len = buffer(2, 2)
|
||||
local pkt_len = b_pkt_len:uint()
|
||||
local is_data_packet = buffer(5, 1):uint() == 1
|
||||
subtree:add(ilnk_proto.fields.len, b_pkt_len)
|
||||
subtree:add(ilnk_proto.fields.m_type, buffer(4, 1))
|
||||
subtree:add(ilnk_proto.fields.m_stream_id, buffer(5, 1))
|
||||
subtree:add(ilnk_proto.fields.pkt_seq, buffer(6, 2))
|
||||
local b_payload_len = buffer(0xc, 2)
|
||||
local payload_len = buffer(0xc, 2):le_uint()
|
||||
subtree:add_le(my_protocol.fields.cmd_payload_len, buffer(0xc, 2))
|
||||
|
||||
subtree:add(my_protocol.fields.encrypted, payload_len >= 5):set_generated()
|
||||
if buffer(0xb, 1):uint() % 2 == 1 then
|
||||
cmdtype = "ack"
|
||||
if not is_data_packet then
|
||||
subtree:add_le(ilnk_proto.fields.start, buffer(8, 2))
|
||||
subtree:add_le(ilnk_proto.fields.cmd, buffer(0xa, 2))
|
||||
subtree:add_le(ilnk_proto.fields.cmd_payload_len, b_payload_len)
|
||||
subtree:add_le(ilnk_proto.fields.cmd_dest, buffer(0xe, 2))
|
||||
-- inline value for short-payload bytes
|
||||
subtree:add(ilnk_proto.fields.auth_token, buffer(0x10, 4))
|
||||
|
||||
if buffer(0xb, 1):uint() % 2 == 1 then
|
||||
cmdtype = "ack"
|
||||
else
|
||||
cmdtype = "cmd"
|
||||
end
|
||||
subtree:add(ilnk_proto.fields.cmd_type, cmdtype):set_generated()
|
||||
subtree:add(ilnk_proto.fields.encrypted, payload_len >= 5):set_generated()
|
||||
|
||||
if payload_len >= 5 then
|
||||
local payload = buffer(0x14, payload_len - 4)
|
||||
local dec_payload = ByteArray.new()
|
||||
dec_payload:set_size(payload_len - 4)
|
||||
for i=4,payload_len-5 do -- inclusive upper range
|
||||
local v = buffer(0x14 + i-4, 1):uint()
|
||||
if (v % 2) == 0 then
|
||||
v = v + 1
|
||||
else
|
||||
v = v - 1
|
||||
end
|
||||
dec_payload:set_index(i, v)
|
||||
end
|
||||
for i=0,3 do
|
||||
local v = buffer(pkt_len+i, 1):uint()
|
||||
if v % 2 == 0 then
|
||||
v = v + 1
|
||||
else
|
||||
v = v - 1
|
||||
end
|
||||
dec_payload:set_index(i, v)
|
||||
end
|
||||
local dec_tvb = ByteArray.tvb(dec_payload, "Decrypted payload")
|
||||
subtree:add(ilnk_proto.fields.decrypted_data, dec_tvb:range(0, payload_len -4) ):set_generated()
|
||||
|
||||
local payload_tvb = ByteArray.tvb(buffer(0x14, payload_len -4):bytes(), "CMD Payload")
|
||||
subtree:add(ilnk_proto.fields.cmd_payload, payload_tvb:range(0, payload_len -4))
|
||||
end
|
||||
else
|
||||
cmdtype = "cmd"
|
||||
local payload_tvb = ByteArray.tvb(buffer(8, packet_length-8):bytes(), "Data Payload")
|
||||
subtree:add(ilnk_proto.fields.data_payload, payload_tvb:range(0, packet_length-8))
|
||||
end
|
||||
subtree:add(my_protocol.fields.cmd_type, cmdtype):set_generated()
|
||||
|
||||
subtree:add_le(my_protocol.fields.cmd_dest, buffer(0xe, 2))
|
||||
subtree:add(my_protocol.fields.cmd_payload, buffer(0x10, payload_len))
|
||||
-- subtree:add(my_protocol.fields.cmd, buffer(0xc, 2))
|
||||
|
||||
end
|
||||
-- AvcLIB = src/IpcSession.cpp, line 1113, CmdSndProc:BATC609531EXLVS[0:0:10] now CmdSend[start=a11,cmd=1032,len=4,dest=0]=12
|
||||
-- UDP PKT SEND Drw (0xf1d0)
|
||||
@@ -94,7 +137,7 @@ function my_protocol.dissector(buffer, pinfo, tree)
|
||||
-- 00000000 f1 d0 00 10 d1 00 00 04 11 0a 32 10 04 00 00 00 ..........2.....
|
||||
-- 00000010 50 70 77 35 Ppw5
|
||||
|
||||
subtree:add(my_protocol.fields.payload, buffer(2, packet_length-2))
|
||||
-- subtree:add(ilnk_proto.fields.payload, buffer(2, packet_length-2))
|
||||
end
|
||||
|
||||
udp_table = DissectorTable.get("udp.port"):add(49512, my_protocol)
|
||||
udp_table = DissectorTable.get("udp.port"):add(49512, ilnk_proto)
|
||||
|
||||
+169
-9
@@ -1,9 +1,10 @@
|
||||
import { replaceFunctions, Commands, CommandsByValue } from "./func_replacements.js";
|
||||
import { Commands, CommandsByValue } from "./datatypes.js";
|
||||
import { replaceFunctions, XqBytesDec } from "./func_replacements.js";
|
||||
import { placeholderTypes, sprintf, u16_swap } from "./utils.js";
|
||||
|
||||
const hook_fn = (name_in_elf, enter, leave) => {
|
||||
var symbol_addr = DebugSymbol.fromName(name_in_elf).address;
|
||||
console.log(`${name_in_elf} addr is: ${symbol_addr}, this is ${this}`);
|
||||
console.log(`${name_in_elf} addr is: ${symbol_addr}`);
|
||||
Interceptor.attach(symbol_addr, {
|
||||
onEnter: enter,
|
||||
onLeave: leave,
|
||||
@@ -11,6 +12,18 @@ const hook_fn = (name_in_elf, enter, leave) => {
|
||||
console.log(`Hooked ${name_in_elf}`);
|
||||
};
|
||||
|
||||
const global = (name_in_elf) => {
|
||||
//var symbol_addr = DebugSymbol.fromName(name_in_elf).address;
|
||||
// Module.enumerateSections("libvdp.so").forEach((s) => console.log(JSON.stringify(s, null, 2)));
|
||||
// "name": ".bss",
|
||||
//Module.enumerateSymbols("libvdp.so").forEach((s) => console.log(JSON.stringify(s, null, 2)));
|
||||
const syms = Module.enumerateSymbols("libvdp.so").filter((s) => s.name == name_in_elf);
|
||||
var symbol_addr = syms[0].address;
|
||||
console.log(`global ${name_in_elf} addr is: ${symbol_addr}`);
|
||||
if (symbol_addr == 0x0 || symbol_addr == null) throw new Error(`can't read ${name_in_elf}`);
|
||||
return new NativePointer(symbol_addr);
|
||||
};
|
||||
|
||||
function hook_export_fn(name_in_elf, enter, leave) {
|
||||
var symbol_addr = Module.findExportByName("libvdp.so", name_in_elf);
|
||||
console.log(`${name_in_elf} addr is: ${symbol_addr}`);
|
||||
@@ -40,6 +53,54 @@ const hook_p2p_read = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const hook_Log = () => {
|
||||
Java.perform(function () {
|
||||
var Log = Java.use("android.util.Log");
|
||||
Log.d.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
||||
console.log("The application reports Log.d(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.d(a, b, c);
|
||||
};
|
||||
Log.v.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
||||
console.log("The application reports Log.v(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.v(a, b, c);
|
||||
};
|
||||
|
||||
Log.i.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
||||
console.log("The application reports Log.i(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.i(a, b, c);
|
||||
};
|
||||
Log.e.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
||||
console.log("The application reports Log.e(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.e(a, b, c);
|
||||
};
|
||||
Log.w.overload("java.lang.String", "java.lang.String", "java.lang.Throwable").implementation = function (a, b, c) {
|
||||
console.log("The application reports Log.w(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.w(a, b, c);
|
||||
};
|
||||
Log.d.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
||||
console.log("The application reports Log.d(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.d(a, b);
|
||||
};
|
||||
Log.v.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
||||
console.log("The application reports Log.v(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.v(a, b);
|
||||
};
|
||||
|
||||
Log.i.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
||||
console.log("The application reports Log.i(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.i(a, b);
|
||||
};
|
||||
Log.e.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
||||
console.log("The application reports Log.e(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.e(a, b);
|
||||
};
|
||||
Log.w.overload("java.lang.String", "java.lang.String").implementation = function (a, b) {
|
||||
console.log("The application reports Log.w(" + a.toString() + ", " + b.toString() + ")");
|
||||
return this.w(a, b);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const hook___android_log_print = () => {
|
||||
const sym = "__android_log_print";
|
||||
hook_fn(
|
||||
@@ -63,21 +124,100 @@ const hook___android_log_print = () => {
|
||||
|
||||
const values = types.map((t, idx) => o[t](args[idx + 3]));
|
||||
const newStr = sprintf(fmt, values);
|
||||
console.log(_tag, newStr);
|
||||
console.log(_tag, newStr.trim());
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
};
|
||||
|
||||
const hook_udpsend = () => {
|
||||
const codeTable = global("codeTable");
|
||||
/*
|
||||
* WanAddrGet
|
||||
139.155.68.77
|
||||
P2PLIB = p2pCommon/XQPPP_Socket.c, line 846, XQ_WanAddrGet:ipv4 cAddr=139.155.68.77
|
||||
WanAddrGet
|
||||
119.45.114.92
|
||||
P2PLIB = p2pCommon/XQPPP_Socket.c, line 846, XQ_WanAddrGet:ipv4 cAddr=119.45.114.92
|
||||
WanAddrGet
|
||||
162.62.63.154
|
||||
P2PLIB = p2pCommon/XQPPP_Socket.c, line 846, XQ_WanAddrGet:ipv4 cAddr=162.62.63.154
|
||||
WanAddrGet
|
||||
3.132.215.40
|
||||
*/
|
||||
let x = {};
|
||||
hook_fn(
|
||||
"XQ_WanAddrGet",
|
||||
(args) => {
|
||||
console.log("WanAddrGet");
|
||||
console.log(args[0].readCString());
|
||||
x.ret = args[2];
|
||||
},
|
||||
(retval) => {
|
||||
console.log("WanAddrGet RET");
|
||||
console.log(x.ret.readCString());
|
||||
//console.log("on wanaddrget, ret");
|
||||
//console.log(hexdump(codeTable.readByteArray(0x548)));
|
||||
},
|
||||
);
|
||||
|
||||
let d = {};
|
||||
hook_fn(
|
||||
"XqStrDec",
|
||||
(args) => {
|
||||
console.log("XqStrDec param1", args[0].readCString());
|
||||
console.log("codetable", codeTable.readCString());
|
||||
console.log("codetable hexarr\n", hexdump(codeTable.readByteArray(0x548)));
|
||||
},
|
||||
(retval) => {
|
||||
console.log("XqStrDec RET");
|
||||
console.log(retval.readCString());
|
||||
//console.log("on wanaddrget, ret");
|
||||
//console.log(hexdump(codeTable.readByteArray(0x548)));
|
||||
},
|
||||
);
|
||||
hook_fn(
|
||||
"XqCodeTableInit",
|
||||
(args) => {
|
||||
console.log("on codetableinit, it was");
|
||||
console.log(hexdump(codeTable.readByteArray(0x548)));
|
||||
},
|
||||
(retval) => {
|
||||
console.log("on codetableinit, ret");
|
||||
console.log(hexdump(codeTable.readByteArray(0x548)));
|
||||
},
|
||||
);
|
||||
hook_fn(
|
||||
"XQ_InitEncryption",
|
||||
(args) => {
|
||||
console.log("on initenc, it was");
|
||||
console.log(hexdump(codeTable.readByteArray(0x548)));
|
||||
},
|
||||
(retval) => {
|
||||
console.log("on initenc, ret");
|
||||
console.log(hexdump(codeTable.readByteArray(0x548)));
|
||||
},
|
||||
);
|
||||
|
||||
hook_fn(
|
||||
"XQ_UdpPktSend",
|
||||
(args) => {
|
||||
const data = args[0].readByteArray(args[1].toInt32());
|
||||
const cmd = u16_swap(args[0].readU16());
|
||||
const name = CommandsByValue[cmd];
|
||||
console.log(`UDP PKT SEND ${name} (0x${cmd.toString(16)})`);
|
||||
console.log(data);
|
||||
if (name != "P2PAliveAck") {
|
||||
if (name != "LanSearch" && name != "LanSearchExt") {
|
||||
let cmd = "";
|
||||
if (name == "Drw") {
|
||||
cmd = args[0].add(0xa).readU16().toString(16);
|
||||
}
|
||||
let tstamp = Date.now();
|
||||
console.log(`${tstamp} UDP PKT SEND ${name} (0x${cmd.toString(16)}) - CMD? ${cmd}`);
|
||||
console.log(data);
|
||||
}
|
||||
} else {
|
||||
console.log("> P2PAliveAck");
|
||||
}
|
||||
},
|
||||
(retval) => {},
|
||||
);
|
||||
@@ -92,14 +232,32 @@ const hook_udpsend = () => {
|
||||
(retval) => {
|
||||
const data = o.buf.readByteArray(retval.toInt32());
|
||||
const cmd = u16_swap(o.buf.readU16());
|
||||
const len = u16_swap(o.buf.add(2).readU16());
|
||||
const name = CommandsByValue[cmd];
|
||||
console.log(`UDP PKT RECV, cmd=${name}, 0x${cmd.toString(16)}, ret=${retval}`);
|
||||
console.log(data);
|
||||
if (cmd == Commands.Drw) {
|
||||
const isData = o.buf.add(5).readU8();
|
||||
if (name != "P2PAlive") {
|
||||
let tstamp = Date.now();
|
||||
console.log(`${tstamp} UDP PKT RECV, len=${len}, cmd=${name}, 0x${cmd.toString(16)}, ret=${retval}`);
|
||||
if (cmd != Commands.Drw || (cmd == Commands.Drw && !isData)) {
|
||||
// dont log data payloads
|
||||
console.log(data);
|
||||
}
|
||||
} else {
|
||||
console.log("< P2PAlive");
|
||||
}
|
||||
if (cmd == Commands.Drw && !isData) {
|
||||
if (len > 0x18) {
|
||||
// pos(0xa11) == 8 + 0xc == 0x14 == 20
|
||||
const under = data.unwrap().add(0x14); //, len - 0x20;
|
||||
XqBytesDec(under, len - 0x10, 4);
|
||||
console.log("decrypted data");
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/*
|
||||
let s = {};
|
||||
hook_fn(
|
||||
"PktSeq_seqGet",
|
||||
@@ -112,6 +270,7 @@ const hook_udpsend = () => {
|
||||
console.log(data);
|
||||
},
|
||||
);
|
||||
*/
|
||||
|
||||
/*
|
||||
hook_fn(
|
||||
@@ -213,10 +372,11 @@ function doHooks() {
|
||||
var libnative_addr = Module.findBaseAddress("libvdp.so");
|
||||
if (libnative_addr) {
|
||||
hook___android_log_print();
|
||||
hook_Log();
|
||||
// hook_in_out_buf("create_LstReq", 0x1c, 0x1c);
|
||||
//hook_in_out_buf("create_P2pRdy", 0x1c, 0x1c);
|
||||
hook_udpsend();
|
||||
doReplaceFunctions();
|
||||
//doReplaceFunctions();
|
||||
|
||||
// hook_p2p_read();
|
||||
// hook_pack_P2pId();
|
||||
|
||||
+44
-32
@@ -1,5 +1,5 @@
|
||||
import { swap_endianness_u32, swap_endianness_u16, u16_swap } from "./utils.js";
|
||||
import { Commands, CommandsByValue } from "./datatypes.js";
|
||||
import { swap_endianness_u16, swap_endianness_u32, u16_swap } from "./utils.js";
|
||||
|
||||
const writeCommand2 = (command, buf) => {
|
||||
buf.writeByteArray([(command & 0xff00) >> 8, command & 0xff]);
|
||||
@@ -12,6 +12,7 @@ export const XqBytesDec = (inoutbuf, buflen, rotate) => {
|
||||
// only rotation is different
|
||||
let new_buf = new Uint8Array(buflen);
|
||||
new_buf.fill(0x1);
|
||||
|
||||
for (let i = 0; i < buflen; i++) {
|
||||
let b = inoutbuf.add(i).readU8();
|
||||
if ((b & 1) != 0) {
|
||||
@@ -206,16 +207,26 @@ const dbg_create_Drw = (og_func) => {
|
||||
};
|
||||
const dbg__ZN12CPPPPChannel10CmdSndPushEiiPci = (og_func) => {
|
||||
const CmdSndPush = (_this, dest, cmdtype, idk, cmdlen) => {
|
||||
console.log("CmdSndPush", _this, dest.toString(16), cmdtype.toString(16), idk, cmdlen);
|
||||
//CmdSndPush 0x1020 ret: 172
|
||||
//CmdSndPush 0x1040 ret: 92
|
||||
//CmdSndPush 0x50ff ret: 564
|
||||
//CmdSndPush 0x1008 ret: 12
|
||||
console.log("CmdSndPushPre", _this, dest.toString(16), cmdtype.toString(16), idk, cmdlen);
|
||||
// CmdSndPush 0x1020 ret: 172
|
||||
// CmdSndPush 0x1040 ret: 92
|
||||
// CmdSndPush 0x50ff ret: 564
|
||||
// CmdSndPush 0x1008 ret: 12
|
||||
//
|
||||
//if (cmdtype == 0x1020) return 172; // mask
|
||||
if (cmdtype == 0x1040) return 92; // mask
|
||||
if (cmdtype == 0x50ff) return 564; // mask
|
||||
if (cmdtype == 0x1008) return 12; // mask
|
||||
//if (cmdtype == 0x1020) return 172; // login
|
||||
|
||||
// maybe these brick it
|
||||
//if (cmdtype == 0x1040) return 92; // mask
|
||||
//if (cmdtype == 0x50ff) return 564; // mask
|
||||
//if (cmdtype == 0x1008) return 12; // battery + status online?
|
||||
//// new
|
||||
////if (cmdtype == 0x6003) return 12; // wifilist
|
||||
//if (cmdtype == 0x6002) return 12; // wifisettings
|
||||
//if (cmdtype == 0x1031) return 44; // "open settings panel" ??
|
||||
//if (cmdtype == 0x1032) return 12; //idk
|
||||
|
||||
//// mb reboot
|
||||
//if (cmdtype == 0x2005) return 20; //idk
|
||||
|
||||
let ret = og_func(_this, dest, cmdtype, idk, cmdlen);
|
||||
console.log(`CmdSndPush 0x${cmdtype.toString(16)} ret: ${ret}`);
|
||||
@@ -224,38 +235,36 @@ const dbg__ZN12CPPPPChannel10CmdSndPushEiiPci = (og_func) => {
|
||||
return CmdSndPush;
|
||||
};
|
||||
|
||||
const dbg__Z6NetCmdP7_JNIEnvPciiP8_jobject = (og_func) => {
|
||||
// "pointer", "pointer", "uint32", "uint32", "pointer"
|
||||
const NetCmd = (java_class, p2pid, sit, cmd, java_param) => {
|
||||
console.log("NetCmd", java_class, p2pid.readCString(), sit, cmd.toString(16), java_param);
|
||||
// if (cmd == 0x0000) return 0;
|
||||
let ret = og_func(java_class, p2pid, sit, cmd, java_param);
|
||||
console.log(`NetCmd 0x${cmd.toString(16)} ret: ${ret}`);
|
||||
return ret;
|
||||
};
|
||||
return NetCmd;
|
||||
};
|
||||
|
||||
const dbg__Z9SystemCmdP7_JNIEnvPciiP8_jobject = (og_func) => {
|
||||
// "pointer", "pointer", "uint32", "uint32", "pointer"
|
||||
const AvCmd = (java_class, p2pid, sit, cmd, java_param) => {
|
||||
const SystemCmd = (java_class, p2pid, sit, cmd, java_param) => {
|
||||
console.log("SystemCmd", java_class, p2pid.readCString(), sit, cmd.toString(16), java_param);
|
||||
if (cmd == 0x3018) return 20; // mask
|
||||
if (cmd == 0x3019) return 12; // mask
|
||||
if (cmd == 0x3005) return 12; // mask
|
||||
if (cmd == 0x3026) return 0; // mask
|
||||
if (cmd == 0x3001) return 12;
|
||||
if (cmd == 0x3003) return 12;
|
||||
|
||||
//if (cmd == 0x3011) return 272; // this is STOP !!
|
||||
//if (cmd == 0x3010) return 272; // borks
|
||||
let ret = og_func(java_class, p2pid, sit, cmd, java_param);
|
||||
console.log(`SystemCmd 0x${cmd.toString(16)} ret: ${ret}`);
|
||||
return ret;
|
||||
};
|
||||
return AvCmd;
|
||||
return SystemCmd;
|
||||
};
|
||||
const dbg__Z5AvCmdP7_JNIEnvPciiP8_jobject = (og_func) => {
|
||||
// "pointer", "pointer", "uint32", "uint32", "pointer"
|
||||
const AvCmd = (java_class, p2pid, sit, cmd, java_param) => {
|
||||
console.log("AvCmd", java_class, p2pid.readCString(), sit, cmd.toString(16), java_param);
|
||||
if (cmd == 0x3018) return 20; // mask
|
||||
if (cmd == 0x3019) return 12; // mask
|
||||
if (cmd == 0x3005) return 12; // mask
|
||||
if (cmd == 0x3026) return 0; // mask
|
||||
if (cmd == 0x3001) return 12;
|
||||
if (cmd == 0x3003) return 12;
|
||||
|
||||
//if (cmd == 0x3011) return 272; // this is STOP !!
|
||||
//if (cmd == 0x3010) return 272; // borks
|
||||
// if (cmd == 0x3011) return 272; // this is STOP !!
|
||||
// if (cmd == 0x3010) return 272; // borks
|
||||
let ret = og_func(java_class, p2pid, sit, cmd, java_param);
|
||||
console.log(`AvCmd 0x${cmd.toString(16)} ret: ${ret}`);
|
||||
return ret;
|
||||
@@ -284,10 +293,10 @@ const dbg_pack_ClntPkt = (og_func) => {
|
||||
},
|
||||
};
|
||||
/*
|
||||
P2PAlive: 0xf1e0,
|
||||
P2PAliveAck: 0xf1e1,
|
||||
P2pRdy: 0xf142, // idk??
|
||||
*/
|
||||
P2PAlive: 0xf1e0,
|
||||
P2PAliveAck: 0xf1e1,
|
||||
P2pRdy: 0xf142, // idk??
|
||||
*/
|
||||
|
||||
const fn = packFn[cmd];
|
||||
if (fn == undefined) {
|
||||
@@ -378,6 +387,7 @@ const replace_func = (stub, ret, args) => {
|
||||
// CSession_CtrlPkt_Proc(struct, *cmd) == control?
|
||||
export const replaceFunctions = () => {
|
||||
const replacements = [
|
||||
/*
|
||||
[create_P2pAlive, "uint8", ["pointer"]],
|
||||
[create_P2pAliveAck, "uint8", ["pointer"]],
|
||||
[create_LanSearch, "uint8", ["pointer"]],
|
||||
@@ -397,6 +407,8 @@ export const replaceFunctions = () => {
|
||||
[dbg_pack_ClntPkt, "uint32", ["uint32", "pointer", "pointer"]],
|
||||
[dbg__Z5AvCmdP7_JNIEnvPciiP8_jobject, "uint32", ["pointer", "pointer", "uint32", "uint32", "pointer"]],
|
||||
[dbg__Z9SystemCmdP7_JNIEnvPciiP8_jobject, "uint32", ["pointer", "pointer", "uint32", "uint32", "pointer"]],
|
||||
*/
|
||||
[dbg__Z6NetCmdP7_JNIEnvPciiP8_jobject, "uint32", ["pointer", "pointer", "uint32", "uint32", "pointer"]],
|
||||
[dbg__ZN12CPPPPChannel10CmdSndPushEiiPci, "uint64", ["pointer", "uint32", "uint32", "pointer", "uint32"]],
|
||||
];
|
||||
|
||||
|
||||
+158
-36
@@ -1,10 +1,9 @@
|
||||
import { Commands, CommandsByValue, ControlCommands } from "./datatypes.js";
|
||||
import { create_P2pRdy, SendStartVideo, SendUsrChk } from "./impl.js";
|
||||
import { Session } from "./server.js";
|
||||
import { u16_swap } from "./utils.js";
|
||||
import { XqBytesDec } from "./func_replacements.js";
|
||||
import { hexdump } from "./hexdump.js";
|
||||
|
||||
let curImage = null;
|
||||
import { create_P2pRdy, SendListWifi, SendUsrChk, DevSerial } from "./impl.js";
|
||||
import { Session } from "./session.js";
|
||||
import { u16_swap, u32_swap } from "./utils.js";
|
||||
|
||||
export const notImpl = (_: Session, dv: DataView) => {
|
||||
const raw = dv.readU16();
|
||||
@@ -24,35 +23,103 @@ export const handle_P2PAlive = (session: Session, _: DataView) => {
|
||||
const b = create_P2pAliveAck();
|
||||
session.send(b);
|
||||
};
|
||||
export const handle_PunchPkt = (session: Session, dv: DataView) => {
|
||||
const punchCmd = dv.readU16();
|
||||
const len = dv.add(2).readU16();
|
||||
const prefix = dv.add(4).readString(4);
|
||||
const serial = dv.add(8).readU64().toString();
|
||||
const suffix = dv.add(16).readString(4);
|
||||
// f141 20 BATC 609531 EXLV
|
||||
session.eventEmitter.emit("connect", prefix.toString() + serial + suffix.toString());
|
||||
session.send(create_P2pRdy(dv.add(4).readByteArray(len)));
|
||||
|
||||
export const handle_P2PRdy = (session: Session, _: DataView) => {
|
||||
const b = SendUsrChk(session, "admin", "admin");
|
||||
session.send(b);
|
||||
};
|
||||
|
||||
export const createResponseForControlCommand = (session: Session, dv: DataView): DataView | null => {
|
||||
export const makePunchPkt = (dev: DevSerial): DataView => {
|
||||
const len = dev.prefix.length + dev.suffix.length + 8;
|
||||
const outbuf = new DataView(new Uint8Array(0x14).buffer); // 8 = serial u64
|
||||
outbuf.add(0).writeString(dev.prefix);
|
||||
outbuf.add(4).writeU64(dev.serialU64);
|
||||
outbuf.add(8 + dev.prefix.length).writeString(dev.suffix);
|
||||
return create_P2pRdy(outbuf);
|
||||
};
|
||||
|
||||
export const createResponseForControlCommand = (session: Session, dv: DataView): DataView[] => {
|
||||
const start_type = dv.add(8).readU16(); // 0xa11 on control; data starts here on DATA pkt
|
||||
const cmd_id = dv.add(10).readU16(); // 0x1120
|
||||
const payload_len = u16_swap(dv.add(0xc).readU16());
|
||||
|
||||
if (start_type != 0x110a) {
|
||||
console.error(`Expected start_type to be 0xa11, got 0x${start_type.toString(16)}`);
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
const rotate_chr = 4;
|
||||
if (payload_len > rotate_chr) {
|
||||
// 20 = 16 (header) + 4 (??)
|
||||
XqBytesDec(dv.add(20), payload_len - 4, rotate_chr);
|
||||
console.log("Decrypted");
|
||||
console.log(hexdump(dv));
|
||||
}
|
||||
|
||||
if (cmd_id == ControlCommands.ConnectUserAck) {
|
||||
let c = new Uint8Array(dv.add(0x14).readByteArray(4).buffer);
|
||||
session.ticket[0] = c[0] % 2 == 0 ? c[0] + 1 : c[0] - 1;
|
||||
session.ticket[1] = c[1] % 2 == 0 ? c[1] + 1 : c[1] - 1;
|
||||
session.ticket[2] = c[2] % 2 == 0 ? c[2] + 1 : c[2] - 1;
|
||||
session.ticket[3] = c[3] % 2 == 0 ? c[3] + 1 : c[3] - 1;
|
||||
const buf = SendStartVideo(session);
|
||||
return buf;
|
||||
let c = new Uint8Array(dv.add(0x18).readByteArray(4).buffer);
|
||||
session.ticket = [...c];
|
||||
session.eventEmitter.emit("login");
|
||||
return [];
|
||||
}
|
||||
|
||||
if (cmd_id == ControlCommands.DevStatusAck) {
|
||||
// ParseDevStatus -> offset relevant?
|
||||
let charging = u32_swap(dv.add(0x28).readU32()) & 1; // 0x14000101 v 0x14000100
|
||||
let power = u16_swap(dv.add(0x18).readU16()); // '3730' or '3765', milliVolts?
|
||||
let dbm = dv.add(0x24).readU8() - 0x100; // 0xbf - 0x100 = -65dbm .. constant??
|
||||
// > -50 = excellent, -50 to -60 good, -60 to -70 fair, <-70 weak
|
||||
|
||||
console.log(`charging? ${charging}, batlevel? ${power}, wifi dbm: ${dbm}`);
|
||||
}
|
||||
|
||||
if (cmd_id == ControlCommands.WifiSettingsAck) {
|
||||
const wifiSettings = {
|
||||
enable: dv.add(0x14).readU32(),
|
||||
status: dv.add(0x18).readU32(),
|
||||
mode: dv.add(0x1c).readU32LE(),
|
||||
channel: dv.add(0x20).readU32(),
|
||||
authtype: dv.add(0x24).readU32(),
|
||||
dhcp: dv.add(0x28).readU32(),
|
||||
ssid: dv.add(0x2c).readString(0x20),
|
||||
psk: dv.add(0x4c).readString(0x80),
|
||||
ip: dv.add(0xcc).readString(0x10),
|
||||
mask: dv.add(0xdc).readString(0x10),
|
||||
gw: dv.add(0xec).readString(0x10),
|
||||
dns1: dv.add(0xfc).readString(0x10),
|
||||
dns2: dv.add(0x10c).readString(0x10),
|
||||
};
|
||||
const buf = SendListWifi(session);
|
||||
console.log(`Current wifi settings: ${JSON.stringify(wifiSettings, null, 2)}`);
|
||||
return [buf];
|
||||
}
|
||||
|
||||
if (cmd_id == ControlCommands.ListWifiAck) {
|
||||
let startat = 0x10;
|
||||
let msg_len = 91;
|
||||
console.log("payload len", payload_len);
|
||||
let msg_count = (payload_len - 9) / msg_len;
|
||||
let remote_msg_count = dv.add(startat).readU32LE();
|
||||
console.log("should get messages:", msg_count, "in payload: ", remote_msg_count);
|
||||
startat += 4;
|
||||
let items = [];
|
||||
for (let i = 0; i < msg_count; i++) {
|
||||
const wifiListItem = {
|
||||
// startat = msg_len * i + 0x14;
|
||||
ssid: dv.add(startat).readString(0x40),
|
||||
mac: dv.add(startat + 0x40).readByteArray(8),
|
||||
security: dv.add(startat + 0x48).readU32LE(),
|
||||
dbm0: dv.add(startat + 0x4c).readU32LE(),
|
||||
dbm1: dv.add(startat + 0x50).readU32LE(),
|
||||
mode: dv.add(startat + 0x54).readU32LE(),
|
||||
channel: dv.add(startat + 0x58).readU32LE(),
|
||||
};
|
||||
console.log(`Wifi Item: ${JSON.stringify(wifiListItem, null, 2)}`);
|
||||
startat += msg_len;
|
||||
console.log("ended at", startat);
|
||||
items.push(wifiListItem);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const deal_with_data = (session: Session, dv: DataView) => {
|
||||
@@ -63,6 +130,7 @@ const deal_with_data = (session: Session, dv: DataView) => {
|
||||
const m_hdr = dv.add(8).readByteArray(4);
|
||||
let is_new_image = true;
|
||||
let audio = true;
|
||||
const pkt_id = dv.add(6).readU16();
|
||||
for (let i = 0; i < 4; i++) {
|
||||
is_new_image = is_new_image && m_hdr.add(i).readU8() == JPEG_HEADER[i];
|
||||
audio = audio && m_hdr.add(i).readU8() == AUDIO_HEADER[i];
|
||||
@@ -72,24 +140,85 @@ const deal_with_data = (session: Session, dv: DataView) => {
|
||||
// "stream_head_t->type == 0x06" per pdf
|
||||
if (dv.add(12).readU8() == 0x06) {
|
||||
const audio_len = u16_swap(dv.add(8 + 16).readU16());
|
||||
// may have received the next 'data' packet id as an audio frame -- jpeg was fine
|
||||
if (pkt_id == session.rcvSeqId + 1) {
|
||||
session.rcvSeqId = session.rcvSeqId + 1;
|
||||
}
|
||||
const audio_buf = dv.add(32 + 8).readByteArray(audio_len).buffer; // 8 for pkt header, 32 for `stream_head_t`
|
||||
session.eventEmitter.emit("audio", Buffer.from(audio_buf));
|
||||
session.eventEmitter.emit("audio", { gap: false, data: Buffer.from(audio_buf) });
|
||||
} else {
|
||||
// not sure what these are for, there's one per frame. maybe alignment?
|
||||
}
|
||||
} else {
|
||||
const data = dv.add(8).readByteArray(pkt_len - 4);
|
||||
if (is_new_image) {
|
||||
if (curImage != null) {
|
||||
session.eventEmitter.emit("frame", curImage);
|
||||
if (session.curImage.length > 0 && !session.frame_is_bad) {
|
||||
session.eventEmitter.emit("frame");
|
||||
}
|
||||
curImage = Buffer.from(data.buffer);
|
||||
|
||||
session.frame_was_fixed = false;
|
||||
session.frame_is_bad = false;
|
||||
session.curImage = [Buffer.from(data.buffer)];
|
||||
session.rcvSeqId = pkt_id;
|
||||
} else {
|
||||
curImage = Buffer.concat([curImage, Buffer.from(data.buffer)]);
|
||||
if (pkt_id <= session.rcvSeqId) {
|
||||
// retransmit
|
||||
return;
|
||||
}
|
||||
|
||||
let b = Buffer.from(data.buffer);
|
||||
|
||||
if (pkt_id > session.rcvSeqId + 1) {
|
||||
session.frame_is_bad = true;
|
||||
// this should always be enabled but currently it seems to cause more visual distortion
|
||||
// than just missing some frames
|
||||
if (!session.options.attempt_to_fix_packet_loss) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.curImage.length == 1) return; // header does not have markers
|
||||
|
||||
let lastFrameSlice = session.curImage[session.curImage.length - 1];
|
||||
const lastResetMarker = findAllResetMarkers(lastFrameSlice).pop();
|
||||
if (lastResetMarker == undefined) {
|
||||
// not storing rcvSeqId as this frame did not put us back in track
|
||||
return;
|
||||
}
|
||||
|
||||
const firstResetMarker = findAllResetMarkers(b).shift();
|
||||
if (firstResetMarker == undefined) {
|
||||
// not storing rcvSeqId as this frame did not put us back in track
|
||||
return;
|
||||
}
|
||||
|
||||
session.curImage[session.curImage.length - 1] = Buffer.from(lastFrameSlice.subarray(0, lastResetMarker));
|
||||
b = Buffer.from(b.subarray(firstResetMarker));
|
||||
session.frame_is_bad = false;
|
||||
session.frame_was_fixed = true;
|
||||
}
|
||||
|
||||
session.rcvSeqId = pkt_id;
|
||||
if (session.curImage != null) {
|
||||
session.curImage.push(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const findAllResetMarkers = (b: Buffer): number[] => {
|
||||
// a reset marker is a byte 0xff followed by a byte 0xd0-0xd7
|
||||
let ret = [];
|
||||
for (let i = 0; i < b.length - 1; i++) {
|
||||
if (b[i] == 0xff) {
|
||||
const nb = b[i + 1];
|
||||
if (nb >= 0xd0 && nb <= 0xd7) {
|
||||
ret.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const makeDrwAck = (dv: DataView): DataView => {
|
||||
const pkt_id = dv.add(6).readU16();
|
||||
const m_stream = dv.add(5).readU8(); // data = 1, control = 0
|
||||
@@ -115,13 +244,6 @@ export const handle_Drw = (session: Session, dv: DataView) => {
|
||||
deal_with_data(session, dv);
|
||||
} else {
|
||||
const b = createResponseForControlCommand(session, dv);
|
||||
if (b != null) {
|
||||
session.send(b);
|
||||
}
|
||||
b.forEach(session.send);
|
||||
}
|
||||
};
|
||||
|
||||
export const handle_P2PRdy = (session: Session, _: DataView) => {
|
||||
const b = SendUsrChk("admin", "admin", session.outgoingCommandId);
|
||||
session.send(b);
|
||||
};
|
||||
|
||||
+5
-2
@@ -1,5 +1,5 @@
|
||||
// hacked hexdump from frida to work on normal ArrayBuffers
|
||||
import "./shim.ts";
|
||||
import "./shim.js";
|
||||
export const hexdump = (target, options) => {
|
||||
options = options || {};
|
||||
|
||||
@@ -10,12 +10,15 @@ export const hexdump = (target, options) => {
|
||||
const ansiColor = options.hasOwnProperty("ansiColor") ? options.ansiColor : 0;
|
||||
|
||||
let buffer;
|
||||
if (target instanceof DataView) {
|
||||
target = target.buffer;
|
||||
}
|
||||
if (target instanceof ArrayBuffer) {
|
||||
if (length === undefined) length = target.byteLength;
|
||||
else length = Math.min(length, target.byteLength);
|
||||
buffer = target;
|
||||
} else {
|
||||
throw "Gimme array buffer";
|
||||
throw "Gimme array buffer or DataView";
|
||||
}
|
||||
|
||||
const startAddress = 0;
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import { RemoteInfo } from "dgram";
|
||||
import { readFileSync } from "node:fs";
|
||||
import http from "node:http";
|
||||
|
||||
import { opt } from "./options.js";
|
||||
import { discoverDevices } from "./discovery.js";
|
||||
import { DevSerial } from "./impl.js";
|
||||
import { Handlers, makeSession, Session, startVideoStream } from "./session.js";
|
||||
|
||||
let BOUNDARY = "a very good boundary line";
|
||||
let responses: Record<string, http.ServerResponse[]> = {};
|
||||
let audioResponses: Record<string, http.ServerResponse[]> = {};
|
||||
let sessions: Record<string, Session> = {};
|
||||
|
||||
export const serveHttp = (opts: opt, port: number, with_audio: boolean) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.url.startsWith("/ui/")) {
|
||||
let devId = req.url.split("/")[2];
|
||||
let s = sessions[devId];
|
||||
if (s === undefined) {
|
||||
res.writeHead(400);
|
||||
res.end("invalid ID");
|
||||
return;
|
||||
}
|
||||
if (!s.connected) {
|
||||
res.writeHead(400);
|
||||
res.end("Nothing online");
|
||||
return;
|
||||
}
|
||||
const ui = readFileSync("asd.html").toString();
|
||||
res.end(ui.replace(/\${id}/g, devId));
|
||||
return;
|
||||
}
|
||||
if (req.url.startsWith("/audio/")) {
|
||||
let devId = req.url.split("/")[2];
|
||||
let s = sessions[devId];
|
||||
if (s === undefined) {
|
||||
res.writeHead(400);
|
||||
res.end("invalid ID");
|
||||
return;
|
||||
}
|
||||
if (!s.connected) {
|
||||
res.writeHead(400);
|
||||
res.end("Nothing online");
|
||||
return;
|
||||
}
|
||||
res.setHeader("Content-Type", `text/event-stream`);
|
||||
audioResponses[devId].push(res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url.startsWith("/camera/")) {
|
||||
let devId = req.url.split("/")[2];
|
||||
console.log("requested for", devId);
|
||||
let s = sessions[devId];
|
||||
|
||||
if (s === undefined) {
|
||||
res.writeHead(400);
|
||||
res.end("invalid ID");
|
||||
return;
|
||||
}
|
||||
if (!s.connected) {
|
||||
res.writeHead(400);
|
||||
res.end("Nothing online");
|
||||
return;
|
||||
}
|
||||
|
||||
res.setHeader("Content-Type", `multipart/x-mixed-replace; boundary="${BOUNDARY}"`);
|
||||
responses[devId].push(res);
|
||||
res.on("close", () => {
|
||||
responses[devId] = responses[devId].filter((r) => r !== res);
|
||||
console.log("Conn closed, kicked");
|
||||
});
|
||||
} else {
|
||||
res.write(`<html>`);
|
||||
Object.keys(sessions).forEach((id) =>
|
||||
res.write(`<h2>${id}</h2><a href="/ui/${id}"><img src="/camera/${id}"/></a><hr/>`),
|
||||
);
|
||||
res.write(`</html>`);
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
|
||||
let devEv = discoverDevices(opts.debug, opts.discovery_ip);
|
||||
devEv.on("discover", (rinfo: RemoteInfo, dev: DevSerial) => {
|
||||
if (dev.devId in sessions) {
|
||||
console.log(`ignoring ${dev.devId} - ${rinfo.address}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`discovered ${dev.devId} - ${rinfo.address}`);
|
||||
responses[dev.devId] = [];
|
||||
audioResponses[dev.devId] = [];
|
||||
const s = makeSession(Handlers, dev, rinfo, startVideoStream, opts);
|
||||
|
||||
const header = Buffer.from(`--${BOUNDARY}\r\nContent-Type: image/jpeg\r\n\r\n`);
|
||||
|
||||
s.eventEmitter.on("frame", () => {
|
||||
const assembled = Buffer.concat(s.curImage);
|
||||
|
||||
responses[dev.devId].forEach((res) => {
|
||||
res.write(header);
|
||||
res.write(assembled);
|
||||
});
|
||||
});
|
||||
|
||||
s.eventEmitter.on("disconnect", () => {
|
||||
console.log("deleting from sessions");
|
||||
delete sessions[dev.devId];
|
||||
});
|
||||
if (with_audio) {
|
||||
s.eventEmitter.on("audio", ({ gap, data }) => {
|
||||
// ew, maybe WS?
|
||||
var b64encoded = Buffer.from(data).toString("base64");
|
||||
audioResponses[dev.devId].forEach((res) => {
|
||||
res.write("data: ");
|
||||
res.write(b64encoded);
|
||||
res.write("\n\n");
|
||||
});
|
||||
});
|
||||
}
|
||||
sessions[dev.devId] = s;
|
||||
});
|
||||
|
||||
console.log(`Starting HTTP server on port ${port}`);
|
||||
server.listen(port);
|
||||
};
|
||||
@@ -1,93 +1,165 @@
|
||||
import "./shim.ts";
|
||||
import "./shim.js";
|
||||
|
||||
import { Commands } from "./datatypes.js";
|
||||
import { Session } from "./server.js";
|
||||
import { ccDest, Commands, ControlCommands } from "./datatypes.js";
|
||||
import { XqBytesEnc } from "./func_replacements.js";
|
||||
import { hexdump } from "./hexdump.js";
|
||||
import { Session } from "./session.js";
|
||||
import { u16_swap } from "./utils.js";
|
||||
|
||||
const str2byte = (s: string): number[] => {
|
||||
return Array.from(s).map((_, i) => s.charCodeAt(i));
|
||||
};
|
||||
|
||||
const CmdSndProcHdr = (start: number, cmd: number, len: number, dest: number): DataView => {
|
||||
len = len + 4; // hdr size?
|
||||
let cmdHeader = new DataView(new Uint8Array(8).buffer);
|
||||
cmdHeader.writeU16(u16_swap(start));
|
||||
cmdHeader.add(2).writeU16(u16_swap(cmd));
|
||||
cmdHeader.add(4).writeU16(u16_swap(len));
|
||||
cmdHeader.add(6).writeU16(u16_swap(dest));
|
||||
return cmdHeader;
|
||||
const makeDataReadWrite = (session: Session, command: number, data: DataView | null): DataView => {
|
||||
const DRW_HEADER_LEN = 0x10;
|
||||
const TOKEN_LEN = 0x4;
|
||||
const CHANNEL = 0;
|
||||
const START_CMD = 0x110a;
|
||||
|
||||
let pkt_len = DRW_HEADER_LEN + TOKEN_LEN;
|
||||
let payload_len = TOKEN_LEN;
|
||||
let bufCopy: Uint8Array | null = null;
|
||||
if (data && data.byteLength > 4) {
|
||||
bufCopy = new Uint8Array(data.buffer);
|
||||
const bufDV = new DataView(bufCopy.buffer);
|
||||
// this mutates the buffer, don't want to mutate the caller
|
||||
XqBytesEnc(bufDV, bufDV.byteLength, 4);
|
||||
pkt_len += bufDV.byteLength;
|
||||
payload_len += bufDV.byteLength;
|
||||
}
|
||||
|
||||
const ret = new DataView(new Uint8Array(pkt_len).buffer);
|
||||
ret.add(0).writeU16(Commands.Drw);
|
||||
ret.add(2).writeU16(pkt_len - 4); // -4 as we ignore the [0xf1, 0xd0, len, len]
|
||||
ret.add(4).writeU8(0xd1); // ?
|
||||
ret.add(5).writeU8(CHANNEL);
|
||||
ret.add(6).writeU16(session.outgoingCommandId);
|
||||
ret.add(8).writeU16(START_CMD);
|
||||
ret.add(10).writeU16(command);
|
||||
ret.add(12).writeU16(u16_swap(payload_len));
|
||||
ret.add(14).writeU16(ccDest[command]);
|
||||
ret.add(16).writeByteArray(session.ticket);
|
||||
if (data && data.byteLength > 4) {
|
||||
ret.add(20).writeByteArray(bufCopy);
|
||||
}
|
||||
|
||||
session.outgoingCommandId++;
|
||||
return ret;
|
||||
};
|
||||
|
||||
const DrwHdr = (cmd: number, len: number, d1_or_d2: 0xd1 | 0xd2, m_chan: number, pkt_id: number): DataView => {
|
||||
let retret = new DataView(new Uint8Array(len + 4).buffer);
|
||||
retret.writeU16(cmd);
|
||||
retret.add(2).writeU16(len); // buflen -4?
|
||||
retret.add(4).writeU8(d1_or_d2);
|
||||
retret.add(5).writeU8(m_chan); // chan? hardcoded
|
||||
retret.add(6).writeU16(pkt_id);
|
||||
return retret;
|
||||
export const SendIRToggle = (session: Session): DataView => {
|
||||
return makeDataReadWrite(session, ControlCommands.IRToggle, null);
|
||||
};
|
||||
|
||||
export const SendDevStatus = (session: Session): DataView => {
|
||||
return makeDataReadWrite(session, ControlCommands.DevStatus, null);
|
||||
};
|
||||
|
||||
export const SendWifiSettings = (session: Session): DataView => {
|
||||
return makeDataReadWrite(session, ControlCommands.WifiSettings, null);
|
||||
};
|
||||
|
||||
export const SendListWifi = (session: Session): DataView => {
|
||||
return makeDataReadWrite(session, ControlCommands.ListWifi, null);
|
||||
};
|
||||
|
||||
export const SendStopVideo = (session: Session): DataView => {
|
||||
return makeDataReadWrite(session, ControlCommands.StopVideo, null);
|
||||
};
|
||||
|
||||
export const SendStartVideo = (session: Session): DataView => {
|
||||
// TODO: extract SendUsrChk
|
||||
let buf = new DataView(new Uint8Array(0x18).buffer);
|
||||
// console.log(hexdump(DrwHdr(0xf1d0, 0x0114, 0xd1, 0, pkt_id).buffer));
|
||||
|
||||
let bytes = [
|
||||
0xf1,
|
||||
0xd0,
|
||||
0x01, // len? lower values= no response, larger values = 1 frame then kicked
|
||||
0x14, // len
|
||||
0xd1, // ?
|
||||
0x00, // chan
|
||||
session.outgoingCommandId >> 8,
|
||||
session.outgoingCommandId,
|
||||
0x11,
|
||||
0x0a,
|
||||
0x10,
|
||||
0x30,
|
||||
0x08,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
session.ticket[0],
|
||||
session.ticket[1],
|
||||
session.ticket[2],
|
||||
session.ticket[3],
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
];
|
||||
buf.writeByteArray(bytes);
|
||||
return buf;
|
||||
return makeDataReadWrite(session, ControlCommands.StartVideo, null);
|
||||
};
|
||||
export const SendUsrChk = (username: string, password: string, pkt_id: number): DataView => {
|
||||
// type is char account[0x20]; char password[0x80];
|
||||
|
||||
export const getVideoKey = (session: Session): void => {
|
||||
// this is not useful at all
|
||||
for (let i = 0; i < 12; i++) {
|
||||
// payload len??
|
||||
const payload = [0x0, i]; //, 0x0, 0x0, 0x0, 0x0];
|
||||
const dv = new DataView(new Uint8Array(payload).buffer);
|
||||
session.send(makeDataReadWrite(session, ControlCommands.VideoParamGet, dv));
|
||||
}
|
||||
};
|
||||
|
||||
export const SendVideoResolution = (session: Session, resol: 1 | 2 | 3 | 4): DataView[] => {
|
||||
// seems like 0x1 = resolution, and is specified by ID not by size
|
||||
// unclear what 0x2-0xf achieve - they report back as '0' always -- ignored?
|
||||
const pairs = {
|
||||
1: [
|
||||
// 320 x 240
|
||||
[0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
|
||||
//[0x7, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0],
|
||||
],
|
||||
2: [
|
||||
// 640x480
|
||||
[0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0],
|
||||
//[0x7, 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0],
|
||||
],
|
||||
3: [
|
||||
// also 640x480 on the X5 -- hwat now?
|
||||
[0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0],
|
||||
//[0x7, 0x0, 0x0, 0x0, 0x78, 0x0, 0x0, 0x0],
|
||||
],
|
||||
4: [
|
||||
// also 640x480 on the X5 -- hwat now?
|
||||
[0x1, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0],
|
||||
//[0x7, 0x0, 0x0, 0x0, 0xa0, 0x0, 0x0, 0x0],
|
||||
],
|
||||
// maybe the 0x7 = bitrate??
|
||||
};
|
||||
|
||||
return pairs[resol].map((payload: number[]) => {
|
||||
const dv = new DataView(new Uint8Array(payload).buffer);
|
||||
return makeDataReadWrite(session, ControlCommands.VideoParamSet, dv);
|
||||
});
|
||||
};
|
||||
|
||||
export const SendReboot = (session: Session): DataView => {
|
||||
let dv = null;
|
||||
return makeDataReadWrite(session, ControlCommands.Reboot, dv);
|
||||
};
|
||||
|
||||
export const SendWifiDetails = (session: Session, ssid: string, password: string, dhcp: boolean): DataView => {
|
||||
if (!dhcp) {
|
||||
throw new Error("only DHCP is supported");
|
||||
}
|
||||
let buf = new Uint8Array(0x108).fill(0);
|
||||
let cmd_payload = new DataView(buf.buffer);
|
||||
let mask_reversed = "0.255.255.255";
|
||||
// unclear which is which ))
|
||||
let m_ip = "0.0.0.0";
|
||||
let m_gw = "0.0.0.0";
|
||||
let m_dns1 = "0.0.0.0";
|
||||
let m_dns2 = "0.0.0.0";
|
||||
|
||||
cmd_payload.add(0x14).writeU8(1); // DHCP ?
|
||||
cmd_payload.add(0x18).writeByteArray(str2byte(ssid));
|
||||
cmd_payload.add(0x38).writeByteArray(str2byte(password));
|
||||
cmd_payload.add(0xb8).writeByteArray(str2byte(mask_reversed));
|
||||
cmd_payload.add(0xc8).writeByteArray(str2byte(m_ip));
|
||||
cmd_payload.add(0xd8).writeByteArray(str2byte(m_gw));
|
||||
cmd_payload.add(0xe8).writeByteArray(str2byte(m_dns1));
|
||||
cmd_payload.add(0xf8).writeByteArray(str2byte(m_dns2));
|
||||
|
||||
const ret = makeDataReadWrite(session, ControlCommands.WifiSettingsSet, cmd_payload);
|
||||
return ret;
|
||||
};
|
||||
|
||||
export const SendUsrChk = (session: Session, username: string, password: string): DataView => {
|
||||
let buf = new Uint8Array(0x20 + 0x80);
|
||||
buf.fill(0);
|
||||
let cmd_payload = new DataView(buf.buffer);
|
||||
// type is char account[0x20]; char password[0x80];
|
||||
cmd_payload.writeByteArray(str2byte(username));
|
||||
cmd_payload.add(0x20).writeByteArray(str2byte(password));
|
||||
return makeDataReadWrite(session, ControlCommands.ConnectUser, cmd_payload);
|
||||
};
|
||||
|
||||
const start = 0xa11;
|
||||
const dest = 0xff;
|
||||
const cmd = 0x1020;
|
||||
const len = buf.byteLength;
|
||||
let cmdHeader = CmdSndProcHdr(start, cmd, len, dest);
|
||||
let ret = new DataView(new Uint8Array(12 + len).buffer);
|
||||
ret.writeByteArray(new Uint8Array(cmdHeader.buffer));
|
||||
XqBytesEnc(cmd_payload, 0x20 + 0x80, 4);
|
||||
ret.add(12).writeByteArray(new Uint8Array(cmd_payload.buffer));
|
||||
|
||||
// need to encapsulate this into create_Drw(outbuf, 0xd1, param4?, svar1?,
|
||||
// copy_len, inbuf); seems like param4/svar1 are overflowing == maybe '0xa'
|
||||
// and '0x2010'??
|
||||
let retret = DrwHdr(0xf1d0, 8 + 12 + len - 4, 0xd1, 0, pkt_id);
|
||||
retret.add(8).writeByteArray(new Uint8Array(ret.buffer));
|
||||
return retret;
|
||||
export const create_LanSearch = (): DataView => {
|
||||
const outbuf = new DataView(new Uint8Array(4).buffer);
|
||||
outbuf.writeU16(Commands.LanSearch);
|
||||
outbuf.add(2).writeU16(0x0);
|
||||
return outbuf;
|
||||
};
|
||||
|
||||
export const create_P2pRdy = (inbuf: DataView): DataView => {
|
||||
@@ -98,3 +170,30 @@ export const create_P2pRdy = (inbuf: DataView): DataView => {
|
||||
outbuf.add(4).writeByteArray(new Uint8Array(inbuf.readByteArray(P2PRDY_SIZE).buffer));
|
||||
return outbuf;
|
||||
};
|
||||
|
||||
export const create_P2pAlive = (): DataView => {
|
||||
const outbuf = new DataView(new Uint8Array(4).buffer);
|
||||
outbuf.writeU16(Commands.P2PAlive);
|
||||
outbuf.add(2).writeU16(0);
|
||||
return outbuf;
|
||||
};
|
||||
|
||||
export const create_P2pClose = (): DataView => {
|
||||
const outbuf = new DataView(new Uint8Array(4).buffer);
|
||||
outbuf.writeU16(Commands.Close);
|
||||
outbuf.add(2).writeU16(0);
|
||||
return outbuf;
|
||||
};
|
||||
|
||||
export type DevSerial = { prefix: string; serial: string; suffix: string; serialU64: bigint; devId: string };
|
||||
export const parse_PunchPkt = (dv: DataView): DevSerial => {
|
||||
const punchCmd = dv.readU16();
|
||||
const len = dv.add(2).readU16();
|
||||
const prefix = dv.add(4).readString(4);
|
||||
const serialU64 = dv.add(8).readU64();
|
||||
const serial = serialU64.toString();
|
||||
const suffix = dv.add(16).readString(len - 16 + 4); // 16 = offset, +4 header
|
||||
const devId = prefix + serial + suffix;
|
||||
|
||||
return { prefix, serial, suffix, serialU64, devId };
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export type opt = {
|
||||
debug: boolean;
|
||||
ansi: boolean;
|
||||
discovery_ip: string;
|
||||
attempt_to_fix_packet_loss: boolean;
|
||||
};
|
||||
Generated
+453
-67
@@ -5,9 +5,11 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"beamcoder": "^0.7.1"
|
||||
"esbuild": "^0.20.2",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/yargs": "^17.0.32",
|
||||
"mocha": "^10.2.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
@@ -25,6 +27,351 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
|
||||
@@ -84,6 +431,21 @@
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.32",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
|
||||
"integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/yargs-parser": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yargs-parser": {
|
||||
"version": "21.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
|
||||
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
@@ -118,7 +480,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -127,7 +488,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
@@ -169,16 +529,6 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/beamcoder": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/beamcoder/-/beamcoder-0.7.1.tgz",
|
||||
"integrity": "sha512-20gpjy6V+9+X3v1H5wfofzc+MbVCPXv3JE4cm9y5R28pW2LRz2LSlJptiUxZJI8ips4xBoq7oYh+1LE5HvZskQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"segfault-handler": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
@@ -188,14 +538,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
@@ -291,21 +633,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||
"dev": true,
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
@@ -316,8 +659,7 @@
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
@@ -378,14 +720,49 @@
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.20.2",
|
||||
"@esbuild/android-arm": "0.20.2",
|
||||
"@esbuild/android-arm64": "0.20.2",
|
||||
"@esbuild/android-x64": "0.20.2",
|
||||
"@esbuild/darwin-arm64": "0.20.2",
|
||||
"@esbuild/darwin-x64": "0.20.2",
|
||||
"@esbuild/freebsd-arm64": "0.20.2",
|
||||
"@esbuild/freebsd-x64": "0.20.2",
|
||||
"@esbuild/linux-arm": "0.20.2",
|
||||
"@esbuild/linux-arm64": "0.20.2",
|
||||
"@esbuild/linux-ia32": "0.20.2",
|
||||
"@esbuild/linux-loong64": "0.20.2",
|
||||
"@esbuild/linux-mips64el": "0.20.2",
|
||||
"@esbuild/linux-ppc64": "0.20.2",
|
||||
"@esbuild/linux-riscv64": "0.20.2",
|
||||
"@esbuild/linux-s390x": "0.20.2",
|
||||
"@esbuild/linux-x64": "0.20.2",
|
||||
"@esbuild/netbsd-x64": "0.20.2",
|
||||
"@esbuild/openbsd-x64": "0.20.2",
|
||||
"@esbuild/sunos-x64": "0.20.2",
|
||||
"@esbuild/win32-arm64": "0.20.2",
|
||||
"@esbuild/win32-ia32": "0.20.2",
|
||||
"@esbuild/win32-x64": "0.20.2"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -402,11 +779,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@@ -468,7 +840,6 @@
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
@@ -586,7 +957,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -734,17 +1104,41 @@
|
||||
"url": "https://opencollective.com/mochajs"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/cliui": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.0",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.18.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
|
||||
"integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w=="
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
|
||||
@@ -860,7 +1254,6 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -885,16 +1278,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/segfault-handler": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/segfault-handler/-/segfault-handler-1.3.0.tgz",
|
||||
"integrity": "sha512-p7kVHo+4uoYkr0jmIiTBthwV5L2qmWtben/KDunDZ834mbos+tY+iO0//HpAJpOFSQZZ+wxKWuRo4DxV02B7Lg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.2.1",
|
||||
"nan": "^2.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
|
||||
@@ -908,7 +1291,6 @@
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
@@ -922,7 +1304,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
@@ -1057,7 +1438,6 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
@@ -1080,27 +1460,25 @@
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"dev": true,
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"dependencies": {
|
||||
"cliui": "^7.0.2",
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.0",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
@@ -1127,6 +1505,14 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
||||
+6
-2
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "mocha tests"
|
||||
"test": "mocha tests",
|
||||
"tsc": "tsc",
|
||||
"build": "esbuild cmd/bin.ts --bundle --platform=node --outfile=dist/bin.cjs --target=node12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/yargs": "^17.0.32",
|
||||
"mocha": "^10.2.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"esbuild": "^0.20.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"beamcoder": "^0.7.1"
|
||||
"yargs": "^17.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { RemoteInfo } from "dgram";
|
||||
|
||||
import { opt } from "./options.js";
|
||||
import { discoverDevices } from "./discovery.js";
|
||||
import { DevSerial } from "./impl.js";
|
||||
import { Handlers, makeSession, Session, configureWifi } from "./session.js";
|
||||
|
||||
export const pair = ({ opts, ssid, password }: { opts: opt; ssid: string; password: string }) => {
|
||||
console.log(`Will configure any devices found to join ${ssid}`);
|
||||
let sessions: Record<string, Session> = {};
|
||||
|
||||
let devEv = discoverDevices(opts.debug, opts.discovery_ip);
|
||||
if (password == "") {
|
||||
throw new Error("You must set a non-zero-length password");
|
||||
}
|
||||
|
||||
const onLogin = configureWifi(ssid, password);
|
||||
|
||||
devEv.on("discover", (rinfo: RemoteInfo, dev: DevSerial) => {
|
||||
if (dev.devId in sessions) {
|
||||
console.log(`ignoring ${dev.devId} - ${rinfo.address}`);
|
||||
return;
|
||||
}
|
||||
console.log(`discovered ${dev.devId} - ${rinfo.address}`);
|
||||
const s = makeSession(Handlers, dev, rinfo, onLogin, opts);
|
||||
|
||||
s.eventEmitter.on("disconnect", () => {
|
||||
console.log("deleting from sessions");
|
||||
delete sessions[dev.devId];
|
||||
});
|
||||
sessions[dev.devId] = s;
|
||||
});
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 247 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 939 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
@@ -273,3 +273,66 @@ AvcLIB = src/object_jni.cpp, line 653, SystemCmd:[p2pID=BATC609531EXLVS]SystemCm
|
||||
AvcLIB = src/object_jni.cpp, line 653, SystemCmd:[p2pID=BATC609531EXLVS]SystemCmd=0x1040
|
||||
SystemCmd 0x1008 ret: 12
|
||||
SystemCmd 0x1040 ret: 92
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
battery max = 3650 while transmitting
|
||||
battery min = 3200 (powered off)
|
||||
bat 3480 -> 3200 while transmitting via wifi = 106 seconds
|
||||
|
||||
----
|
||||
XqStrDec param1 SWPNPDPFLVAOLNSXPHSQPIEOPAIDENLXHXEHIFLKPGLRHUARSTLQEEEPSUIHPDLSPEAOICLOSQEMLPPALNIBIAERHZLKHXEJHYHUEIEHELEEEKEG => strlen'd => 112
|
||||
=> 4;139.155.68.77;119.45.114.92;162.62.63.154;3.132.215.40
|
||||
112 /2 = 56
|
||||
buf = 57
|
||||
res = 56 char + \0
|
||||
|
||||
codetable = AAABACADAEAFAGAHAIAJAKALAMANAOAPAQARASATAUAVAWAXAYAZBABBBCBDBEBFBGBHBIBJBKBLBMBNBOBPBQBRBSBTBUBVBWBXBYBZCACBCCCDCECFCGCHCICJCKCLCMCNCOCPCQCRCSCTCUCVCWCXCYCZDADBDCDDDEDFDGDHDIDJDKDLDMDNDODPDQDRDSDTDUDVDWDXDYDZEAEBECEDEEEFEGEHEIEJEKELEMENEOEPEQERESETEUEVEWEXEYEZFAFBFCFDFEFFFGFHFIFJFKFLFMFNFOFPFQFRFSFTFUFVFWFXFYFZGAGBGCGDGEGFGGGHGIGJGKGLGMGNGOGPGQGRGSGTGUGVGWGXGYGZHAHBHCHDHEHFHGHHHIHJHKHLHMHNHOHPHQHRHSHTHUHVHWHXHYHZIAIBICIDIEIFIGIHIIIJIKILIMINIOIPIQIRISITIUIVIWIXIYIZJAJBJCJDJEJFJGJHJIJJJKJLJMJNJOJPJQJRJSJTJUJVJWJXJYJZKAKBKCKDKEKFKGKHKIKJKKKLKMKNKOKPKQKRKSKTKUKVKWKXKYKZLALBLCLDLELFLGLHLILJLKLLLMLNLOLPLQLRLSLTLULVLWLXLYLZMAMBMCMDMEMFMGMHMIMJMKMLMMMNMOMPMQMRMSMTMUMVMWMXMYMZNANBNCNDNENFNGNHNINJNKNLNMNNNONPNQNRNSNTNUNVNWNXNYNZOAOBOCODOEOFOGOHOIOJOKOLOMONOOOPOQOROSOTOUOVOWOXOYOZPAPBPCPDPEPFPGPHPIPJPKPLPMPNPOPPPQPRPSPTPUPVPWPXPYPZQAQBQCQDQEQFQGQHQIQJQKQLQMQNQOQPQQQRQSQTQUQVQWQXQYQZRARBRCRDRERFRGRHRIRJRKRLRMRNRORPRQRRRSRTRURVRWRXRYRZSASBSCSDSESFSGSHSISJSKSLSMSNSOSPSQSRSSSTSUSVSWSXSYSZTATBTCTDTETFTGTHTITJTKTLTMTNTOTPTQTRTSTTTUTVTWTXTYTZUAUBUCUDUEUFUGUHUIUJUKULUMUNUOUPUQURUSUTUUUVUWUXUYUZVAVBVCVDVEVFVGVHVIVJVKVLVMVNVOVPVQVRVSVTVUVVVWVXVYVZWAWBWCWDWEWFWGWHWIWJWKWLWMWNWOWPWQWRWSWTWUWVWWWXWYWZXAXBXCXDXEXFXGXHXIXJXKXLXMXNXOXPXQXRXSXTXUXVXWXXXYXZYAYBYCYDYEYFYGYHYIYJYKYLYMYNYOYPYQYRYSYTYUYVYWYXYYYZZAZBZCZDZEZFZGZHZIZJZKZLZMZNZOZPZQZRZSZTZUZVZWZXZYZZ
|
||||
=
|
||||
for i in string.ascii_uppercase:
|
||||
for j in string.ascii_uppercase:
|
||||
print(f'{i}{j}', end='')
|
||||
|
||||
|
||||
|
||||
what is this??
|
||||
|
||||
0000 24 02 00 00 01 0a 12 00 02 1c ff ff 00 00 00 00 $...............
|
||||
0010 01 00 00 00 65 64 61 31 38 34 34 64 30 30 34 64 ....eda1844d004d
|
||||
0020 33 36 34 33 61 62 66 64 66 62 36 63 38 62 34 32 3643abfdfb6c8b42
|
||||
0030 35 36 30 33 00 00 00 00 00 00 00 00 00 00 00 00 5603............
|
||||
0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0050 00 00 00 00 01 00 00 00 09 00 00 00 78 00 00 00 ............x...
|
||||
0060 61 36 65 34 36 34 37 38 34 35 33 63 39 61 65 61 a6e46478453c9aea
|
||||
0070 63 61 35 61 66 36 32 34 00 00 00 00 00 00 00 00 ca5af624........
|
||||
0080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
00a0 31 61 64 37 35 61 65 37 32 65 30 35 64 63 66 34 1ad75ae72e05dcf4
|
||||
00b0 64 61 64 62 64 31 37 61 00 00 00 00 00 00 00 00 dadbd17a........
|
||||
00c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
00d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
00e0 42 41 54 43 36 30 39 35 38 30 48 56 44 43 53 00 BATC609580HVDCS.
|
||||
00f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0120 02 00 00 00 41 49 7a 61 53 79 42 2d 62 6f 78 4f ....AIzaSyB-boxO
|
||||
0130 47 35 6e 36 41 62 4b 4d 4c 41 4f 77 6d 6d 31 50 G5n6AbKMLAOwmm1P
|
||||
0140 5a 7a 71 50 6b 79 5a 6a 4d 77 63 00 00 00 00 00 ZzqPkyZjMwc.....
|
||||
0150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
01a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
01b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
01c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
01d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
01e0 00 00 00 00 00 00 00 00 41 49 7a 61 53 79 42 2d ........AIzaSyB-
|
||||
01f0 62 6f 78 4f 47 35 6e 36 41 62 4b 4d 4c 41 4f 77 boxOG5n6AbKMLAOw
|
||||
0200 6d 6d 31 50 5a 7a 71 50 6b 79 5a 6a 4d 77 63 00 mm1PZzqPkyZjMwc.
|
||||
0210 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
|
||||
0220 00 00 00 00 00 00 00 00 ........
|
||||
|
||||
-- this looks like google keys: AIzaSyB
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import string
|
||||
data = [
|
||||
0xf1, 0xd0, 0x01, 0x18, 0xd1, 0x00, 0x00, 0x02, 0x11, 0x0a, 0x01, 0x60, 0x0c, 0x01, 0x00, 0x00,
|
||||
0x4c, 0x31, 0x67, 0x4e, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x72, 0x6a, 0x78, 0x6f, 0x64, 0x75, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x72, 0x74, 0x71, 0x64, 0x73, 0x62, 0x73, 0x60,
|
||||
0x71, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x33, 0x34, 0x34, 0x2f, 0x33, 0x34,
|
||||
0x34, 0x2f, 0x33, 0x34, 0x34, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x2f, 0x31, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
]
|
||||
def dec(d):
|
||||
ret = []
|
||||
for b in d[0x14:]:
|
||||
if b % 2:
|
||||
ret.append(b - 1)
|
||||
else:
|
||||
ret.append(b + 1)
|
||||
ret = d[:0x14] + ret[-4:] + ret[:-4]
|
||||
return ret
|
||||
|
||||
def hexdump(dump):
|
||||
print(" " * 10, end="")
|
||||
for i in range(0, 0xF+1):
|
||||
print(f"{i:2X}", end=" ")
|
||||
|
||||
print(" ", end="")
|
||||
for i in range(0, 0xF+1):
|
||||
print(f"{i:X}", end="")
|
||||
print()
|
||||
|
||||
for i in range(0, len(dump), 0x10):
|
||||
line = dump[i:i+0x10]
|
||||
print(f"{i:08x}", end=" ")
|
||||
for b in line:
|
||||
print(f"{b:02x}", end=" ")
|
||||
|
||||
if len(line) < 16:
|
||||
print(" " * (16 - len(line)), end="")
|
||||
print(" ", end="")
|
||||
for b in line:
|
||||
rep = "."
|
||||
if chr(b) in string.digits or chr(b) in string.ascii_letters:
|
||||
rep = chr(b)
|
||||
print(rep, end="")
|
||||
|
||||
print("")
|
||||
|
||||
_dec = dec(data)
|
||||
hexdump(_dec[0x14:])
|
||||
#print([hex(n) for n in _dec])
|
||||
print("for test values, DEC")
|
||||
print("".join([f"{n:02x}" for n in _dec]))
|
||||
print("for test values, enc")
|
||||
print("".join([f"{n:02x}" for n in data]))
|
||||
@@ -0,0 +1,20 @@
|
||||
import string
|
||||
ct = []
|
||||
for i in string.ascii_uppercase:
|
||||
for j in string.ascii_uppercase:
|
||||
ct.append(f'{i}{j}')
|
||||
|
||||
insec = 'SWPNPDPFLVAOLNSXPHSQPIEOPAIDENLXHXEHIFLKPGLRHUARSTLQEEEPSUIHPDLSPEAOICLOSQEMLPPALNIBIAERHZLKHXEJHYHUEIEHELEEEKEG'
|
||||
insec = 'PFLXLSTBLKHYLPLRHUEHIEEGEEARLQPLIHIAEKAOSTLVEOSQPDHZLNPAICIFEREJLKEMENHUHXIBEPEEEHEIEL'
|
||||
insec = 'EKTDROREHXHURHRKRMCXEEKPRNKKUZPNLXNYNOHYAONRNUNWRJSQGZNXGUNTIHKIJYEHPAKBHTKEKGKDLKJVKHKFERGSGIEIHUGLEDGOGQGNEEGFGRGP'
|
||||
out = '4;139.155.68.77;119.45.114.92;162.62.63.154;3.132.215.40'
|
||||
|
||||
dec = ''
|
||||
for i in range(0, len(insec), 2):
|
||||
cur = insec[i:i+2]
|
||||
idx = ct.index(cur)
|
||||
mod_cnt = idx // 0x5e
|
||||
_sum = (idx + (mod_cnt * -0x5e) + 0x20) & 0x7f
|
||||
dec += chr(_sum)
|
||||
|
||||
print(dec)
|
||||
@@ -1,136 +0,0 @@
|
||||
import dgram from "node:dgram";
|
||||
import { createWriteStream } from "node:fs";
|
||||
import { create_LanSearch } from "./func_replacements.js";
|
||||
import { Commands, CommandsByValue } from "./datatypes.js";
|
||||
import { handle_P2PAlive, handle_PunchPkt, handle_P2PRdy, handle_Drw, notImpl, noop } from "./handlers.js";
|
||||
import { hexdump } from "./hexdump.js";
|
||||
import EventEmitter from "node:events";
|
||||
|
||||
export type Session = {
|
||||
send: (msg: DataView) => void;
|
||||
broadcast: (msg: DataView) => void;
|
||||
outgoingCommandId: number;
|
||||
ticket: number[];
|
||||
eventEmitter: EventEmitter;
|
||||
};
|
||||
|
||||
export type PacketHandler = (session: Session, dv: DataView) => void;
|
||||
|
||||
type opt = {
|
||||
debug: boolean;
|
||||
ansi: boolean;
|
||||
};
|
||||
|
||||
type msgCb = (session: Session, msg: Buffer, rinfo: any, options: opt) => void;
|
||||
type connCb = (session: Session) => void;
|
||||
|
||||
const makeSession = (cb: msgCb, connCb: connCb, options?: opt): Session => {
|
||||
const sock = dgram.createSocket("udp4");
|
||||
|
||||
sock.on("error", (err) => {
|
||||
console.error(`sock error:\n${err.stack}`);
|
||||
sock.close();
|
||||
});
|
||||
|
||||
sock.on("message", (msg, rinfo) => cb(session, msg, rinfo, options));
|
||||
|
||||
sock.on("listening", () => {
|
||||
const address = sock.address();
|
||||
console.log(`sock listening ${address.address}:${address.port}`);
|
||||
sock.setBroadcast(true);
|
||||
connCb(session);
|
||||
});
|
||||
|
||||
const RECV_PORT = 49512; // important?
|
||||
const DST_IP = "192.168.1.1";
|
||||
const BCAST_IP = "192.168.1.255";
|
||||
const SEND_PORT = 32108;
|
||||
sock.bind(RECV_PORT);
|
||||
|
||||
const session: Session = {
|
||||
outgoingCommandId: 0,
|
||||
ticket: [0, 0, 0, 0],
|
||||
eventEmitter: new EventEmitter(),
|
||||
send: (msg: DataView) => {
|
||||
const raw = msg.readU16();
|
||||
const cmd = CommandsByValue[raw];
|
||||
if (options.debug) {
|
||||
console.log(`>> ${cmd}`);
|
||||
console.log(hexdump(msg.buffer, { ansi: options.ansi, ansiColor: 0 }));
|
||||
}
|
||||
if (raw == Commands.Drw) {
|
||||
// not sure why cmd == Commands.Drw does not work
|
||||
session.outgoingCommandId++;
|
||||
}
|
||||
sock.send(new Uint8Array(msg.buffer), SEND_PORT, DST_IP);
|
||||
},
|
||||
broadcast: (msg: DataView) => sock.send(new Uint8Array(msg.buffer), SEND_PORT, BCAST_IP),
|
||||
};
|
||||
return session;
|
||||
};
|
||||
|
||||
const Handlers: Record<keyof typeof Commands, PacketHandler> = {
|
||||
PunchPkt: handle_PunchPkt,
|
||||
|
||||
Close: notImpl,
|
||||
LanSearchExt: notImpl,
|
||||
LanSearch: notImpl,
|
||||
P2PAlive: handle_P2PAlive,
|
||||
P2PAliveAck: notImpl,
|
||||
Hello: notImpl,
|
||||
P2pRdy: handle_P2PRdy,
|
||||
P2pReq: notImpl,
|
||||
LstReq: notImpl,
|
||||
DrwAck: noop,
|
||||
Drw: handle_Drw,
|
||||
|
||||
// From CSession_CtrlPkt_Proc, incomplete
|
||||
PunchTo: notImpl,
|
||||
HelloAck: notImpl,
|
||||
RlyTo: notImpl,
|
||||
DevLgnAck: notImpl,
|
||||
P2PReqAck: notImpl,
|
||||
ListenReqAck: notImpl,
|
||||
RlyHelloAck: notImpl, // always
|
||||
RlyHelloAck2: notImpl, // if len >1??
|
||||
};
|
||||
|
||||
const s = makeSession(
|
||||
(session, msg, _, options) => {
|
||||
const ab = new Uint8Array(msg).buffer;
|
||||
const dv = new DataView(ab);
|
||||
const cmd = CommandsByValue[dv.readU16()];
|
||||
if (options.debug) {
|
||||
console.log(`<< ${cmd}`);
|
||||
console.log(hexdump(msg.buffer, { ansi: options.ansi, ansiColor: 1 }));
|
||||
}
|
||||
Handlers[cmd](session, dv);
|
||||
},
|
||||
(session) => {
|
||||
const int = setInterval(() => {
|
||||
let buf = new DataView(new Uint8Array(4).buffer);
|
||||
create_LanSearch(buf);
|
||||
session.broadcast(buf);
|
||||
}, 1000);
|
||||
},
|
||||
{ debug: false, ansi: false },
|
||||
);
|
||||
|
||||
let cur_image_index = 0;
|
||||
const audioFd = createWriteStream(`captures/audio.pcm`);
|
||||
s.eventEmitter.on("frame", (frame: Buffer) => {
|
||||
const fname = `captures/${cur_image_index.toString().padStart(4, "0")}.jpg`;
|
||||
let cur_image = createWriteStream(fname);
|
||||
cur_image_index++;
|
||||
cur_image.write(frame);
|
||||
cur_image.close();
|
||||
console.log("got an entire frame", frame.length);
|
||||
});
|
||||
|
||||
s.eventEmitter.on("audio", (frame: Buffer) => {
|
||||
audioFd.write(frame);
|
||||
});
|
||||
|
||||
s.eventEmitter.on("connect", (name: string) => {
|
||||
console.log(`Connected to ${name}`);
|
||||
});
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
import { createSocket, RemoteInfo } from "node:dgram";
|
||||
import EventEmitter from "node:events";
|
||||
|
||||
import { Commands, CommandsByValue } from "./datatypes.js";
|
||||
import { handle_Drw, handle_P2PAlive, handle_P2PRdy, makePunchPkt, noop, notImpl } from "./handlers.js";
|
||||
import { hexdump } from "./hexdump.js";
|
||||
import { create_P2pAlive, DevSerial, SendStartVideo, SendVideoResolution, SendWifiDetails } from "./impl.js";
|
||||
import { opt } from "./options.js";
|
||||
|
||||
export type Session = {
|
||||
send: (msg: DataView) => void;
|
||||
outgoingCommandId: number;
|
||||
ticket: number[];
|
||||
eventEmitter: EventEmitter;
|
||||
dst_ip: string;
|
||||
lastReceivedPacket: number;
|
||||
connected: boolean;
|
||||
devName: string;
|
||||
timers: ReturnType<typeof setInterval>[];
|
||||
curImage: Buffer[];
|
||||
rcvSeqId: number;
|
||||
frame_is_bad: boolean;
|
||||
frame_was_fixed: boolean;
|
||||
options: opt;
|
||||
};
|
||||
|
||||
export type PacketHandler = (session: Session, dv: DataView, rinfo: RemoteInfo) => void;
|
||||
|
||||
type msgCb = (
|
||||
session: Session,
|
||||
handlers: Record<keyof typeof Commands, PacketHandler>,
|
||||
msg: Buffer,
|
||||
rinfo: RemoteInfo,
|
||||
) => void;
|
||||
|
||||
const handleIncoming: msgCb = (session, handlers, msg, rinfo) => {
|
||||
const ab = new Uint8Array(msg).buffer;
|
||||
const dv = new DataView(ab);
|
||||
const cmd = CommandsByValue[dv.readU16()];
|
||||
if (session.options.debug) {
|
||||
console.log(`<< ${cmd}`);
|
||||
console.log(hexdump(msg.buffer, { ansi: session.options.ansi, ansiColor: 1 }));
|
||||
}
|
||||
handlers[cmd](session, dv, rinfo);
|
||||
session.lastReceivedPacket = Date.now();
|
||||
};
|
||||
|
||||
export const makeSession = (
|
||||
handlers: Record<keyof typeof Commands, PacketHandler>,
|
||||
dev: DevSerial,
|
||||
ra: RemoteInfo,
|
||||
onLogin: (s: Session) => void,
|
||||
options: opt,
|
||||
): Session => {
|
||||
const sock = createSocket("udp4");
|
||||
|
||||
sock.on("error", (err) => {
|
||||
console.error(`sock error:\n${err.stack}`);
|
||||
sock.close();
|
||||
});
|
||||
|
||||
sock.on("message", (msg, rinfo) => handleIncoming(session, handlers, msg, rinfo));
|
||||
|
||||
sock.on("listening", () => {
|
||||
const buf = makePunchPkt(dev);
|
||||
session.send(buf);
|
||||
});
|
||||
|
||||
const SEND_PORT = 32108;
|
||||
sock.bind();
|
||||
const sessTimer = setInterval(() => {
|
||||
const delta = Date.now() - session.lastReceivedPacket;
|
||||
if (delta > 600) {
|
||||
let buf = create_P2pAlive();
|
||||
session.send(buf);
|
||||
}
|
||||
if (delta > 8000) {
|
||||
session.eventEmitter.emit("disconnect");
|
||||
}
|
||||
}, 400);
|
||||
|
||||
const session: Session = {
|
||||
outgoingCommandId: 0,
|
||||
ticket: [0, 0, 0, 0],
|
||||
lastReceivedPacket: 0,
|
||||
eventEmitter: new EventEmitter(),
|
||||
connected: true,
|
||||
timers: [sessTimer],
|
||||
devName: dev.devId,
|
||||
send: (msg: DataView) => {
|
||||
const raw = msg.readU16();
|
||||
const cmd = CommandsByValue[raw];
|
||||
if (options.debug) {
|
||||
console.log(`>> ${cmd}`);
|
||||
if (raw != Commands.P2PAlive) {
|
||||
console.log(hexdump(msg.buffer, { ansi: options.ansi, ansiColor: 0 }));
|
||||
}
|
||||
}
|
||||
sock.send(new Uint8Array(msg.buffer), SEND_PORT, session.dst_ip);
|
||||
},
|
||||
dst_ip: ra.address,
|
||||
curImage: [],
|
||||
rcvSeqId: 0,
|
||||
frame_is_bad: false,
|
||||
frame_was_fixed: false,
|
||||
options: options,
|
||||
};
|
||||
|
||||
session.eventEmitter.on("disconnect", () => {
|
||||
console.log(`Disconnected from ${session.devName} - ${session.dst_ip}`);
|
||||
session.dst_ip = "0.0.0.0";
|
||||
session.connected = false;
|
||||
session.timers.forEach((x) => clearInterval(x));
|
||||
session.timers = [];
|
||||
});
|
||||
|
||||
session.eventEmitter.on("login", () => {
|
||||
console.log(`Logged in - ${session.devName}`);
|
||||
onLogin(session);
|
||||
});
|
||||
return session;
|
||||
};
|
||||
|
||||
export const configureWifi = (ssid: string, password: string) => {
|
||||
return (s: Session) => {
|
||||
[SendWifiDetails(s, ssid, password, true)].forEach(s.send);
|
||||
};
|
||||
};
|
||||
|
||||
export const startVideoStream = (s: Session) => {
|
||||
[
|
||||
...SendVideoResolution(s, 2), // 640x480
|
||||
SendStartVideo(s),
|
||||
].forEach(s.send);
|
||||
};
|
||||
|
||||
export const Handlers: Record<keyof typeof Commands, PacketHandler> = {
|
||||
PunchPkt: notImpl,
|
||||
P2PAlive: handle_P2PAlive,
|
||||
P2pRdy: handle_P2PRdy,
|
||||
DrwAck: noop,
|
||||
Drw: handle_Drw,
|
||||
|
||||
Close: notImpl,
|
||||
LanSearchExt: notImpl,
|
||||
LanSearch: notImpl,
|
||||
P2PAliveAck: notImpl,
|
||||
Hello: notImpl,
|
||||
P2pReq: notImpl,
|
||||
LstReq: notImpl,
|
||||
PunchTo: notImpl,
|
||||
HelloAck: notImpl,
|
||||
RlyTo: notImpl,
|
||||
DevLgnAck: notImpl,
|
||||
P2PReqAck: notImpl,
|
||||
ListenReqAck: notImpl,
|
||||
RlyHelloAck: notImpl, // always
|
||||
RlyHelloAck2: notImpl, // if len >1??
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
DataView.prototype.add = function (offset) {
|
||||
return new DataView(this.buffer, offset);
|
||||
return new DataView(this.buffer, offset + this.byteOffset);
|
||||
};
|
||||
DataView.prototype.writeU8 = function (val) {
|
||||
return this.setUint8(0, val);
|
||||
@@ -19,9 +19,15 @@ DataView.prototype.readU8 = function () {
|
||||
DataView.prototype.readU16 = function () {
|
||||
return this.getUint16(0);
|
||||
};
|
||||
DataView.prototype.readU16LE = function () {
|
||||
return this.getUint16(0, true);
|
||||
};
|
||||
DataView.prototype.readU32 = function () {
|
||||
return this.getUint32(0);
|
||||
};
|
||||
DataView.prototype.readU32LE = function () {
|
||||
return this.getUint32(0, true);
|
||||
};
|
||||
DataView.prototype.readU64 = function () {
|
||||
return this.getBigUint64(0);
|
||||
};
|
||||
@@ -44,23 +50,33 @@ DataView.prototype.readByteArray = function (len) {
|
||||
};
|
||||
DataView.prototype.readString = function (len) {
|
||||
const ba = this.readByteArray(len);
|
||||
return String.fromCharCode.apply(null, new Uint8Array(ba.buffer));
|
||||
const s = String.fromCharCode.apply(null, new Uint8Array(ba.buffer));
|
||||
const nullByte = s.indexOf("\0");
|
||||
if (nullByte !== -1) return s.substring(0, nullByte);
|
||||
return s;
|
||||
};
|
||||
DataView.prototype.writeString = function (str) {
|
||||
const bytes = [...str].map((_, i) => str.charCodeAt(i));
|
||||
return this.writeByteArray(bytes);
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface DataView {
|
||||
add(offset: number): DataView;
|
||||
readByteArray(len: number): DataView;
|
||||
writeString(str: string): void;
|
||||
readString(len: number): string;
|
||||
readU16(): number;
|
||||
readU16LE(): number;
|
||||
readU32(): number;
|
||||
readU64(): number;
|
||||
readU32LE(): number;
|
||||
readU64(): bigint;
|
||||
readU8(): number;
|
||||
writeByteArray(arr: Uint8Array | number[]): undefined;
|
||||
writeU16(n: number): undefined;
|
||||
writeU32(n: number): undefined;
|
||||
writeU64(n: number): undefined;
|
||||
writeU8(n: number): undefined;
|
||||
writeByteArray(arr: Uint8Array | number[]): void;
|
||||
writeU16(n: number): void;
|
||||
writeU32(n: number): void;
|
||||
writeU64(n: bigint): void;
|
||||
writeU8(n: number): void;
|
||||
}
|
||||
}
|
||||
export default global;
|
||||
|
||||
+35
-9
@@ -5,7 +5,7 @@ import assert from "assert";
|
||||
import { XqBytesDec, XqBytesEnc } from "../func_replacements.js";
|
||||
import { createResponseForControlCommand } from "../handlers.js";
|
||||
import { hexdump } from "../hexdump.js";
|
||||
import { SendUsrChk } from "../impl.ts";
|
||||
import { SendStartVideo, SendDevStatus, SendUsrChk, SendWifiDetails } from "../impl.ts";
|
||||
import { placeholderTypes, sprintf } from "../utils.js";
|
||||
|
||||
describe("debug_tools", () => {
|
||||
@@ -106,6 +106,13 @@ describe("module", () => {
|
||||
XqBytesEnc(in_buf, long_dec_bytes.byteLength, 4); // this mutates in_buf
|
||||
assert.deepEqual(new Uint8Array(in_buf.buffer), long_enc_bytes);
|
||||
});
|
||||
/* TODO
|
||||
it("decrypts offset dataviews", () => {
|
||||
const in_buf = new DataView(simple_enc_bytes.buffer.slice(0));
|
||||
XqBytesDec(in_buf, simple_enc_bytes.byteLength, 4); // this mutates in_buf
|
||||
assert.deepEqual(new Uint8Array(in_buf.buffer), simple_dec_bytes);
|
||||
});
|
||||
*/
|
||||
it("reverts Enc with Dec", () => {
|
||||
const in_buf = new DataView(long_dec_bytes.buffer.slice(0));
|
||||
XqBytesEnc(in_buf, long_dec_bytes.byteLength, 4); // this mutates in_buf
|
||||
@@ -116,23 +123,42 @@ describe("module", () => {
|
||||
});
|
||||
|
||||
const hstrToBA = (hs) => new Uint8Array(hs.match(/../g).map((h) => parseInt(h, 16))).buffer;
|
||||
describe("events", () => {
|
||||
it("emits login event upon logging in", () => {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
describe("make packet", () => {
|
||||
it("builds a good SendUsrChk", () => {
|
||||
const expected_str =
|
||||
"f1d000b0d1000000110a2010a400ff00000000006f01010101010101010101010101010101010101010101010101010160656c686f01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010160656c68";
|
||||
const expected = hstrToBA(expected_str);
|
||||
assert.deepEqual(SendUsrChk("admin", "admin").buffer, expected);
|
||||
const sess = { outgoingCommandId: 0, ticket: [0, 0, 0, 0] };
|
||||
assert.deepEqual(SendUsrChk(sess, "admin", "admin").buffer, expected);
|
||||
});
|
||||
it("builds a good SendStartVideo", () => {
|
||||
const input_pkt_str = "f1d00018d1000000110a20110c00ff000000000064504737fe010101";
|
||||
// token-in = 0x64 0x50 0x47 0x37
|
||||
const _expected_str = "f1d00114d1000000110a1030080100006551463601010101";
|
||||
// output is 0x3010; 'start video'; hardcoded but shouldnt
|
||||
const _expected_str = "f1d00010d1000000110a10300400000001020304";
|
||||
const expected = hstrToBA(_expected_str);
|
||||
|
||||
const sess = { outgoingCommandId: 0, ticket: [0, 0, 0, 0] };
|
||||
const got = createResponseForControlCommand(sess, new DataView(hstrToBA(input_pkt_str)));
|
||||
const sess = { outgoingCommandId: 0, ticket: [1, 2, 3, 4] };
|
||||
const got = SendStartVideo(sess);
|
||||
assert.deepEqual(got.buffer, expected);
|
||||
});
|
||||
it("builds a good SendDevStatus", () => {
|
||||
const sess = { outgoingCommandId: 0, ticket: [1, 2, 3, 4] };
|
||||
const _expected_str = "f1d00010d1000000110a08100400000001020304";
|
||||
const expected = hstrToBA(_expected_str);
|
||||
|
||||
const got = SendDevStatus(sess);
|
||||
assert.deepEqual(got.buffer, expected);
|
||||
});
|
||||
it("builds a good WifiSettingsSet", () => {
|
||||
const sess = { outgoingCommandId: 2, ticket: [1, 2, 3, 4] };
|
||||
const _expected_str =
|
||||
"f1d00118d1000002110a01600c010000010203040101010101010101010101010101010100010101726a786f647501010101010101010101010101010101010101010101010101017274716473627360710101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101312f3334342f3334342f333434010101312f312f312f31010101010101010101312f312f312f31010101010101010101312f312f312f31010101010101010101312f312f312f3101010101010101010101010101";
|
||||
const expected = hstrToBA(_expected_str);
|
||||
|
||||
const got = SendWifiDetails(sess, "skynet", "supercrap", true);
|
||||
assert.deepEqual(got.buffer, expected);
|
||||
assert.deepEqual(sess.ticket, [0x65, 0x51, 0x46, 0x36]);
|
||||
});
|
||||
});
|
||||
|
||||
+8
-2
@@ -1,10 +1,16 @@
|
||||
{
|
||||
|
||||
"include": ["*.ts"],
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "esnext",
|
||||
"module": "es6",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node"
|
||||
"preserveConstEnums": true,
|
||||
"outDir": "./build/",
|
||||
"rootDir": "./",
|
||||
"strict": false,
|
||||
"sourceMap": false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ export const sprintf = (str, values) => {
|
||||
.join("");
|
||||
return s + str.slice(lastScanned);
|
||||
};
|
||||
export const u32_swap = (x) =>
|
||||
((x & 0xff000000) >> 24) | ((x & 0xff0000) >> 8) | ((x & 0xff00) << 8) | ((x & 0xff) << 24);
|
||||
export const u16_swap = (x) => ((x & 0xff00) >> 8) | ((x & 0x00ff) << 8);
|
||||
export const swap_endianness_u16 = (ptr) => {
|
||||
const bytes = ptr.readU16();
|
||||
|
||||
Reference in New Issue
Block a user