Skip to content

Instantly share code, notes, and snippets.

@SoundBlaster
Last active November 12, 2025 15:18
Show Gist options
  • Select an option

  • Save SoundBlaster/b8d56d4a0ef1777fbaa138e5505634a5 to your computer and use it in GitHub Desktop.

Select an option

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
// 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;
}
// 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