Skip to the content.

[iOS 17] Live Photo 배경화면에 대해

iOS 17 부터 Live Photo를 배경화면으로 정할 수 있다. (사실 예전부터 있던거긴 한데 iOS 16 때 사라졌다가 다시 생김)

하지만 이상한 점을 발견했다.

Reverse Engineering을 해보니 -[PIParallaxSegmentationItem isSettlingEffectAvailable] (PhotoImaging API)에서 YES가 나올 때만 가능한 모양이다.

아마 Vision을 통해서 Live Photo 영상을 분석하여 배경화면으로 쓰기 적합한 부분만 Trim하고 시점을 변경해주는 것이 아닐까? 싶다. 암튼 이런 머신러닝이 들어가기 때문에 구형 기기에서는 안 되며, 부적합다고 판단되는 사진들은 배경화면으로 쓸 수 없는 것이다.

한 번 iPad Pro 10.5 (2017)에서 -[PIParallaxSegmentationItem isSettlingEffectAvailable]를 hooking해서 YES를 반환하도록 강제하니 튕겨버린다… 참고로 PhotosPosterProvider (com.apple.PhotosUIPrivate.PhotosPosterProvider) target을 hooking해야 한다.

#import <Foundation/Foundation.h>
#import <substrate.h>

namespace PUParallaxLayerStackViewModel {
    namespace canEnableSettlingEffect {
        BOOL (*original)(id, SEL);
        BOOL custom(id self, SEL _cmd) {
            return YES;
        }
    }

    namespace canCreateSettlingEffectLayerView {
        BOOL (*original)(id, SEL);
        BOOL custom(id self, SEL _cmd) {
            return YES;
        }
    }
}

namespace PUWallpaperPosterEditorController{
    namespace _canEnableDepthEffect {
        BOOL (*original)(id, SEL);
        BOOL custom(id self, SEL _cmd) {
            return YES;
        }
    }
}

namespace PIParallaxSegmentationItem{
    namespace isSettlingEffectAvailable {
        BOOL (*original)(id, SEL);
        BOOL custom(id self, SEL _cmd) {
            return YES;
        }
    }
}

__attribute__((constructor)) static void init() {
    @autoreleasepool {
        // MSHookMessageEx(
        //     NSClassFromString(@"PUParallaxLayerStackViewModel"),
        //     NSSelectorFromString(@"canEnableSettlingEffect"),
        //     reinterpret_cast<IMP>(PUParallaxLayerStackViewModel::canEnableSettlingEffect::custom),
        //     reinterpret_cast<IMP *>(PUParallaxLayerStackViewModel::canEnableSettlingEffect::original)
        //     );

        // MSHookMessageEx(
        //     NSClassFromString(@"PUParallaxLayerStackViewModel"),
        //     NSSelectorFromString(@"canCreateSettlingEffectLayerView"),
        //     reinterpret_cast<IMP>(PUParallaxLayerStackViewModel::canCreateSettlingEffectLayerView::custom),
        //     reinterpret_cast<IMP *>(PUParallaxLayerStackViewModel::canCreateSettlingEffectLayerView::original)
        //     );

        // MSHookMessageEx(
        //     NSClassFromString(@"PUWallpaperPosterEditorController"),
        //     NSSelectorFromString(@"_canEnableDepthEffect"),
        //     reinterpret_cast<IMP>(PUWallpaperPosterEditorController::_canEnableDepthEffect::custom),
        //     reinterpret_cast<IMP *>(PUWallpaperPosterEditorController::_canEnableDepthEffect::original)
        //     );

        MSHookMessageEx(
            NSClassFromString(@"PIParallaxSegmentationItem"),
            NSSelectorFromString(@"isSettlingEffectAvailable"),
            reinterpret_cast<IMP>(PIParallaxSegmentationItem::isSettlingEffectAvailable::custom),
            reinterpret_cast<IMP *>(PIParallaxSegmentationItem::isSettlingEffectAvailable::original)
            );
    }
}

하드웨어 이슈로 안 되는 것 같다… 포기 ㅎ

Reverse Engineering하면서 PhotosPosterProvider에 대해 알게 된 점을 적자면

-[PUParallaxLayerStackViewModel showsDebugHUD]

Debug HUD를 볼 수 있음

HUDEnablerPU

PhotosPosterProvider

PhotosPosterProvider은 ExtensionKit 기반으로 작동하는 PhotosUIPrivate Extension이다. PosterBoard와 XPC로 통신한다.

PhotosUIPrivatePhotosUI의 Private Framework다. 엄청 방대한 Framework이며 Poster 기능을 포함한다.

하단 버튼들

위 사진을 보면 하단에 3개의 버튼이 있다. PosterKit에서는 하단에 3개의 버튼을 지원한다.

위는 PREditorCombinedRootViewController라는 View Controller이며 이는 PREditorRootViewController를 subclassing한다. PREditorRootViewController은 아래와 같이 3개의 버튼을 가진다.

-[PREditorRootViewController primaryActionButton]
-[PREditorRootViewController secondaryActionButton]
-[PREditorRootViewController actionsMenuButton]

모든 버튼들은 UIAction을 통해 속성을 정의한다.

primaryActionButton의 Action은 어디서 생성되는건지 모르겠다. primaryActionButton은 Live Photo (Settling Effect)을 toggle한다. 이 버튼의 Action은 -[PUWallpaperPosterEditorController _toggleSettlingEffectAction]에서 생성되며, 이 Action은 target = self, action = _toggleSettlingEffectEnabled다.

secondaryActionButtonactionsMenuButton의 Action은 -[PUWallpaperPosterEditorController trailingMenuElementsForEditor:]에서 생성된다. NSMutableArray를 반환한다. secondaryActionButton은 현재 페이지가 컬러 필터일 경우에만 생성되며, actionsMenuButton은 항상 생성된다. -[PREditorRootViewController scrollView]를 스크롤 할 때마다 reload 된다.

PUWallpaperPosterEditDebugViewController

내부 개발용 View Controller인듯?

띄워봤더니 일반 View랑 똑같이 생김

-tapToRadarAction이라는게 있긴 한데 nil 반환함

-[PUWallpaperPosterEditorController _enableSettlingEffect]

Settling Effect (Live Photo) 효과를 켜는 method

-[PUParallaxLayerStackViewModel canEnableSettlingEffect]가 YES일 때만 가능하며, -canEnableSettlingEffect은 위에서 설명한 -[PIParallaxSegmentationItem isSettlingEffectAvailable]로 가능하다.

-isSettlingEffectAvailable에 상관 없이 강제로 -[PUParallaxLayerStackViewModel canEnableSettlingEffect]에서 YES를 반환하도록 하면

(lldb) disassemble -a 0x1ba970634
PhotosUIPrivate`<redacted>:
    0x1ba970634 <+0>:   stp    x22, x21, [sp, #-0x30]!
    0x1ba970638 <+4>:   stp    x20, x19, [sp, #0x10]
    0x1ba97063c <+8>:   stp    x29, x30, [sp, #0x20]
    0x1ba970640 <+12>:  add    x29, sp, #0x20
    0x1ba970644 <+16>:  mov    x19, x0
    0x1ba970648 <+20>:  bl     0x1bad94420
    0x1ba97064c <+24>:  bl     0x1bb7dfa20
    0x1ba970650 <+28>:  mov    x20, x0
    0x1ba970654 <+32>:  bl     0x1bad7a2a0
    0x1ba970658 <+36>:  mov    x21, x0
    0x1ba97065c <+40>:  bl     0x1bb7dfb58
    0x1ba970660 <+44>:  cbz    w21, 0x1ba97068c          ; <+88>
    0x1ba970664 <+48>:  mov    x0, x19
    0x1ba970668 <+52>:  bl     0x1bad94420
    0x1ba97066c <+56>:  bl     0x1bb7dfa20
    0x1ba970670 <+60>:  mov    x19, x0
    0x1ba970674 <+64>:  bl     0x1badb0780
    0x1ba970678 <+68>:  bl     0x1bb7dde78
    0x1ba97067c <+72>:  mov    x20, x0
    0x1ba970680 <+76>:  bl     0x1bb7dfb40
    0x1ba970684 <+80>:  eor    w0, w20, #0x1
    0x1ba970688 <+84>:  b      0x1ba970690               ; <+92>
    0x1ba97068c <+88>:  mov    w0, #0x0
    0x1ba970690 <+92>:  ldp    x29, x30, [sp, #0x20]
    0x1ba970694 <+96>:  ldp    x20, x19, [sp, #0x10]
    0x1ba970698 <+100>: ldp    x22, x21, [sp], #0x30
    0x1ba97069c <+104>: ret    

0x1ba97069c에서 register write x0 0x1을 해주면 된다.

당연히 UI만 바뀌고 Live Photo 효과는 나오지 않는다 ㅎ