mirror of
https://github.com/samuelthomas2774/nxapi.git
synced 2026-03-21 18:04:10 -05:00
Move f generation server to a separate repository
This commit is contained in:
parent
8bf06ae439
commit
32475b76d5
|
|
@ -15,17 +15,12 @@ RUN npx tsc
|
|||
|
||||
FROM node:18
|
||||
|
||||
RUN apt update && \
|
||||
apt install -y android-tools-adb && \
|
||||
apt-get clean
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ADD package.json /app
|
||||
ADD package-lock.json /app
|
||||
|
||||
RUN npm ci --production
|
||||
RUN npm install --no-save frida
|
||||
|
||||
COPY bin /app/bin
|
||||
COPY resources /app/resources
|
||||
|
|
@ -35,8 +30,6 @@ RUN ln -s /app/bin/nxapi.js /usr/local/bin/nxapi
|
|||
ENV NXAPI_DATA_PATH=/data
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN ln -s /data/android /root/.android
|
||||
|
||||
VOLUME [ "/data" ]
|
||||
|
||||
ENTRYPOINT [ "/app/resources/docker-entrypoint.sh" ]
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ Environment variable | Description
|
|||
`NXAPI_DATA_PATH` | Sets the location to store user data. See [data location](#data-location).
|
||||
`ZNC_PROXY_URL` | Sets the URL of the nxapi znc API proxy server. See [API proxy server](docs/cli.md#api-proxy-server).
|
||||
`NXAPI_ZNCA_API` | Sets the API to use for Coral client authentication. Either `flapg` or `imink`. See [Coral client authentication](#coral-client-authentication).
|
||||
`ZNCA_API_URL` | Sets the URL of the nxapi znca API server to use for Coral client authentication, if `NXAPI_ZNCA_API` is not set. See [znca API server](docs/cli.md#znca-api-server).
|
||||
`ZNCA_API_URL` | Sets the URL of the nxapi znca API server to use for Coral client authentication, if `NXAPI_ZNCA_API` is not set. See https://gitlab.fancy.org.uk/samuel/nxapi-znca-api or https://github.com/samuelthomas2774/nxapi-znca-api.
|
||||
`NXAPI_USER_AGENT` | Sets the application/script user agent string used by the nxapi command. See [user agent strings](#user-agent-strings).
|
||||
`NXAPI_ENABLE_REMOTE_CONFIG` | Disables fetching and using remote configuration data if set to `0`. Do not disable remote configuration if nxapi has run with it enabled.
|
||||
`NXAPI_REMOTE_CONFIG_FALLBACK` | Allows using local configuration data if the remote configuration data cannot be fetched if set to `1`. This should not be used, as it can cause nxapi to revert to local configuration data after previously using newer remote configuration data.
|
||||
|
|
|
|||
|
|
@ -13,39 +13,9 @@ services:
|
|||
traefik.http.services.nxapi-znc.loadbalancer.server.port: 80
|
||||
environment:
|
||||
DEBUG: '*,-express:*'
|
||||
ZNCA_API_URL: http://znca-api/api/znca
|
||||
# ZNCA_API_URL: http://znca-api/api/znca
|
||||
volumes:
|
||||
- data:/data
|
||||
|
||||
znca-api:
|
||||
image: ${COMPOSE_PROJECT_NAME:-nxapi}_web
|
||||
command:
|
||||
- android-znca-api-server-frida
|
||||
- $ZNCA_API_ANDROID_DEVICE
|
||||
- --exec-command
|
||||
- ${ZNCA_API_EXEC_COMMAND:-}
|
||||
- --frida-server-path
|
||||
- ${ZNCA_API_FRIDA_SERVER_PATH:-/data/local/tmp/frida-server}
|
||||
- --listen
|
||||
- '[::]:80'
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- web
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.nxapi-znca.entrypoints: websecure
|
||||
traefik.http.routers.nxapi-znca.rule: Host(`${TRAEFIK_HOST:-nxapi.ta.fancy.org.uk}`) && PathPrefix(`/api/znca/`)
|
||||
traefik.http.routers.nxapi-znca.tls: true
|
||||
traefik.http.services.nxapi-znca.loadbalancer.server.port: 80
|
||||
environment:
|
||||
DEBUG: '*,-express:*,-body-parser:*'
|
||||
volumes:
|
||||
- data:/data
|
||||
healthcheck:
|
||||
test: [ "ENTRYPOINT", "curl", "http://[::1]:80" ]
|
||||
timeout: 45s
|
||||
interval: 10s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
data:
|
||||
|
|
|
|||
109
docs/cli.md
109
docs/cli.md
|
|
@ -566,111 +566,4 @@ This command has no options, but environment variables can still be used.
|
|||
|
||||
A server for controlling the Nintendo Switch Online app on an Android device/emulator using Frida can be used instead of the imink/flapg APIs to generate `f` parameters for authentication.
|
||||
|
||||
This server has a single endpoint, `/api/znca/f`, which is fully compatible with [the imink API](https://github.com/JoneWang/imink/wiki/imink-API-Documentation)'s `/f` endpoint. The following data should be sent as JSON:
|
||||
|
||||
```ts
|
||||
interface AndroidZncaApiRequest {
|
||||
/**
|
||||
* `"1"` or `1` for Coral (Nintendo Switch Online app) authentication (`Account/Login` and `Account/GetToken`).
|
||||
* `"2"` or `2` for web service authentication (`Game/GetWebServiceToken`).
|
||||
*/
|
||||
hash_method: '1' | '2' | 1 | 2;
|
||||
/**
|
||||
* The token used to authenticate to the Coral API:
|
||||
* The Nintendo Account `id_token` for Coral authentication.
|
||||
* The Coral access token for web service authentication.
|
||||
*/
|
||||
token: string;
|
||||
/**
|
||||
* The current timestamp in milliseconds, either as a number or a string.
|
||||
*/
|
||||
timestamp?: string | number;
|
||||
/**
|
||||
* A random (v4) UUID.
|
||||
*/
|
||||
request_id?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Due to changes to Nintendo's API on [23/08/2022](https://github.com/samuelthomas2774/nxapi/discussions/10#discussioncomment-3464443) the `timestamp` parameter should not be sent. If the `timestamp` or `request_id` parameters are not sent their values will be generated and returned in the response. Note that unlike the imink API and [nsotokengen](https://github.com/clovervidia/nsotokengen), only parameters not included in the request will be included in the response.
|
||||
|
||||
This requires:
|
||||
|
||||
- adb is installed on the computer running nxapi
|
||||
- The Android device is running adbd as root or a su-like command can be used to escalate to root
|
||||
- The frida-server executable is located at `/data/local/tmp/frida-server` on the Android device (a different path can be provided using the `--frida-server-path` option)
|
||||
- The Nintendo Switch Online app is installed on the Android device
|
||||
|
||||
No other software (e.g. frida-tools) needs to be installed on the computer running nxapi. The Android device must be constantly reachable using ADB. The server will attempt to reconnect to the Android device and will automatically retry any requests that would fail due to the device disconnecting. The server will exit if it fails to reconnect to the device. A service manager should be used to restart the server if it exits.
|
||||
|
||||
```sh
|
||||
# Start the server using the ADB server "android.local:5555" listening on all interfaces on a random port
|
||||
nxapi android-znca-api-server-frida android.local:5555
|
||||
|
||||
# Start the server listening on a specific address/port
|
||||
# The `--listen` option can be used multiple times
|
||||
nxapi android-znca-api-server-frida android.local:5555 --listen "[::1]:12345"
|
||||
|
||||
# Use a command to escalate to root to start frida-server and the Nintendo Switch Online app
|
||||
# "{cmd}" will be replaced with the path to a temporary script in double quotes
|
||||
nxapi android-znca-api-server-frida android.local:5555 --exec-command "/system/bin/su -c {cmd}"
|
||||
|
||||
# Specify a different location for the adb executable if it is not in the search path
|
||||
nxapi android-znca-api-server-frida android.local:5555 --adb-path "/usr/local/bin/adb"
|
||||
|
||||
# Run `adb root` when connecting to the device to restart adbd as root
|
||||
nxapi android-znca-api-server-frida android.local:5555 --adb-root
|
||||
|
||||
# Specify a different location for the frida-server executable on the device
|
||||
nxapi android-znca-api-server-frida android.local:5555 --frida-server-path "/data/local/tmp/frida-server-15.1.17-android-arm"
|
||||
|
||||
# Use Frida to start the app on the device (even if it is already running) (recommended)
|
||||
nxapi android-znca-api-server-frida android.local:5555 --start-method spawn
|
||||
# Use `am start-activity` to ensure the app process is running
|
||||
nxapi android-znca-api-server-frida android.local:5555 --start-method activity
|
||||
# Use `am start-service` to ensure the app process is running, without causing Android to show the app (default)
|
||||
nxapi android-znca-api-server-frida android.local:5555 --start-method service
|
||||
# Do not attempt to start the app on the device automatically - this will cause the server to fail if the app is not already running
|
||||
nxapi android-znca-api-server-frida android.local:5555 --start-method none
|
||||
|
||||
# Strictly validate the timestamp and request_id parameters sent by the client are likely to be accepted by Nintendo's API
|
||||
nxapi android-znca-api-server-frida android.local:5555 --strict-validate
|
||||
|
||||
# Don't validate the token sent by the client
|
||||
nxapi android-znca-api-server-frida android.local:5555 --no-validate-tokens
|
||||
|
||||
# Make imink-compatible API requests using curl
|
||||
curl --header "Content-Type: application/json" --data '{"hash_method": "1", "token": "..."}' "http://[::1]:12345/api/znca/f"
|
||||
curl --header "Content-Type: application/json" --data '{"hash_method": "1", "token": "...", "request_id": "..."}' "http://[::1]:12345/api/znca/f"
|
||||
curl --header "Content-Type: application/json" --data '{"hash_method": "1", "token": "...", "timestamp": "...", "request_id": "..."}' "http://[::1]:12345/api/znca/f"
|
||||
|
||||
# Make legacy nxapi v1.3.0-compatible API requests using curl
|
||||
curl --header "Content-Type: application/json" --data '{"type": "nso", "token": "..."}' "http://[::1]:12345/api/znca/f"
|
||||
curl --header "Content-Type: application/json" --data '{"type": "nso", "token": "...", "uuid": "..."}' "http://[::1]:12345/api/znca/f"
|
||||
curl --header "Content-Type: application/json" --data '{"type": "nso", "token": "...", "timestamp": "...", "uuid": "..."}' "http://[::1]:12345/api/znca/f"
|
||||
|
||||
# Use the znca API server in other commands
|
||||
# This should be set when running any nso commands as the access token will be refreshed automatically when it expires
|
||||
ZNCA_API_URL=http://[::1]:12345/api/znca nxapi nso ...
|
||||
```
|
||||
|
||||
Information about the device and the Nintendo Switch Online app, as well as information on how long the request took to process will be included in the response headers.
|
||||
|
||||
Header | Description
|
||||
--------------------------------|------------------
|
||||
`X-Android-Build-Type` | Android build type, e.g. `user`
|
||||
`X-Android-Release` | Android release/marketing version, e.g. `8.0.0`
|
||||
`X-Android-Platform-Version` | Android SDK version, e.g. `26`
|
||||
`X-znca-Platform` | Device platform - always `Android`
|
||||
`X-znca-Version` | App release/marketing version, e.g. `2.2.0`
|
||||
`X-znca-Build` | App build/internal version, e.g. `2832`
|
||||
|
||||
The following performance metrics are included in the `Server-Timing` header:
|
||||
|
||||
Name | Description
|
||||
------------|------------------
|
||||
`validate` | Time validating the request body.
|
||||
`attach` | Time waiting for the device to become available, start frida-server, start the app and attach the Frida script to the app process. This metric will not be included if the server is already connected to the device.
|
||||
`queue` | Time waiting for the processing thread to become available.
|
||||
`init` | Time waiting for `com.nintendo.coral.core.services.voip.Libvoipjni.init`.
|
||||
`process` | Time waiting for `com.nintendo.coral.core.services.voip.Libvoipjni.genAudioH`/`genAudioH2`.
|
||||
This is now a separate project at https://gitlab.fancy.org.uk/samuel/nxapi-znca-api or https://github.com/samuelthomas2774/nxapi-znca-api.
|
||||
|
|
|
|||
543
package-lock.json
generated
543
package-lock.json
generated
|
|
@ -53,7 +53,6 @@
|
|||
"@types/yargs": "^17.0.12",
|
||||
"electron": "^20.2.0",
|
||||
"electron-builder": "^23.3.3",
|
||||
"frida": "^15.2.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-native-web": "^0.17.7",
|
||||
|
|
@ -61,9 +60,6 @@
|
|||
"rollup-plugin-polyfill-node": "^0.10.2",
|
||||
"ts-json-schema-generator": "^1.1.1",
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"frida": "^15.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@develar/schema-utils": {
|
||||
|
|
@ -995,22 +991,11 @@
|
|||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"devOptional": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
|
|
@ -1132,6 +1117,7 @@
|
|||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
|
|
@ -1762,15 +1748,6 @@
|
|||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
|
||||
"integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
|
|
@ -2266,15 +2243,6 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
|
||||
|
|
@ -2427,7 +2395,7 @@
|
|||
"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==",
|
||||
"devOptional": true
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
|
|
@ -2530,49 +2498,6 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/frida": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/frida/-/frida-15.2.2.tgz",
|
||||
"integrity": "sha512-/6HOAhNLKEgwLsJU7DZuNHTCadCTnp+paElp9fcIgGfBC4yqAZqz+LyJ6b8lVgS9xzdtPf3KMn40y2WXYjgRsw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"minimatch": "^5.0.1",
|
||||
"nan": "^2.13.2",
|
||||
"prebuild-install": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/frida/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/frida/node_modules/minimatch": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
|
|
@ -2660,12 +2585,6 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
|
|
@ -2991,7 +2910,8 @@
|
|||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
],
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/import-lazy": {
|
||||
"version": "2.1.0",
|
||||
|
|
@ -3513,12 +3433,6 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
|
@ -3529,18 +3443,6 @@
|
|||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.16.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz",
|
||||
"integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
|
|
@ -3549,33 +3451,6 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.24.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.24.0.tgz",
|
||||
"integrity": "sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abi/node_modules/semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
|
||||
|
|
@ -3839,32 +3714,6 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/prepend-http": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
|
||||
|
|
@ -4098,20 +3947,6 @@
|
|||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/register-scheme": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "git+ssh://git@github.com/devsnek/node-register-scheme.git#e7cc9a63a1f512565da44cb57316d9fb10750e17",
|
||||
|
|
@ -4465,78 +4300,6 @@
|
|||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-get/node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-get/node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
|
|
@ -4621,15 +4384,6 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
|
|
@ -4718,40 +4472,6 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs/node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/temp-file": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz",
|
||||
|
|
@ -4934,18 +4654,6 @@
|
|||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||
|
|
@ -5129,12 +4837,6 @@
|
|||
"integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
|
|
@ -6098,22 +5800,11 @@
|
|||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"devOptional": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
|
|
@ -6209,6 +5900,7 @@
|
|||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
|
|
@ -6696,12 +6388,6 @@
|
|||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
|
||||
"integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==",
|
||||
"dev": true
|
||||
},
|
||||
"detect-node": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
|
|
@ -7087,12 +6773,6 @@
|
|||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
||||
},
|
||||
"expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"dev": true
|
||||
},
|
||||
"express": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz",
|
||||
|
|
@ -7220,7 +6900,7 @@
|
|||
"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==",
|
||||
"devOptional": true
|
||||
"optional": true
|
||||
},
|
||||
"filelist": {
|
||||
"version": "1.0.4",
|
||||
|
|
@ -7309,44 +6989,6 @@
|
|||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
|
||||
},
|
||||
"frida": {
|
||||
"version": "15.2.2",
|
||||
"resolved": "https://registry.npmjs.org/frida/-/frida-15.2.2.tgz",
|
||||
"integrity": "sha512-/6HOAhNLKEgwLsJU7DZuNHTCadCTnp+paElp9fcIgGfBC4yqAZqz+LyJ6b8lVgS9xzdtPf3KMn40y2WXYjgRsw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"minimatch": "^5.0.1",
|
||||
"nan": "^2.13.2",
|
||||
"prebuild-install": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||
|
|
@ -7409,12 +7051,6 @@
|
|||
"pump": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
|
|
@ -7654,7 +7290,8 @@
|
|||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"import-lazy": {
|
||||
"version": "2.1.0",
|
||||
|
|
@ -8041,12 +7678,6 @@
|
|||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
|
@ -8057,43 +7688,11 @@
|
|||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.16.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz",
|
||||
"integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==",
|
||||
"dev": true
|
||||
},
|
||||
"napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
|
||||
"dev": true
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "3.24.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.24.0.tgz",
|
||||
"integrity": "sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz",
|
||||
|
|
@ -8282,26 +7881,6 @@
|
|||
"xmlbuilder": "^15.1.1"
|
||||
}
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
|
||||
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^1.0.1",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"prepend-http": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
|
||||
|
|
@ -8488,17 +8067,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"register-scheme": {
|
||||
"version": "git+ssh://git@github.com/devsnek/node-register-scheme.git#e7cc9a63a1f512565da44cb57316d9fb10750e17",
|
||||
"from": "register-scheme@github:devsnek/node-register-scheme",
|
||||
|
|
@ -8771,40 +8339,6 @@
|
|||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mimic-response": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
|
|
@ -8870,15 +8404,6 @@
|
|||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
|
|
@ -8940,39 +8465,6 @@
|
|||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"tar-fs": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"temp-file": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz",
|
||||
|
|
@ -9121,15 +8613,6 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||
|
|
@ -9259,12 +8742,6 @@
|
|||
"integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==",
|
||||
"dev": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -50,9 +50,6 @@
|
|||
"uuid": "^8.3.2",
|
||||
"yargs": "^17.5.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"frida": "^15.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-alias": "^3.1.9",
|
||||
"@rollup/plugin-commonjs": "^22.0.2",
|
||||
|
|
@ -77,7 +74,6 @@
|
|||
"@types/yargs": "^17.0.12",
|
||||
"electron": "^20.2.0",
|
||||
"electron-builder": "^23.3.3",
|
||||
"frida": "^15.2.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-native-web": "^0.17.7",
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ const main = {
|
|||
],
|
||||
external: [
|
||||
'node-notifier',
|
||||
'frida',
|
||||
'register-scheme',
|
||||
'bindings',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,738 +1,20 @@
|
|||
import process from 'node:process';
|
||||
import * as path from 'node:path';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import * as net from 'node:net';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as crypto from 'node:crypto';
|
||||
import createDebug from 'debug';
|
||||
import { v4 as uuidgen } from 'uuid';
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import mkdirp from 'mkdirp';
|
||||
import type { Arguments as ParentArguments } from '../cli.js';
|
||||
import { NintendoAccountIdTokenJwtPayload } from '../api/na.js';
|
||||
import { CoralJwtPayload, ZNCA_CLIENT_ID } from '../api/coral.js';
|
||||
import { ArgumentsCamelCase, Argv, YargsArguments } from '../util/yargs.js';
|
||||
import { initStorage, paths } from '../util/storage.js';
|
||||
import { getJwks, Jwt } from '../util/jwt.js';
|
||||
import { product } from '../util/product.js';
|
||||
import { parseListenAddress } from '../util/net.js';
|
||||
|
||||
const debug = createDebug('cli:android-znca-api-server-frida');
|
||||
const debugApi = createDebug('cli:android-znca-api-server-frida:api');
|
||||
|
||||
const script_dir = path.join(paths.temp, 'android-znca-api-server');
|
||||
|
||||
export const command = 'android-znca-api-server-frida <device>';
|
||||
export const desc = 'Connect to a rooted Android device with frida-server over ADB running the Nintendo Switch Online app and start a HTTP server to generate f parameters';
|
||||
export const command = 'android-znca-api-server-frida';
|
||||
export const desc = null;
|
||||
|
||||
export function builder(yargs: Argv<ParentArguments>) {
|
||||
return yargs.positional('device', {
|
||||
describe: 'ADB server address/port',
|
||||
type: 'string',
|
||||
demandOption: true,
|
||||
}).option('exec-command', {
|
||||
describe: 'Command to use to run a file on the device',
|
||||
type: 'string',
|
||||
}).option('adb-path', {
|
||||
describe: 'Path to the adb executable',
|
||||
type: 'string',
|
||||
}).option('adb-root', {
|
||||
describe: 'Run `adb root` to restart adbd as root',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('frida-server-path', {
|
||||
describe: 'Path to the frida-server executable on the device',
|
||||
type: 'string',
|
||||
default: '/data/local/tmp/frida-server',
|
||||
}).option('start-method', {
|
||||
describe: 'Method to ensure the app is running (one of "spawn", "none", "activity", "service")',
|
||||
type: 'string',
|
||||
default: 'service',
|
||||
}).option('strict-validate', {
|
||||
describe: 'Validate data exactly matches the format that would be generated by Nintendo\'s Android app',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
}).option('validate-tokens', {
|
||||
describe: 'Validate tokens before passing them to znca',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
}).option('listen', {
|
||||
describe: 'Server address and port',
|
||||
type: 'array',
|
||||
default: ['[::]:0'],
|
||||
});
|
||||
return yargs;
|
||||
}
|
||||
|
||||
type Arguments = YargsArguments<ReturnType<typeof builder>>;
|
||||
|
||||
enum StartMethod {
|
||||
/** Spawn the app process with Frida, even if the app is already running (recommended) */
|
||||
SPAWN,
|
||||
/** Start the app's main activity using the am command */
|
||||
ACTIVITY,
|
||||
/**
|
||||
* Start a background service using the am command (default)
|
||||
* This tricks Android into not killing the app for some reason and allows the process to be started
|
||||
* in the background.
|
||||
*/
|
||||
SERVICE,
|
||||
/** Do not attempt to start the app - if it is not already running the server will fail */
|
||||
NONE,
|
||||
}
|
||||
|
||||
interface PackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
build: number;
|
||||
}
|
||||
interface SystemInfo {
|
||||
board: string;
|
||||
bootloader: string;
|
||||
brand: string;
|
||||
abis: string[];
|
||||
device: string;
|
||||
display: string;
|
||||
fingerprint: string;
|
||||
hardware: string;
|
||||
host: string;
|
||||
id: string;
|
||||
manufacturer: string;
|
||||
model: string;
|
||||
product: string;
|
||||
tags: string;
|
||||
time: string;
|
||||
type: string;
|
||||
user: string;
|
||||
|
||||
version: {
|
||||
codename: string;
|
||||
release: string;
|
||||
// release_display: string;
|
||||
sdk: string;
|
||||
sdk_int: number;
|
||||
security_patch: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface FResult {
|
||||
f: string;
|
||||
timestamp: string;
|
||||
/** Queue wait duration */
|
||||
dw: number;
|
||||
/** Initialisation duration */
|
||||
di: number;
|
||||
/** Processing duration */
|
||||
dp: number;
|
||||
}
|
||||
|
||||
export async function handler(argv: ArgumentsCamelCase<Arguments>) {
|
||||
const start_method =
|
||||
argv.startMethod === 'spawn' ? StartMethod.SPAWN :
|
||||
argv.startMethod === 'activity' ? StartMethod.ACTIVITY :
|
||||
argv.startMethod === 'service' ? StartMethod.SERVICE :
|
||||
StartMethod.NONE;
|
||||
|
||||
await mkdirp(script_dir);
|
||||
|
||||
const storage = await initStorage(argv.dataPath);
|
||||
await setup(argv, start_method);
|
||||
|
||||
let {session, script} = await attach(argv, start_method);
|
||||
let ready: Promise<void> | null = null;
|
||||
|
||||
let api: {
|
||||
ping(): Promise<true>;
|
||||
getPackageInfo(): Promise<PackageInfo>;
|
||||
getSystemInfo(): Promise<SystemInfo>;
|
||||
genAudioH(token: string, timestamp: string | number | undefined, request_id: string): Promise<FResult>;
|
||||
genAudioH2(token: string, timestamp: string | number | undefined, request_id: string): Promise<FResult>;
|
||||
} = script.exports as any;
|
||||
|
||||
let system_info = await api.getSystemInfo();
|
||||
let package_info = await api.getPackageInfo();
|
||||
|
||||
const onexit = (code: number | NodeJS.Signals) => {
|
||||
// @ts-expect-error
|
||||
process.removeListener('exit', onexit);
|
||||
// @ts-expect-error
|
||||
process.removeListener('SIGTERM', onexit);
|
||||
// @ts-expect-error
|
||||
process.removeListener('SIGINT', onexit);
|
||||
|
||||
debug('Exiting', code);
|
||||
debug('Releasing wake lock', argv.device);
|
||||
execScript(argv.device, '/data/local/tmp/android-znca-api-server-shutdown.sh', argv.execCommand, argv.adbPath);
|
||||
process.exit(typeof code === 'number' ? code : 0);
|
||||
};
|
||||
|
||||
process.on('exit', onexit);
|
||||
process.on('SIGTERM', onexit);
|
||||
process.on('SIGINT', onexit);
|
||||
|
||||
function reattach() {
|
||||
// Already attempting to reattach
|
||||
if (ready) return;
|
||||
|
||||
debug('Attempting to reconnect to the device');
|
||||
|
||||
ready = attach(argv, start_method).then(async a => {
|
||||
ready = null;
|
||||
session = a.session;
|
||||
script = a.script;
|
||||
api = script.exports as any;
|
||||
|
||||
const new_system_info = await api.getSystemInfo();
|
||||
const new_package_info = await api.getPackageInfo();
|
||||
|
||||
if (system_info.version.sdk_int !== new_system_info.version.sdk_int) {
|
||||
debug('Android system version updated while disconnected');
|
||||
}
|
||||
if (package_info.build !== new_package_info.build) {
|
||||
debug('znca version updated while disconnected');
|
||||
}
|
||||
|
||||
system_info = new_system_info;
|
||||
package_info = new_package_info;
|
||||
}).catch(err => {
|
||||
console.error('Reattach failed', err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use('/api/znca', (req, res, next) => {
|
||||
console.log('[%s] %s %s HTTP/%s from %s, port %d%s, %s',
|
||||
new Date(), req.method, req.path, req.httpVersion,
|
||||
req.socket.remoteAddress, req.socket.remotePort,
|
||||
req.headers['x-forwarded-for'] ? ' (' + req.headers['x-forwarded-for'] + ')' : '',
|
||||
req.headers['user-agent']);
|
||||
|
||||
res.setHeader('Server', product + ' android-znca-api-frida');
|
||||
res.setHeader('X-Android-Build-Type', system_info.type);
|
||||
res.setHeader('X-Android-Release', system_info.version.release);
|
||||
res.setHeader('X-Android-Platform-Version', system_info.version.sdk_int);
|
||||
res.setHeader('X-znca-Platform', 'Android');
|
||||
res.setHeader('X-znca-Version', package_info.version);
|
||||
res.setHeader('X-znca-Build', package_info.build);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
app.post('/api/znca/f', bodyParser.json(), async (req, res) => {
|
||||
const start = Date.now();
|
||||
|
||||
if (req.body && 'type' in req.body) req.body = {
|
||||
hash_method:
|
||||
req.body.type === 'nso' ? '1' :
|
||||
req.body.type === 'app' ? '2' : null!,
|
||||
token: req.body.token,
|
||||
timestamp: '' + req.body.timestamp,
|
||||
request_id: req.body.uuid,
|
||||
};
|
||||
|
||||
if (req.body && typeof req.body.hash_method === 'number') req.body.hash_method = '' + req.body.hash_method;
|
||||
|
||||
const data: {
|
||||
hash_method: '1' | '2' | 1 | 2;
|
||||
token: string;
|
||||
timestamp?: string | number;
|
||||
request_id?: string;
|
||||
} = req.body;
|
||||
|
||||
if (
|
||||
!data ||
|
||||
typeof data !== 'object' ||
|
||||
(data.hash_method !== '1' && data.hash_method !== '2') ||
|
||||
typeof data.token !== 'string' ||
|
||||
(data.timestamp && typeof data.timestamp !== 'string' && typeof data.timestamp !== 'number') ||
|
||||
(data.request_id && typeof data.request_id !== 'string')
|
||||
) {
|
||||
res.statusCode = 400;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({error: 'invalid_request'}));
|
||||
return;
|
||||
}
|
||||
|
||||
const timestamp = 'timestamp' in data ? '' + data.timestamp : undefined;
|
||||
const request_id = 'request_id' in data ? data.request_id! : uuidgen();
|
||||
|
||||
try {
|
||||
try {
|
||||
const [jwt, sig] = Jwt.decode<NintendoAccountIdTokenJwtPayload | CoralJwtPayload>(data.token);
|
||||
|
||||
const check_signature = jwt.payload.iss === 'https://accounts.nintendo.com';
|
||||
|
||||
if (data.hash_method === '1' && jwt.payload.iss !== 'https://accounts.nintendo.com') {
|
||||
throw new Error('Invalid token issuer');
|
||||
}
|
||||
if (data.hash_method === '1' && jwt.payload.aud !== ZNCA_CLIENT_ID) {
|
||||
throw new Error('Invalid token audience');
|
||||
}
|
||||
if (data.hash_method === '2' && jwt.payload.iss !== 'api-lp1.znc.srv.nintendo.net') {
|
||||
throw new Error('Invalid token issuer');
|
||||
}
|
||||
|
||||
if (jwt.payload.exp <= (Date.now() / 1000)) {
|
||||
throw new Error('Token expired');
|
||||
}
|
||||
|
||||
const jwks = jwt.header.kid &&
|
||||
jwt.header.jku?.match(/^https\:\/\/([^/]+\.)?nintendo\.(com|net)(\/|$)/i) ?
|
||||
await getJwks(jwt.header.jku, storage) : null;
|
||||
|
||||
if (check_signature && !jwks) {
|
||||
throw new Error('Requires signature verification, but trusted JWKS URL and key ID not included in token');
|
||||
}
|
||||
|
||||
const jwk = jwks?.keys.find(jwk => jwk.use === 'sig' && jwk.alg === jwt.header.alg &&
|
||||
jwk.kid === jwt.header.kid && jwk.x5c?.length);
|
||||
const cert = jwk?.x5c?.[0] ? '-----BEGIN CERTIFICATE-----\n' +
|
||||
jwk.x5c[0].match(/.{1,64}/g)!.join('\n') + '\n-----END CERTIFICATE-----\n' : null;
|
||||
|
||||
if (!cert) {
|
||||
if (check_signature) throw new Error('Not verifying signature, no JKW found for this token');
|
||||
else debug('Not verifying signature, no JKW found for this token');
|
||||
}
|
||||
|
||||
const signature_valid = cert && jwt.verify(sig, cert);
|
||||
|
||||
if (check_signature && !signature_valid) {
|
||||
throw new Error('Invalid signature');
|
||||
}
|
||||
|
||||
if (!check_signature) {
|
||||
if (signature_valid) debug('JWT signature is valid');
|
||||
else debug('JWT signature is not valid or not checked');
|
||||
}
|
||||
} catch (err) {
|
||||
if (argv.validateTokens) throw err;
|
||||
debug('Error validating token from %s, continuing anyway', req.ip, err);
|
||||
}
|
||||
|
||||
if (argv.strictValidate && 'timestamp' in data) {
|
||||
if (!timestamp!.match(/^\d+$/)) {
|
||||
throw new Error('Non-numeric timestamp is not likely to be accepted by the Coral API');
|
||||
}
|
||||
|
||||
// For Android the timestamp should be in milliseconds
|
||||
const timestamp_ms = parseInt(timestamp!);
|
||||
const now_ms = Date.now();
|
||||
|
||||
if (timestamp_ms > now_ms + 10000 || timestamp_ms + 10000 < now_ms) {
|
||||
throw new Error('Timestamp not matching the Android device is not likely to be accepted by the Coral API');
|
||||
}
|
||||
}
|
||||
|
||||
if (argv.strictValidate && 'request_id' in data) {
|
||||
// For Android the request_id should be lowercase hex
|
||||
if (!request_id!.match(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/)) {
|
||||
throw new Error('Request ID not a valid lowercase-hex v4 UUID is not likely to be accepted by the Coral API');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
debug('Error validating request from %s', req.ip, err);
|
||||
res.statusCode = 400;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Server-Timing', 'validate;dur=' + (Date.now() - start));
|
||||
res.end(JSON.stringify({error: 'invalid_request', error_message: (err as Error)?.message}));
|
||||
return;
|
||||
}
|
||||
|
||||
const validated = Date.now();
|
||||
|
||||
const handle = async () => {
|
||||
const was_connected = !ready;
|
||||
await ready;
|
||||
const connected = Date.now();
|
||||
|
||||
debugApi('Calling %s', data.hash_method === '2' ? 'genAudioH2' : 'genAudioH');
|
||||
|
||||
const result = data.hash_method === '2' ?
|
||||
await api.genAudioH2(data.token, timestamp, request_id) :
|
||||
await api.genAudioH(data.token, timestamp, request_id);
|
||||
|
||||
debugApi('Returned %s', result);
|
||||
|
||||
const response = {
|
||||
f: result.f,
|
||||
timestamp: data.timestamp ? undefined : result.timestamp,
|
||||
request_id: data.request_id ? undefined : request_id,
|
||||
};
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Server-Timing',
|
||||
'validate;dur=' + (validated - start) + ',' +
|
||||
(!was_connected ? 'attach;dur=' + (connected - validated) + ',' : '') +
|
||||
'queue;dur=' + result.dw + ',' +
|
||||
'init;dur=' + result.di + ',' +
|
||||
'process;dur=' + result.dp);
|
||||
res.end(JSON.stringify(response));
|
||||
};
|
||||
|
||||
try {
|
||||
await handle();
|
||||
} catch (err) {
|
||||
if ((err as any)?.message === 'Script is destroyed') {
|
||||
debugApi('Error in request from %s, retrying', req.ip, err);
|
||||
|
||||
reattach();
|
||||
|
||||
try {
|
||||
await handle();
|
||||
} catch (err) {
|
||||
debugApi('Error in request from %s', req.ip, err);
|
||||
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({error: 'unknown'}));
|
||||
|
||||
if ((err as any)?.message === 'Script is destroyed') {
|
||||
reattach();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debugApi('Error in request from %s', req.ip, err);
|
||||
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({error: 'unknown'}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (const address of argv.listen) {
|
||||
const [host, port] = parseListenAddress(address);
|
||||
const server = app.listen(port, host ?? '::');
|
||||
server.on('listening', () => {
|
||||
const address = server.address() as net.AddressInfo;
|
||||
console.log('Listening on %s, port %d', address.address, address.port);
|
||||
});
|
||||
}
|
||||
|
||||
setInterval(async () => {
|
||||
try {
|
||||
await api.ping();
|
||||
} catch (err) {
|
||||
if ((err as any)?.message === 'Script is destroyed') {
|
||||
reattach();
|
||||
return;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
debug('System info', system_info);
|
||||
debug('Package info', package_info);
|
||||
|
||||
try {
|
||||
debug('Test gen_audio_h');
|
||||
const result = await api.genAudioH('id_token', 'timestamp', 'request_id');
|
||||
debug('Test returned', result);
|
||||
} catch (err) {
|
||||
debug('Test failed', err);
|
||||
}
|
||||
}
|
||||
|
||||
const frida_script = `
|
||||
const perform = callback => new Promise((rs, rj) => {
|
||||
Java.perform(() => {
|
||||
Java.scheduleOnMainThread(() => {
|
||||
try {
|
||||
rs(callback());
|
||||
} catch (err) {
|
||||
rj(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
rpc.exports = {
|
||||
ping() {
|
||||
return true;
|
||||
},
|
||||
getPackageInfo() {
|
||||
return perform(() => {
|
||||
const context = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
|
||||
|
||||
const info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
||||
|
||||
return {
|
||||
name: info.packageName.value,
|
||||
version: info.versionName.value,
|
||||
build: info.versionCode.value,
|
||||
// build: info.getLongVersionCode(),
|
||||
};
|
||||
});
|
||||
},
|
||||
getSystemInfo() {
|
||||
return perform(() => {
|
||||
const Build = Java.use('android.os.Build');
|
||||
const Version = Java.use('android.os.Build$VERSION');
|
||||
|
||||
return {
|
||||
board: Build.BOARD.value,
|
||||
bootloader: Build.BOOTLOADER.value,
|
||||
brand: Build.BRAND.value,
|
||||
abis: Build.SUPPORTED_ABIS.value,
|
||||
device: Build.DEVICE.value,
|
||||
display: Build.DISPLAY.value,
|
||||
fingerprint: Build.FINGERPRINT.value,
|
||||
hardware: Build.HARDWARE.value,
|
||||
host: Build.HOST.value,
|
||||
id: Build.ID.value,
|
||||
manufacturer: Build.MANUFACTURER.value,
|
||||
model: Build.MODEL.value,
|
||||
product: Build.PRODUCT.value,
|
||||
tags: Build.TAGS.value,
|
||||
time: Build.TIME.value,
|
||||
type: Build.TYPE.value,
|
||||
user: Build.USER.value,
|
||||
|
||||
version: {
|
||||
codename: Version.CODENAME.value,
|
||||
release: Version.RELEASE.value,
|
||||
sdk: Version.SDK.value,
|
||||
sdk_int: Version.SDK_INT.value,
|
||||
security_patch: Version.SECURITY_PATCH.value,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
genAudioH(token, timestamp, request_id) {
|
||||
const called = Date.now();
|
||||
|
||||
return perform(() => {
|
||||
const start = Date.now();
|
||||
|
||||
const libvoip = Java.use('com.nintendo.coral.core.services.voip.LibvoipJni');
|
||||
const context = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
|
||||
libvoip.init(context);
|
||||
|
||||
if (!timestamp) timestamp = Date.now();
|
||||
|
||||
const init = Date.now();
|
||||
const f = libvoip.genAudioH(token, '' + timestamp, request_id);
|
||||
const end = Date.now();
|
||||
|
||||
return {
|
||||
f, timestamp,
|
||||
dw: start - called,
|
||||
di: init - start,
|
||||
dp: end - init,
|
||||
};
|
||||
});
|
||||
},
|
||||
genAudioH2(token, timestamp, request_id) {
|
||||
const called = Date.now();
|
||||
|
||||
return perform(() => {
|
||||
const start = Date.now();
|
||||
|
||||
const libvoip = Java.use('com.nintendo.coral.core.services.voip.LibvoipJni');
|
||||
const context = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
|
||||
libvoip.init(context);
|
||||
|
||||
if (!timestamp) timestamp = Date.now();
|
||||
|
||||
const init = Date.now();
|
||||
const f = libvoip.genAudioH2(token, '' + timestamp, request_id);
|
||||
const end = Date.now();
|
||||
|
||||
return {
|
||||
f, timestamp,
|
||||
dw: start - called,
|
||||
di: init - start,
|
||||
dp: end - init,
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
`;
|
||||
|
||||
const setup_script = (options: {
|
||||
frida_server_path: string;
|
||||
start_method: StartMethod;
|
||||
}) => `#!/system/bin/sh
|
||||
|
||||
if [ "\`id -u\`" != "0" ]; then
|
||||
echo ""
|
||||
echo "-- Not running as root, this will not work --"
|
||||
echo "Use --adb-root to restart adbd as root (or run adb root manually) or use --exec-command to specify a su-like command to escalate to root."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Ensure frida-server is running
|
||||
echo "Running frida-server"
|
||||
killall ${JSON.stringify(path.basename(options.frida_server_path))}
|
||||
nohup ${JSON.stringify(options.frida_server_path)} >/dev/null 2>&1 &
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "Failed to start frida-server"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
|
||||
${(options.start_method === StartMethod.ACTIVITY ? `
|
||||
# Ensure the app is running
|
||||
echo "Starting com.nintendo.znca in foreground"
|
||||
am start-activity com.nintendo.znca/com.nintendo.coral.ui.boot.BootActivity
|
||||
` : options.start_method === StartMethod.SERVICE ? `
|
||||
# Ensure the app is running
|
||||
echo "Starting com.nintendo.znca"
|
||||
am start-foreground-service com.nintendo.znca/com.google.firebase.messaging.FirebaseMessagingService
|
||||
am start-service com.nintendo.znca/com.google.firebase.messaging.FirebaseMessagingService
|
||||
` : '').trim()}
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "Failed to start com.nintendo.znca"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Acquiring wake lock"
|
||||
echo androidzncaapiserver > /sys/power/wake_lock
|
||||
|
||||
exit 0
|
||||
`;
|
||||
|
||||
const shutdown_script = `#!/system/bin/sh
|
||||
|
||||
echo "Releasing wake lock"
|
||||
echo androidzncaapiserver > /sys/power/wake_unlock
|
||||
|
||||
exit 0
|
||||
`;
|
||||
|
||||
async function setup(argv: ArgumentsCamelCase<Arguments>, start_method: StartMethod) {
|
||||
debug('Connecting to device %s', argv.device);
|
||||
let co = execFileSync(argv.adbPath ?? 'adb', [
|
||||
'connect',
|
||||
argv.device,
|
||||
], {
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
while (co.toString().includes('failed to authenticate')) {
|
||||
console.log('');
|
||||
console.log('-- Allow this computer to connect to the device. --');
|
||||
console.log('');
|
||||
await new Promise(rs => setTimeout(rs, 5 * 1000));
|
||||
|
||||
execAdb([
|
||||
'disconnect',
|
||||
argv.device,
|
||||
], argv.adbPath);
|
||||
|
||||
debug('Connecting to device %s', argv.device);
|
||||
co = execFileSync(argv.adbPath ?? 'adb', [
|
||||
'connect',
|
||||
argv.device,
|
||||
], {
|
||||
windowsHide: true,
|
||||
});
|
||||
}
|
||||
|
||||
debug('Pushing scripts');
|
||||
|
||||
await pushScript(argv.device, setup_script({
|
||||
frida_server_path: argv.fridaServerPath,
|
||||
start_method,
|
||||
}), '/data/local/tmp/android-znca-api-server-setup.sh', argv.adbPath);
|
||||
await pushScript(argv.device, shutdown_script, '/data/local/tmp/android-znca-api-server-shutdown.sh', argv.adbPath);
|
||||
}
|
||||
|
||||
async function attach(argv: ArgumentsCamelCase<Arguments>, start_method: StartMethod) {
|
||||
const frida = await import('frida');
|
||||
type Session = import('frida').Session;
|
||||
|
||||
if (argv.adbRoot) {
|
||||
debug('Restarting adbd as root');
|
||||
|
||||
execAdb([
|
||||
'root',
|
||||
], argv.adbPath, argv.device);
|
||||
}
|
||||
|
||||
debug('Running scripts');
|
||||
execScript(argv.device, '/data/local/tmp/android-znca-api-server-setup.sh', argv.execCommand, argv.adbPath);
|
||||
|
||||
debug('Done');
|
||||
|
||||
const device = await frida.getDevice(argv.device);
|
||||
debug('Connected to frida device %s', device.name);
|
||||
|
||||
let session: Session;
|
||||
|
||||
try {
|
||||
const process = start_method === StartMethod.SPAWN ?
|
||||
{pid: await device.spawn('com.nintendo.znca')} :
|
||||
await device.getProcess('Nintendo Switch Online');
|
||||
|
||||
debug('process', process);
|
||||
|
||||
session = await device.attach(process.pid);
|
||||
|
||||
if (start_method === StartMethod.SPAWN) {
|
||||
await device.resume(session.pid);
|
||||
}
|
||||
} catch (err) {
|
||||
debug('Could not attach to process', err);
|
||||
throw new Error('Failed to attach to process');
|
||||
}
|
||||
|
||||
debug('Attached to app process, pid %d', session.pid);
|
||||
|
||||
const script = await session.createScript(frida_script);
|
||||
await script.load();
|
||||
|
||||
return {session, script};
|
||||
}
|
||||
|
||||
function execAdb(args: string[], adb_path?: string, device?: string) {
|
||||
execFileSync(adb_path ?? 'adb', device ? ['-s', device, ...args] : args, {
|
||||
stdio: 'inherit',
|
||||
windowsHide: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function getScriptPath(content: string) {
|
||||
const filename = path.join(script_dir, crypto.createHash('sha256').update(content).digest('hex') + '.sh');
|
||||
|
||||
await fs.writeFile(filename, content);
|
||||
await fs.chmod(filename, 0o755);
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
async function pushScript(device: string, content: string, path: string, adb_path?: string) {
|
||||
const filename = await getScriptPath(content);
|
||||
|
||||
debug('Pushing script', path, filename);
|
||||
|
||||
execAdb([
|
||||
'push',
|
||||
filename,
|
||||
path,
|
||||
], adb_path, device);
|
||||
|
||||
execAdb([
|
||||
'shell',
|
||||
'chmod 755 ' + JSON.stringify(path),
|
||||
], adb_path, device);
|
||||
}
|
||||
|
||||
function execScript(device: string, path: string, exec_command?: string, adb_path?: string) {
|
||||
const command = exec_command ?
|
||||
exec_command.replace('{cmd}', JSON.stringify(path)) :
|
||||
path;
|
||||
|
||||
debug('Running script', command);
|
||||
|
||||
execAdb([
|
||||
'shell',
|
||||
command,
|
||||
], adb_path, device);
|
||||
console.log('This command is now part of a separate package available at https://gitlab.fancy.org.uk/samuel/nxapi-znca-api or https://github.com/samuelthomas2774/nxapi-znca-api.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user