serve-sim 0.1.25 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/Sources/SimCameraHelper/build.sh +1 -1
- package/Sources/SimCameraHelper/main.m +28 -17
- package/Sources/SimCameraInjector/SimCamFakes.h +86 -0
- package/Sources/SimCameraInjector/SimCamFakes.m +635 -0
- package/Sources/SimCameraInjector/SimCamFrameSource.h +26 -0
- package/Sources/SimCameraInjector/SimCamFrameSource.m +546 -0
- package/Sources/SimCameraInjector/SimCamLog.h +5 -0
- package/Sources/SimCameraInjector/SimCamLog.m +9 -0
- package/Sources/SimCameraInjector/SimCamSwizzles.h +3 -0
- package/Sources/SimCameraInjector/SimCamSwizzles.m +1014 -0
- package/Sources/SimCameraInjector/SimCameraInjector.m +9 -1150
- package/Sources/SimCameraInjector/build.sh +6 -1
- package/bin/serve-sim-bin +0 -0
- package/dist/middleware.js +18 -18
- package/dist/serve-sim.js +31 -31
- package/dist/simcam/libSimCameraInjector.dylib +0 -0
- package/dist/simcam/serve-sim-camera-helper +0 -0
- package/package.json +3 -1
- package/src/middleware.ts +39 -23
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
#import "SimCamSwizzles.h"
|
|
2
|
+
#import "SimCamFakes.h"
|
|
3
|
+
#import "SimCamFrameSource.h"
|
|
4
|
+
#import "SimCamLog.h"
|
|
5
|
+
|
|
6
|
+
#import <AVFoundation/AVFoundation.h>
|
|
7
|
+
#import <CoreImage/CoreImage.h>
|
|
8
|
+
#import <CoreMedia/CoreMedia.h>
|
|
9
|
+
#import <CoreMotion/CoreMotion.h>
|
|
10
|
+
#import <CoreVideo/CoreVideo.h>
|
|
11
|
+
#import <UIKit/UIKit.h>
|
|
12
|
+
#import <objc/runtime.h>
|
|
13
|
+
#import <objc/message.h>
|
|
14
|
+
#include <stdatomic.h>
|
|
15
|
+
#include <execinfo.h>
|
|
16
|
+
#include <dlfcn.h>
|
|
17
|
+
#include <string.h>
|
|
18
|
+
|
|
19
|
+
#pragma mark - Swizzling helpers
|
|
20
|
+
|
|
21
|
+
static BOOL SwizzleClassMethod(Class cls, SEL orig, SEL swiz) {
|
|
22
|
+
Method o = class_getClassMethod(cls, orig);
|
|
23
|
+
Method s = class_getClassMethod(cls, swiz);
|
|
24
|
+
if (!o) {
|
|
25
|
+
simcam_log(@"swizzle FAILED: +[%@ %@] (orig method not found)",
|
|
26
|
+
NSStringFromClass(cls), NSStringFromSelector(orig));
|
|
27
|
+
return NO;
|
|
28
|
+
}
|
|
29
|
+
if (!s) {
|
|
30
|
+
simcam_log(@"swizzle FAILED: +[%@ %@] (replacement %@ not found)",
|
|
31
|
+
NSStringFromClass(cls), NSStringFromSelector(orig),
|
|
32
|
+
NSStringFromSelector(swiz));
|
|
33
|
+
return NO;
|
|
34
|
+
}
|
|
35
|
+
method_exchangeImplementations(o, s);
|
|
36
|
+
return YES;
|
|
37
|
+
}
|
|
38
|
+
static BOOL SwizzleInstanceMethod(Class cls, SEL orig, SEL swiz) {
|
|
39
|
+
Method o = class_getInstanceMethod(cls, orig);
|
|
40
|
+
Method s = class_getInstanceMethod(cls, swiz);
|
|
41
|
+
if (!o) {
|
|
42
|
+
simcam_log(@"swizzle FAILED: -[%@ %@] (orig method not found)",
|
|
43
|
+
NSStringFromClass(cls), NSStringFromSelector(orig));
|
|
44
|
+
return NO;
|
|
45
|
+
}
|
|
46
|
+
if (!s) {
|
|
47
|
+
simcam_log(@"swizzle FAILED: -[%@ %@] (replacement %@ not found)",
|
|
48
|
+
NSStringFromClass(cls), NSStringFromSelector(orig),
|
|
49
|
+
NSStringFromSelector(swiz));
|
|
50
|
+
return NO;
|
|
51
|
+
}
|
|
52
|
+
method_exchangeImplementations(o, s);
|
|
53
|
+
return YES;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#pragma mark - NSNotificationCenter swizzle (suppress AVF runtime error)
|
|
57
|
+
|
|
58
|
+
@interface NSNotificationCenter (SimCam)
|
|
59
|
+
@end
|
|
60
|
+
@implementation NSNotificationCenter (SimCam)
|
|
61
|
+
|
|
62
|
+
- (void)simcam_postNotificationName:(NSNotificationName)name
|
|
63
|
+
object:(id)object
|
|
64
|
+
userInfo:(NSDictionary *)userInfo {
|
|
65
|
+
if (SimCamShouldSwallowAVFRuntimeError(name, object)) {
|
|
66
|
+
SimCamLogSwallowedRuntimeError(@"postNotificationName:object:userInfo:", object, userInfo);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
[self simcam_postNotificationName:name object:object userInfo:userInfo];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void)simcam_postNotificationName:(NSNotificationName)name object:(id)object {
|
|
73
|
+
if (SimCamShouldSwallowAVFRuntimeError(name, object)) {
|
|
74
|
+
SimCamLogSwallowedRuntimeError(@"postNotificationName:object:", object, nil);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
[self simcam_postNotificationName:name object:object];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
- (void)simcam_postNotification:(NSNotification *)note {
|
|
81
|
+
if (SimCamShouldSwallowAVFRuntimeError(note.name, note.object)) {
|
|
82
|
+
SimCamLogSwallowedRuntimeError(@"postNotification:", note.object, note.userInfo);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
[self simcam_postNotification:note];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@end
|
|
89
|
+
|
|
90
|
+
#pragma mark - AVCaptureDevice swizzles
|
|
91
|
+
|
|
92
|
+
@interface AVCaptureDevice (SimCam)
|
|
93
|
+
@end
|
|
94
|
+
@implementation AVCaptureDevice (SimCam)
|
|
95
|
+
+ (AVCaptureDevice *)simcam_defaultDeviceWithDeviceType:(AVCaptureDeviceType)t
|
|
96
|
+
mediaType:(AVMediaType)m
|
|
97
|
+
position:(AVCaptureDevicePosition)p {
|
|
98
|
+
if ([m isEqualToString:AVMediaTypeVideo] || m == nil) {
|
|
99
|
+
AVCaptureDevicePosition resolved =
|
|
100
|
+
(p == AVCaptureDevicePositionBack) ? AVCaptureDevicePositionBack
|
|
101
|
+
: AVCaptureDevicePositionFront;
|
|
102
|
+
simcam_log(@"defaultDeviceWithDeviceType: %@ position: %d → fake",
|
|
103
|
+
t, (int)resolved);
|
|
104
|
+
return SimCamFakeDeviceForPosition(resolved);
|
|
105
|
+
}
|
|
106
|
+
return [self simcam_defaultDeviceWithDeviceType:t mediaType:m position:p];
|
|
107
|
+
}
|
|
108
|
+
+ (NSArray<AVCaptureDevice *> *)simcam_devicesWithMediaType:(AVMediaType)m {
|
|
109
|
+
if ([m isEqualToString:AVMediaTypeVideo]) {
|
|
110
|
+
return @[
|
|
111
|
+
SimCamFakeDeviceForPosition(AVCaptureDevicePositionFront),
|
|
112
|
+
SimCamFakeDeviceForPosition(AVCaptureDevicePositionBack),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
return [self simcam_devicesWithMediaType:m];
|
|
116
|
+
}
|
|
117
|
+
+ (NSArray<AVCaptureDevice *> *)simcam_devices {
|
|
118
|
+
NSArray *real = [self simcam_devices];
|
|
119
|
+
NSArray *fakes = @[
|
|
120
|
+
SimCamFakeDeviceForPosition(AVCaptureDevicePositionFront),
|
|
121
|
+
SimCamFakeDeviceForPosition(AVCaptureDevicePositionBack),
|
|
122
|
+
];
|
|
123
|
+
return [fakes arrayByAddingObjectsFromArray:real ?: @[]];
|
|
124
|
+
}
|
|
125
|
+
@end
|
|
126
|
+
|
|
127
|
+
#pragma mark - AVCaptureDeviceDiscoverySession swizzles
|
|
128
|
+
|
|
129
|
+
@interface AVCaptureDeviceDiscoverySession (SimCam)
|
|
130
|
+
@end
|
|
131
|
+
@implementation AVCaptureDeviceDiscoverySession (SimCam)
|
|
132
|
+
+ (AVCaptureDeviceDiscoverySession *)simcam_discoverySessionWithDeviceTypes:(NSArray<AVCaptureDeviceType> *)types
|
|
133
|
+
mediaType:(AVMediaType)m
|
|
134
|
+
position:(AVCaptureDevicePosition)p {
|
|
135
|
+
AVCaptureDeviceDiscoverySession *real =
|
|
136
|
+
[self simcam_discoverySessionWithDeviceTypes:types mediaType:m position:p];
|
|
137
|
+
if ([m isEqualToString:AVMediaTypeVideo] || m == nil) {
|
|
138
|
+
NSMutableArray *list = [NSMutableArray new];
|
|
139
|
+
if (p == AVCaptureDevicePositionUnspecified || p == AVCaptureDevicePositionFront)
|
|
140
|
+
[list addObject:SimCamFakeDeviceForPosition(AVCaptureDevicePositionFront)];
|
|
141
|
+
if (p == AVCaptureDevicePositionUnspecified || p == AVCaptureDevicePositionBack)
|
|
142
|
+
[list addObject:SimCamFakeDeviceForPosition(AVCaptureDevicePositionBack)];
|
|
143
|
+
@try {
|
|
144
|
+
[real setValue:list forKey:@"devices"];
|
|
145
|
+
} @catch (__unused id e) {
|
|
146
|
+
simcam_log(@"could not override discovery session devices");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return real;
|
|
150
|
+
}
|
|
151
|
+
@end
|
|
152
|
+
|
|
153
|
+
#pragma mark - AVCaptureDeviceInput swizzle
|
|
154
|
+
|
|
155
|
+
@interface AVCaptureDeviceInput (SimCam)
|
|
156
|
+
@end
|
|
157
|
+
@implementation AVCaptureDeviceInput (SimCam)
|
|
158
|
+
- (instancetype)simcam_initWithDevice:(AVCaptureDevice *)device error:(NSError **)err {
|
|
159
|
+
if ([device isKindOfClass:[SimCamFakeDevice class]]) {
|
|
160
|
+
if (err) *err = nil;
|
|
161
|
+
struct objc_super sup = { self, [NSObject class] };
|
|
162
|
+
id obj = ((id (*)(struct objc_super *, SEL))objc_msgSendSuper)(&sup, @selector(init));
|
|
163
|
+
if (obj) {
|
|
164
|
+
SimCamMarkFakeInput(obj, device);
|
|
165
|
+
SimCamSetPosition(obj, device.position);
|
|
166
|
+
SimCamMarkCameraInUse();
|
|
167
|
+
}
|
|
168
|
+
return obj;
|
|
169
|
+
}
|
|
170
|
+
return [self simcam_initWithDevice:device error:err];
|
|
171
|
+
}
|
|
172
|
+
- (AVCaptureDevice *)simcam_device {
|
|
173
|
+
AVCaptureDevice *fake = SimCamFakeInputDevice(self);
|
|
174
|
+
if (fake) return fake;
|
|
175
|
+
return [self simcam_device];
|
|
176
|
+
}
|
|
177
|
+
- (NSArray *)simcam_ports {
|
|
178
|
+
if (SimCamIsFakeInput(self)) return @[];
|
|
179
|
+
return [self simcam_ports];
|
|
180
|
+
}
|
|
181
|
+
@end
|
|
182
|
+
|
|
183
|
+
#pragma mark - AVCaptureSession swizzles
|
|
184
|
+
|
|
185
|
+
static char kSimCamSessionRunningKey;
|
|
186
|
+
static char kSimCamSessionInputsKey;
|
|
187
|
+
static char kSimCamSessionOutputsKey;
|
|
188
|
+
|
|
189
|
+
static NSMutableArray *SimCamSessionTrackedInputs(AVCaptureSession *s) {
|
|
190
|
+
NSMutableArray *arr = objc_getAssociatedObject(s, &kSimCamSessionInputsKey);
|
|
191
|
+
if (!arr) {
|
|
192
|
+
arr = [NSMutableArray new];
|
|
193
|
+
objc_setAssociatedObject(s, &kSimCamSessionInputsKey, arr, OBJC_ASSOCIATION_RETAIN);
|
|
194
|
+
}
|
|
195
|
+
return arr;
|
|
196
|
+
}
|
|
197
|
+
static NSMutableArray *SimCamSessionTrackedOutputs(AVCaptureSession *s) {
|
|
198
|
+
NSMutableArray *arr = objc_getAssociatedObject(s, &kSimCamSessionOutputsKey);
|
|
199
|
+
if (!arr) {
|
|
200
|
+
arr = [NSMutableArray new];
|
|
201
|
+
objc_setAssociatedObject(s, &kSimCamSessionOutputsKey, arr, OBJC_ASSOCIATION_RETAIN);
|
|
202
|
+
}
|
|
203
|
+
return arr;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@interface AVCaptureSession (SimCam)
|
|
207
|
+
@end
|
|
208
|
+
@implementation AVCaptureSession (SimCam)
|
|
209
|
+
- (void)simcam_addInput:(AVCaptureInput *)input {
|
|
210
|
+
if (SimCamIsFakeInput(input)) {
|
|
211
|
+
AVCaptureDevicePosition p = SimCamPositionOf(input);
|
|
212
|
+
SimCamSetPosition(self, p);
|
|
213
|
+
SimCamMarkCameraInUse();
|
|
214
|
+
SimCamMarkSessionUsingFakeCamera(self, YES);
|
|
215
|
+
NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
|
|
216
|
+
if (![tracked containsObject:input]) [tracked addObject:input];
|
|
217
|
+
simcam_log(@"addInput: fake input (%@) — tracked (count=%lu), skipping native add",
|
|
218
|
+
p == AVCaptureDevicePositionBack ? @"back" : @"front",
|
|
219
|
+
(unsigned long)tracked.count);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
[self simcam_addInput:input];
|
|
223
|
+
}
|
|
224
|
+
- (BOOL)simcam_canAddInput:(AVCaptureInput *)input {
|
|
225
|
+
if (SimCamIsFakeInput(input)) return YES;
|
|
226
|
+
return [self simcam_canAddInput:input];
|
|
227
|
+
}
|
|
228
|
+
- (void)simcam_addInputWithNoConnections:(AVCaptureInput *)input {
|
|
229
|
+
if (SimCamIsFakeInput(input)) {
|
|
230
|
+
AVCaptureDevicePosition p = SimCamPositionOf(input);
|
|
231
|
+
SimCamSetPosition(self, p);
|
|
232
|
+
SimCamMarkCameraInUse();
|
|
233
|
+
SimCamMarkSessionUsingFakeCamera(self, YES);
|
|
234
|
+
NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
|
|
235
|
+
if (![tracked containsObject:input]) [tracked addObject:input];
|
|
236
|
+
simcam_log(@"addInputWithNoConnections: fake input (%@) — tracked (count=%lu), skipping native add",
|
|
237
|
+
p == AVCaptureDevicePositionBack ? @"back" : @"front",
|
|
238
|
+
(unsigned long)tracked.count);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
[self simcam_addInputWithNoConnections:input];
|
|
242
|
+
}
|
|
243
|
+
- (void)simcam_removeInput:(AVCaptureInput *)input {
|
|
244
|
+
if (SimCamIsFakeInput(input)) {
|
|
245
|
+
NSMutableArray *tracked = SimCamSessionTrackedInputs(self);
|
|
246
|
+
[tracked removeObject:input];
|
|
247
|
+
if (tracked.count == 0) SimCamMarkSessionUsingFakeCamera(self, NO);
|
|
248
|
+
simcam_log(@"removeInput: fake input — untracked (count=%lu)",
|
|
249
|
+
(unsigned long)tracked.count);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
[self simcam_removeInput:input];
|
|
253
|
+
}
|
|
254
|
+
- (void)simcam_addOutput:(AVCaptureOutput *)output {
|
|
255
|
+
SimCamSetPosition(output, SimCamPositionOf(self));
|
|
256
|
+
SimCamMarkCameraInUse();
|
|
257
|
+
NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
|
|
258
|
+
if (![tracked containsObject:output]) [tracked addObject:output];
|
|
259
|
+
simcam_log(@"addOutput: %@ (intercepted, tracked count=%lu, pos=%d)",
|
|
260
|
+
NSStringFromClass([output class]),
|
|
261
|
+
(unsigned long)tracked.count,
|
|
262
|
+
(int)SimCamPositionOf(self));
|
|
263
|
+
}
|
|
264
|
+
- (BOOL)simcam_canAddOutput:(AVCaptureOutput *)output { return YES; }
|
|
265
|
+
- (void)simcam_addOutputWithNoConnections:(AVCaptureOutput *)output {
|
|
266
|
+
SimCamSetPosition(output, SimCamPositionOf(self));
|
|
267
|
+
SimCamMarkCameraInUse();
|
|
268
|
+
NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
|
|
269
|
+
if (![tracked containsObject:output]) [tracked addObject:output];
|
|
270
|
+
simcam_log(@"addOutputWithNoConnections: %@ (intercepted, tracked count=%lu, pos=%d)",
|
|
271
|
+
NSStringFromClass([output class]),
|
|
272
|
+
(unsigned long)tracked.count,
|
|
273
|
+
(int)SimCamPositionOf(self));
|
|
274
|
+
}
|
|
275
|
+
- (void)simcam_removeOutput:(AVCaptureOutput *)output {
|
|
276
|
+
NSMutableArray *tracked = SimCamSessionTrackedOutputs(self);
|
|
277
|
+
[tracked removeObject:output];
|
|
278
|
+
simcam_log(@"removeOutput: %@ — untracked (count=%lu)",
|
|
279
|
+
NSStringFromClass([output class]), (unsigned long)tracked.count);
|
|
280
|
+
}
|
|
281
|
+
- (void)simcam_beginConfiguration {
|
|
282
|
+
simcam_log(@"beginConfiguration intercepted (session=%p)", self);
|
|
283
|
+
}
|
|
284
|
+
- (void)simcam_commitConfiguration {
|
|
285
|
+
NSUInteger inCount =
|
|
286
|
+
((NSArray *)objc_getAssociatedObject(self, &kSimCamSessionInputsKey)).count;
|
|
287
|
+
NSUInteger outCount =
|
|
288
|
+
((NSArray *)objc_getAssociatedObject(self, &kSimCamSessionOutputsKey)).count;
|
|
289
|
+
simcam_log(@"commitConfiguration intercepted (session=%p, fakeInputs=%lu, fakeOutputs=%lu)",
|
|
290
|
+
self, (unsigned long)inCount, (unsigned long)outCount);
|
|
291
|
+
}
|
|
292
|
+
- (BOOL)simcam_canAddConnection:(AVCaptureConnection *)c { (void)c; return YES; }
|
|
293
|
+
- (void)simcam_addConnection:(AVCaptureConnection *)c {
|
|
294
|
+
simcam_log(@"addConnection intercepted (session=%p, conn=%p)", self, c);
|
|
295
|
+
}
|
|
296
|
+
- (NSArray<AVCaptureInput *> *)simcam_inputs {
|
|
297
|
+
NSMutableArray *tracked = objc_getAssociatedObject(self, &kSimCamSessionInputsKey);
|
|
298
|
+
NSArray *native = [self simcam_inputs];
|
|
299
|
+
if (tracked.count == 0) return native ?: @[];
|
|
300
|
+
if (native.count == 0) return [tracked copy];
|
|
301
|
+
NSMutableArray *merged = [tracked mutableCopy];
|
|
302
|
+
for (AVCaptureInput *n in native) {
|
|
303
|
+
if (![merged containsObject:n]) [merged addObject:n];
|
|
304
|
+
}
|
|
305
|
+
return [merged copy];
|
|
306
|
+
}
|
|
307
|
+
- (NSArray<AVCaptureOutput *> *)simcam_outputs {
|
|
308
|
+
NSMutableArray *tracked = objc_getAssociatedObject(self, &kSimCamSessionOutputsKey);
|
|
309
|
+
NSArray *native = [self simcam_outputs];
|
|
310
|
+
if (tracked.count == 0) return native ?: @[];
|
|
311
|
+
if (native.count == 0) return [tracked copy];
|
|
312
|
+
NSMutableArray *merged = [tracked mutableCopy];
|
|
313
|
+
for (AVCaptureOutput *n in native) {
|
|
314
|
+
if (![merged containsObject:n]) [merged addObject:n];
|
|
315
|
+
}
|
|
316
|
+
return [merged copy];
|
|
317
|
+
}
|
|
318
|
+
- (NSArray<AVCaptureConnection *> *)simcam_connections {
|
|
319
|
+
NSMutableArray *trackedOut = objc_getAssociatedObject(self, &kSimCamSessionOutputsKey);
|
|
320
|
+
NSArray *native = [self simcam_connections];
|
|
321
|
+
if (trackedOut.count == 0) return native ?: @[];
|
|
322
|
+
NSMutableArray *merged = [NSMutableArray arrayWithCapacity:trackedOut.count + native.count];
|
|
323
|
+
for (AVCaptureOutput *o in trackedOut) {
|
|
324
|
+
AVCaptureConnection *c = SimCamFakeConnectionForOutput(o);
|
|
325
|
+
if (c) [merged addObject:c];
|
|
326
|
+
}
|
|
327
|
+
for (AVCaptureConnection *n in native) {
|
|
328
|
+
if (![merged containsObject:n]) [merged addObject:n];
|
|
329
|
+
}
|
|
330
|
+
return [merged copy];
|
|
331
|
+
}
|
|
332
|
+
- (void)simcam_startRunning {
|
|
333
|
+
objc_setAssociatedObject(self, &kSimCamSessionRunningKey, @YES, OBJC_ASSOCIATION_RETAIN);
|
|
334
|
+
SimCamMarkCameraInUse();
|
|
335
|
+
NSUInteger inCount =
|
|
336
|
+
((NSArray *)objc_getAssociatedObject(self, &kSimCamSessionInputsKey)).count;
|
|
337
|
+
NSUInteger outCount =
|
|
338
|
+
((NSArray *)objc_getAssociatedObject(self, &kSimCamSessionOutputsKey)).count;
|
|
339
|
+
simcam_log(@"startRunning intercepted (fake inputs=%lu outputs=%lu)",
|
|
340
|
+
(unsigned long)inCount, (unsigned long)outCount);
|
|
341
|
+
[[SimCamRegistry shared] startPumpingIfNeeded];
|
|
342
|
+
[self willChangeValueForKey:@"running"];
|
|
343
|
+
[self didChangeValueForKey:@"running"];
|
|
344
|
+
AVCaptureSession *strong = self;
|
|
345
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
346
|
+
[[NSNotificationCenter defaultCenter]
|
|
347
|
+
postNotificationName:AVCaptureSessionDidStartRunningNotification
|
|
348
|
+
object:strong];
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
- (void)simcam_stopRunning {
|
|
352
|
+
objc_setAssociatedObject(self, &kSimCamSessionRunningKey, @NO, OBJC_ASSOCIATION_RETAIN);
|
|
353
|
+
simcam_log(@"stopRunning intercepted");
|
|
354
|
+
[self willChangeValueForKey:@"running"];
|
|
355
|
+
[self didChangeValueForKey:@"running"];
|
|
356
|
+
AVCaptureSession *strong = self;
|
|
357
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
358
|
+
[[NSNotificationCenter defaultCenter]
|
|
359
|
+
postNotificationName:AVCaptureSessionDidStopRunningNotification
|
|
360
|
+
object:strong];
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
- (BOOL)simcam_isRunning {
|
|
364
|
+
NSNumber *v = objc_getAssociatedObject(self, &kSimCamSessionRunningKey);
|
|
365
|
+
return v.boolValue;
|
|
366
|
+
}
|
|
367
|
+
@end
|
|
368
|
+
|
|
369
|
+
#pragma mark - AVCaptureVideoDataOutput swizzle
|
|
370
|
+
|
|
371
|
+
@interface AVCaptureVideoDataOutput (SimCam)
|
|
372
|
+
@end
|
|
373
|
+
@implementation AVCaptureVideoDataOutput (SimCam)
|
|
374
|
+
- (void)simcam_setSampleBufferDelegate:(id<AVCaptureVideoDataOutputSampleBufferDelegate>)delegate
|
|
375
|
+
queue:(dispatch_queue_t)queue {
|
|
376
|
+
[self simcam_setSampleBufferDelegate:delegate queue:queue];
|
|
377
|
+
SimCamMarkCameraInUse();
|
|
378
|
+
if (delegate) {
|
|
379
|
+
[[SimCamRegistry shared] addOutput:self delegate:delegate queue:queue];
|
|
380
|
+
} else {
|
|
381
|
+
[[SimCamRegistry shared] removeOutput:self];
|
|
382
|
+
simcam_log(@"setSampleBufferDelegate nil — removed output %p", self);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
@end
|
|
386
|
+
|
|
387
|
+
#pragma mark - AVCaptureVideoPreviewLayer swizzle
|
|
388
|
+
|
|
389
|
+
@interface AVCaptureVideoPreviewLayer (SimCam)
|
|
390
|
+
@end
|
|
391
|
+
@implementation AVCaptureVideoPreviewLayer (SimCam)
|
|
392
|
+
- (void)simcam_setSession:(AVCaptureSession *)session {
|
|
393
|
+
[self simcam_setSession:session];
|
|
394
|
+
AVCaptureDevicePosition p = SimCamPositionOf(session);
|
|
395
|
+
SimCamSetPosition(self, p);
|
|
396
|
+
SimCamMarkCameraInUse();
|
|
397
|
+
[[SimCamRegistry shared] addPreviewLayer:self];
|
|
398
|
+
}
|
|
399
|
+
@end
|
|
400
|
+
|
|
401
|
+
#pragma mark - AVCaptureDeviceFormat private-accessor swizzle
|
|
402
|
+
|
|
403
|
+
@interface AVCaptureDeviceFormat (SimCamPrivate)
|
|
404
|
+
- (id)figCaptureSourceVideoFormat;
|
|
405
|
+
@end
|
|
406
|
+
|
|
407
|
+
@interface AVCaptureDeviceFormat (SimCam)
|
|
408
|
+
@end
|
|
409
|
+
@implementation AVCaptureDeviceFormat (SimCam)
|
|
410
|
+
- (id)simcam_figCaptureSourceVideoFormat {
|
|
411
|
+
if ([self isKindOfClass:[SimCamFakeFormat class]]) return nil;
|
|
412
|
+
return [self simcam_figCaptureSourceVideoFormat];
|
|
413
|
+
}
|
|
414
|
+
@end
|
|
415
|
+
|
|
416
|
+
#pragma mark - AVCaptureOutput connection swizzles
|
|
417
|
+
|
|
418
|
+
@interface AVCaptureOutput (SimCamConn)
|
|
419
|
+
@end
|
|
420
|
+
@implementation AVCaptureOutput (SimCamConn)
|
|
421
|
+
- (AVCaptureConnection *)simcam_connectionWithMediaType:(AVMediaType)mediaType {
|
|
422
|
+
AVCaptureConnection *real = [self simcam_connectionWithMediaType:mediaType];
|
|
423
|
+
if (real) return real;
|
|
424
|
+
if (!SimCamCameraIsInUse()) return nil;
|
|
425
|
+
if (![mediaType isEqualToString:AVMediaTypeVideo]) return nil;
|
|
426
|
+
AVCaptureConnection *fake = SimCamFakeConnectionForOutput(self);
|
|
427
|
+
simcam_log(@"connectionWithMediaType:%@ → fake %p for %@ %p",
|
|
428
|
+
mediaType, fake, NSStringFromClass([self class]), self);
|
|
429
|
+
return fake;
|
|
430
|
+
}
|
|
431
|
+
- (NSArray<AVCaptureConnection *> *)simcam_connections {
|
|
432
|
+
NSArray *real = [self simcam_connections];
|
|
433
|
+
if (real.count > 0) return real;
|
|
434
|
+
if (!SimCamCameraIsInUse()) return real ?: @[];
|
|
435
|
+
AVCaptureConnection *fake = SimCamFakeConnectionForOutput(self);
|
|
436
|
+
return fake ? @[fake] : @[];
|
|
437
|
+
}
|
|
438
|
+
@end
|
|
439
|
+
|
|
440
|
+
#pragma mark - AVCaptureOutput codec enumeration swizzle
|
|
441
|
+
|
|
442
|
+
@interface AVCaptureOutput (SimCamPrivate)
|
|
443
|
+
+ (NSArray<AVVideoCodecType> *)availableVideoCodecTypesForSourceDevice:(AVCaptureDevice *)device
|
|
444
|
+
sourceFormat:(AVCaptureDeviceFormat *)format
|
|
445
|
+
outputDimensions:(CMVideoDimensions)dims
|
|
446
|
+
fileType:(AVFileType)fileType
|
|
447
|
+
videoCodecTypesAllowList:(NSArray<AVVideoCodecType> *)allow;
|
|
448
|
+
@end
|
|
449
|
+
|
|
450
|
+
@interface AVCaptureOutput (SimCam)
|
|
451
|
+
@end
|
|
452
|
+
@implementation AVCaptureOutput (SimCam)
|
|
453
|
+
+ (NSArray<AVVideoCodecType> *)simcam_availableVideoCodecTypesForSourceDevice:(AVCaptureDevice *)device
|
|
454
|
+
sourceFormat:(AVCaptureDeviceFormat *)format
|
|
455
|
+
outputDimensions:(CMVideoDimensions)dims
|
|
456
|
+
fileType:(AVFileType)fileType
|
|
457
|
+
videoCodecTypesAllowList:(NSArray<AVVideoCodecType> *)allow {
|
|
458
|
+
BOOL fakeDevice = [device isKindOfClass:[SimCamFakeDevice class]];
|
|
459
|
+
BOOL fakeFormat = [format isKindOfClass:[SimCamFakeFormat class]];
|
|
460
|
+
BOOL nilArgs = (device == nil) && (format == nil);
|
|
461
|
+
if (fakeDevice || fakeFormat || nilArgs) {
|
|
462
|
+
simcam_log(@"availableVideoCodecTypes intercepted (device=%@ format=%@ allow=%lu)",
|
|
463
|
+
device ? NSStringFromClass([device class]) : @"<nil>",
|
|
464
|
+
format ? NSStringFromClass([format class]) : @"<nil>",
|
|
465
|
+
(unsigned long)allow.count);
|
|
466
|
+
NSArray *defaults = @[ AVVideoCodecTypeJPEG, AVVideoCodecTypeHEVC ];
|
|
467
|
+
if (allow.count == 0) return defaults;
|
|
468
|
+
NSMutableArray *filtered = [NSMutableArray new];
|
|
469
|
+
for (AVVideoCodecType t in defaults) if ([allow containsObject:t]) [filtered addObject:t];
|
|
470
|
+
return filtered.count > 0 ? [filtered copy] : defaults;
|
|
471
|
+
}
|
|
472
|
+
return [self simcam_availableVideoCodecTypesForSourceDevice:device
|
|
473
|
+
sourceFormat:format
|
|
474
|
+
outputDimensions:dims
|
|
475
|
+
fileType:fileType
|
|
476
|
+
videoCodecTypesAllowList:allow];
|
|
477
|
+
}
|
|
478
|
+
@end
|
|
479
|
+
|
|
480
|
+
#pragma mark - AVCapturePhotoOutput swizzle
|
|
481
|
+
|
|
482
|
+
@interface AVCapturePhotoOutput (SimCam)
|
|
483
|
+
@end
|
|
484
|
+
@implementation AVCapturePhotoOutput (SimCam)
|
|
485
|
+
- (void)simcam_capturePhotoWithSettings:(AVCapturePhotoSettings *)settings
|
|
486
|
+
delegate:(id<AVCapturePhotoCaptureDelegate>)delegate {
|
|
487
|
+
if (!delegate) return;
|
|
488
|
+
SimCamRegistry *reg = [SimCamRegistry shared];
|
|
489
|
+
CVPixelBufferRef pb = [reg currentPixelBuffer];
|
|
490
|
+
AVCaptureDevicePosition p = SimCamPositionOf(self);
|
|
491
|
+
if (p == 0) p = AVCaptureDevicePositionFront;
|
|
492
|
+
BOOL mirror = SimCamShouldMirror(p);
|
|
493
|
+
if (SimCamGetMirrorMode() == SimCamMirrorAuto) {
|
|
494
|
+
AVCaptureConnection *conn = [self connectionWithMediaType:AVMediaTypeVideo];
|
|
495
|
+
if (conn && conn.isVideoMirroringSupported && !conn.automaticallyAdjustsVideoMirroring) {
|
|
496
|
+
mirror = conn.isVideoMirrored;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
CGImageRef cg = NULL;
|
|
500
|
+
if (pb) {
|
|
501
|
+
CIImage *ci = [CIImage imageWithCVPixelBuffer:pb];
|
|
502
|
+
if (mirror) ci = [ci imageByApplyingOrientation:kCGImagePropertyOrientationUpMirrored];
|
|
503
|
+
static CIContext *ctx = nil; static dispatch_once_t once;
|
|
504
|
+
dispatch_once(&once, ^{ ctx = [CIContext contextWithOptions:nil]; });
|
|
505
|
+
cg = [ctx createCGImage:ci fromRect:ci.extent];
|
|
506
|
+
CVPixelBufferRelease(pb);
|
|
507
|
+
}
|
|
508
|
+
if (!cg) {
|
|
509
|
+
simcam_log(@"capturePhoto: no source frame, synthesizing 1x1 black");
|
|
510
|
+
size_t bpr = 4;
|
|
511
|
+
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
|
|
512
|
+
CGContextRef bmp = CGBitmapContextCreate(NULL, 1, 1, 8, bpr, cs,
|
|
513
|
+
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little);
|
|
514
|
+
CGContextSetFillColorWithColor(bmp, [UIColor blackColor].CGColor);
|
|
515
|
+
CGContextFillRect(bmp, CGRectMake(0, 0, 1, 1));
|
|
516
|
+
cg = CGBitmapContextCreateImage(bmp);
|
|
517
|
+
CGContextRelease(bmp);
|
|
518
|
+
CGColorSpaceRelease(cs);
|
|
519
|
+
}
|
|
520
|
+
SimCamFakePhoto *photo = [SimCamFakePhoto photoFromImage:cg
|
|
521
|
+
jpegQuality:0.92
|
|
522
|
+
mirrored:mirror];
|
|
523
|
+
if (cg) CGImageRelease(cg);
|
|
524
|
+
AVCaptureResolvedPhotoSettings *resolved = photo.resolvedSettings;
|
|
525
|
+
simcam_log(@"capturePhoto intercepted (pos=%d, mirror=%d, jpeg=%lu bytes, dims=%dx%d)",
|
|
526
|
+
(int)p, (int)mirror, (unsigned long)photo.fileDataRepresentation.length,
|
|
527
|
+
resolved.photoDimensions.width, resolved.photoDimensions.height);
|
|
528
|
+
AVCapturePhotoOutput *output = self;
|
|
529
|
+
SEL selWillBegin = @selector(photoOutput:willBeginCaptureForResolvedSettings:);
|
|
530
|
+
SEL selWillCapture = @selector(photoOutput:willCapturePhotoForResolvedSettings:);
|
|
531
|
+
SEL selDidProcess = @selector(photoOutput:didFinishProcessingPhoto:error:);
|
|
532
|
+
SEL selDidCapture = @selector(photoOutput:didCapturePhotoForResolvedSettings:);
|
|
533
|
+
SEL selDidFinish = @selector(photoOutput:didFinishCaptureForResolvedSettings:error:);
|
|
534
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
535
|
+
if ([delegate respondsToSelector:selWillBegin]) {
|
|
536
|
+
((void (*)(id, SEL, AVCapturePhotoOutput *, AVCaptureResolvedPhotoSettings *))
|
|
537
|
+
objc_msgSend)(delegate, selWillBegin, output, resolved);
|
|
538
|
+
}
|
|
539
|
+
if ([delegate respondsToSelector:selWillCapture]) {
|
|
540
|
+
((void (*)(id, SEL, AVCapturePhotoOutput *, AVCaptureResolvedPhotoSettings *))
|
|
541
|
+
objc_msgSend)(delegate, selWillCapture, output, resolved);
|
|
542
|
+
}
|
|
543
|
+
BOOL delivered = NO;
|
|
544
|
+
if ([delegate respondsToSelector:selDidProcess]) {
|
|
545
|
+
((void (*)(id, SEL, AVCapturePhotoOutput *, AVCapturePhoto *, NSError *))
|
|
546
|
+
objc_msgSend)(delegate, selDidProcess, output, photo, (NSError *)nil);
|
|
547
|
+
delivered = YES;
|
|
548
|
+
}
|
|
549
|
+
if ([delegate respondsToSelector:selDidCapture]) {
|
|
550
|
+
((void (*)(id, SEL, AVCapturePhotoOutput *, AVCaptureResolvedPhotoSettings *))
|
|
551
|
+
objc_msgSend)(delegate, selDidCapture, output, resolved);
|
|
552
|
+
}
|
|
553
|
+
if ([delegate respondsToSelector:selDidFinish]) {
|
|
554
|
+
((void (*)(id, SEL, AVCapturePhotoOutput *, AVCaptureResolvedPhotoSettings *, NSError *))
|
|
555
|
+
objc_msgSend)(delegate, selDidFinish, output, resolved, (NSError *)nil);
|
|
556
|
+
}
|
|
557
|
+
simcam_log(@"capturePhoto lifecycle complete (delivered photo=%d)", (int)delivered);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
@end
|
|
561
|
+
|
|
562
|
+
#pragma mark - NSData write redirect (expo-camera placeholder substitution)
|
|
563
|
+
|
|
564
|
+
static BOOL SimCamLooksLikeCameraDropPath(NSString *path) {
|
|
565
|
+
if (!path.length) return NO;
|
|
566
|
+
if (![path containsString:@"/Camera/"]) return NO;
|
|
567
|
+
NSString *lower = path.lowercaseString;
|
|
568
|
+
return [lower hasSuffix:@".jpg"] || [lower hasSuffix:@".jpeg"];
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
@interface NSData (SimCam)
|
|
572
|
+
@end
|
|
573
|
+
@implementation NSData (SimCam)
|
|
574
|
+
|
|
575
|
+
- (BOOL)simcam_writeToURL:(NSURL *)url
|
|
576
|
+
options:(NSDataWritingOptions)opts
|
|
577
|
+
error:(NSError **)err {
|
|
578
|
+
NSString *path = url.isFileURL ? url.path : nil;
|
|
579
|
+
if (SimCamLooksLikeCameraDropPath(path)) {
|
|
580
|
+
NSData *snap = [[SimCamRegistry shared] currentSnapshotJPEGAtQuality:0.92];
|
|
581
|
+
if (snap.length > 0) {
|
|
582
|
+
simcam_log(@"NSData writeToURL → substituted %lu→%lu bytes (%@)",
|
|
583
|
+
(unsigned long)self.length, (unsigned long)snap.length, path.lastPathComponent);
|
|
584
|
+
return [snap simcam_writeToURL:url options:opts error:err];
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return [self simcam_writeToURL:url options:opts error:err];
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
- (BOOL)simcam_writeToFile:(NSString *)path
|
|
591
|
+
options:(NSDataWritingOptions)opts
|
|
592
|
+
error:(NSError **)err {
|
|
593
|
+
if (SimCamLooksLikeCameraDropPath(path)) {
|
|
594
|
+
NSData *snap = [[SimCamRegistry shared] currentSnapshotJPEGAtQuality:0.92];
|
|
595
|
+
if (snap.length > 0) {
|
|
596
|
+
simcam_log(@"NSData writeToFile → substituted %lu→%lu bytes (%@)",
|
|
597
|
+
(unsigned long)self.length, (unsigned long)snap.length, path.lastPathComponent);
|
|
598
|
+
return [snap simcam_writeToFile:path options:opts error:err];
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return [self simcam_writeToFile:path options:opts error:err];
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
@end
|
|
605
|
+
|
|
606
|
+
#pragma mark - UIGraphicsImageRenderer redirect (camera-placeholder generators)
|
|
607
|
+
|
|
608
|
+
static BOOL SimCamCallerLooksLikeCameraPlaceholder(void) {
|
|
609
|
+
if (!SimCamCameraIsInUse()) return NO;
|
|
610
|
+
|
|
611
|
+
void *frames[12];
|
|
612
|
+
int n = backtrace(frames, 12);
|
|
613
|
+
if (n < 4) return NO;
|
|
614
|
+
|
|
615
|
+
static _Atomic uintptr_t cachedFrame = 0;
|
|
616
|
+
static _Atomic int cachedAnswer = -1;
|
|
617
|
+
uintptr_t topFrame = (uintptr_t)frames[3];
|
|
618
|
+
if (atomic_load_explicit(&cachedFrame, memory_order_relaxed) == topFrame) {
|
|
619
|
+
int v = atomic_load_explicit(&cachedAnswer, memory_order_relaxed);
|
|
620
|
+
if (v >= 0) return (BOOL)v;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
BOOL match = NO;
|
|
624
|
+
int end = (n < 12) ? n : 12;
|
|
625
|
+
for (int i = 3; i < end; i++) {
|
|
626
|
+
Dl_info info;
|
|
627
|
+
if (dladdr(frames[i], &info) == 0 || !info.dli_sname) continue;
|
|
628
|
+
const char *name = info.dli_sname;
|
|
629
|
+
if (strstr(name, "generatePhoto") ||
|
|
630
|
+
strstr(name, "generatePicture") ||
|
|
631
|
+
strstr(name, "generateImage") ||
|
|
632
|
+
strstr(name, "placeholderPhoto") ||
|
|
633
|
+
strstr(name, "placeholderImage") ||
|
|
634
|
+
strstr(name, "simulatorPhoto") ||
|
|
635
|
+
strstr(name, "PictureForSimulator") ||
|
|
636
|
+
strstr(name, "PhotoForSimulator") ||
|
|
637
|
+
strstr(name, "ImageForSimulator") ||
|
|
638
|
+
strstr(name, "mockPhoto") ||
|
|
639
|
+
strstr(name, "fakePhoto")) { match = YES; break; }
|
|
640
|
+
if (strstr(name, "Camera") || strstr(name, "camera")) {
|
|
641
|
+
if (strstr(name, "Simulator") ||
|
|
642
|
+
strstr(name, "simulator") ||
|
|
643
|
+
strstr(name, "Placeholder") ||
|
|
644
|
+
strstr(name, "placeholder") ||
|
|
645
|
+
strstr(name, "generate")) { match = YES; break; }
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
atomic_store_explicit(&cachedFrame, topFrame, memory_order_relaxed);
|
|
649
|
+
atomic_store_explicit(&cachedAnswer, (int)match, memory_order_relaxed);
|
|
650
|
+
return match;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
@interface UIGraphicsImageRenderer (SimCam)
|
|
654
|
+
@end
|
|
655
|
+
@implementation UIGraphicsImageRenderer (SimCam)
|
|
656
|
+
- (UIImage *)simcam_imageWithActions:(void (NS_NOESCAPE ^)(UIGraphicsImageRendererContext *))actions {
|
|
657
|
+
if (SimCamCallerLooksLikeCameraPlaceholder()) {
|
|
658
|
+
NSData *jpeg = [[SimCamRegistry shared] currentSnapshotJPEGAtQuality:0.92];
|
|
659
|
+
if (jpeg.length > 0) {
|
|
660
|
+
UIImage *snap = [UIImage imageWithData:jpeg];
|
|
661
|
+
if (snap) {
|
|
662
|
+
simcam_log(@"UIGraphicsImageRenderer image: → live frame (jpeg %lu bytes)",
|
|
663
|
+
(unsigned long)jpeg.length);
|
|
664
|
+
return snap;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return [self simcam_imageWithActions:actions];
|
|
669
|
+
}
|
|
670
|
+
@end
|
|
671
|
+
|
|
672
|
+
#pragma mark - CoreMotion stubs
|
|
673
|
+
|
|
674
|
+
static char kSimCamAccelTimerKey;
|
|
675
|
+
static char kSimCamGyroTimerKey;
|
|
676
|
+
static char kSimCamMagTimerKey;
|
|
677
|
+
static char kSimCamDeviceMotionTimerKey;
|
|
678
|
+
|
|
679
|
+
static void SimCamStartTimer(id manager, char *key, NSTimeInterval interval,
|
|
680
|
+
dispatch_block_t tick) {
|
|
681
|
+
if (interval <= 0) interval = 0.1;
|
|
682
|
+
dispatch_source_t existing = objc_getAssociatedObject(manager, key);
|
|
683
|
+
if (existing) dispatch_source_cancel(existing);
|
|
684
|
+
dispatch_queue_t q = dispatch_queue_create("dev.servesim.simcam.motion",
|
|
685
|
+
DISPATCH_QUEUE_SERIAL);
|
|
686
|
+
dispatch_source_t t = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, q);
|
|
687
|
+
uint64_t ns = (uint64_t)(interval * NSEC_PER_SEC);
|
|
688
|
+
dispatch_source_set_timer(t, DISPATCH_TIME_NOW, ns, ns / 10);
|
|
689
|
+
dispatch_source_set_event_handler(t, tick);
|
|
690
|
+
dispatch_resume(t);
|
|
691
|
+
objc_setAssociatedObject(manager, key, t, OBJC_ASSOCIATION_RETAIN);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
static void SimCamStopTimer(id manager, char *key) {
|
|
695
|
+
dispatch_source_t t = objc_getAssociatedObject(manager, key);
|
|
696
|
+
if (t) {
|
|
697
|
+
dispatch_source_cancel(t);
|
|
698
|
+
objc_setAssociatedObject(manager, key, nil, OBJC_ASSOCIATION_RETAIN);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
@interface CMMotionManager (SimCam)
|
|
703
|
+
@end
|
|
704
|
+
@implementation CMMotionManager (SimCam)
|
|
705
|
+
|
|
706
|
+
- (BOOL)simcam_isAccelerometerAvailable { return YES; }
|
|
707
|
+
- (BOOL)simcam_isGyroAvailable { return YES; }
|
|
708
|
+
- (BOOL)simcam_isMagnetometerAvailable { return YES; }
|
|
709
|
+
- (BOOL)simcam_isDeviceMotionAvailable { return YES; }
|
|
710
|
+
|
|
711
|
+
- (BOOL)simcam_isAccelerometerActive {
|
|
712
|
+
return objc_getAssociatedObject(self, &kSimCamAccelTimerKey) != nil;
|
|
713
|
+
}
|
|
714
|
+
- (BOOL)simcam_isGyroActive {
|
|
715
|
+
return objc_getAssociatedObject(self, &kSimCamGyroTimerKey) != nil;
|
|
716
|
+
}
|
|
717
|
+
- (BOOL)simcam_isMagnetometerActive {
|
|
718
|
+
return objc_getAssociatedObject(self, &kSimCamMagTimerKey) != nil;
|
|
719
|
+
}
|
|
720
|
+
- (BOOL)simcam_isDeviceMotionActive {
|
|
721
|
+
return objc_getAssociatedObject(self, &kSimCamDeviceMotionTimerKey) != nil;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
- (CMAccelerometerData *)simcam_accelerometerData { return SimCamSharedAccelerometerData(); }
|
|
725
|
+
- (CMGyroData *)simcam_gyroData { return SimCamSharedGyroData(); }
|
|
726
|
+
- (CMMagnetometerData *)simcam_magnetometerData { return SimCamSharedMagnetometerData(); }
|
|
727
|
+
- (CMDeviceMotion *)simcam_deviceMotion { return SimCamSharedDeviceMotion(); }
|
|
728
|
+
|
|
729
|
+
- (void)simcam_startAccelerometerUpdates {
|
|
730
|
+
if ([self simcam_isAccelerometerActive]) return;
|
|
731
|
+
SimCamStartTimer(self, &kSimCamAccelTimerKey,
|
|
732
|
+
self.accelerometerUpdateInterval, ^{});
|
|
733
|
+
}
|
|
734
|
+
- (void)simcam_startAccelerometerUpdatesToQueue:(NSOperationQueue *)queue
|
|
735
|
+
withHandler:(CMAccelerometerHandler)handler {
|
|
736
|
+
if (!handler) { [self simcam_startAccelerometerUpdates]; return; }
|
|
737
|
+
CMAccelerometerHandler block = [handler copy];
|
|
738
|
+
SimCamStartTimer(self, &kSimCamAccelTimerKey,
|
|
739
|
+
self.accelerometerUpdateInterval, ^{
|
|
740
|
+
CMAccelerometerData *data = SimCamSharedAccelerometerData();
|
|
741
|
+
if (queue) [queue addOperationWithBlock:^{ block(data, nil); }];
|
|
742
|
+
else block(data, nil);
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
- (void)simcam_stopAccelerometerUpdates {
|
|
746
|
+
SimCamStopTimer(self, &kSimCamAccelTimerKey);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
- (void)simcam_startGyroUpdates {
|
|
750
|
+
if ([self simcam_isGyroActive]) return;
|
|
751
|
+
SimCamStartTimer(self, &kSimCamGyroTimerKey,
|
|
752
|
+
self.gyroUpdateInterval, ^{});
|
|
753
|
+
}
|
|
754
|
+
- (void)simcam_startGyroUpdatesToQueue:(NSOperationQueue *)queue
|
|
755
|
+
withHandler:(CMGyroHandler)handler {
|
|
756
|
+
if (!handler) { [self simcam_startGyroUpdates]; return; }
|
|
757
|
+
CMGyroHandler block = [handler copy];
|
|
758
|
+
SimCamStartTimer(self, &kSimCamGyroTimerKey, self.gyroUpdateInterval, ^{
|
|
759
|
+
CMGyroData *data = SimCamSharedGyroData();
|
|
760
|
+
if (queue) [queue addOperationWithBlock:^{ block(data, nil); }];
|
|
761
|
+
else block(data, nil);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
- (void)simcam_stopGyroUpdates {
|
|
765
|
+
SimCamStopTimer(self, &kSimCamGyroTimerKey);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
- (void)simcam_startMagnetometerUpdates {
|
|
769
|
+
if ([self simcam_isMagnetometerActive]) return;
|
|
770
|
+
SimCamStartTimer(self, &kSimCamMagTimerKey,
|
|
771
|
+
self.magnetometerUpdateInterval, ^{});
|
|
772
|
+
}
|
|
773
|
+
- (void)simcam_startMagnetometerUpdatesToQueue:(NSOperationQueue *)queue
|
|
774
|
+
withHandler:(CMMagnetometerHandler)handler {
|
|
775
|
+
if (!handler) { [self simcam_startMagnetometerUpdates]; return; }
|
|
776
|
+
CMMagnetometerHandler block = [handler copy];
|
|
777
|
+
SimCamStartTimer(self, &kSimCamMagTimerKey, self.magnetometerUpdateInterval, ^{
|
|
778
|
+
CMMagnetometerData *data = SimCamSharedMagnetometerData();
|
|
779
|
+
if (queue) [queue addOperationWithBlock:^{ block(data, nil); }];
|
|
780
|
+
else block(data, nil);
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
- (void)simcam_stopMagnetometerUpdates {
|
|
784
|
+
SimCamStopTimer(self, &kSimCamMagTimerKey);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
- (void)simcam_startDeviceMotionUpdates {
|
|
788
|
+
if ([self simcam_isDeviceMotionActive]) return;
|
|
789
|
+
SimCamStartTimer(self, &kSimCamDeviceMotionTimerKey,
|
|
790
|
+
self.deviceMotionUpdateInterval, ^{});
|
|
791
|
+
}
|
|
792
|
+
- (void)simcam_startDeviceMotionUpdatesUsingReferenceFrame:(CMAttitudeReferenceFrame)frame {
|
|
793
|
+
(void)frame;
|
|
794
|
+
[self simcam_startDeviceMotionUpdates];
|
|
795
|
+
}
|
|
796
|
+
- (void)simcam_startDeviceMotionUpdatesToQueue:(NSOperationQueue *)queue
|
|
797
|
+
withHandler:(CMDeviceMotionHandler)handler {
|
|
798
|
+
if (!handler) { [self simcam_startDeviceMotionUpdates]; return; }
|
|
799
|
+
CMDeviceMotionHandler block = [handler copy];
|
|
800
|
+
SimCamStartTimer(self, &kSimCamDeviceMotionTimerKey,
|
|
801
|
+
self.deviceMotionUpdateInterval, ^{
|
|
802
|
+
CMDeviceMotion *data = SimCamSharedDeviceMotion();
|
|
803
|
+
if (queue) [queue addOperationWithBlock:^{ block(data, nil); }];
|
|
804
|
+
else block(data, nil);
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
- (void)simcam_startDeviceMotionUpdatesUsingReferenceFrame:(CMAttitudeReferenceFrame)frame
|
|
808
|
+
toQueue:(NSOperationQueue *)queue
|
|
809
|
+
withHandler:(CMDeviceMotionHandler)handler {
|
|
810
|
+
(void)frame;
|
|
811
|
+
[self simcam_startDeviceMotionUpdatesToQueue:queue withHandler:handler];
|
|
812
|
+
}
|
|
813
|
+
- (void)simcam_stopDeviceMotionUpdates {
|
|
814
|
+
SimCamStopTimer(self, &kSimCamDeviceMotionTimerKey);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
@end
|
|
818
|
+
|
|
819
|
+
static void InstallCoreMotionSwizzles(void) {
|
|
820
|
+
Class mm = [CMMotionManager class];
|
|
821
|
+
if (!mm) return;
|
|
822
|
+
SwizzleInstanceMethod(mm, @selector(isAccelerometerAvailable),
|
|
823
|
+
@selector(simcam_isAccelerometerAvailable));
|
|
824
|
+
SwizzleInstanceMethod(mm, @selector(isGyroAvailable),
|
|
825
|
+
@selector(simcam_isGyroAvailable));
|
|
826
|
+
SwizzleInstanceMethod(mm, @selector(isMagnetometerAvailable),
|
|
827
|
+
@selector(simcam_isMagnetometerAvailable));
|
|
828
|
+
SwizzleInstanceMethod(mm, @selector(isDeviceMotionAvailable),
|
|
829
|
+
@selector(simcam_isDeviceMotionAvailable));
|
|
830
|
+
SwizzleInstanceMethod(mm, @selector(isAccelerometerActive),
|
|
831
|
+
@selector(simcam_isAccelerometerActive));
|
|
832
|
+
SwizzleInstanceMethod(mm, @selector(isGyroActive),
|
|
833
|
+
@selector(simcam_isGyroActive));
|
|
834
|
+
SwizzleInstanceMethod(mm, @selector(isMagnetometerActive),
|
|
835
|
+
@selector(simcam_isMagnetometerActive));
|
|
836
|
+
SwizzleInstanceMethod(mm, @selector(isDeviceMotionActive),
|
|
837
|
+
@selector(simcam_isDeviceMotionActive));
|
|
838
|
+
SwizzleInstanceMethod(mm, @selector(accelerometerData),
|
|
839
|
+
@selector(simcam_accelerometerData));
|
|
840
|
+
SwizzleInstanceMethod(mm, @selector(gyroData),
|
|
841
|
+
@selector(simcam_gyroData));
|
|
842
|
+
SwizzleInstanceMethod(mm, @selector(magnetometerData),
|
|
843
|
+
@selector(simcam_magnetometerData));
|
|
844
|
+
SwizzleInstanceMethod(mm, @selector(deviceMotion),
|
|
845
|
+
@selector(simcam_deviceMotion));
|
|
846
|
+
SwizzleInstanceMethod(mm, @selector(startAccelerometerUpdates),
|
|
847
|
+
@selector(simcam_startAccelerometerUpdates));
|
|
848
|
+
SwizzleInstanceMethod(mm, @selector(startAccelerometerUpdatesToQueue:withHandler:),
|
|
849
|
+
@selector(simcam_startAccelerometerUpdatesToQueue:withHandler:));
|
|
850
|
+
SwizzleInstanceMethod(mm, @selector(stopAccelerometerUpdates),
|
|
851
|
+
@selector(simcam_stopAccelerometerUpdates));
|
|
852
|
+
SwizzleInstanceMethod(mm, @selector(startGyroUpdates),
|
|
853
|
+
@selector(simcam_startGyroUpdates));
|
|
854
|
+
SwizzleInstanceMethod(mm, @selector(startGyroUpdatesToQueue:withHandler:),
|
|
855
|
+
@selector(simcam_startGyroUpdatesToQueue:withHandler:));
|
|
856
|
+
SwizzleInstanceMethod(mm, @selector(stopGyroUpdates),
|
|
857
|
+
@selector(simcam_stopGyroUpdates));
|
|
858
|
+
SwizzleInstanceMethod(mm, @selector(startMagnetometerUpdates),
|
|
859
|
+
@selector(simcam_startMagnetometerUpdates));
|
|
860
|
+
SwizzleInstanceMethod(mm, @selector(startMagnetometerUpdatesToQueue:withHandler:),
|
|
861
|
+
@selector(simcam_startMagnetometerUpdatesToQueue:withHandler:));
|
|
862
|
+
SwizzleInstanceMethod(mm, @selector(stopMagnetometerUpdates),
|
|
863
|
+
@selector(simcam_stopMagnetometerUpdates));
|
|
864
|
+
SwizzleInstanceMethod(mm, @selector(startDeviceMotionUpdates),
|
|
865
|
+
@selector(simcam_startDeviceMotionUpdates));
|
|
866
|
+
SwizzleInstanceMethod(mm, @selector(startDeviceMotionUpdatesUsingReferenceFrame:),
|
|
867
|
+
@selector(simcam_startDeviceMotionUpdatesUsingReferenceFrame:));
|
|
868
|
+
SwizzleInstanceMethod(mm, @selector(startDeviceMotionUpdatesToQueue:withHandler:),
|
|
869
|
+
@selector(simcam_startDeviceMotionUpdatesToQueue:withHandler:));
|
|
870
|
+
SwizzleInstanceMethod(mm,
|
|
871
|
+
@selector(startDeviceMotionUpdatesUsingReferenceFrame:toQueue:withHandler:),
|
|
872
|
+
@selector(simcam_startDeviceMotionUpdatesUsingReferenceFrame:toQueue:withHandler:));
|
|
873
|
+
SwizzleInstanceMethod(mm, @selector(stopDeviceMotionUpdates),
|
|
874
|
+
@selector(simcam_stopDeviceMotionUpdates));
|
|
875
|
+
simcam_log(@"CoreMotion stubs installed (portrait, face-up)");
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
#pragma mark - Install
|
|
879
|
+
|
|
880
|
+
void SimCamInstallSwizzles(void) {
|
|
881
|
+
Class dev = [AVCaptureDevice class];
|
|
882
|
+
SwizzleClassMethod(dev,
|
|
883
|
+
@selector(defaultDeviceWithDeviceType:mediaType:position:),
|
|
884
|
+
@selector(simcam_defaultDeviceWithDeviceType:mediaType:position:));
|
|
885
|
+
SwizzleClassMethod(dev,
|
|
886
|
+
@selector(devicesWithMediaType:),
|
|
887
|
+
@selector(simcam_devicesWithMediaType:));
|
|
888
|
+
SwizzleClassMethod(dev, @selector(devices), @selector(simcam_devices));
|
|
889
|
+
|
|
890
|
+
Class disc = [AVCaptureDeviceDiscoverySession class];
|
|
891
|
+
SwizzleClassMethod(disc,
|
|
892
|
+
@selector(discoverySessionWithDeviceTypes:mediaType:position:),
|
|
893
|
+
@selector(simcam_discoverySessionWithDeviceTypes:mediaType:position:));
|
|
894
|
+
|
|
895
|
+
Class input = [AVCaptureDeviceInput class];
|
|
896
|
+
SwizzleInstanceMethod(input,
|
|
897
|
+
@selector(initWithDevice:error:),
|
|
898
|
+
@selector(simcam_initWithDevice:error:));
|
|
899
|
+
SwizzleInstanceMethod(input, @selector(device), @selector(simcam_device));
|
|
900
|
+
SwizzleInstanceMethod(input, @selector(ports), @selector(simcam_ports));
|
|
901
|
+
|
|
902
|
+
Class sess = [AVCaptureSession class];
|
|
903
|
+
SwizzleInstanceMethod(sess, @selector(addInput:), @selector(simcam_addInput:));
|
|
904
|
+
SwizzleInstanceMethod(sess, @selector(canAddInput:), @selector(simcam_canAddInput:));
|
|
905
|
+
SwizzleInstanceMethod(sess, @selector(addOutput:), @selector(simcam_addOutput:));
|
|
906
|
+
SwizzleInstanceMethod(sess, @selector(canAddOutput:), @selector(simcam_canAddOutput:));
|
|
907
|
+
SwizzleInstanceMethod(sess, @selector(startRunning), @selector(simcam_startRunning));
|
|
908
|
+
SwizzleInstanceMethod(sess, @selector(stopRunning), @selector(simcam_stopRunning));
|
|
909
|
+
SwizzleInstanceMethod(sess, @selector(isRunning), @selector(simcam_isRunning));
|
|
910
|
+
SwizzleInstanceMethod(sess, @selector(inputs), @selector(simcam_inputs));
|
|
911
|
+
SwizzleInstanceMethod(sess, @selector(outputs), @selector(simcam_outputs));
|
|
912
|
+
SwizzleInstanceMethod(sess, @selector(connections), @selector(simcam_connections));
|
|
913
|
+
SwizzleInstanceMethod(sess,
|
|
914
|
+
@selector(addInputWithNoConnections:),
|
|
915
|
+
@selector(simcam_addInputWithNoConnections:));
|
|
916
|
+
SwizzleInstanceMethod(sess,
|
|
917
|
+
@selector(addOutputWithNoConnections:),
|
|
918
|
+
@selector(simcam_addOutputWithNoConnections:));
|
|
919
|
+
SwizzleInstanceMethod(sess, @selector(removeInput:), @selector(simcam_removeInput:));
|
|
920
|
+
SwizzleInstanceMethod(sess, @selector(removeOutput:), @selector(simcam_removeOutput:));
|
|
921
|
+
SwizzleInstanceMethod(sess,
|
|
922
|
+
@selector(beginConfiguration),
|
|
923
|
+
@selector(simcam_beginConfiguration));
|
|
924
|
+
SwizzleInstanceMethod(sess,
|
|
925
|
+
@selector(commitConfiguration),
|
|
926
|
+
@selector(simcam_commitConfiguration));
|
|
927
|
+
SwizzleInstanceMethod(sess,
|
|
928
|
+
@selector(addConnection:),
|
|
929
|
+
@selector(simcam_addConnection:));
|
|
930
|
+
SwizzleInstanceMethod(sess,
|
|
931
|
+
@selector(canAddConnection:),
|
|
932
|
+
@selector(simcam_canAddConnection:));
|
|
933
|
+
|
|
934
|
+
Class nc = [NSNotificationCenter class];
|
|
935
|
+
SwizzleInstanceMethod(nc,
|
|
936
|
+
@selector(postNotificationName:object:userInfo:),
|
|
937
|
+
@selector(simcam_postNotificationName:object:userInfo:));
|
|
938
|
+
SwizzleInstanceMethod(nc,
|
|
939
|
+
@selector(postNotificationName:object:),
|
|
940
|
+
@selector(simcam_postNotificationName:object:));
|
|
941
|
+
SwizzleInstanceMethod(nc,
|
|
942
|
+
@selector(postNotification:),
|
|
943
|
+
@selector(simcam_postNotification:));
|
|
944
|
+
simcam_log(@"NSNotificationCenter swizzles installed (AVCaptureSessionRuntimeErrorNotification gated)");
|
|
945
|
+
|
|
946
|
+
[[NSNotificationCenter defaultCenter]
|
|
947
|
+
addObserverForName:AVCaptureSessionRuntimeErrorNotification
|
|
948
|
+
object:nil
|
|
949
|
+
queue:nil
|
|
950
|
+
usingBlock:^(NSNotification *note) {
|
|
951
|
+
NSError *err = note.userInfo[AVCaptureSessionErrorKey];
|
|
952
|
+
simcam_log(@"DIAG runtime-error delivered (post swizzle MISSED) object=%@ code=%ld desc=%@",
|
|
953
|
+
NSStringFromClass([note.object class]),
|
|
954
|
+
(long)err.code,
|
|
955
|
+
err.localizedDescription ?: @"<nil>");
|
|
956
|
+
}];
|
|
957
|
+
|
|
958
|
+
Class out = [AVCaptureVideoDataOutput class];
|
|
959
|
+
SwizzleInstanceMethod(out,
|
|
960
|
+
@selector(setSampleBufferDelegate:queue:),
|
|
961
|
+
@selector(simcam_setSampleBufferDelegate:queue:));
|
|
962
|
+
|
|
963
|
+
Class outBase = [AVCaptureOutput class];
|
|
964
|
+
SwizzleInstanceMethod(outBase,
|
|
965
|
+
@selector(connectionWithMediaType:),
|
|
966
|
+
@selector(simcam_connectionWithMediaType:));
|
|
967
|
+
SwizzleInstanceMethod(outBase,
|
|
968
|
+
@selector(connections),
|
|
969
|
+
@selector(simcam_connections));
|
|
970
|
+
|
|
971
|
+
Class pl = [AVCaptureVideoPreviewLayer class];
|
|
972
|
+
SwizzleInstanceMethod(pl, @selector(setSession:), @selector(simcam_setSession:));
|
|
973
|
+
|
|
974
|
+
Class fmtClass = [AVCaptureDeviceFormat class];
|
|
975
|
+
SEL figFmtSel = NSSelectorFromString(@"figCaptureSourceVideoFormat");
|
|
976
|
+
SEL figFmtSwizSel = @selector(simcam_figCaptureSourceVideoFormat);
|
|
977
|
+
BOOL figOk = SwizzleInstanceMethod(fmtClass, figFmtSel, figFmtSwizSel);
|
|
978
|
+
simcam_log(@"swizzle -[AVCaptureDeviceFormat figCaptureSourceVideoFormat] → %@",
|
|
979
|
+
figOk ? @"installed" : @"FAILED");
|
|
980
|
+
|
|
981
|
+
Class outClass = [AVCaptureOutput class];
|
|
982
|
+
SEL availCodecsSel = NSSelectorFromString(
|
|
983
|
+
@"availableVideoCodecTypesForSourceDevice:sourceFormat:outputDimensions:fileType:videoCodecTypesAllowList:");
|
|
984
|
+
SEL availCodecsSwizSel = @selector(simcam_availableVideoCodecTypesForSourceDevice:sourceFormat:outputDimensions:fileType:videoCodecTypesAllowList:);
|
|
985
|
+
Method origAvailCodecs = class_getClassMethod(outClass, availCodecsSel);
|
|
986
|
+
Method swizAvailCodecs = class_getClassMethod(outClass, availCodecsSwizSel);
|
|
987
|
+
if (origAvailCodecs && swizAvailCodecs) {
|
|
988
|
+
method_exchangeImplementations(origAvailCodecs, swizAvailCodecs);
|
|
989
|
+
simcam_log(@"swizzle +[AVCaptureOutput availableVideoCodecTypes…] → installed");
|
|
990
|
+
} else {
|
|
991
|
+
simcam_log(@"swizzle +[AVCaptureOutput availableVideoCodecTypes…] FAILED (orig=%p swiz=%p)",
|
|
992
|
+
origAvailCodecs, swizAvailCodecs);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
Class photoOut = [AVCapturePhotoOutput class];
|
|
996
|
+
SwizzleInstanceMethod(photoOut,
|
|
997
|
+
@selector(capturePhotoWithSettings:delegate:),
|
|
998
|
+
@selector(simcam_capturePhotoWithSettings:delegate:));
|
|
999
|
+
|
|
1000
|
+
Class data = [NSData class];
|
|
1001
|
+
SwizzleInstanceMethod(data,
|
|
1002
|
+
@selector(writeToURL:options:error:),
|
|
1003
|
+
@selector(simcam_writeToURL:options:error:));
|
|
1004
|
+
SwizzleInstanceMethod(data,
|
|
1005
|
+
@selector(writeToFile:options:error:),
|
|
1006
|
+
@selector(simcam_writeToFile:options:error:));
|
|
1007
|
+
|
|
1008
|
+
Class renderer = [UIGraphicsImageRenderer class];
|
|
1009
|
+
SwizzleInstanceMethod(renderer,
|
|
1010
|
+
@selector(imageWithActions:),
|
|
1011
|
+
@selector(simcam_imageWithActions:));
|
|
1012
|
+
|
|
1013
|
+
InstallCoreMotionSwizzles();
|
|
1014
|
+
}
|