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).
120 lines
7.5 KiB
Objective-C
120 lines
7.5 KiB
Objective-C
// 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;
|
||
}
|