Files
nxnative-easy/dm/host/front_mac.m
T
seedds 79d82f7b64 Remove the D2R code and the legacy frame step
D2R (Diablo II Resurrected) has no profile and its code could not run here: the
host-call svcs its cuts emitted only died, the touch-run-v4 rule set needed a
missing dm/PRODUCT, and the host files its hooks called were never in this
repository. Removed with it:
- rewriter: D2R cuts, observation gates and parameter slots, the touch-run-v4
  rule set and private gates, the boot list, the cut scope column and the
  KRES / KASPECT key slots (fixups.tsv keeps the four thread slots; no
  params.tsv any more); dm/tools/product.sh
- host: D2R save edits, add-on content, render-scale controller, time()
  takeover and Terror Zone clock, crash decoding at D2R offsets, host-call
  registry and stubs, i18n, UI-screen, shell-app loader, zero-stripped
  archives, boot profiling hooks with no implementation; D2R headers, tests
  and scripts; dm/ios/Sources/dm_touch_controls; dm/third_party/libdeflate
- graphics: runtime extrapolation, world freeze, iPad UI shift, shop drag
  image, brightness LUT, visual ruler, bloom / FXAA shader hashes, DM_GFXUSE,
  the eco-fps setting, the loading-screen gate; shader_map.py,
  nometal_run.py, nvn_slots.py, nvn_slots.tsv and the PC-texture samples

The legacy frame step (frame_step legacy, DM_FRAME_STEP) is retired: the
Breath of the Wild and Tears of the Kingdom stubs lose their mode test.
Breath of the Wild's interval-controller cuts stay: the framework keeps the
controller's interval while the game's VFR override holds.

patch_nso.py output is byte-identical for all 15 games on this machine except
the two frame-step stubs; all 18 profiles build; the host tests pass (their
Makefile now builds the general tests only).
2026-10-21 02:09:49 +08:00

120 lines
7.5 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Mac front end: with DM_WINDOW=1 open a window (CAMetalLayer, for the graphics backend to present frames) + keyboard → dm_input; the guest runs on a large-stack thread instead and the main thread belongs to AppKit.
// Without DM_WINDOW: the guest runs directly on the current thread, behaving the same as with no front end (windowless tests use this).
// Key map (Nintendo layout): WASD left stick, arrow keys D-pad, J=A K=B U=X I=Y, Q=L E=R, Z=ZL C=ZR, F=press left stick G=press right stick, Return=+ Tab=−.
#import <Cocoa/Cocoa.h>
#import <QuartzCore/CAMetalLayer.h>
#import <Metal/Metal.h>
#include "dm.h"
#include "dm_input.h"
#include "front.h"
#include "hle.h"
#include <signal.h>
static CAMetalLayer *g_layer;
static uint64_t g_btn;
static int g_w, g_a, g_s, g_d;
void *front_metal_layer(void) { return (__bridge void *)g_layer; }
static void push(void) {
int32_t lx = (g_d - g_a) * 32767, ly = (g_w - g_s) * 32767;
dm_input_set(g_btn, lx, ly, 0, 0);
}
static int key_bit(unsigned short code) {
switch (code) {
case 38: return 0; case 40: return 1; case 32: return 2; case 34: return 3; // J K U I → A B X Y
case 3: return 4; case 5: return 5; // F G → press stick
case 12: return 6; case 14: return 7; case 6: return 8; case 8: return 9; // Q E Z C → L R ZL ZR
case 36: return 10; case 48: return 11; // Return Tab → + −
case 123: return 12; case 126: return 13; case 124: return 14; case 125: return 15; // ← ↑ → ↓
}
return -1;
}
static void on_key(NSEvent *e, int down) {
unsigned short c = e.keyCode;
switch (c) { case 13: g_w = down; break; case 0: g_a = down; break; case 1: g_s = down; break; case 2: g_d = down; break; }
int b = key_bit(c);
if (b >= 0) g_btn = down ? (g_btn | 1ull << b) : (g_btn & ~(1ull << b));
push();
}
typedef struct { void (*fn)(void); } guest_arg;
static void *guest_tramp(void *p) {
static char alt[65536]; // The signal alternate stack is per thread; crash printing must also work on the guest thread
stack_t ss = {.ss_sp = alt, .ss_size = sizeof alt}; sigaltstack(&ss, 0);
((guest_arg *)p)->fn(); LOG("guest thread finished"); exit(0);
}
void front_run(void (*guest)(void)) {
if (!getenv("DM_WINDOW")) { guest(); return; }
@autoreleasepool {
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
NSRect r = NSMakeRect(0, 0, 1280, 720);
NSWindow *win = [[NSWindow alloc] initWithContentRect:r
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable
backing:NSBackingStoreBuffered defer:NO];
win.title = @DM_GAME_NAME; // the game profile's name (game name label)
NSView *v = win.contentView;
v.wantsLayer = YES;
g_layer = [CAMetalLayer layer];
g_layer.device = MTLCreateSystemDefaultDevice();
g_layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
g_layer.contentsScale = win.backingScaleFactor;
v.layer = g_layer;
[win center];
[win makeKeyAndOrderFront:nil];
[NSApp activateIgnoringOtherApps:YES];
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown | NSEventMaskKeyUp handler:^NSEvent *(NSEvent *e) {
if (e.window != win) return e; // Only swallow keys for the game window; other windows such as the software keyboard input box receive typing as usual
if (!e.isARepeat) on_key(e, e.type == NSEventTypeKeyDown);
return nil; // Swallow it to avoid the system beep
}];
// Mouse as a single-point touchscreen (for testing touch on the local machine, front.h dm_touch_set): left button down / drag = finger 0 at the normalized coordinates, release = lifted
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown | NSEventMaskLeftMouseDragged | NSEventMaskLeftMouseUp handler:^NSEvent *(NSEvent *e) {
if (e.window != win) return e;
if (e.type == NSEventTypeLeftMouseUp) { dm_touch_set(NULL, 0); return nil; }
NSPoint p = [v convertPoint:e.locationInWindow fromView:nil]; NSSize sz = v.bounds.size;
if (sz.width <= 0 || sz.height <= 0) return e;
dm_touch t = {0, (float)(p.x / sz.width), (float)(1.0 - p.y / sz.height)}; // The AppKit view origin is at bottom left, the touchscreen origin is at top left
dm_touch_set(&t, 1); return nil;
}];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowWillCloseNotification object:win queue:nil
usingBlock:^(NSNotification *n) { (void)n; exit(0); }];
// Lifecycle (hle.h / dm.h): focus lost → 2, focus regained → 1; minimized → 3 + audio paused + GPU suspended, restored → resume (followed by focus regained → 1)
NSNotificationCenter *nc = NSNotificationCenter.defaultCenter;
[nc addObserverForName:NSWindowDidResignKeyNotification object:win queue:nil usingBlock:^(NSNotification *n) { (void)n; if (!win.miniaturized) dm_focus_set(2); }];
[nc addObserverForName:NSWindowDidBecomeKeyNotification object:win queue:nil usingBlock:^(NSNotification *n) { (void)n; dm_focus_set(1); }];
[nc addObserverForName:NSWindowDidMiniaturizeNotification object:win queue:nil usingBlock:^(NSNotification *n) { (void)n; dm_focus_set(3); dm_audio_suspend(1); dm_gfx_suspend(1); }];
[nc addObserverForName:NSWindowDidDeminiaturizeNotification object:win queue:nil usingBlock:^(NSNotification *n) { (void)n; dm_gfx_suspend(0); dm_audio_suspend(0); dm_focus_set(2); }];
// Guest thread: 64 MiB stack (the Switch main thread stack is specified by npdm; the host gives plenty)
static guest_arg ga; ga.fn = guest;
pthread_attr_t a; pthread_attr_init(&a); pthread_attr_setstacksize(&a, 64u << 20);
pthread_t th; if (pthread_create(&th, &a, guest_tramp, &ga)) die("pthread_create guest");
[NSApp run];
}
}
// Software keyboard input box (front.h): the main thread shows an NSAlert + text field and the guest thread waits synchronously for the result. Not shown in windowless mode (returns -1, the caller treats it as cancel)
int front_text_input(const char *title, const char *sub, const char *guide, const char *initial, int max_chars, char *out, int out_cap) {
if (!g_layer) return -1;
__block int r = 0;
__block NSString *text = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
NSAlert *a = [NSAlert new];
a.messageText = title && *title ? @(title) : @"Input";
NSString *info = [NSString stringWithFormat:@"%s%s%s", sub ? sub : "", sub && *sub && guide && *guide ? "\n" : "", guide ? guide : ""];
a.informativeText = info;
[a addButtonWithTitle:@"OK"]; [a addButtonWithTitle:@"Cancel"];
NSTextField *f = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 300, 24)];
f.stringValue = initial ? @(initial) : @"";
if (guide && *guide) f.placeholderString = @(guide);
a.accessoryView = f; a.window.initialFirstResponder = f;
r = [a runModal] == NSAlertFirstButtonReturn;
text = f.stringValue;
});
if (!r) return 0;
(void)max_chars; // Truncation is done uniformly in hle_swkbd.c in UTF-16 units
snprintf(out, out_cap, "%s", text.UTF8String);
return 1;
}