serve-sim 0.1.24 → 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.
@@ -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
+ }