ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/MacOSX/main_macosx.mm
Revision: 1.23
Committed: 2011-03-11T16:43:09Z (13 years, 2 months ago) by asvitkine
Branch: MAIN
CVS Tags: HEAD
Changes since 1.22: +2 -2 lines
Log Message:
[Joseph Oswald]
Skip -psn_XXX command-line argument.

File Contents

# Content
1 /*
2 * $Id: main_macosx.mm,v 1.22 2009/10/08 08:56:23 nigel Exp $
3 *
4 * main_macosx.mm - Startup code for MacOS X
5 * Based (in a small way) on the default main.m,
6 and on Basilisk's main_unix.cpp
7 *
8 * Basilisk II (C) 1997-2008 Christian Bauer
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 */
24
25 #import <AppKit/AppKit.h>
26 #undef check
27
28 #define PTHREADS // Why is this here?
29 #include "sysdeps.h"
30
31 #ifdef HAVE_PTHREADS
32 # include <pthread.h>
33 #endif
34
35 #if REAL_ADDRESSING || DIRECT_ADDRESSING
36 # include <sys/mman.h>
37 #endif
38
39 #include <string>
40 using std::string;
41
42 #include "cpu_emulation.h"
43 #include "sys.h"
44 #include "rom_patches.h"
45 #include "xpram.h"
46 #include "video.h"
47 #include "prefs.h"
48 #include "prefs_editor.h"
49 #include "macos_util_macosx.h"
50 #include "user_strings.h"
51 #include "version.h"
52 #include "main.h"
53 #include "vm_alloc.h"
54 #include "sigsegv.h"
55
56 #if USE_JIT
57 extern void flush_icache_range(uint8 *start, uint32 size); // from compemu_support.cpp
58 #endif
59
60 #ifdef ENABLE_MON
61 # include "mon.h"
62 #endif
63
64 #define DEBUG 0
65 #include "debug.h"
66
67
68 #include "main_macosx.h" // To bridge between main() and misc. classes
69
70
71 // Constants
72 const char ROM_FILE_NAME[] = "ROM";
73 const int SCRATCH_MEM_SIZE = 0x10000; // Size of scratch memory area
74
75
76 static char *bundle = NULL; // If in an OS X application bundle, its path
77
78
79 // CPU and FPU type, addressing mode
80 int CPUType;
81 bool CPUIs68060;
82 int FPUType;
83 bool TwentyFourBitAddressing;
84
85
86 // Global variables
87
88 #ifdef HAVE_PTHREADS
89
90 static pthread_mutex_t intflag_lock = PTHREAD_MUTEX_INITIALIZER; // Mutex to protect InterruptFlags
91 #define LOCK_INTFLAGS pthread_mutex_lock(&intflag_lock)
92 #define UNLOCK_INTFLAGS pthread_mutex_unlock(&intflag_lock)
93
94 #else
95
96 #define LOCK_INTFLAGS
97 #define UNLOCK_INTFLAGS
98
99 #endif
100
101 #if USE_SCRATCHMEM_SUBTERFUGE
102 uint8 *ScratchMem = NULL; // Scratch memory for Mac ROM writes
103 #endif
104
105 #ifdef ENABLE_MON
106 static struct sigaction sigint_sa; // sigaction for SIGINT handler
107 static void sigint_handler(...);
108 #endif
109
110 #if REAL_ADDRESSING
111 static bool lm_area_mapped = false; // Flag: Low Memory area mmap()ped
112 #endif
113
114
115 /*
116 * Helpers to map memory that can be accessed from the Mac side
117 */
118
119 // NOTE: VM_MAP_32BIT is only used when compiling a 64-bit JIT on specific platforms
120 void *vm_acquire_mac(size_t size)
121 {
122 return vm_acquire(size, VM_MAP_DEFAULT | VM_MAP_32BIT);
123 }
124
125 static int vm_acquire_mac_fixed(void *addr, size_t size)
126 {
127 return vm_acquire_fixed(addr, size, VM_MAP_DEFAULT | VM_MAP_32BIT);
128 }
129
130
131 /*
132 * SIGSEGV handler
133 */
134
135 static sigsegv_return_t sigsegv_handler(sigsegv_info_t *sip)
136 {
137 const uintptr fault_address = (uintptr)sigsegv_get_fault_address(sip);
138 #if ENABLE_VOSF
139 // Handle screen fault
140 extern bool Screen_fault_handler(sigsegv_info_t *sip);
141 if (Screen_fault_handler(sip))
142 return SIGSEGV_RETURN_SUCCESS;
143 #endif
144
145 #ifdef HAVE_SIGSEGV_SKIP_INSTRUCTION
146 // Ignore writes to ROM
147 if (((uintptr)fault_address - (uintptr)ROMBaseHost) < ROMSize)
148 return SIGSEGV_RETURN_SKIP_INSTRUCTION;
149
150 // Ignore all other faults, if requested
151 if (PrefsFindBool("ignoresegv"))
152 return SIGSEGV_RETURN_SKIP_INSTRUCTION;
153 #endif
154
155 return SIGSEGV_RETURN_FAILURE;
156 }
157
158 /*
159 * Dump state when everything went wrong after a SEGV
160 */
161
162 static void sigsegv_dump_state(sigsegv_info_t *sip)
163 {
164 const sigsegv_address_t fault_address = sigsegv_get_fault_address(sip);
165 const sigsegv_address_t fault_instruction = sigsegv_get_fault_instruction_address(sip);
166 fprintf(stderr, "Caught SIGSEGV at address %p", fault_address);
167 if (fault_instruction != SIGSEGV_INVALID_ADDRESS)
168 fprintf(stderr, " [IP=%p]", fault_instruction);
169 fprintf(stderr, "\n");
170 uaecptr nextpc;
171 extern void m68k_dumpstate(uaecptr *nextpc);
172 m68k_dumpstate(&nextpc);
173 #if USE_JIT && JIT_DEBUG
174 extern void compiler_dumpstate(void);
175 compiler_dumpstate();
176 #endif
177 VideoQuitFullScreen();
178 #ifdef ENABLE_MON
179 char *arg[4] = {"mon", "-m", "-r", NULL};
180 mon(3, arg);
181 #endif
182 QuitEmulator();
183 }
184
185
186 /*
187 * Screen fault handler
188 */
189
190 bool Screen_fault_handler(sigsegv_info_t *sip)
191 {
192 return true;
193 }
194
195
196 /*
197 * Main program
198 */
199
200 static void usage(const char *prg_name)
201 {
202 printf(
203 "Usage: %s [OPTION...]\n"
204 "\nUnix options:\n"
205 " --config FILE\n read/write configuration from/to FILE\n"
206 " --break ADDRESS\n set ROM breakpoint\n"
207 " --rominfo\n dump ROM information\n", prg_name
208 );
209 LoadPrefs(NULL); // read the prefs file so PrefsPrintUsage() will print the correct default values
210 PrefsPrintUsage();
211 exit(0);
212 }
213
214 int main(int argc, char **argv)
215 {
216 const char *vmdir = NULL;
217 char str[256];
218
219 // Initialize variables
220 RAMBaseHost = NULL;
221 ROMBaseHost = NULL;
222 srand(time(NULL));
223 tzset();
224
225 // Print some info
226 printf(GetString(STR_ABOUT_TEXT1), VERSION_MAJOR, VERSION_MINOR);
227 printf(" %s\n", GetString(STR_ABOUT_TEXT2));
228
229 // Parse command line arguments
230 for (int i=1; i<argc; i++) {
231 if (strcmp(argv[i], "--help") == 0) {
232 usage(argv[0]);
233 } else if (strncmp(argv[i], "-psn_", 5) == 0) {// OS X process identifier
234 argv[i++] = NULL;
235 } else if (strcmp(argv[i], "--break") == 0) {
236 argv[i++] = NULL;
237 if (i < argc) {
238 ROMBreakpoint = strtol(argv[i], NULL, 0);
239 argv[i] = NULL;
240 }
241 } else if (strcmp(argv[i], "--config") == 0) {
242 argv[i++] = NULL;
243 if (i < argc) {
244 extern string UserPrefsPath; // from prefs_unix.cpp
245 UserPrefsPath = argv[i];
246 argv[i] = NULL;
247 }
248 } else if (strcmp(argv[i], "--rominfo") == 0) {
249 argv[i] = NULL;
250 PrintROMInfo = true;
251 }
252 }
253
254 // Remove processed arguments
255 for (int i=1; i<argc; i++) {
256 int k;
257 for (k=i; k<argc; k++)
258 if (argv[k] != NULL)
259 break;
260 if (k > i) {
261 k -= i;
262 for (int j=i+k; j<argc; j++)
263 argv[j-k] = argv[j];
264 argc -= k;
265 }
266 }
267
268 // Read preferences
269 PrefsInit(vmdir, argc, argv);
270
271 // Any command line arguments left?
272 for (int i=1; i<argc; i++) {
273 if (argv[i][0] == '-') {
274 fprintf(stderr, "Unrecognized option '%s'\n", argv[i]);
275 usage(argv[0]);
276 }
277 }
278
279 // Init system routines
280 SysInit();
281
282 // Set the current directory somewhere useful.
283 // Handy for storing the ROM file
284 bundle = strstr(argv[0], "BasiliskII.app/Contents/MacOS/BasiliskII");
285 if (bundle)
286 {
287 while (*bundle != '/')
288 ++bundle;
289
290 *bundle = 0; // Throw away Contents/... on end of argv[0]
291 bundle = argv[0];
292
293 chdir(bundle);
294 }
295
296 // Open display, attach to window server,
297 // load pre-instantiated classes from MainMenu.nib, start run loop
298 int i = NSApplicationMain(argc, (const char **)argv);
299 // We currently never get past here, because QuitEmulator() does an exit()
300
301 // Exit system routines
302 SysExit();
303
304 // Exit preferences
305 PrefsExit();
306
307 return i;
308 }
309
310 #define QuitEmulator() { QuitEmuNoExit() ; return NO; }
311
312 bool InitEmulator (void)
313 {
314 const char *vmdir = NULL;
315 char str[256];
316
317
318 // Install the handler for SIGSEGV
319 if (!sigsegv_install_handler(sigsegv_handler)) {
320 sprintf(str, GetString(STR_SIG_INSTALL_ERR), "SIGSEGV", strerror(errno));
321 ErrorAlert(str);
322 QuitEmulator();
323 }
324
325 // Register dump state function when we got mad after a segfault
326 sigsegv_set_dump_state(sigsegv_dump_state);
327
328 // Read RAM size
329 RAMSize = PrefsFindInt32("ramsize") & 0xfff00000; // Round down to 1MB boundary
330 if (RAMSize < 1024*1024) {
331 WarningAlert(GetString(STR_SMALL_RAM_WARN));
332 RAMSize = 1024*1024;
333 }
334 if (RAMSize > 1023*1024*1024) // Cap to 1023MB (APD crashes at 1GB)
335 RAMSize = 1023*1024*1024;
336
337 #if REAL_ADDRESSING || DIRECT_ADDRESSING
338 RAMSize = RAMSize & -getpagesize(); // Round down to page boundary
339 #endif
340
341 // Initialize VM system
342 vm_init();
343
344 #if REAL_ADDRESSING
345 // Flag: RAM and ROM are contigously allocated from address 0
346 bool memory_mapped_from_zero = false;
347
348 // Make sure to map RAM & ROM at address 0 only on platforms that
349 // supports linker scripts to relocate the Basilisk II executable
350 // above 0x70000000
351 #if HAVE_LINKER_SCRIPT
352 const bool can_map_all_memory = true;
353 #else
354 const bool can_map_all_memory = false;
355 #endif
356
357 // Try to allocate all memory from 0x0000, if it is not known to crash
358 if (can_map_all_memory && (vm_acquire_mac_fixed(0, RAMSize + 0x100000) == 0)) {
359 D(bug("Could allocate RAM and ROM from 0x0000\n"));
360 memory_mapped_from_zero = true;
361 }
362
363 #ifndef PAGEZERO_HACK
364 // Otherwise, just create the Low Memory area (0x0000..0x2000)
365 else if (vm_acquire_mac_fixed(0, 0x2000) == 0) {
366 D(bug("Could allocate the Low Memory globals\n"));
367 lm_area_mapped = true;
368 }
369
370 // Exit on failure
371 else {
372 sprintf(str, GetString(STR_LOW_MEM_MMAP_ERR), strerror(errno));
373 ErrorAlert(str);
374 QuitEmulator();
375 }
376 #endif
377 #else
378 *str = 0; // Eliminate unused variable warning
379 #endif /* REAL_ADDRESSING */
380
381 // Create areas for Mac RAM and ROM
382 #if REAL_ADDRESSING
383 if (memory_mapped_from_zero) {
384 RAMBaseHost = (uint8 *)0;
385 ROMBaseHost = RAMBaseHost + RAMSize;
386 }
387 else
388 #endif
389 {
390 uint8 *ram_rom_area = (uint8 *)vm_acquire_mac(RAMSize + 0x100000);
391 if (ram_rom_area == VM_MAP_FAILED) {
392 ErrorAlert(STR_NO_MEM_ERR);
393 QuitEmulator();
394 }
395 RAMBaseHost = ram_rom_area;
396 ROMBaseHost = RAMBaseHost + RAMSize;
397 }
398
399 #if USE_SCRATCHMEM_SUBTERFUGE
400 // Allocate scratch memory
401 ScratchMem = (uint8 *)vm_acquire_mac(SCRATCH_MEM_SIZE);
402 if (ScratchMem == VM_MAP_FAILED) {
403 ErrorAlert(STR_NO_MEM_ERR);
404 QuitEmulator();
405 }
406 ScratchMem += SCRATCH_MEM_SIZE/2; // ScratchMem points to middle of block
407 #endif
408
409 #if DIRECT_ADDRESSING
410 // RAMBaseMac shall always be zero
411 MEMBaseDiff = (uintptr)RAMBaseHost;
412 RAMBaseMac = 0;
413 ROMBaseMac = Host2MacAddr(ROMBaseHost);
414 #endif
415 #if REAL_ADDRESSING
416 RAMBaseMac = Host2MacAddr(RAMBaseHost);
417 ROMBaseMac = Host2MacAddr(ROMBaseHost);
418 #endif
419 D(bug("Mac RAM starts at %p (%08x)\n", RAMBaseHost, RAMBaseMac));
420 D(bug("Mac ROM starts at %p (%08x)\n", ROMBaseHost, ROMBaseMac));
421
422 // Get rom file path from preferences
423 const char *rom_path = PrefsFindString("rom");
424 if ( ! rom_path )
425 if ( bundle )
426 WarningAlert("No rom pathname set. Trying BasiliskII.app/ROM");
427 else
428 WarningAlert("No rom pathname set. Trying ./ROM");
429
430 // Load Mac ROM
431 int rom_fd = open(rom_path ? rom_path : ROM_FILE_NAME, O_RDONLY);
432 if (rom_fd < 0) {
433 ErrorAlert(STR_NO_ROM_FILE_ERR);
434 QuitEmulator();
435 }
436 printf(GetString(STR_READING_ROM_FILE));
437 ROMSize = lseek(rom_fd, 0, SEEK_END);
438 if (ROMSize != 64*1024 && ROMSize != 128*1024 && ROMSize != 256*1024 && ROMSize != 512*1024 && ROMSize != 1024*1024) {
439 ErrorAlert(STR_ROM_SIZE_ERR);
440 close(rom_fd);
441 QuitEmulator();
442 }
443 lseek(rom_fd, 0, SEEK_SET);
444 if (read(rom_fd, ROMBaseHost, ROMSize) != (ssize_t)ROMSize) {
445 ErrorAlert(STR_ROM_FILE_READ_ERR);
446 close(rom_fd);
447 QuitEmulator();
448 }
449
450
451 // Initialize everything
452 if (!InitAll(vmdir))
453 QuitEmulator();
454 D(bug("Initialization complete\n"));
455
456
457 #ifdef ENABLE_MON
458 // Setup SIGINT handler to enter mon
459 sigemptyset(&sigint_sa.sa_mask);
460 sigint_sa.sa_handler = (void (*)(int))sigint_handler;
461 sigint_sa.sa_flags = 0;
462 sigaction(SIGINT, &sigint_sa, NULL);
463 #endif
464
465
466 return YES;
467 }
468
469 #undef QuitEmulator()
470
471
472 /*
473 * Quit emulator
474 */
475
476 void QuitEmuNoExit()
477 {
478 D(bug("QuitEmulator\n"));
479
480 // Exit 680x0 emulation
481 Exit680x0();
482
483 // Deinitialize everything
484 ExitAll();
485
486 // Free ROM/RAM areas
487 if (RAMBaseHost != VM_MAP_FAILED) {
488 vm_release(RAMBaseHost, RAMSize + 0x100000);
489 RAMBaseHost = NULL;
490 ROMBaseHost = NULL;
491 }
492
493 #if USE_SCRATCHMEM_SUBTERFUGE
494 // Delete scratch memory area
495 if (ScratchMem != (uint8 *)VM_MAP_FAILED) {
496 vm_release((void *)(ScratchMem - SCRATCH_MEM_SIZE/2), SCRATCH_MEM_SIZE);
497 ScratchMem = NULL;
498 }
499 #endif
500
501 #if REAL_ADDRESSING
502 // Delete Low Memory area
503 if (lm_area_mapped)
504 vm_release(0, 0x2000);
505 #endif
506
507 // Exit VM wrappers
508 vm_exit();
509
510 // Exit system routines
511 SysExit();
512
513 // Exit preferences
514 PrefsExit();
515 }
516
517 void QuitEmulator(void)
518 {
519 QuitEmuNoExit();
520
521 // Stop run loop?
522 [NSApp terminate: nil];
523
524 exit(0);
525 }
526
527
528 /*
529 * Code was patched, flush caches if neccessary (i.e. when using a real 680x0
530 * or a dynamically recompiling emulator)
531 */
532
533 void FlushCodeCache(void *start, uint32 size)
534 {
535 #if USE_JIT
536 if (UseJIT)
537 flush_icache_range((uint8 *)start, size);
538 #endif
539 }
540
541
542 /*
543 * SIGINT handler, enters mon
544 */
545
546 #ifdef ENABLE_MON
547 static void sigint_handler(...)
548 {
549 uaecptr nextpc;
550 extern void m68k_dumpstate(uaecptr *nextpc);
551 m68k_dumpstate(&nextpc);
552 VideoQuitFullScreen();
553 char *arg[4] = {"mon", "-m", "-r", NULL};
554 mon(3, arg);
555 QuitEmulator();
556 }
557 #endif
558
559
560 #ifdef HAVE_PTHREADS
561 /*
562 * Pthread configuration
563 */
564
565 void Set_pthread_attr(pthread_attr_t *attr, int priority)
566 {
567 pthread_attr_init(attr);
568 #if defined(_POSIX_THREAD_PRIORITY_SCHEDULING)
569 // Some of these only work for superuser
570 if (geteuid() == 0) {
571 pthread_attr_setinheritsched(attr, PTHREAD_EXPLICIT_SCHED);
572 pthread_attr_setschedpolicy(attr, SCHED_FIFO);
573 struct sched_param fifo_param;
574 fifo_param.sched_priority = ((sched_get_priority_min(SCHED_FIFO)
575 + sched_get_priority_max(SCHED_FIFO))
576 / 2 + priority);
577 pthread_attr_setschedparam(attr, &fifo_param);
578 }
579 if (pthread_attr_setscope(attr, PTHREAD_SCOPE_SYSTEM) != 0) {
580 #ifdef PTHREAD_SCOPE_BOUND_NP
581 // If system scope is not available (eg. we're not running
582 // with CAP_SCHED_MGT capability on an SGI box), try bound
583 // scope. It exposes pthread scheduling to the kernel,
584 // without setting realtime priority.
585 pthread_attr_setscope(attr, PTHREAD_SCOPE_BOUND_NP);
586 #endif
587 }
588 #endif
589 }
590 #endif // HAVE_PTHREADS
591
592
593 /*
594 * Mutexes
595 */
596
597 #ifdef HAVE_PTHREADS
598
599 struct B2_mutex {
600 B2_mutex() {
601 pthread_mutexattr_t attr;
602 pthread_mutexattr_init(&attr);
603 // Initialize the mutex for priority inheritance --
604 // required for accurate timing.
605 #ifdef HAVE_PTHREAD_MUTEXATTR_SETPROTOCOL
606 pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
607 #endif
608 #if defined(HAVE_PTHREAD_MUTEXATTR_SETTYPE) && defined(PTHREAD_MUTEX_NORMAL)
609 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
610 #endif
611 #ifdef HAVE_PTHREAD_MUTEXATTR_SETPSHARED
612 pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_PRIVATE);
613 #endif
614 pthread_mutex_init(&m, &attr);
615 pthread_mutexattr_destroy(&attr);
616 }
617 ~B2_mutex() {
618 pthread_mutex_trylock(&m); // Make sure it's locked before
619 pthread_mutex_unlock(&m); // unlocking it.
620 pthread_mutex_destroy(&m);
621 }
622 pthread_mutex_t m;
623 };
624
625 B2_mutex *B2_create_mutex(void)
626 {
627 return new B2_mutex;
628 }
629
630 void B2_lock_mutex(B2_mutex *mutex)
631 {
632 pthread_mutex_lock(&mutex->m);
633 }
634
635 void B2_unlock_mutex(B2_mutex *mutex)
636 {
637 pthread_mutex_unlock(&mutex->m);
638 }
639
640 void B2_delete_mutex(B2_mutex *mutex)
641 {
642 delete mutex;
643 }
644
645 #else
646
647 struct B2_mutex {
648 int dummy;
649 };
650
651 B2_mutex *B2_create_mutex(void)
652 {
653 return new B2_mutex;
654 }
655
656 void B2_lock_mutex(B2_mutex *mutex)
657 {
658 }
659
660 void B2_unlock_mutex(B2_mutex *mutex)
661 {
662 }
663
664 void B2_delete_mutex(B2_mutex *mutex)
665 {
666 delete mutex;
667 }
668
669 #endif
670
671
672 /*
673 * Interrupt flags (must be handled atomically!)
674 */
675
676 uint32 InterruptFlags = 0;
677
678 void SetInterruptFlag(uint32 flag)
679 {
680 LOCK_INTFLAGS;
681 InterruptFlags |= flag;
682 UNLOCK_INTFLAGS;
683 }
684
685 void ClearInterruptFlag(uint32 flag)
686 {
687 LOCK_INTFLAGS;
688 InterruptFlags &= ~flag;
689 UNLOCK_INTFLAGS;
690 }
691
692
693 /*
694 * Display error alert
695 */
696
697 void ErrorAlert(const char *text)
698 {
699 NSString *title = [NSString stringWithCString:
700 GetString(STR_ERROR_ALERT_TITLE) ];
701 NSString *error = [NSString stringWithCString: text];
702 NSString *button = [NSString stringWithCString: GetString(STR_QUIT_BUTTON) ];
703
704 NSLog(error);
705 if ( PrefsFindBool("nogui") )
706 return;
707 VideoQuitFullScreen();
708 NSRunCriticalAlertPanel(title, error, button, nil, nil);
709 }
710
711
712 /*
713 * Display warning alert
714 */
715
716 void WarningAlert(const char *text)
717 {
718 NSString *title = [NSString stringWithCString:
719 GetString(STR_WARNING_ALERT_TITLE) ];
720 NSString *warning = [NSString stringWithCString: text];
721 NSString *button = [NSString stringWithCString: GetString(STR_OK_BUTTON) ];
722
723 NSLog(warning);
724 if ( PrefsFindBool("nogui") )
725 return;
726 VideoQuitFullScreen();
727 NSRunAlertPanel(title, warning, button, nil, nil);
728 }
729
730
731 /*
732 * Display choice alert
733 */
734
735 bool ChoiceAlert(const char *text, const char *pos, const char *neg)
736 {
737 NSString *title = [NSString stringWithCString:
738 GetString(STR_WARNING_ALERT_TITLE) ];
739 NSString *warning = [NSString stringWithCString: text];
740 NSString *yes = [NSString stringWithCString: pos];
741 NSString *no = [NSString stringWithCString: neg];
742
743 return NSRunInformationalAlertPanel(title, warning, yes, no, nil);
744 }