forked from master131/iFakeLocation
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor image mounter and location simulation
- Loading branch information
Showing
8 changed files
with
823 additions
and
709 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
using System; | ||
using System.Globalization; | ||
using System.Text; | ||
using iMobileDevice; | ||
using iMobileDevice.iDevice; | ||
using iMobileDevice.Lockdown; | ||
using iMobileDevice.Service; | ||
|
||
namespace iFakeLocation.Services.Location | ||
{ | ||
internal class DtSimulateLocation : LocationService | ||
{ | ||
public DtSimulateLocation(DeviceInformation device) : base(device) { | ||
} | ||
|
||
private static byte[] ToBytesBE(int i) { | ||
var b = BitConverter.GetBytes((uint) i); | ||
if (BitConverter.IsLittleEndian) Array.Reverse(b); | ||
return b; | ||
} | ||
|
||
public override void SetLocation(PointLatLng? target) { | ||
iDeviceHandle deviceHandle = null; | ||
LockdownClientHandle lockdownHandle = null; | ||
LockdownServiceDescriptorHandle simulateDescriptor = null; | ||
ServiceClientHandle serviceClientHandle = null; | ||
|
||
var idevice = LibiMobileDevice.Instance.iDevice; | ||
var lockdown = LibiMobileDevice.Instance.Lockdown; | ||
var service = LibiMobileDevice.Instance.Service; | ||
|
||
try { | ||
// Get device handle | ||
var err = idevice.idevice_new_with_options(out deviceHandle, _device.UDID, (int) (_device.IsNetwork ? iDeviceOptions.LookupNetwork : iDeviceOptions.LookupUsbmux)); | ||
if (err != iDeviceError.Success) | ||
throw new Exception("Unable to connect to the device. Make sure it is connected."); | ||
|
||
// Obtain a lockdown client handle | ||
if (lockdown.lockdownd_client_new_with_handshake(deviceHandle, out lockdownHandle, "iFakeLocation") != | ||
LockdownError.Success) | ||
throw new Exception("Unable to connect to lockdownd."); | ||
|
||
// Start the simulatelocation service | ||
if (lockdown.lockdownd_start_service(lockdownHandle, "com.apple.dt.simulatelocation", | ||
out simulateDescriptor) != LockdownError.Success || | ||
simulateDescriptor.IsInvalid) | ||
throw new Exception("Unable to start simulatelocation service."); | ||
|
||
// Create new service client | ||
if (service.service_client_new(deviceHandle, simulateDescriptor, out serviceClientHandle) != | ||
ServiceError.Success) | ||
throw new Exception("Unable to create simulatelocation service client."); | ||
|
||
if (!target.HasValue) { | ||
// Send stop | ||
var stopMessage = ToBytesBE(1); // 0x1 (32-bit big-endian uint) | ||
uint sent = 0; | ||
if (service.service_send(serviceClientHandle, stopMessage, (uint) stopMessage.Length, ref sent) != | ||
ServiceError.Success) | ||
throw new Exception("Unable to send stop message to device."); | ||
} | ||
else { | ||
// Send start | ||
var startMessage = ToBytesBE(0); // 0x0 (32-bit big-endian uint) | ||
var lat = Encoding.ASCII.GetBytes(target.Value.Lat.ToString(CultureInfo.InvariantCulture)); | ||
var lng = Encoding.ASCII.GetBytes(target.Value.Lng.ToString(CultureInfo.InvariantCulture)); | ||
var latLen = ToBytesBE(lat.Length); | ||
var lngLen = ToBytesBE(lng.Length); | ||
uint sent = 0; | ||
|
||
if (service.service_send(serviceClientHandle, startMessage, (uint) startMessage.Length, ref sent) != | ||
ServiceError.Success || | ||
service.service_send(serviceClientHandle, latLen, (uint) latLen.Length, ref sent) != | ||
ServiceError.Success || | ||
service.service_send(serviceClientHandle, lat, (uint) lat.Length, ref sent) != | ||
ServiceError.Success || | ||
service.service_send(serviceClientHandle, lngLen, (uint) lngLen.Length, ref sent) != | ||
ServiceError.Success || | ||
service.service_send(serviceClientHandle, lng, (uint) lng.Length, ref sent) != | ||
ServiceError.Success) { | ||
throw new Exception("Unable to send co-ordinates to device."); | ||
} | ||
} | ||
} | ||
finally { | ||
// Cleanup | ||
if (serviceClientHandle != null) | ||
serviceClientHandle.Close(); | ||
|
||
if (simulateDescriptor != null) | ||
simulateDescriptor.Close(); | ||
|
||
if (lockdownHandle != null) | ||
lockdownHandle.Close(); | ||
|
||
if (deviceHandle != null) | ||
deviceHandle.Close(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace iFakeLocation.Services.Location | ||
{ | ||
internal class DvtSimulateLocation : LocationService | ||
{ | ||
public DvtSimulateLocation(DeviceInformation device) : base(device) { | ||
} | ||
|
||
public override void SetLocation(PointLatLng? target) { | ||
|
||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace iFakeLocation.Services.Location | ||
{ | ||
internal abstract class LocationService | ||
{ | ||
protected readonly DeviceInformation _device; | ||
|
||
protected LocationService(DeviceInformation device) { | ||
_device = device; | ||
} | ||
|
||
public abstract void SetLocation(PointLatLng? target); | ||
} | ||
} |
222 changes: 222 additions & 0 deletions
222
iFakeLocation/Services/Mount/DeveloperDiskImageMounter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
using System; | ||
using System.Collections.ObjectModel; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
using iMobileDevice; | ||
using iMobileDevice.Afc; | ||
using iMobileDevice.iDevice; | ||
using iMobileDevice.Lockdown; | ||
using iMobileDevice.MobileImageMounter; | ||
using iMobileDevice.Plist; | ||
|
||
namespace iFakeLocation.Services.Mount | ||
{ | ||
internal class DeveloperDiskImageMounter : MobileImageMounter | ||
{ | ||
|
||
public DeveloperDiskImageMounter(DeviceInformation device) : base(device) { | ||
} | ||
|
||
private enum DiskImageUploadMode { | ||
AFC, | ||
UploadImage | ||
} | ||
|
||
private static readonly MobileImageMounterUploadCallBack MounterUploadCallback = MounterReadCallback; | ||
|
||
private static int MounterReadCallback(IntPtr buffer, uint size, IntPtr userData) { | ||
var imageStream = (FileStream) GCHandle.FromIntPtr(userData).Target; | ||
var buf = new byte[size]; | ||
var rl = imageStream.Read(buf, 0, buf.Length); | ||
Marshal.Copy(buf, 0, buffer, buf.Length); | ||
return rl; | ||
} | ||
|
||
public override void EnableDeveloperMode(string[] resourcePaths) { | ||
EnableDeveloperMode(resourcePaths[0], resourcePaths[1]); | ||
} | ||
|
||
private void EnableDeveloperMode(string deviceImagePath, string deviceImageSignaturePath) { | ||
if (!File.Exists(deviceImagePath) || !File.Exists(deviceImageSignaturePath)) | ||
throw new FileNotFoundException("The specified device image files do not exist."); | ||
|
||
iDeviceHandle deviceHandle = null; | ||
LockdownClientHandle lockdownHandle = null; | ||
LockdownServiceDescriptorHandle serviceDescriptor = null; | ||
MobileImageMounterClientHandle mounterHandle = null; | ||
AfcClientHandle afcHandle = null; | ||
PlistHandle plistHandle = null; | ||
FileStream imageStream = null; | ||
|
||
// Use upload image for iOS 7 and above, otherwise use AFC | ||
DiskImageUploadMode mode = int.Parse(((string) _device.Properties["ProductVersion"]).Split('.')[0]) >= 7 | ||
? DiskImageUploadMode.UploadImage | ||
: DiskImageUploadMode.AFC; | ||
|
||
var idevice = LibiMobileDevice.Instance.iDevice; | ||
var lockdown = LibiMobileDevice.Instance.Lockdown; | ||
var mounter = LibiMobileDevice.Instance.MobileImageMounter; | ||
var afc = LibiMobileDevice.Instance.Afc; | ||
|
||
try { | ||
// Get device handle | ||
if (idevice.idevice_new_with_options(out deviceHandle, _device.UDID, (int) (_device.IsNetwork ? iDeviceOptions.LookupNetwork : iDeviceOptions.LookupUsbmux)) != iDeviceError.Success) | ||
throw new Exception("Unable to open device, is it connected?"); | ||
|
||
// Get lockdownd handle | ||
if (lockdown.lockdownd_client_new_with_handshake(deviceHandle, out lockdownHandle, "iFakeLocation") != | ||
LockdownError.Success) | ||
throw new Exception("Unable to connect to lockdownd."); | ||
|
||
// Start image mounter service | ||
if (lockdown.lockdownd_start_service(lockdownHandle, "com.apple.mobile.mobile_image_mounter", | ||
out serviceDescriptor) != LockdownError.Success) | ||
throw new Exception("Unable to start the mobile image mounter service."); | ||
|
||
// Create mounter instance | ||
if (mounter.mobile_image_mounter_new(deviceHandle, serviceDescriptor, out mounterHandle) != | ||
MobileImageMounterError.Success) | ||
throw new Exception("Unable to create mobile image mounter instance."); | ||
|
||
// Close service descriptor | ||
serviceDescriptor.Close(); | ||
serviceDescriptor = null; | ||
|
||
// Start the AFC service | ||
if (mode == DiskImageUploadMode.AFC) { | ||
if (lockdown.lockdownd_start_service(lockdownHandle, "com.apple.afc", out serviceDescriptor) != | ||
LockdownError.Success) | ||
throw new Exception("Unable to start AFC service."); | ||
|
||
if (afc.afc_client_new(deviceHandle, serviceDescriptor, out afcHandle) != AfcError.Success) | ||
throw new Exception("Unable to connect to AFC service."); | ||
|
||
serviceDescriptor.Close(); | ||
serviceDescriptor = null; | ||
} | ||
|
||
// Close lockdown handle | ||
lockdownHandle.Close(); | ||
lockdownHandle = null; | ||
|
||
// Check if the developer image has already been mounted | ||
const string imageType = "Developer"; | ||
if (mounter.mobile_image_mounter_lookup_image(mounterHandle, imageType, out plistHandle) == | ||
MobileImageMounterError.Success) { | ||
var results = | ||
PlistHelper.ReadPlistDictFromNode(plistHandle, new[] {"ImagePresent", "ImageSignature"}); | ||
|
||
// Some iOS use ImagePresent to verify presence, while others use ImageSignature instead | ||
// Ensure to check the content of the ImageSignature value as iOS 14 returns a value even | ||
// if it is empty. | ||
if ((results.ContainsKey("ImagePresent") && | ||
results["ImagePresent"] is bool && | ||
(bool) results["ImagePresent"]) || | ||
(results.ContainsKey("ImageSignature") && | ||
results["ImageSignature"] is string && | ||
((string)results["ImageSignature"]).IndexOf("<data>", StringComparison.InvariantCulture) >= 0)) | ||
return; | ||
} | ||
|
||
plistHandle.Close(); | ||
plistHandle = null; | ||
|
||
// Configure paths for upload | ||
const string PkgPath = "PublicStaging"; | ||
const string PathPrefix = "/private/var/mobile/Media"; | ||
|
||
var targetName = PkgPath + "/staging.dimage"; | ||
var mountName = PathPrefix + "/" + targetName; | ||
|
||
imageStream = new FileStream(deviceImagePath, FileMode.Open, FileAccess.Read, FileShare.Read); | ||
var sig = File.ReadAllBytes(deviceImageSignaturePath); | ||
|
||
switch (mode) { | ||
case DiskImageUploadMode.UploadImage: | ||
// Create stream for device image and wrap as a pointer for callback | ||
var handle = GCHandle.Alloc(imageStream); | ||
// Upload the image and then free unmanaged wrapper | ||
mounter.mobile_image_mounter_upload_image(mounterHandle, imageType, (uint) imageStream.Length, | ||
sig, (ushort) sig.Length, MounterUploadCallback, GCHandle.ToIntPtr(handle)); | ||
handle.Free(); | ||
break; | ||
case DiskImageUploadMode.AFC: | ||
// Create directory for package | ||
ReadOnlyCollection<string> strs; | ||
if (afc.afc_get_file_info(afcHandle, PkgPath, out strs) != AfcError.Success || | ||
afc.afc_make_directory(afcHandle, PkgPath) != AfcError.Success) | ||
throw new Exception("Unable to create directory '" + PkgPath + "' on the device."); | ||
|
||
// Create the target file | ||
ulong af = 0; | ||
if (afc.afc_file_open(afcHandle, targetName, AfcFileMode.FopenWronly, ref af) != | ||
AfcError.Success) | ||
throw new Exception("Unable to create file '" + targetName + "'."); | ||
|
||
// Read the file in chunks and write via AFC | ||
uint amount = 0; | ||
byte[] buf = new byte[8192]; | ||
do { | ||
amount = (uint) imageStream.Read(buf, 0, buf.Length); | ||
if (amount > 0) { | ||
uint written = 0, total = 0; | ||
while (total < amount) { | ||
// Write and ensure that it succeeded | ||
if (afc.afc_file_write(afcHandle, af, buf, amount, ref written) != | ||
AfcError.Success) { | ||
afc.afc_file_close(afcHandle, af); | ||
throw new Exception("An AFC write error occurred."); | ||
} | ||
|
||
total += written; | ||
} | ||
|
||
if (total != amount) { | ||
afc.afc_file_close(afcHandle, af); | ||
throw new Exception("The developer image was not written completely."); | ||
} | ||
} | ||
} while (amount > 0); | ||
|
||
afc.afc_file_close(afcHandle, af); | ||
break; | ||
} | ||
|
||
// Mount the image | ||
if (mounter.mobile_image_mounter_mount_image(mounterHandle, mountName, sig, (ushort) sig.Length, | ||
imageType, out plistHandle) != MobileImageMounterError.Success) | ||
throw new Exception("Unable to mount developer image."); | ||
|
||
// Parse the plist result | ||
var result = PlistHelper.ReadPlistDictFromNode(plistHandle); | ||
if (!result.ContainsKey("Status") || | ||
result["Status"] as string != "Complete") | ||
throw new Exception("Mount failed with status: " + | ||
(result.ContainsKey("Status") ? result["Status"] : "N/A") + " and error: " + | ||
(result.ContainsKey("Error") ? result["Error"] : "N/A")); | ||
} | ||
finally { | ||
if (imageStream != null) | ||
imageStream.Close(); | ||
|
||
if (plistHandle != null) | ||
plistHandle.Close(); | ||
|
||
if (afcHandle != null) | ||
afcHandle.Close(); | ||
|
||
if (mounterHandle != null) | ||
mounterHandle.Close(); | ||
|
||
if (serviceDescriptor != null) | ||
serviceDescriptor.Close(); | ||
|
||
if (lockdownHandle != null) | ||
lockdownHandle.Close(); | ||
|
||
if (deviceHandle != null) | ||
deviceHandle.Close(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
| ||
namespace iFakeLocation.Services.Mount | ||
{ | ||
internal abstract class MobileImageMounter | ||
{ | ||
protected readonly DeviceInformation _device; | ||
|
||
protected MobileImageMounter(DeviceInformation device) { | ||
_device = device; | ||
} | ||
|
||
public abstract void EnableDeveloperMode(string[] resourcePaths); | ||
} | ||
} |
Oops, something went wrong.