ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/MacOSX/main_macosx.mm
Revision: 1.14
Committed: 2005-09-19T07:49:12Z (18 years, 8 months ago) by nigel
Branch: MAIN
CVS Tags: nigel-build-17
Changes since 1.13: +63 -14 lines
Log Message:
Fixes from latest Unix version (no more black screen bug!)

File Contents

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