Last active
November 12, 2025 15:18
-
-
Save SoundBlaster/b8d56d4a0ef1777fbaa138e5505634a5 to your computer and use it in GitHub Desktop.
The method `-[CALayer actionForKey:]` from QuartzCore approximately reversed from ASM to pseudo Objective-C by DeepSeek
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // QuartzCore`-[CALayer actionForKey:] | |
| - (id<CAAction>)actionForKey:(NSString *)event { | |
| // <+0>: pacibsp | |
| // <+4>: sub sp, sp, #0x60 | |
| // <+8>: stp x24, x23, [sp, #0x20] | |
| // <+12>: stp x22, x21, [sp, #0x30] | |
| // <+16>: stp x20, x19, [sp, #0x40] | |
| // <+20>: stp x29, x30, [sp, #0x50] | |
| // <+24>: add x29, sp, #0x50 | |
| // Сохраняем параметры: x21 = self, x20 = event | |
| // <+28>: mov x20, x2 | |
| // <+32>: mov x21, x0 | |
| // Инициализация транзакции и блокировка | |
| // <+36>: adrp x8, 404928 | |
| // <+40>: ldr x8, [x8, #0x418] | |
| // <+44>: ldr x8, [x8] | |
| // <+48>: str x8, [sp, #0x18] | |
| // <+52>: bl 0x18bf53758 ; CA::Transaction::ensure_compat() | |
| // <+56>: mov x19, x0 ; x19 = transaction | |
| // <+60>: ldr w8, [x0, #0x74] ; nesting count | |
| // <+64>: add w9, w8, #0x1 | |
| // <+68>: str w9, [x0, #0x74] | |
| // <+72>: cbnz w8, 0x18c176ba4 ; если nesting_count != 0, пропускаем блокировку | |
| // <+76>: adrp x0, 432323 | |
| // <+80>: add x0, x0, #0x698 ; CA::Transaction::transaction_lock | |
| // <+84>: bl 0x18ea1bb00 ; lock() | |
| CA::Transaction *transaction = CA::Transaction::ensure_compat(); | |
| BOOL shouldUnlock = (transaction->nesting_count() == 0); | |
| transaction->increment_nesting_count(); | |
| if (shouldUnlock) { | |
| CA::Transaction::transaction_lock().lock(); | |
| } | |
| id<CAAction> action = nil; | |
| // 1. Пробуем получить action от делегата | |
| // <+88>: stp xzr, xzr, [sp, #0x8] | |
| // <+92>: ldr x23, [x21, #0x10] ; x23 = self->_attr | |
| // <+96>: ldrb w8, [x23, #0x3a] | |
| // <+100>: tbz w8, #0x5, 0x18c176bec ; проверка has_delegate | |
| id delegate = self.delegate; | |
| if (delegate && [delegate respondsToSelector:@selector(actionForLayer:forKey:)]) { | |
| // <+104>: ldrb w8, [x23, #0x88] | |
| // <+108>: cmp w8, #0x1 | |
| // <+112>: b.ne 0x18c176bd0 | |
| // <+116>: add x0, x23, #0x80 | |
| // <+120>: bl 0x18ea1b7a0 | |
| // <+124>: cbnz x0, 0x18c176bd8 | |
| // <+128>: b 0x18c176bec | |
| // <+132>: ldur x0, [x23, #0x80] | |
| // <+136>: cbz x0, 0x18c176bec | |
| // <+140>: mov x2, x21 ; self | |
| // <+144>: mov x3, x20 ; event | |
| // <+148>: bl 0x18c2b2a80 ; objc_msgSend$actionForLayer:forKey: | |
| // -> 0x18c176be4 <+152>: mov x22, x0 ; сохраняем результат | |
| // <+156>: cbnz x0, 0x18c176c1c ; если action найден - выходим | |
| action = [delegate actionForLayer:self forKey:event]; | |
| if (action) { | |
| goto found_action; // <+208> | |
| } | |
| } | |
| // 2. Ищем в actions dictionary слоя | |
| // <+160>: ldr x0, [x23, #0x48] ; actions dictionary | |
| // <+164>: cbz x0, 0x18c176c28 | |
| // <+168>: add x3, sp, #0x10 | |
| // <+172>: mov w1, #0x2 ; atom for actions | |
| // <+176>: mov w2, #0x1 ; type | |
| // <+180>: bl 0x18bf58c50 ; CA::AttrList::get() | |
| // <+184>: cbz w0, 0x18c176c28 | |
| // <+188>: ldr x0, [sp, #0x10] ; actions dict | |
| // <+192>: mov x2, x20 ; event key | |
| // <+196>: bl 0x18c2b9000 ; objc_msgSend$objectForKey: | |
| // <+200>: mov x22, x0 ; результат | |
| // <+204>: cbz x0, 0x18c176c28 | |
| NSDictionary *actions = self.actions; | |
| if (actions) { | |
| action = [actions objectForKey:event]; | |
| if (action) { | |
| goto found_action; // <+208> | |
| } | |
| } | |
| // 3. Ищем в style dictionary (рекурсивно) | |
| // <+220>: ldrb w8, [x23, #0x3a] | |
| // <+224>: tbz w8, #0x1, 0x18c176cac ; проверка has_style | |
| NSDictionary *style = self.style; | |
| if (style) { | |
| // Рекурсивный поиск в style[@"actions"] и style[@"style"] | |
| // <+228>: ldr x0, [x23, #0x48] ; attr list | |
| // <+232>: cbz x0, 0x18c176c4c | |
| // <+236>: add x3, sp, #0x8 | |
| // <+240>: mov w1, #0x2b4 ; atom for style | |
| // <+244>: mov w2, #0x1 | |
| // <+248>: bl 0x18bf58c50 ; CA::AttrList::get() | |
| // <+252>: tbnz w0, #0x0, 0x18c176c64 | |
| NSDictionary *currentStyle = style; | |
| while (currentStyle) { | |
| // Ищем в actions текущего style | |
| NSDictionary *styleActions = [currentStyle objectForKey:@"actions"]; // <+304> | |
| if (styleActions) { | |
| action = [styleActions objectForKey:event]; // <+320> | |
| if (action) { | |
| goto found_action; // <+208> | |
| } | |
| } | |
| // Переходим к вложенному style | |
| currentStyle = [currentStyle objectForKey:@"style"]; // <+340> | |
| if (!currentStyle) break; | |
| } | |
| } | |
| // 4. Пробуем defaultActionForKey: | |
| // <+352>: mov x0, x21 ; self | |
| // <+356>: bl 0x18ea1ba70 ; подготовка | |
| // <+360>: mov x2, x20 ; event | |
| // <+364>: bl 0x18c2b4b40 ; objc_msgSend$defaultActionForKey: | |
| // <+368>: mov x22, x0 ; результат | |
| if ([self respondsToSelector:@selector(defaultActionForKey:)]) { | |
| action = [self defaultActionForKey:event]; | |
| } | |
| // 5. Проверяем неявные анимации | |
| // <+372>: mov x0, x19 ; transaction | |
| // <+376>: bl 0x18bf53274 ; CA::Transaction::unlock() | |
| // <+380>: cbnz x22, 0x18c176cf8 ; если action найден | |
| if (!action) { | |
| // <+384>: ldr x8, [x21, #0x10] ; _attr | |
| // <+388>: ldr w8, [x8, #0x4] | |
| // <+392>: tbnz w8, #0xe, 0x18c176ce0 ; проверка allows_implicit_anim | |
| // <+396>: mov x22, #0x0 | |
| // <+400>: b 0x18c176cf8 | |
| if (self.allowsImplicitAnimation) { | |
| // <+404>: adrp x8, 405953 | |
| // <+408>: ldr x0, [x8, #0x8c8] ; класс/селектор | |
| // <+412>: mov x2, x21 ; layer | |
| // <+416>: mov x3, x20 ; keyPath | |
| // <+420>: bl 0x18c2b21c0 ; objc_msgSend$_implicitAnimationForLayer:keyPath: | |
| // <+424>: mov x22, x0 | |
| action = [CALayer _implicitAnimationForLayer:self keyPath:event]; | |
| } | |
| } | |
| found_action: // <+208>, <+428> | |
| // Разблокировка и возврат результата | |
| // <+208>: mov x0, x19 | |
| // <+212>: bl 0x18bf53274 ; CA::Transaction::unlock() | |
| // <+216>: b 0x18c176cf8 | |
| // Финализация | |
| // <+428>: adrp x8, 404792 | |
| // <+432>: ldr x8, [x8, #0x7c8] | |
| // <+436>: ldr x8, [x8] | |
| // <+440>: cmp x22, x8 ; проверка на [NSNull null] | |
| // <+444>: csel x0, xzr, x22, eq ; если NSNull - возвращаем nil | |
| if (shouldUnlock) { | |
| CA::Transaction::transaction_lock().unlock(); | |
| } | |
| transaction->decrement_nesting_count(); | |
| // Возвращаем action, но фильтруем NSNull | |
| return (action == (id)[NSNull null]) ? nil : action; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // QuartzCore`CA::Layer::set_bounds: | |
| void CA::Layer::set_bounds(CGRect newBounds, bool force) { | |
| // <+0>: pacibsp | |
| // <+4>: sub sp, sp, #0xc0 | |
| // <+8>: stp d9, d8, [sp, #0x80] | |
| // <+12>: stp x22, x21, [sp, #0x90] | |
| // <+16>: stp x20, x19, [sp, #0xa0] | |
| // <+20>: stp x29, x30, [sp, #0xb0] | |
| // <+24>: add x29, sp, #0xb0 | |
| // Сохраняем параметры: x19 = this (CA::Layer*), x20 = newBounds (CGRect*) | |
| // <+28>: mov x20, x1 | |
| // <+32>: mov x19, x0 | |
| // 1. Проверка на NaN и валидность bounds | |
| // <+36>: adrp x8, 405473 | |
| // <+40>: ldr x8, [x8, #0x418] | |
| // <+44>: ldr x8, [x8] | |
| // <+48>: stur x8, [x29, #-0x38] | |
| // Векторные операции для проверки NaN: (origin.x + origin.y + size.width + size.height) | |
| // <+52>: ldp q1, q0, [x1] ; q1 = origin (x,y), q0 = size (width,height) | |
| // <+56>: fadd.2d v2, v1, v0 ; складываем origin + size | |
| // <+60>: faddp.2d d2, v2 ; горизонтальное сложение: (x+y) + (width+height) | |
| // <+64>: fcmeq.2d v0, v0, v0 ; проверка size на NaN (результат маска) | |
| // <+68>: fcmeq.2d v1, v1, v1 ; проверка origin на NaN | |
| // <+72>: uzp1.4s v0, v1, v0 ; объединяем маски | |
| // <+76>: adrp x8, 747 | |
| // <+80>: ldr q1, [x8, #0x950] ; загружаем маску | |
| // <+84>: bic.16b v0, v1, v0 ; битовая очистка | |
| // <+88>: addv.4s s0, v0 ; суммируем элементы вектора | |
| // <+92>: fmov w8, s0 | |
| // <+96>: and w8, w8, #0xff | |
| // <+100>: fcmp d2, d2 ; проверка суммы на NaN | |
| // <+104>: ccmp w2, #0x0, #0x4, vs ; force flag проверка | |
| // <+108>: and w8, w8, #0xf | |
| // <+112>: ccmp w8, #0x0, #0x4, ne | |
| // <+116>: b.eq 0x18bf551d8 ; <+220> - перейти если bounds валидны | |
| BOOL hasNaN = isnan(newBounds.origin.x) || isnan(newBounds.origin.y) || | |
| isnan(newBounds.size.width) || isnan(newBounds.size.height); | |
| if (hasNaN && !force) { | |
| // <+120>: ldr w8, [x19, #0x4] ; flags слоя | |
| // <+124>: tst w8, #0x60000 ; проверка каких-то флагов | |
| // <+128>: b.ne 0x18bf551d8 ; если флаги установлены - пропустить ошибку | |
| // 2. Обработка NaN bounds - assertion/exception | |
| // <+132>: mov x0, #0x2 ; =2 | |
| // <+136>: movk x0, #0x200, lsl #32 | |
| // <+140>: movk x0, #0x4, lsl #48 | |
| // <+144>: bl 0x18ea1b0f0 ; проверка окружения (debug/release?) | |
| // <+148>: cbz w0, 0x18bf55324 ; если 0 - перейти к "тихой" обработке NaN | |
| // 3. Явная ошибка с сообщением (debug сборка) | |
| // <+152>: adrp x8, 405336 | |
| // <+156>: ldr x21, [x8, #0xfc0] ; класс/объект для исключения | |
| // <+160>: ldr q0, [x20] ; origin | |
| // <+164>: str q0, [sp, #0x30] | |
| // <+168>: ldp d8, d9, [x20, #0x10] ; size | |
| // <+172>: ldr x0, [x19, #0x10] ; CALayer* (обертка) | |
| // <+176>: bl 0x18c2b4a20 ; objc_msgSend$debugDescription | |
| // <+180>: str x0, [sp, #0x20] ; описание слоя | |
| // <+184>: stp d8, d9, [sp, #0x10] ; size | |
| // <+188>: adrp x2, 440296 | |
| // <+192>: add x2, x2, #0xf20 ; @"CALayerInvalidGeometry" | |
| // <+196>: ldr q0, [sp, #0x30] ; origin | |
| // <+200>: str q0, [sp] ; аргументы для форматирования | |
| // <+204>: adrp x3, 440296 | |
| // <+208>: add x3, x3, #0xf00 ; @"CALayer bounds contains NaN: [%g %g; %g %g]. Layer: %@" | |
| // <+212>: mov x0, x21 | |
| // <+216>: bl 0x18c2b9b80 ; objc_msgSend$raise:format: - выброс исключения | |
| [NSException raise:@"CALayerInvalidGeometry" | |
| format:@"CALayer bounds contains NaN: [%g %g; %g %g]. Layer: %@", | |
| newBounds.origin.x, newBounds.origin.y, | |
| newBounds.size.width, newBounds.size.height, | |
| [(id)this->m_layer debugDescription]]; | |
| return; | |
| } | |
| nan_quiet_handling: // <+552> - "тихая" обработка NaN (release сборка) | |
| // Если NaN в release - заменяем на 0 и рекурсивно вызываем себя | |
| // <+552>: ldr q0, [x20] | |
| // <+556>: str q0, [sp, #0x30] | |
| // <+560>: mov x21, x20 | |
| // <+564>: ldr d8, [x21, #0x10]! | |
| // <+568>: ldr d9, [x20, #0x18] | |
| // <+572>: ldr x0, [x19, #0x10] | |
| // <+576>: bl 0x18c2b4a20 ; debugDescription | |
| // <+580>: str x0, [sp, #0x20] | |
| // <+584>: stp d8, d9, [sp, #0x10] | |
| // <+588>: ldr q0, [sp, #0x30] | |
| // <+592>: str q0, [sp] | |
| // <+596>: adrp x0, 440296 | |
| // <+600>: add x0, x0, #0xf00 ; формат строка | |
| // <+604>: bl 0x18ea1a4e0 ; логирование (NSLog?) | |
| // Замена NaN на 0 и рекурсивный вызов | |
| // <+608>: ldr q0, [x20] ; origin | |
| // <+612>: fcmeq.2d v1, v0, v0 ; маска валидных чисел | |
| // <+616>: and.16b v0, v0, v1 ; обнуляем NaN | |
| // <+620>: ldr q1, [x21] ; size | |
| // <+624>: fcmeq.2d v2, v1, v1 ; маска валидных чисел | |
| // <+628>: and.16b v1, v1, v2 ; обнуляем NaN | |
| // <+632>: stp q0, q1, [sp, #0x50] ; сохраняем исправленные bounds | |
| // <+636>: add x1, sp, #0x50 ; указатель на исправленные bounds | |
| // <+640>: mov x0, x19 ; this | |
| // <+644>: mov w2, #0x0 ; force = false | |
| // <+648>: bl 0x18bf550fc ; рекурсивный вызов set_bounds | |
| if (hasNaN) { | |
| // В release: логируем и исправляем NaN | |
| CGRect sanitizedBounds = newBounds; | |
| if (isnan(sanitizedBounds.origin.x)) sanitizedBounds.origin.x = 0; | |
| if (isnan(sanitizedBounds.origin.y)) sanitizedBounds.origin.y = 0; | |
| if (isnan(sanitizedBounds.size.width)) sanitizedBounds.size.width = 0; | |
| if (isnan(sanitizedBounds.size.height)) sanitizedBounds.size.height = 0; | |
| NSLog(@"CALayer bounds contains NaN: [%g %g; %g %g]. Layer: %@", | |
| newBounds.origin.x, newBounds.origin.y, | |
| newBounds.size.width, newBounds.size.height, | |
| [(id)m_layer debugDescription]); | |
| set_bounds(sanitizedBounds, false); | |
| return; | |
| } | |
| bounds_valid: // <+220> | |
| // 4. Основная логика установки bounds | |
| // <+220>: bl 0x18bf53758 ; CA::Transaction::ensure_compat() | |
| // <+224>: mov x21, x0 ; transaction | |
| // <+228>: ldr w8, [x0, #0x74] ; nesting count | |
| // <+232>: add w9, w8, #0x1 | |
| // <+236>: str w9, [x0, #0x74] | |
| // <+240>: cbnz w8, 0x18bf551fc ; если nesting_count != 0, пропускаем блокировку | |
| // <+244>: adrp x0, 432868 | |
| // <+248>: add x0, x0, #0x698 ; CA::Transaction::transaction_lock | |
| // <+252>: bl 0x18ea1bb00 ; lock() | |
| CA::Transaction *transaction = CA::Transaction::ensure_compat(); | |
| BOOL shouldUnlock = (transaction->nesting_count() == 0); | |
| transaction->increment_nesting_count(); | |
| if (shouldUnlock) { | |
| CA::Transaction::transaction_lock().lock(); | |
| } | |
| // 5. Проверка изменились ли bounds | |
| // <+256>: ldr d0, [x19, #0x60] ; текущий origin.x | |
| // <+260>: ldr d1, [x20] ; новый origin.x | |
| // <+264>: fcmp d0, d1 | |
| // <+268>: b.ne 0x18bf5523c ; <+320> | |
| // <+272>: ldr d0, [x19, #0x68] ; текущий origin.y | |
| // <+276>: ldr d1, [x20, #0x8] ; новый origin.y | |
| // <+280>: fcmp d0, d1 | |
| // <+284>: b.ne 0x18bf5523c ; <+320> | |
| // <+288>: ldr d0, [x19, #0x70] ; текущий size.width | |
| // <+292>: ldr d1, [x20, #0x10] ; новый size.width | |
| // <+296>: fcmp d0, d1 | |
| // <+300>: b.ne 0x18bf5523c ; <+320> | |
| // <+304>: ldr d0, [x19, #0x78] ; текущий size.height | |
| // <+308>: ldr d1, [x20, #0x18] ; новый size.height | |
| // <+312>: fcmp d0, d1 | |
| // <+316>: b.eq 0x18bf553b8 ; <+700> - bounds не изменились | |
| CGRect currentBounds = this->get_bounds(); | |
| if (CGRectEqualToRect(currentBounds, newBounds)) { | |
| goto no_change; // <+700> | |
| } | |
| bounds_changed: // <+320> | |
| // 6. Начало изменения - подготовка анимаций | |
| // <+320>: str xzr, [sp, #0x50] ; очистка action | |
| // <+324>: adrp x2, 440280 | |
| // <+328>: add x2, x2, #0xc80 ; @"bounds" | |
| // <+332>: add x3, sp, #0x50 ; указатель на action | |
| // <+336>: mov x0, x19 ; this | |
| // <+340>: mov x1, x21 ; transaction | |
| // <+344>: bl 0x18bf54d8c ; CA::Layer::begin_change() | |
| id action = nil; | |
| begin_change(transaction, 0x52, @"bounds", &action); // 0x52 = атом для bounds | |
| // 7. Обновление состояния слоя | |
| // <+348>: mov x0, x19 ; this | |
| // <+352>: mov x1, x21 ; transaction | |
| // <+356>: bl 0x18bf54fd8 ; CA::Layer::writable_state() | |
| // <+360>: mov x22, x0 ; writable state | |
| CA::Layer::State* state = writable_state(transaction); | |
| // 8. Установка новых bounds | |
| // <+364>: ldr q0, [x20] ; origin | |
| // <+368>: stur q0, [x0, #0x38] ; state->m_bounds_origin | |
| // <+372>: ldr q1, [x20, #0x10] ; size | |
| // <+376>: stur q1, [x0, #0x48] ; state->m_bounds_size | |
| state->m_bounds_origin = newBounds.origin; | |
| state->m_bounds_size = newBounds.size; | |
| // 9. Обновление флагов (нулевой размер?) | |
| // <+380>: ldr d1, [x20, #0x8] ; origin.y | |
| // <+384>: fcmp d1, #0.0 | |
| // <+388>: movi d1, #0000000000000000 | |
| // <+392>: fccmp d0, d1, #0x0, eq ; сравнение origin.x с 0 если origin.y == 0 | |
| // <+396>: ldr w8, [x0, #0x10] ; state flags | |
| // <+400>: and w8, w8, #0xfffffff7 ; очищаем бит | |
| // <+404>: mov w9, #0x8 ; =8 - бит для "has_non_zero_bounds" | |
| // <+408>: csel w9, w9, wzr, ne ; устанавливаем если bounds не нулевые | |
| // <+412>: orr w8, w8, w9 | |
| // <+416>: str w8, [x0, #0x10] ; обновляем флаги | |
| BOOL hasNonZeroBounds = (newBounds.origin.x != 0 || newBounds.origin.y != 0 || | |
| newBounds.size.width != 0 || newBounds.size.height != 0); | |
| state->set_has_non_zero_bounds(hasNonZeroBounds); | |
| // 10. Завершение изменения - применение анимаций | |
| // <+420>: ldr wzr, [x19, #0x4] ; загрузка (side effect?) | |
| // <+424>: ldr x4, [sp, #0x50] ; action | |
| // <+428>: adrp x3, 440280 | |
| // <+432>: add x3, x3, #0xc80 ; @"bounds" | |
| // <+436>: mov x0, x19 ; this | |
| // <+440>: mov x1, x21 ; transaction | |
| // <+444>: mov w2, #0x52 ; =82 - атом bounds | |
| // <+448>: bl 0x18bf54f54 ; CA::Layer::end_change() | |
| end_change(transaction, 0x52, @"bounds", action); | |
| post_change_processing: // <+452> | |
| // 11. Пост-обработка - обновление флагов перерисовки | |
| // <+452>: ldrb w8, [x22, #0x13] ; state flags | |
| // <+456>: tbnz w8, #0x5, 0x18bf552e0 ; <+484> - проверка needs_display_in_bounds | |
| if (state->needs_display_in_bounds()) { | |
| // <+460>: mov x0, x19 ; this | |
| // <+464>: mov x1, x21 ; transaction | |
| // <+468>: bl 0x18bf54fd8 ; writable_state() | |
| // <+472>: ldr w8, [x0, #0x10] ; state flags | |
| // <+476>: orr w8, w8, #0x20000000 ; устанавливаем флаг needs_display | |
| // <+480>: str w8, [x0, #0x10] | |
| CA::Layer::State* updatedState = writable_state(transaction); | |
| updatedState->set_needs_display(true); | |
| } | |
| cleanup: // <+484>, <+700> | |
| no_change: // <+700> | |
| // <+700>: add x22, x19, #0x28 ; использование read-only state если нет изменений | |
| // <+704>: b 0x18bf552c0 ; <+452> - перейти к пост-обработке | |
| // 12. Финализация - разблокировка транзакции | |
| // <+508>: mov x0, x21 ; transaction | |
| // <+512>: ldp x29, x30, [sp, #0xb0] | |
| // ... восстановление регистров ... | |
| // <+532>: autibsp | |
| // <+548>: b 0x18bf53274 ; CA::Transaction::unlock() | |
| if (shouldUnlock) { | |
| CA::Transaction::transaction_lock().unlock(); | |
| } | |
| transaction->decrement_nesting_count(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment