Skip to content
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ body:
- Window Corners
- App Icon
- Hide Window Controls
- Skip Account
- Allow 1Password
- Allow Bitwarden
- Allow iCloud Passkeys
Expand Down
36 changes: 36 additions & 0 deletions src/dylib/Patches/SkipAccount/Code.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#import "Startup.h"
#import <libkern/OSCacheControl.h>
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>
#import <mach/mach_vm.h>

uint32_t *DPSkipFindCode(const DPSkipInstruction *signature, NSUInteger length) {
const struct mach_header_64 *header = NULL;
for (uint32_t i = 0; i < _dyld_image_count(); i++)
if (_dyld_get_image_header(i)->filetype == MH_EXECUTE) header = (const struct mach_header_64 *)_dyld_get_image_header(i);
if (!header) return NULL;
unsigned long size = 0;
const uint32_t *text = (const uint32_t *)getsectiondata(header, "__TEXT", "__text", &size);
uint32_t *found = NULL;
for (NSUInteger i = 0; text && i + length <= size / sizeof *text; i++) {
NSUInteger j = 0;
while (j < length && (text[i + j] & signature[j].mask) == signature[j].value) j++;
if (j != length) continue;
if (found) return NULL;
found = (uint32_t *)(text + i);
}
return found;
}

BOOL DPSkipWriteCode(uint32_t *site, uint32_t expected, uint32_t value) {
if (*site != expected) return NO;
mach_vm_address_t page = (mach_vm_address_t)site & ~((mach_vm_address_t)vm_page_size - 1);
if (mach_vm_protect(mach_task_self(), page, vm_page_size, FALSE, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY)) return NO;
*site = value;
sys_icache_invalidate(site, sizeof *site);
if (!mach_vm_protect(mach_task_self(), page, vm_page_size, FALSE, VM_PROT_READ | VM_PROT_EXECUTE)) return YES;
*site = expected;
sys_icache_invalidate(site, sizeof *site);
mach_vm_protect(mach_task_self(), page, vm_page_size, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
return NO;
}
56 changes: 56 additions & 0 deletions src/dylib/Patches/SkipAccount/Menu.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#import "Startup.h"

static BOOL DPIsSignIn(NSMenuItem *item) {
return item.menu && item.menu == NSApp.mainMenu.itemArray.firstObject.submenu
&& [@[ @"Sign In…", @"Sign In..." ] containsObject:item.title];
}

static BOOL DPIsLocalAction(NSMenuItem *item) {
if (!item.menu) return NO;
NSMenu *bar = NSApp.mainMenu;
if (item.menu == bar.itemArray.firstObject.submenu)
return [@[ @"Settings…", @"Settings...", @"Set as Default Browser", @"Import from Another Browser…" ] containsObject:item.title];
return item.menu && item.menu == [bar itemWithTitle:@"File"].submenu
&& [@[ @"New Window", @"New Incognito Window" ] containsObject:item.title];
}

static void DPItem_setEnabled(NSMenuItem *self, SEL _cmd, BOOL enabled) {
DPCallOriginal(DPItem_setEnabled, self, enabled || DPIsLocalAction(self));
}

static void DPUpdateMenu(NSMenu *menu) {
for (NSMenuItem *item in menu.itemArray) {
if (DPIsSignIn(item)) item.hidden = YES;
if (DPIsLocalAction(item)) item.enabled = YES;
}
}

static void DPItem_setHidden(NSMenuItem *self, SEL _cmd, BOOL hidden) {
DPCallOriginal(DPItem_setHidden, self, hidden || DPIsSignIn(self));
}

static void DPMenu_addItem(NSMenu *self, SEL _cmd, NSMenuItem *item) {
DPCallOriginal(DPMenu_addItem, self, item);
if (DPIsSignIn(item)) item.hidden = YES;
if (DPIsLocalAction(item)) item.enabled = YES;
}

static void DPMenu_insertItem(NSMenu *self, SEL _cmd, NSMenuItem *item, NSInteger index) {
DPCallOriginal(DPMenu_insertItem, self, item, index);
if (DPIsSignIn(item)) item.hidden = YES;
if (DPIsLocalAction(item)) item.enabled = YES;
}

static void DPMenu_update(NSMenu *self, SEL _cmd) {
DPCallOriginal(DPMenu_update, self);
DPUpdateMenu(self);
}

void DPSkipAccountMenus(void) {
DPHookClass(NSMenuItem.class, @selector(setHidden:), (IMP)DPItem_setHidden);
DPHookClass(NSMenuItem.class, @selector(setEnabled:), (IMP)DPItem_setEnabled);
DPHookClass(NSMenu.class, @selector(addItem:), (IMP)DPMenu_addItem);
DPHookClass(NSMenu.class, @selector(insertItem:atIndex:), (IMP)DPMenu_insertItem);
DPHookClass(NSMenu.class, @selector(update), (IMP)DPMenu_update);
DPOnLaunch(^{ DPUpdateMenu(NSApp.mainMenu.itemArray.firstObject.submenu); });
}
34 changes: 34 additions & 0 deletions src/dylib/Patches/SkipAccount/Settings.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#import "Startup.h"

static NSHashTable<NSView *> *DPAccountSections;

static BOOL DPIsAccountHeader(NSView *header) {
NSTextField *title = (NSTextField *)DPFindView(header, ^BOOL(NSView *view) { return [view isKindOfClass:NSTextField.class]; });
return title.stringValue && [@[ @"Account", @"Morning Brief", @"Sync" ] containsObject:title.stringValue];
}

static void DPSection_setHidden(NSView *self, SEL _cmd, BOOL hidden) {
DPCallOriginal(DPSection_setHidden, self, hidden || [DPAccountSections containsObject:self]);
}

static void DPHeader_layout(NSView *self, SEL _cmd) {
DPCallOriginal(DPHeader_layout, self);
// Hide the arranged section, including its heading, controls, and any account error message.
if (!DPIsAccountHeader(self) || ![self.superview isKindOfClass:NSStackView.class]) return;
[DPAccountSections addObject:self.superview];
self.superview.hidden = YES;
}

void DPSkipAccountSettings(void) {
DPAccountSections = [NSHashTable weakObjectsHashTable];
DPHookClass(NSStackView.class, @selector(setHidden:), (IMP)DPSection_setHidden);
DPHook(DPDiaSettingsHeader, @selector(layout), (IMP)DPHeader_layout);
}

NSString *DPSkipVerifyAccountSettings(void) {
for (NSView *header in DPFindViews(DPDiaSettingsHeader))
if (DPIsAccountHeader(header) && !header.isHiddenOrHasHiddenAncestor) return @"account-only settings section is visible";
for (NSView *button in DPFindViews("NSButton"))
if (DPHasLabel(button, @"More account options") && !button.isHiddenOrHasHiddenAncestor) return @"account options button is visible";
return nil;
}
87 changes: 87 additions & 0 deletions src/dylib/Patches/SkipAccount/SkipAccount.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#import "Startup.h"
#import "Updates/Updates.h"

extern DPPatch DPSkipAccount;
static BOOL gSkipping;

static id DPDefaults_objectForKey(NSUserDefaults *self, SEL _cmd, NSString *key) {
return [key isEqual:@"hasPresentedOnboardingIntro"] ? @YES : DPCallOriginal(DPDefaults_objectForKey, self, key);
}

static BOOL DPDefaults_boolForKey(NSUserDefaults *self, SEL _cmd, NSString *key) {
return [key isEqual:@"hasPresentedOnboardingIntro"] ? YES : DPCallOriginal(DPDefaults_boolForKey, self, key);
}

static void DPWaitForBrowser(void) {
__block NSUInteger polls = 0;
[NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer *timer) {
void **slot = DPIvar(NSApp.delegate, "controller");
id controller = slot ? (__bridge id)*slot : nil;
BOOL restored = [DPSwiftValue(controller, @"hasFinishedRestoringWindows") boolValue];
if (!restored && ++polls < 120) return;
[timer invalidate];
if (!restored) return;
for (NSWindow *window in NSApp.windows) if (DPIsKind(window, DPDiaWindow)) return;
// The File item carries the default profile even before any windows have been opened.
NSMenu *file = [NSApp.mainMenu itemWithTitle:@"File"].submenu;
for (NSMenuItem *item in file.itemArray) {
if (![item.title hasPrefix:@"New Window"]) continue;
[NSApp sendAction:item.action to:item.target from:item];
break;
}
}];
}

static NSString *DPSkipAccountProblem(DPPatch *patch) {
if (gSkipping || ![NSUserDefaults.standardUserDefaults boolForKey:@"hasUserAuthenticated"]) return nil;
return @"This Dia has signed in to an account before. Skip Account only works on a Dia that never has.";
}

static void DPInstallSkipAccount(void) {
if (!DPSkipAccount.on || DPSkipAccountProblem(&DPSkipAccount)) return;
gSkipping = DPSkipAccountStartup();
if (gSkipping) {
DPHookClass(NSUserDefaults.class, @selector(objectForKey:), (IMP)DPDefaults_objectForKey);
DPHookClass(NSUserDefaults.class, @selector(boolForKey:), (IMP)DPDefaults_boolForKey);
DPSkipAccountMenus();
DPSkipAccountSettings();
DPOnLaunch(^{ DPWaitForBrowser(); });
return;
}
DPSkipAccount.unavailable = YES;
DPSkipAccount.on = NO;
NSLog(@"DiaPatches: Skip Account doesn't know this Dia's startup code, so it stays off");
}

static void DPApplySkipAccount(DPPatch *patch) {
if (patch->on == gSkipping || patch->on != DPSavedOn(patch) || DPSkipAccountProblem(patch)) return;
NSAlert *alert = [NSAlert new];
alert.messageText = patch->on ? @"Relaunch Dia to skip account sign-in" : @"Relaunch Dia to use account sign-in";
[alert addButtonWithTitle:@"Relaunch"];
[alert addButtonWithTitle:@"Later"];
if ([alert runModal] == NSAlertFirstButtonReturn) DPHandOffAndQuit(@[ @"apply" ]);
}

static NSString *DPVerifySkipAccount(DPPatch *patch) {
if (!gSkipping || !patch->on) return nil; // Startup changes need separate launches, not the self-test's unsaved toggles.
for (NSMenuItem *item in NSApp.mainMenu.itemArray.firstObject.submenu.itemArray)
if ([item.title hasPrefix:@"Sign In"] && !item.hidden) return @"sign-in menu item is visible";
NSString *settings = DPSkipVerifyAccountSettings();
if (settings) return settings;
return DPFindViews(DPDiaContentPane).count ? nil : @"no browser page opened after skipping sign-in";
}

DP_PATCH(DPSkipAccount) {
.key = @"skipAccount",
.order = 120,
.title = @"Skip Account",
.detail = @"Use Dia without signing in. Relaunch to apply. Hides Sign In and the Account, Morning Brief and Sync settings. Only works on a Dia that has never signed in. Chat needs an account, so pair it with Turn Off AI. Experimental.",
.symbol = @"person.crop.circle.badge.minus person.crop.circle",
.tint = @"systemBrownColor",
.section = @"Window",
.classes = DPClasses(DPDiaAppDelegate, DPDiaSettingsHeader),
.install = DPInstallSkipAccount,
.apply = DPApplySkipAccount,
.verify = DPVerifySkipAccount,
.ready = DPSkipAccountProblem,
};
9 changes: 9 additions & 0 deletions src/dylib/Patches/SkipAccount/Startup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#import "StdPatch/StdPatch.h"

typedef struct { uint32_t value, mask; } DPSkipInstruction;
uint32_t *DPSkipFindCode(const DPSkipInstruction *signature, NSUInteger length);
BOOL DPSkipWriteCode(uint32_t *site, uint32_t expected, uint32_t value);
BOOL DPSkipAccountStartup(void);
void DPSkipAccountMenus(void);
void DPSkipAccountSettings(void);
NSString *DPSkipVerifyAccountSettings(void);
53 changes: 53 additions & 0 deletions src/dylib/Patches/SkipAccount/Startup.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#import "Startup.h"

// Dia 1.57.1 (90935), arm64. Unknown or ambiguous code stays untouched.
// initializeAuth() has finished initializing auth here. With no user it waits for sign-in; otherwise it returns .restored.
static const DPSkipInstruction DPLaunch[] = {
{0xb24403bd, 0xffffffff},
{0xd10083ff, 0xffffffff},
{0xa9017bfd, 0xffffffff},
{0xf90007f6, 0xffffffff},
{0x910043fd, 0xffffffff},
{0x3944e6c8, 0xffffffff},
{0x7100051f, 0xffffffff},
{0x540001a1, 0xffffffff},
{0xf940b2c0, 0xffffffff},
{0xf940aad4, 0xffffffff},
{0x94000000, 0xfc000000},
{0x528000e0, 0xffffffff},
{0x94000000, 0xfc000000},
{0xf94006c1, 0xffffffff},
{0x52800040, 0xffffffff},
{0xd2800014, 0xffffffff},
{0xa9417bfd, 0xffffffff},
{0x9243fbbd, 0xffffffff},
{0x910083ff, 0xffffffff},
{0xd61f0020, 0xffffffff},
};

// Window creation separately rejects a signed-out user. Keep the readiness and termination checks around it.
// The first load's field offset changes in every Dia release (0xf28, 0xf78, 0xf68), so only its registers are matched.
static const DPSkipInstruction DPWindow[] = {
{0xf9400114, 0xffc003ff},
{0xb4000014, 0xff00001f},
{0xaa1403e0, 0xffffffff},
{0x94000000, 0xfc000000},
{0x94000000, 0xfc000000},
{0xaa0003f3, 0xffffffff},
{0xaa1403e0, 0xffffffff},
{0x94000000, 0xfc000000},
{0x36002133, 0xffffffff},
{0xf946eed4, 0xffffffff},
{0xf946eac0, 0xffffffff},
{0x52800021, 0xffffffff},
};

BOOL DPSkipAccountStartup(void) {
uint32_t *launch = DPSkipFindCode(DPLaunch, sizeof DPLaunch / sizeof *DPLaunch);
uint32_t *window = DPSkipFindCode(DPWindow, sizeof DPWindow / sizeof *DPWindow);
if (!launch || !window) return NO;
if (!DPSkipWriteCode(launch + 7, DPLaunch[7].value, 0xd503201f)) return NO;
if (DPSkipWriteCode(window + 8, DPWindow[8].value, 0xd503201f)) return YES;
DPSkipWriteCode(launch + 7, 0xd503201f, DPLaunch[7].value);
return NO;
}
1 change: 0 additions & 1 deletion src/dylib/Patches/TurnOffAI/TurnOffAI.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#define DPDiaCapabilitiesToolbar "_TtC23AssistantCapabilitiesUI32AssistantCapabilitiesToolbarView"
#define DPDiaTabCardActions "_TtC5TabUI21TabPopoverActionsView"
#define DPDiaSettingsHeader "_TtC11Preferences25SettingsSectionHeaderView"
#define DPDiaPlaceholderText "_TtC5ARCUI23TextViewWithPlaceholder"
#define DPDiaChatSidebar "_TtC6Window30AssistantSidebarViewController"

Expand Down
1 change: 1 addition & 0 deletions src/dylib/StdPatch/Dia.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#define DPDiaTabSwitcher "_TtC10RecentTabs26RecentTabsWindowController"
#define DPDiaTabSwitcherList "_TtC10RecentTabs24RecentTabsViewController"
#define DPDiaSettingsController "_TtC11Preferences38SidebarPreferencesWindowViewController"
#define DPDiaSettingsHeader "_TtC11Preferences25SettingsSectionHeaderView"
#define DPDiaSettingsSidebar "_TtC11Preferences15SidebarListView"
#define DPDiaSpaceSwitcher "_TtC10TabSidebar17SpaceSwitcherView"
#define DPDiaSpaceSwitcherItem "_TtC10TabSidebar21SpaceSwitcherItemView"
Expand Down
Loading