Skip to content

Commit e468a9a

Browse files
committed
cpu determination fix
Game was trying to measure cpu speed in order to change graphics but failed to measure which returns to losing some animations
1 parent 30a8152 commit e468a9a

16 files changed

Lines changed: 386 additions & 29 deletions

File tree

‎PROJECT.md‎

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,8 +1523,9 @@ not embedded.
15231523

15241524
Writing inside a signed bundle breaks the signature, so a **write root** was
15251525
added to `fileio.c` (`fileio_set_write_root`): writes / creates / deletes go to
1526-
`~/Library/Application Support/NFSU2 Kayitlar` (the folder name is Turkish for
1527-
"NFSU2 Saves" and is kept as-is so existing saves keep working), reads look
1526+
`~/Library/Application Support/NFSU2 Saves` (until 2026-10-25 it was called
1527+
"NFSU2 Kayitlar"; the runtime renames the old folder once on first start, so
1528+
existing saves keep working), reads look
15281529
there first and then in the game root, and directory listings are the union of
15291530
both. Active only when the data is found inside the bundle (or when
15301531
`NFSU2_SAVEDIR` is given); running from the terminal and a bundle with its data
@@ -2016,7 +2017,7 @@ text input arrives with `WM_CHAR` (we didn't generate it; `metal.m` character ri
20162017
the attribute as a file (profiles are looked up as FOLDERS; the wildcard pattern
20172018
was ignored too), `FileTimeToLocalFileTime`/`FileTimeToSystemTime` were missing (a
20182019
crash when a save existed), `RemoveDirectoryA`/`GetTimeZoneInformation` were
2019-
added. Saves are in `~/Library/Application Support/NFSU2 Kayitlar` for every
2020+
added. Saves are in `~/Library/Application Support/NFSU2 Saves` for every
20202021
bundle; in tests ALWAYS use `NFSU2_SAVEDIR=<scratch>`. Test:
20212022
`AUTOTEXT='1650:\b\b\b\bTalha\n'` types a name, `FILEOPLOG=1` logs file operations.
20222023

@@ -2039,3 +2040,71 @@ tests can't produce (the end of a career race). Test hook `YAKALAKARE=<frame>`.
20392040
Open item: in career mode the post-race scene (behind the results panel) stays
20402041
black for the user; after a Quick Race drag loss the same scene is correct.
20412042

2043+
2044+
## 2026-10-25: first public feedback (Reddit)
2045+
2046+
Five reports from a player on another Mac. Findings and fixes:
2047+
2048+
### First-run settings were all at minimum (pop-in, no crowds)
2049+
2050+
On an empty registry the game runs its own hardware detection once
2051+
(`sub_005ced70` → `sub_005ceca0` → `sub_005cde50`): score = CPU MHz bracket × 0.8
2052+
+ GPU table score × 0.2, truncated → `g_PerformanceLevel`, which picks the preset
2053+
for every graphics option. CPU detection (`sub_005cdd00`) first checks for CPUID
2054+
the classic way (pushfd, flip EFLAGS bit 21, popfd, pushfd, compare).
2055+
`eflags_get/eflags_set` (`cpu.h`) did not keep bit 21, so the test always said
2056+
"no CPUID", the MHz stayed 0, the level came out 0 and EVERY option was saved at
2057+
its minimum (world detail 0 = visible building of the world ahead, no crowds,
2058+
reflections, shadows). `NotFirstTime=1` was saved too, so it never detected again.
2059+
2060+
- `g_eflags_hi` (`mem.c`) now carries the AC/ID bits through pushfd/popfd.
2061+
- `rdtsc_shim` counts real time at a nominal 3.0 GHz (it used to add a fixed
2062+
3000 per call; the MHz measurement times RDTSC over 50 ms). `RDTSC=fixed`: old.
2063+
- `GetAdapterIdentifier` fills the whole structure: NVIDIA 10DE/0040 (a GeForce
2064+
6800 Ultra, score 5.0 in the game's table). `ADAPTERID=ven:dev` overrides.
2065+
- The `NotFirstTime` fake is gone (`REGFIRST=1` restores it).
2066+
2067+
Result on an empty registry: level 4 (4.0 × 0.8 + 5.0 × 0.2 = 4.2; level 4 is the
2068+
highest the game ever picks by itself, 5 is manual only), world LOD 2, crowds on,
2069+
1080p row. Registries saved by the buggy detection (performance level, world
2070+
LOD, car LOD and crowds all 0, no `PortSettingsRepaired` marker) are repaired
2071+
once at load: `NotFirstTime` and the `g_*` values are dropped, so the game detects
2072+
again.
2073+
2074+
**Side effect, fixed:** the same CPUID test is in ten functions. Now that it
2075+
passes, a library at 0x72d369 picks its SSE variant (0x7337d0 instead of
2076+
0x733700), which uses `cvtpi2ps` and `shufps xmm, m128`; the lifter had neither
2077+
(9.3 million traps in one Quick Race). Both were added to `lift.py`, with a
2078+
`fuzzisa.py` template (all cases match real x86 under Rosetta), and `gen/all`
2079+
was regenerated: the only code differences were these instructions. Traps per
2080+
race: 0. The CRT's `stmxcsr` path stays unreachable (it needs SSE2, which our
2081+
CPUID does not report).
2082+
2083+
### Cinematic letterbox bars stopped at the 4:3 edges
2084+
2085+
With `HUDFIX` every orthographic draw is narrowed to the centre so the HUD keeps
2086+
its shape; the NIS letterbox bars were narrowed too and the 3D scene showed at the
2087+
sides. Orthographic draws that span the whole width but only part of the height
2088+
(the bars: y 0.75..1 and -1..-0.75) are now left at full width; full-screen
2089+
pictures (splash, loading screen) keep their 4:3 shape. `HUDWIDE=0`: old.
2090+
`ASPLOG=<frame>` logs each kind of full-width draw once.
2091+
2092+
### Quit froze the app
2093+
2094+
`ExitProcess` from a guest thread other than the main one parked that thread
2095+
and nothing told the window to close. `guest_exit_now()` now asks the Metal side
2096+
to quit first.
2097+
2098+
### Menu music "skipping back" for a moment
2099+
2100+
When the game stalls (first-launch shader compiles, loading), DirectSound keeps
2101+
looping its 0.5 s buffer and replays old data. The mixer now tracks which 256-byte
2102+
blocks were rewritten since playback last passed them and plays silence for
2103+
stale ones (`SNDSTALE=0`: old). `STALL=<present>:<ms>` reproduces a stall: 2 s
2104+
stall → 1.8 s muted instead of a replay; a normal race mutes ~85 ms.
2105+
2106+
### Unsupported executables
2107+
2108+
`SPEED2.EXE` is checked by SHA-256 at startup; any other build (the US retail
2109+
executable, for one) gets a clear message instead of a crash.
2110+
`NFSU2_ANY_EXE=1` skips the check.

‎README.md‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ The game boots, reaches the menus, and races are playable with sound.
5151
- One workaround with an unknown root cause remains: the front end's
5252
world-view-projection matrix arrives broken and an identity matrix is injected
5353
instead (`FIXMAT`, see `run.sh`).
54-
- 400 instructions in rarely used opcodes are still emitted as traps. None are
55-
hit in the measured race scene.
54+
- About 310 instructions in rarely used opcodes (many of them data the
55+
disassembler mistook for code) are still emitted as traps. None are hit in the
56+
measured race scene.
5657
- Some visuals are unverified against a Windows reference (crowds / skinned
5758
meshes). See "Remaining graphics items" in `PROJECT.md`.
5859
- Under investigation: in career mode, the scene behind the post-race results
@@ -156,9 +157,12 @@ HD build draws about 18 W.
156157
| | |
157158
|---|---|
158159
| File | `SPEED2.EXE`, 4,788,224 bytes |
160+
| Build | European v1.2 executable without the CD check |
159161
| SHA-256 | `8f6a9ae2c4cfa01181d2fd755d99c6f4b7103f60273908ea1de1e719dfe2a297` |
160162

161-
Other builds have not been tested and will not work without changes.
163+
Other builds (for example the US retail executable) have not been tested and
164+
will not work without changes. The app checks the file at startup and says so
165+
instead of crashing; `NFSU2_ANY_EXE=1` skips the check.
162166

163167
## Build and run
164168

‎pack.sh‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# and everyone runs it with their own installation.
1717
#
1818
# Saves (career profiles) are, in EVERY bundle, OUTSIDE the app and the game
19-
# folder, in ~/Library/Application Support/NFSU2 Kayitlar; settings in
19+
# folder, in ~/Library/Application Support/NFSU2 Saves; settings in
2020
# ~/.nfsu2_registry. When a new version is shipped and the .app or the game
2121
# folder is replaced, the player's progress isn't lost (runtime/bundle.m).
2222
set -e
@@ -137,7 +137,7 @@ echo "ready: $APP ($SZ)"
137137

138138
if [ "$EMBED" = 1 ]; then
139139
echo "personal single-piece app ready (DON'T DISTRIBUTE: it contains the game itself)."
140-
echo "saves: ~/Library/Application Support/NFSU2 Kayitlar"
140+
echo "saves: ~/Library/Application Support/NFSU2 Saves"
141141
exit 0
142142
fi
143143

@@ -188,7 +188,7 @@ IF SOMETHING GOES WRONG
188188
defaults delete $BID
189189
190190
SAVES
191-
Career saves: ~/Library/Application Support/NFSU2 Kayitlar
191+
Career saves: ~/Library/Application Support/NFSU2 Saves
192192
Settings: ~/.nfsu2_registry
193193
Both live OUTSIDE the app and the game folder: when a new version comes
194194
out, replacing $NAME.app (or the game folder) doesn't delete your

‎run.sh‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ unset AUTOKEY AUTOCLICK FORCESWITCH FETICK GAMELOG DUMPFRAME BOOTFLOW \
5050
DRAWSUM PSISO SKIPPS PSINFO BLITLOG SNDCSV SNDDUMP SNDBACKEND \
5151
FPSCSV AUTOKEYSTOP VCLOCK VCLOCK_MAXD DECLLOG CULLWIND RSLOG NOFOG BLITQUAD RESSCAN ASPFIX ASPLOG PSHIST MIPLIN NOANISO LODMAX \
5252
RTIDLOG DRAWVPS ZSHARELOG XFORMAT CAMLOG HD_SSR_DEBUG VIEWLOG HD_RTPROBE HD_RTPROBE2 HD_RT_STAGE HD_RT_NOTEX HD_RT_PLAIN HD_RT_ONLYCX HD_RT_SLOWBUILD HD_RT_LOG HD_RT_DUMPTAB HD_RT_STRESS HD_COMP HD_MSLAZY HD_REORDER HD_RT_AB HD_RT_FIX HD_RT_POISON FORCERAIN FORCETOD FORCETOD_STATE GPUWIN PASSPROF PASSPROF_MODE GPUVSDUMP \
53-
SNDMUTE YAKALAKARE DRAWLIST NODRAW LIMFREE ABTEST PASSPROF_CSV HD_MBLOG HD_RT_DLOG HD_RT_ONLY HD_RTPROBE2_HER
53+
SNDMUTE YAKALAKARE DRAWLIST NODRAW LIMFREE ABTEST PASSPROF_CSV HD_MBLOG HD_RT_DLOG HD_RT_ONLY HD_RTPROBE2_HER \
54+
STALL MEMDUMP REGFIRST ADAPTERID
5455

5556
export FIXMAT="${FIXMAT:-1}"
5657
export NOMOVIES="${NOMOVIES:-1}"

‎runtime/audio.m‎

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,14 @@ static void update_rate_ratio(void){
7474
double freq; /* SetFrequency ratio (1.0 = original rate) */
7575
uint64_t plays, stops, wr_bytes, wr_calls, wr_nonzero;
7676
uint64_t over; /* number of callbacks where we passed the writer */
77+
/* STALE DATA GUARD (see audio_note_region): which 256-byte blocks hold data
78+
* the game wrote and we have not played yet. Only on streaming buffers. */
79+
uint8_t *fresh;
80+
uint32_t wr_play; /* writes since this Play started (>= 8: a streaming buffer) */
81+
uint64_t muted; /* output frames muted because the data was stale */
82+
uint32_t lastblk; /* block the cursor was in at the end of the last callback */
7783
} SndBuf;
84+
#define FRESH_SH 8u /* block = 256 bytes (~1.5 ms of 44.1 kHz stereo 16-bit) */
7885

7986
static SndBuf g_snd[MAXSND];
8087
static int g_sndn = 1; /* 0 = invalid id */
@@ -111,6 +118,7 @@ int audio_register(uint32_t data, uint32_t bytes, uint32_t rate,
111118
b->wpos = 0; b->wseen = 0; b->trim = 1.0; b->target = 0.0; b->over = 0;
112119
b->freq = 1.0; b->plays = b->stops = 0;
113120
b->wr_bytes = b->wr_calls = b->wr_nonzero = 0;
121+
b->fresh = calloc((bytes >> FRESH_SH) + 1u, 1); b->wr_play = 0; b->muted = 0; b->lastblk = 0xFFFFFFFFu;
114122
fprintf(stderr, " [sound] buffer #%d: %u Hz, %u channels, %u bits, %u bytes (%.2f s)\n",
115123
id, rate, chans, bits, bytes,
116124
(double)bytes / (double)(rate * chans * (bits/8u)));
@@ -125,7 +133,7 @@ void audio_play(int id, int loop){
125133
/* CAUTION: DON'T RESET the cursor on a buffer that is already playing. The game
126134
* calls Play over and over on its streaming buffers; going back to the
127135
* start on every call made the song stutter "stop-start". */
128-
if (!g_snd[id].playing){ g_snd[id].cursor = 0.0; g_replay++; }
136+
if (!g_snd[id].playing){ g_snd[id].cursor = 0.0; g_snd[id].wr_play = 0; g_replay++; }
129137
else g_reissue++;
130138
g_snd[id].playing = 1;
131139
g_snd[id].loop = loop;
@@ -145,6 +153,37 @@ void audio_note_write(int id, uint32_t end_off){
145153
}
146154
pthread_mutex_unlock(&g_slock);
147155
}
156+
/* ---- STALE DATA GUARD --------------------------------------------------------
157+
* The game streams music (and the mixed sound) into a looping 0.5 s buffer,
158+
* writing what we consumed and keeping ~180 ms ahead (measured). If it stops
159+
* writing for a while (a stall: slow disk, a long frame), the play cursor -- which
160+
* has to keep moving, the game picks where to write from it -- goes round the ring
161+
* and plays the SAME old half second again and again: the "broken record, it
162+
* jumps back for two seconds" glitch reported from menus. Windows does the same
163+
* with a looping buffer, the game just never stalls there. Now every written
164+
* region is marked fresh, a block is unmarked once played, and an unmarked block
165+
* of a streaming buffer plays as silence: a stall becomes a short gap instead of
166+
* a repeat. The cursor keeps moving exactly as before (no change for the game).
167+
* Static buffers (written once, then looped: engine sounds) are not streaming
168+
* (fewer than 8 writes since Play) and loop as before. SNDSTALE=0 disables. */
169+
static int stale_guard_on(void){
170+
static int v = -1; if (v < 0){ const char *e = getenv("SNDSTALE"); v = !(e && *e == '0'); }
171+
return v;
172+
}
173+
void audio_note_region(int id, uint32_t off, uint32_t n){
174+
if (id <= 0 || id >= MAXSND || !n) return;
175+
pthread_mutex_lock(&g_slock);
176+
SndBuf *b = &g_snd[id];
177+
if (b->used && b->bytes && b->fresh){
178+
if (off >= b->bytes) off %= b->bytes;
179+
if (n > b->bytes) n = b->bytes;
180+
uint32_t end = off + n; /* may run past the end: wrap */
181+
for (uint32_t q = off >> FRESH_SH; (q << FRESH_SH) < end; q++)
182+
b->fresh[q % ((b->bytes >> FRESH_SH) + 1u)] = 1;
183+
if (b->playing) b->wr_play++;
184+
}
185+
pthread_mutex_unlock(&g_slock);
186+
}
148187
void audio_note_write_stat(int id, uint32_t n, int nonzero){
149188
if (id <= 0 || id >= MAXSND) return;
150189
pthread_mutex_lock(&g_slock);
@@ -321,13 +360,23 @@ static void mix_into(int16_t *out, unsigned frames){
321360
}
322361
double step = (double)b->rate / (double)OUT_RATE * g_rate_ratio * b->trim * b->freq;
323362
active++;
363+
/* see STALE DATA GUARD */
364+
int guard = b->fresh && b->loop && b->wr_play >= 8u && stale_guard_on();
365+
uint32_t nblk = (b->bytes >> FRESH_SH) + 1u, lastblk = b->lastblk;
324366
for (unsigned f = 0; f < frames; f++){
325367
double sp = b->cursor + step * (double)f;
326368
if (sp >= (double)nsrc){
327369
if (b->loop) sp = fmod(sp, (double)nsrc);
328370
else { b->playing = 0; break; }
329371
}
330372
uint32_t si = (uint32_t)sp;
373+
if (guard){
374+
uint32_t blk = (si * bpf) >> FRESH_SH;
375+
if (blk != lastblk){ /* leaving a block: it is played */
376+
if (lastblk != 0xFFFFFFFFu) b->fresh[lastblk % nblk] = 0;
377+
lastblk = blk; }
378+
if (!b->fresh[blk % nblk]){ b->muted++; continue; } /* stale: silence */
379+
}
331380
for (int c = 0; c < OUT_CH; c++){
332381
uint32_t sc = (b->chans == 2u) ? (uint32_t)c : 0u;
333382
uint32_t off = b->data + si*bpf + sc*bps;
@@ -353,6 +402,7 @@ static void mix_into(int16_t *out, unsigned frames){
353402
fprintf(g_csv, "%.4f\n", nby ? (double)z/(double)nby : 0.0);
354403
}
355404
b->cursor += step * (double)frames;
405+
b->lastblk = guard ? lastblk : 0xFFFFFFFFu;
356406
if (nsrc){
357407
if (b->cursor >= (double)nsrc){
358408
if (b->loop) b->cursor = fmod(b->cursor, (double)nsrc);
@@ -567,6 +617,9 @@ void audio_report(void){
567617
(unsigned long long)b->wr_calls, (unsigned long long)b->wr_nonzero,
568618
b->wr_bytes/1024.0, b->playing ? "PLAYING" : "stopped");
569619
}
620+
{ uint64_t mu = 0; for (int i = 1; i < g_sndn; i++) mu += g_snd[i].muted;
621+
fprintf(stderr, " sound stale guard : %s, %.1f ms of not-yet-rewritten data muted instead of replayed\n",
622+
stale_guard_on() ? "on" : "off (SNDSTALE=0)", (double)mu * 1000.0 / (double)OUT_RATE); }
570623
{ uint64_t ov = 0; for (int i = 1; i < g_sndn; i++) ov += g_snd[i].over;
571624
fprintf(stderr, " sound cursor overrun: %llu callbacks where playback passed the writer\n",
572625
(unsigned long long)ov); }

‎runtime/bundle.m‎

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <sys/stat.h>
1717
#include <libgen.h>
1818
#include <mach-o/dyld.h>
19+
#include <CommonCrypto/CommonDigest.h>
1920

2021
static char g_exe_dir[1024]; /* .../NFSU2.app/Contents/MacOS */
2122
static char g_app_dir[1024]; /* the directory CONTAINING the .app */
@@ -125,15 +126,20 @@ static int find_under(const char *dir, char *out, size_t osz, int depth){
125126
const char *env = getenv("NFSU2_SAVEDIR");
126127
if (env && *env){ snprintf(dir, sizeof dir, "%s", env); return dir; }
127128
const char *home = getenv("HOME"); if (!home) return NULL;
128-
snprintf(dir, sizeof dir, "%s/Library/Application Support/NFSU2 Kayitlar", home);
129+
snprintf(dir, sizeof dir, "%s/Library/Application Support/NFSU2 Saves", home);
130+
/* The folder used to be called "NFSU2 Kayitlar": move existing saves over once. */
131+
{ char old[1200]; struct stat st;
132+
snprintf(old, sizeof old, "%s/Library/Application Support/NFSU2 Kayitlar", home);
133+
if (stat(dir, &st) != 0 && stat(old, &st) == 0 && S_ISDIR(st.st_mode)){
134+
if (rename(old, dir) == 0) fprintf(stderr, "saves: moved \"%s\" -> \"%s\"\n", old, dir); } }
129135
return dir;
130136
}
131137

132138
static void app_alert(const char *title, const char *msg){
133139
NSAlert *a = [NSAlert new];
134140
[a setMessageText:@(title)];
135141
[a setInformativeText:@(msg)];
136-
[a addButtonWithTitle:@"Tamam"];
142+
[a addButtonWithTitle:@"OK"];
137143
[a runModal];
138144
}
139145

@@ -143,7 +149,7 @@ static int app_ask(char *out, size_t osz){
143149
[op setCanChooseFiles:NO];
144150
[op setCanChooseDirectories:YES];
145151
[op setAllowsMultipleSelection:NO];
146-
[op setPrompt:@"Sec"];
152+
[op setPrompt:@"Choose"];
147153
[op setMessage:@"Choose the Need for Speed Underground 2 folder "
148154
@"(the folder containing SPEED2.EXE)."];
149155
[NSApp activateIgnoringOtherApps:YES];
@@ -222,6 +228,49 @@ static int app_ask(char *out, size_t osz){
222228
return NULL;
223229
}
224230

231+
/* SUPPORTED EXECUTABLE. Game addresses are hard-coded in the runtime (and the
232+
* generated code is lifted from this exact file), so any other SPEED2.EXE --
233+
* e.g. the US release -- crashes or hangs somewhere at startup with no hint why.
234+
* Check the SHA-256 up front and say so. NFSU2_ANY_EXE=1 skips the check. */
235+
#define NFSU2_EXE_SIZE 4788224u
236+
#define NFSU2_EXE_SHA256 "8f6a9ae2c4cfa01181d2fd755d99c6f4b7103f60273908ea1de1e719dfe2a297"
237+
int app_check_exe(const char *exe){
238+
const char *any = getenv("NFSU2_ANY_EXE");
239+
if (any && *any == '1') return 1;
240+
FILE *f = fopen(exe, "rb");
241+
if (!f) return 1; /* the loader reports a missing file */
242+
CC_SHA256_CTX c; CC_SHA256_Init(&c);
243+
unsigned char buf[65536]; size_t n, tot = 0;
244+
while ((n = fread(buf, 1, sizeof buf, f)) > 0){ CC_SHA256_Update(&c, buf, (CC_LONG)n); tot += n; }
245+
fclose(f);
246+
unsigned char md[CC_SHA256_DIGEST_LENGTH]; CC_SHA256_Final(md, &c);
247+
char hex[2 * CC_SHA256_DIGEST_LENGTH + 1];
248+
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) snprintf(hex + 2 * i, 3, "%02x", md[i]);
249+
if (!strcmp(hex, NFSU2_EXE_SHA256)) return 1;
250+
fprintf(stderr, "unsupported SPEED2.EXE: %s\n size %zu bytes, sha256 %s\n"
251+
" supported: %u bytes, sha256 %s (European v1.2 no-CD executable)\n"
252+
" NFSU2_ANY_EXE=1 skips this check (it will almost certainly not run)\n",
253+
exe, tot, hex, NFSU2_EXE_SIZE, NFSU2_EXE_SHA256);
254+
if (app_is_bundled()){
255+
char msg[1600];
256+
snprintf(msg, sizeof msg,
257+
"This port runs exactly one build of the game's executable: the European "
258+
"v1.2 no-CD SPEED2.EXE (%u bytes). The SPEED2.EXE in the chosen folder is "
259+
"different (%zu bytes), for example the US release, and would crash.\n\n"
260+
"Replace SPEED2.EXE in your game folder with the supported one and open the "
261+
"app again. Details: README, \"Requirements\".\n\n%s",
262+
NFSU2_EXE_SIZE, tot, exe);
263+
[NSApplication sharedApplication];
264+
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
265+
[NSApp activateIgnoringOtherApps:YES];
266+
app_alert("Unsupported SPEED2.EXE", msg);
267+
/* forget the folder so the next launch asks again */
268+
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kPrefKey];
269+
[[NSUserDefaults standardUserDefaults] synchronize];
270+
}
271+
return 0;
272+
}
273+
225274
/* When exiting without finding the game, tell the user why. */
226275
void app_no_game(void){
227276
app_alert("Game files not found",

0 commit comments

Comments
 (0)