fix gitignore again

This commit is contained in:
olcxja 2026-05-10 16:36:35 +02:00
commit 5da5c2afe2
3329 changed files with 364540 additions and 3 deletions

View file

@ -0,0 +1,164 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AFCClient = void 0;
const Debug = require("debug");
const fs = require("fs");
const path = require("path");
const util_1 = require("util");
const afc_1 = require("../protocol/afc");
const client_1 = require("./client");
const debug = Debug('native-run:ios:lib:client:afc');
const MAX_OPEN_FILES = 240;
class AFCClient extends client_1.ServiceClient {
constructor(socket) {
super(socket, new afc_1.AFCProtocolClient(socket));
this.socket = socket;
}
async getFileInfo(path) {
debug(`getFileInfo: ${path}`);
const resp = await this.protocolClient.sendMessage({
operation: afc_1.AFC_OPS.GET_FILE_INFO,
data: toCString(path),
});
const strings = [];
let currentString = '';
const tokens = resp.data;
tokens.forEach((token) => {
if (token === 0) {
strings.push(currentString);
currentString = '';
}
else {
currentString += String.fromCharCode(token);
}
});
return strings;
}
async writeFile(fd, data) {
debug(`writeFile: ${Array.prototype.toString.call(fd)}`);
return this.protocolClient.sendMessage({
operation: afc_1.AFC_OPS.FILE_WRITE,
data: fd,
payload: data,
});
}
async openFile(path) {
debug(`openFile: ${path}`);
// mode + path + null terminator
const data = Buffer.alloc(8 + Buffer.byteLength(path) + 1);
// write mode
data.writeUInt32LE(afc_1.AFC_FILE_OPEN_FLAGS.WRONLY, 0);
// then path to file
toCString(path).copy(data, 8);
const resp = await this.protocolClient.sendMessage({
operation: afc_1.AFC_OPS.FILE_OPEN,
data,
});
if (resp.operation === afc_1.AFC_OPS.FILE_OPEN_RES) {
return resp.data;
}
throw new Error(`There was an unknown error opening file ${path}, response: ${Array.prototype.toString.call(resp.data)}`);
}
async closeFile(fd) {
debug(`closeFile fd: ${Array.prototype.toString.call(fd)}`);
return this.protocolClient.sendMessage({
operation: afc_1.AFC_OPS.FILE_CLOSE,
data: fd,
});
}
async uploadFile(srcPath, destPath) {
debug(`uploadFile: ${srcPath}`);
// read local file and get fd of destination
const [srcFile, destFile] = await Promise.all([
await (0, util_1.promisify)(fs.readFile)(srcPath),
await this.openFile(destPath),
]);
try {
await this.writeFile(destFile, srcFile);
await this.closeFile(destFile);
}
catch (err) {
await this.closeFile(destFile);
throw err;
}
}
async makeDirectory(path) {
debug(`makeDirectory: ${path}`);
return this.protocolClient.sendMessage({
operation: afc_1.AFC_OPS.MAKE_DIR,
data: toCString(path),
});
}
async uploadDirectory(srcPath, destPath) {
debug(`uploadDirectory: ${srcPath}`);
await this.makeDirectory(destPath);
// AFC doesn't seem to give out more than 240 file handles,
// so we delay any requests that would push us over until more open up
let numOpenFiles = 0;
const pendingFileUploads = [];
const _this = this;
return uploadDir(srcPath);
async function uploadDir(dirPath) {
const promises = [];
for (const file of fs.readdirSync(dirPath)) {
const filePath = path.join(dirPath, file);
const remotePath = path.join(destPath, path.relative(srcPath, filePath));
if (fs.lstatSync(filePath).isDirectory()) {
promises.push(_this.makeDirectory(remotePath).then(() => uploadDir(filePath)));
}
else {
// Create promise to add to promises array
// this way it can be resolved once a pending upload has finished
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
promises.push(promise);
// wrap upload in a function in case we need to save it for later
const uploadFile = (tries = 0) => {
numOpenFiles++;
_this
.uploadFile(filePath, remotePath)
.then(() => {
resolve();
numOpenFiles--;
const fn = pendingFileUploads.pop();
if (fn) {
fn();
}
})
.catch((err) => {
// Couldn't get fd for whatever reason, try again
// # of retries is arbitrary and can be adjusted
if (err.status === afc_1.AFC_STATUS.NO_RESOURCES && tries < 10) {
debug(`Received NO_RESOURCES from AFC, retrying ${filePath} upload. ${tries}`);
uploadFile(tries++);
}
else {
numOpenFiles--;
reject(err);
}
});
};
if (numOpenFiles < MAX_OPEN_FILES) {
uploadFile();
}
else {
debug(`numOpenFiles >= ${MAX_OPEN_FILES}, adding to pending queue. Length: ${pendingFileUploads.length}`);
pendingFileUploads.push(uploadFile);
}
}
}
await Promise.all(promises);
}
}
}
exports.AFCClient = AFCClient;
function toCString(s) {
const buf = Buffer.alloc(Buffer.byteLength(s) + 1);
const len = buf.write(s);
buf.writeUInt8(0, len);
return buf;
}

View file

@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResponseError = exports.ServiceClient = void 0;
class ServiceClient {
constructor(socket, protocolClient) {
this.socket = socket;
this.protocolClient = protocolClient;
}
}
exports.ServiceClient = ServiceClient;
class ResponseError extends Error {
constructor(msg, response) {
super(msg);
this.response = response;
}
}
exports.ResponseError = ResponseError;

View file

@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DebugserverClient = void 0;
const Debug = require("debug");
const path = require("path");
const gdb_1 = require("../protocol/gdb");
const client_1 = require("./client");
const debug = Debug('native-run:ios:lib:client:debugserver');
class DebugserverClient extends client_1.ServiceClient {
constructor(socket) {
super(socket, new gdb_1.GDBProtocolClient(socket));
this.socket = socket;
}
async setMaxPacketSize(size) {
return this.sendCommand('QSetMaxPacketSize:', [size.toString()]);
}
async setWorkingDir(workingDir) {
return this.sendCommand('QSetWorkingDir:', [workingDir]);
}
async checkLaunchSuccess() {
return this.sendCommand('qLaunchSuccess', []);
}
async attachByName(name) {
const hexName = Buffer.from(name).toString('hex');
return this.sendCommand(`vAttachName;${hexName}`, []);
}
async continue() {
return this.sendCommand('c', []);
}
halt() {
// ^C
debug('Sending ^C to debugserver');
return this.protocolClient.socket.write('\u0003');
}
async kill() {
const msg = { cmd: 'k', args: [] };
return this.protocolClient.sendMessage(msg, (resp, resolve, reject) => {
this.protocolClient.socket.write('+');
const parts = resp.split(';');
for (const part of parts) {
if (part.includes('description')) {
// description:{hex encoded message like: "Terminated with signal 9"}
resolve(Buffer.from(part.split(':')[1], 'hex').toString('ascii'));
}
}
});
}
// TODO support app args
// https://sourceware.org/gdb/onlinedocs/gdb/Packets.html#Packets
// A arglen,argnum,arg,
async launchApp(appPath, executableName) {
const fullPath = path.join(appPath, executableName);
const hexAppPath = Buffer.from(fullPath).toString('hex');
const appCommand = `A${hexAppPath.length},0,${hexAppPath}`;
return this.sendCommand(appCommand, []);
}
async sendCommand(cmd, args) {
const msg = { cmd, args };
debug(`Sending command: ${cmd}, args: ${args}`);
const resp = await this.protocolClient.sendMessage(msg);
// we need to ACK as well
this.protocolClient.socket.write('+');
return resp;
}
}
exports.DebugserverClient = DebugserverClient;

View file

@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
tslib_1.__exportStar(require("./client"), exports);
tslib_1.__exportStar(require("./afc"), exports);
tslib_1.__exportStar(require("./debugserver"), exports);
tslib_1.__exportStar(require("./installation_proxy"), exports);
tslib_1.__exportStar(require("./lockdownd"), exports);
tslib_1.__exportStar(require("./mobile_image_mounter"), exports);
tslib_1.__exportStar(require("./usbmuxd"), exports);

View file

@ -0,0 +1,72 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstallationProxyClient = void 0;
const Debug = require("debug");
const lockdown_1 = require("../protocol/lockdown");
const client_1 = require("./client");
const debug = Debug('native-run:ios:lib:client:installation_proxy');
function isIPLookupResponse(resp) {
return resp.length && resp[0].LookupResult !== undefined;
}
function isIPInstallPercentCompleteResponse(resp) {
return resp.length && resp[0].PercentComplete !== undefined;
}
function isIPInstallCFBundleIdentifierResponse(resp) {
return resp.length && resp[0].CFBundleIdentifier !== undefined;
}
function isIPInstallCompleteResponse(resp) {
return resp.length && resp[0].Status === 'Complete';
}
class InstallationProxyClient extends client_1.ServiceClient {
constructor(socket) {
super(socket, new lockdown_1.LockdownProtocolClient(socket));
this.socket = socket;
}
async lookupApp(bundleIds, options = {
ReturnAttributes: ['Path', 'Container', 'CFBundleExecutable', 'CFBundleIdentifier'],
ApplicationsType: 'Any',
}) {
debug(`lookupApp, options: ${JSON.stringify(options)}`);
const resp = await this.protocolClient.sendMessage({
Command: 'Lookup',
ClientOptions: {
BundleIDs: bundleIds,
...options,
},
});
if (isIPLookupResponse(resp)) {
return resp[0].LookupResult;
}
else {
throw new client_1.ResponseError(`There was an error looking up app`, resp);
}
}
async installApp(packagePath, bundleId, options = {
ApplicationsType: 'Any',
PackageType: 'Developer',
}) {
debug(`installApp, packagePath: ${packagePath}, bundleId: ${bundleId}`);
return this.protocolClient.sendMessage({
Command: 'Install',
PackagePath: packagePath,
ClientOptions: {
CFBundleIdentifier: bundleId,
...options,
},
}, (resp, resolve, reject) => {
if (isIPInstallCompleteResponse(resp)) {
resolve();
}
else if (isIPInstallPercentCompleteResponse(resp)) {
debug(`Installation status: ${resp[0].Status}, %${resp[0].PercentComplete}`);
}
else if (isIPInstallCFBundleIdentifierResponse(resp)) {
debug(`Installed app: ${resp[0].CFBundleIdentifier}`);
}
else {
reject(new client_1.ResponseError('There was an error installing app', resp));
}
});
}
}
exports.InstallationProxyClient = InstallationProxyClient;

View file

@ -0,0 +1,111 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LockdowndClient = void 0;
const Debug = require("debug");
const tls = require("tls");
const lockdown_1 = require("../protocol/lockdown");
const client_1 = require("./client");
const debug = Debug('native-run:ios:lib:client:lockdownd');
function isLockdowndServiceResponse(resp) {
return resp.Request === 'StartService' && resp.Service !== undefined && resp.Port !== undefined;
}
function isLockdowndSessionResponse(resp) {
return resp.Request === 'StartSession';
}
function isLockdowndAllValuesResponse(resp) {
return resp.Request === 'GetValue' && resp.Value !== undefined;
}
function isLockdowndValueResponse(resp) {
return resp.Request === 'GetValue' && resp.Key !== undefined && typeof resp.Value === 'string';
}
function isLockdowndQueryTypeResponse(resp) {
return resp.Request === 'QueryType' && resp.Type !== undefined;
}
class LockdowndClient extends client_1.ServiceClient {
constructor(socket) {
super(socket, new lockdown_1.LockdownProtocolClient(socket));
this.socket = socket;
}
async startService(name) {
debug(`startService: ${name}`);
const resp = await this.protocolClient.sendMessage({
Request: 'StartService',
Service: name,
});
if (isLockdowndServiceResponse(resp)) {
return { port: resp.Port, enableServiceSSL: !!resp.EnableServiceSSL };
}
else {
throw new client_1.ResponseError(`Error starting service ${name}`, resp);
}
}
async startSession(pairRecord) {
debug(`startSession: ${pairRecord}`);
const resp = await this.protocolClient.sendMessage({
Request: 'StartSession',
HostID: pairRecord.HostID,
SystemBUID: pairRecord.SystemBUID,
});
if (isLockdowndSessionResponse(resp)) {
if (resp.EnableSessionSSL) {
this.protocolClient.socket = new tls.TLSSocket(this.protocolClient.socket, {
secureContext: tls.createSecureContext({
secureProtocol: 'TLSv1_2_method',
cert: pairRecord.RootCertificate,
key: pairRecord.RootPrivateKey,
}),
});
debug(`Socket upgraded to TLS connection`);
}
// TODO: save sessionID for StopSession?
}
else {
throw new client_1.ResponseError('Error starting session', resp);
}
}
async getAllValues() {
debug(`getAllValues`);
const resp = await this.protocolClient.sendMessage({ Request: 'GetValue' });
if (isLockdowndAllValuesResponse(resp)) {
return resp.Value;
}
else {
throw new client_1.ResponseError('Error getting lockdown value', resp);
}
}
async getValue(val) {
debug(`getValue: ${val}`);
const resp = await this.protocolClient.sendMessage({
Request: 'GetValue',
Key: val,
});
if (isLockdowndValueResponse(resp)) {
return resp.Value;
}
else {
throw new client_1.ResponseError('Error getting lockdown value', resp);
}
}
async queryType() {
debug('queryType');
const resp = await this.protocolClient.sendMessage({
Request: 'QueryType',
});
if (isLockdowndQueryTypeResponse(resp)) {
return resp.Type;
}
else {
throw new client_1.ResponseError('Error getting lockdown query type', resp);
}
}
async doHandshake(pairRecord) {
debug('doHandshake');
// if (await this.lockdownQueryType() !== 'com.apple.mobile.lockdown') {
// throw new Error('Invalid type received from lockdown handshake');
// }
// await this.getLockdownValue('ProductVersion');
// TODO: validate pair and pair
await this.startSession(pairRecord);
}
}
exports.LockdowndClient = LockdowndClient;

View file

@ -0,0 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MobileImageMounterClient = void 0;
const Debug = require("debug");
const fs = require("fs");
const lockdown_1 = require("../protocol/lockdown");
const client_1 = require("./client");
const debug = Debug('native-run:ios:lib:client:mobile_image_mounter');
function isMIMUploadCompleteResponse(resp) {
return resp.Status === 'Complete';
}
function isMIMUploadReceiveBytesResponse(resp) {
return resp.Status === 'ReceiveBytesAck';
}
class MobileImageMounterClient extends client_1.ServiceClient {
constructor(socket) {
super(socket, new lockdown_1.LockdownProtocolClient(socket));
}
async mountImage(imagePath, imageSig) {
debug(`mountImage: ${imagePath}`);
const resp = await this.protocolClient.sendMessage({
Command: 'MountImage',
ImagePath: imagePath,
ImageSignature: imageSig,
ImageType: 'Developer',
});
if (!(0, lockdown_1.isLockdownResponse)(resp) || resp.Status !== 'Complete') {
throw new client_1.ResponseError(`There was an error mounting ${imagePath} on device`, resp);
}
}
async uploadImage(imagePath, imageSig) {
debug(`uploadImage: ${imagePath}`);
const imageSize = fs.statSync(imagePath).size;
return this.protocolClient.sendMessage({
Command: 'ReceiveBytes',
ImageSize: imageSize,
ImageSignature: imageSig,
ImageType: 'Developer',
}, (resp, resolve, reject) => {
if (isMIMUploadReceiveBytesResponse(resp)) {
const imageStream = fs.createReadStream(imagePath);
imageStream.pipe(this.protocolClient.socket, { end: false });
imageStream.on('error', (err) => reject(err));
}
else if (isMIMUploadCompleteResponse(resp)) {
resolve();
}
else {
reject(new client_1.ResponseError(`There was an error uploading image ${imagePath} to the device`, resp));
}
});
}
async lookupImage() {
debug('lookupImage');
return this.protocolClient.sendMessage({
Command: 'LookupImage',
ImageType: 'Developer',
});
}
}
exports.MobileImageMounterClient = MobileImageMounterClient;

View file

@ -0,0 +1,103 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UsbmuxdClient = void 0;
const Debug = require("debug");
const net = require("net");
const plist = require("plist");
const usbmux_1 = require("../protocol/usbmux");
const client_1 = require("./client");
const debug = Debug('native-run:ios:lib:client:usbmuxd');
function isUsbmuxdConnectResponse(resp) {
return resp.MessageType === 'Result' && resp.Number !== undefined;
}
function isUsbmuxdDeviceResponse(resp) {
return resp.DeviceList !== undefined;
}
function isUsbmuxdPairRecordResponse(resp) {
return resp.PairRecordData !== undefined;
}
class UsbmuxdClient extends client_1.ServiceClient {
constructor(socket) {
super(socket, new usbmux_1.UsbmuxProtocolClient(socket));
this.socket = socket;
}
static connectUsbmuxdSocket() {
debug('connectUsbmuxdSocket');
if ('win32' === process.platform) {
return net.connect({ port: 27015, host: 'localhost' });
}
else {
return net.connect({ path: '/var/run/usbmuxd' });
}
}
async connect(device, port) {
debug(`connect: ${device.DeviceID} on port ${port}`);
const resp = await this.protocolClient.sendMessage({
messageType: 'Connect',
extraFields: {
DeviceID: device.DeviceID,
PortNumber: htons(port),
},
});
if (isUsbmuxdConnectResponse(resp) && resp.Number === 0) {
return this.protocolClient.socket;
}
else {
throw new client_1.ResponseError(`There was an error connecting to ${device.DeviceID} on port ${port}`, resp);
}
}
async getDevices() {
debug('getDevices');
const resp = await this.protocolClient.sendMessage({
messageType: 'ListDevices',
});
if (isUsbmuxdDeviceResponse(resp)) {
return resp.DeviceList;
}
else {
throw new client_1.ResponseError('Invalid response from getDevices', resp);
}
}
async getDevice(udid) {
debug(`getDevice ${udid ? 'udid: ' + udid : ''}`);
const devices = await this.getDevices();
if (!devices.length) {
throw new Error('No devices found');
}
if (!udid) {
return devices[0];
}
for (const device of devices) {
if (device.Properties && device.Properties.SerialNumber === udid) {
return device;
}
}
throw new Error(`No device with udid ${udid} found`);
}
async readPairRecord(udid) {
debug(`readPairRecord: ${udid}`);
const resp = await this.protocolClient.sendMessage({
messageType: 'ReadPairRecord',
extraFields: { PairRecordID: udid },
});
if (isUsbmuxdPairRecordResponse(resp)) {
// the pair record can be created as a binary plist
const BPLIST_MAGIC = Buffer.from('bplist00');
if (BPLIST_MAGIC.compare(resp.PairRecordData, 0, 8) === 0) {
debug('Binary plist pair record detected.');
const bplistParser = await Promise.resolve().then(() => require('bplist-parser'));
return bplistParser.parseBuffer(resp.PairRecordData)[0];
}
else {
return plist.parse(resp.PairRecordData.toString()); // TODO: type guard
}
}
else {
throw new client_1.ResponseError(`There was an error reading pair record for udid: ${udid}`, resp);
}
}
}
exports.UsbmuxdClient = UsbmuxdClient;
function htons(n) {
return ((n & 0xff) << 8) | ((n >> 8) & 0xff);
}