Chameleon

Chameleon Svn Source Tree

Root/branches/Chimera/i386/boot2/options.c

1/*
2 * Copyright (c) 1999-2004 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Portions Copyright (c) 1999-2004 Apple Computer, Inc. All Rights
7 * Reserved. This file contains Original Code and/or Modifications of
8 * Original Code as defined in and that are subject to the Apple Public
9 * Source License Version 2.0 (the "License"). You may not use this file
10 * except in compliance with the License. Please obtain a copy of the
11 * License at http://www.apple.com/publicsource and read it before using
12 * this file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
19 * License for the specific language governing rights and limitations
20 * under the License.
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24
25#include "boot.h"
26#include "bootstruct.h"
27#include "fdisk.h"
28#include "ramdisk.h"
29#include "gui.h"
30#include "term.h"
31#include "embedded.h"
32#include "pci.h"
33#include "modules.h"
34
35#if DEBUG
36#define DBG(x...)printf(x)
37#else
38#define DBG(x...)msglog(x)
39#endif
40
41bool showBootBanner = true; //Azi:showinfo
42static bool shouldboot = false;
43
44extern int multiboot_timeout;
45extern int multiboot_timeout_set;
46
47extern BVRef bvChain;
48//extern intmenucount;
49
50extern intgDeviceCount;
51
52intselectIndex = 0;
53MenuItem * menuItems = NULL;
54
55enum {
56 kMenuTopRow = 5,
57 kMenuMaxItems = 10,
58 kScreenLastRow = 24
59};
60
61//==========================================================================
62
63typedef struct {
64 int x;
65 int y;
66 int type;
67} CursorState;
68
69static void changeCursor( int col, int row, int type, CursorState * cs )
70{
71 if (cs) getCursorPositionAndType( &cs->x, &cs->y, &cs->type );
72 setCursorType( type );
73 setCursorPosition( col, row, 0 );
74}
75
76static void moveCursor( int col, int row )
77{
78 setCursorPosition( col, row, 0 );
79}
80
81static void restoreCursor( const CursorState * cs )
82{
83 setCursorPosition( cs->x, cs->y, 0 );
84 setCursorType( cs->type );
85}
86
87//==========================================================================
88
89/* Flush keyboard buffer; returns TRUE if any of the flushed
90 * characters was F8.
91 */
92
93static bool flushKeyboardBuffer(void)
94{
95 bool status = false;
96
97 while ( readKeyboardStatus() ) {
98 if (bgetc() == 0x4200) status = true;
99 }
100 return status;
101}
102
103//==========================================================================
104
105static int countdown( const char * msg, int row, int timeout )
106{
107 unsigned long time;
108 int ch = 0;
109 int col = strlen(msg) + 1;
110
111 flushKeyboardBuffer();
112
113if( bootArgs->Video.v_display == VGA_TEXT_MODE )
114{
115moveCursor( 0, row );
116printf(msg);
117
118} else {
119
120position_t p = pos( gui.screen.width / 2 + 1 , ( gui.devicelist.pos.y + 3 ) + ( ( gui.devicelist.height - gui.devicelist.iconspacing ) / 2 ) );
121
122char dummy[80];
123getBootVolumeDescription( gBootVolume, dummy, sizeof(dummy) - 1, true );
124drawDeviceIcon( gBootVolume, gui.screen.pixmap, p, true );
125drawStrCenteredAt( (char *) msg, &font_small, gui.screen.pixmap, gui.countdown.pos );
126
127// make this screen the new background
128memcpy( gui.backbuffer->pixels, gui.screen.pixmap->pixels, gui.backbuffer->width * gui.backbuffer->height * 4 );
129
130}
131
132int multi_buff = 18 * (timeout);
133 int multi = ++multi_buff;
134
135 int lasttime=0;
136
137 for ( time = time18(), timeout++; timeout > 0; )
138 {
139if( time18() > lasttime)
140{
141multi--;
142lasttime=time18();
143}
144
145 if ( (ch = readKeyboardStatus()) )
146 break;
147
148 // Count can be interrupted by holding down shift,
149 // control or alt key
150 if ( ( readKeyboardShiftFlags() & 0x0F ) != 0 )
151{
152 ch = 1;
153 break;
154 }
155
156 if ( time18() >= time )
157 {
158 time += 18;
159 timeout--;
160
161if( bootArgs->Video.v_display == VGA_TEXT_MODE )
162{
163moveCursor( col, row );
164printf("(%d) ", timeout);
165}
166 }
167
168if( bootArgs->Video.v_display != VGA_TEXT_MODE )
169{
170drawProgressBar( gui.screen.pixmap, 100, gui.progressbar.pos , ( multi * 100 / multi_buff ) );
171gui.redraw = true;
172updateVRAM();
173}
174
175 }
176
177 flushKeyboardBuffer();
178
179 return ch;
180}
181
182//==========================================================================
183
184char gBootArgs[BOOT_STRING_LEN];
185static char * gBootArgsPtr = gBootArgs;
186static char * gBootArgsEnd = gBootArgs + BOOT_STRING_LEN - 1;
187static char booterCommand[BOOT_STRING_LEN];
188static char booterParam[BOOT_STRING_LEN];
189
190static void clearBootArgs(void)
191{
192gBootArgsPtr = gBootArgs;
193memset(gBootArgs, '\0', BOOT_STRING_LEN);
194
195if (bootArgs->Video.v_display != VGA_TEXT_MODE)
196 {
197clearGraphicBootPrompt();
198}
199execute_hook("ClearArgs", NULL, NULL, NULL, NULL);
200}
201
202void addBootArg(const char * argStr)
203{
204if ( (gBootArgsPtr + strlen(argStr) + 1) < gBootArgsEnd)
205{
206if(gBootArgsPtr != gBootArgs) *gBootArgsPtr++ = ' ';
207strcat(gBootArgs, argStr);
208gBootArgsPtr += strlen(argStr);
209}
210}
211
212//==========================================================================
213
214static void showBootPrompt(int row, bool visible)
215{
216extern char bootPrompt[];
217extern char bootRescanPrompt[];
218
219if( bootArgs->Video.v_display == VGA_TEXT_MODE ) {
220changeCursor( 0, row, kCursorTypeUnderline, 0 );
221clearScreenRows( row, kScreenLastRow );
222}
223
224clearBootArgs();
225
226if (visible) {
227if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
228if (gEnableCDROMRescan) {
229printf( bootRescanPrompt );
230} else {
231printf( bootPrompt );
232printf( gBootArgs );
233}
234}
235} else {
236if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
237clearGraphicBootPrompt();
238} else {
239printf("Press Enter to start up the foreign OS. ");
240}
241}
242}
243
244//==========================================================================
245
246static void updateBootArgs( int key )
247{
248 key = ASCII_KEY(key);
249
250 switch ( key )
251 {
252 case KEY_BKSP:
253 if ( gBootArgsPtr > gBootArgs )
254 {
255 *--gBootArgsPtr = '\0';
256
257 int x, y, t;
258 getCursorPositionAndType( &x, &y, &t );
259 if ( x == 0 && y )
260 {
261 x = 80; y--;
262 }
263 if (x) {
264x--;
265}
266
267if( bootArgs->Video.v_display == VGA_TEXT_MODE )
268{
269setCursorPosition( x, y, 0 );
270putca(' ', 0x07, 1);
271}
272 else
273 {
274 updateGraphicBootPrompt();
275 }
276 }
277break;
278
279 default:
280 if ( key >= ' ' && gBootArgsPtr < gBootArgsEnd)
281 {
282 *gBootArgsPtr++ = key;
283
284 if( bootArgs->Video.v_display != VGA_TEXT_MODE ) updateGraphicBootPrompt();
285 else if ( key >= ' ' && key < 0x7f) putchar(key);
286}
287
288break;
289 }
290}
291
292//==========================================================================
293
294static const MenuItem * gMenuItems = NULL;
295
296static int gMenuItemCount;
297static int gMenuRow;
298static int gMenuHeight;
299static int gMenuTop;
300static int gMenuBottom;
301static int gMenuSelection;
302
303static int gMenuStart;
304static int gMenuEnd;
305
306static void printMenuItem( const MenuItem * item, int highlight )
307{
308 printf(" ");
309
310 if ( highlight )
311 putca(' ', 0x70, strlen(item->name) + 4);
312 else
313 putca(' ', 0x07, 40);
314
315 printf(" %40s\n", item->name);
316}
317
318//==========================================================================
319
320static void showMenu( const MenuItem * items, int count,
321 int selection, int row, int height )
322{
323 int i;
324 CursorState cursorState;
325
326 if ( items == NULL || count == 0 )
327return;
328
329 // head and tail points to the start and the end of the list.
330 // top and bottom points to the first and last visible items
331 // in the menu window.
332
333 gMenuItems= items;
334 gMenuRow= row;
335 gMenuHeight= height;
336 gMenuItemCount= count;
337 gMenuTop= 0;
338 gMenuBottom= MIN( count, height ) - 1;
339 gMenuSelection= selection;
340
341 gMenuStart= 0;
342 gMenuEnd = MIN( count, gui.maxdevices ) - 1;
343
344// If the selected item is not visible, shift the list down.
345
346 if ( gMenuSelection > gMenuBottom )
347 {
348 gMenuTop += ( gMenuSelection - gMenuBottom );
349 gMenuBottom = gMenuSelection;
350 }
351
352if ( gMenuSelection > gMenuEnd )
353 {
354gMenuStart += ( gMenuSelection - gMenuEnd );
355 gMenuEnd = gMenuSelection;
356 }
357
358// Draw the visible items.
359
360if( bootArgs->Video.v_display != VGA_TEXT_MODE )
361
362drawDeviceList(gMenuStart, gMenuEnd, gMenuSelection);
363
364else {
365
366changeCursor( 0, row, kCursorTypeHidden, &cursorState );
367
368for ( i = gMenuTop; i <= gMenuBottom; i++ )
369{
370printMenuItem( &items[i], (i == gMenuSelection) );
371}
372
373restoreCursor( &cursorState );
374 }
375}
376
377//==========================================================================
378
379static int updateMenu( int key, void ** paramPtr )
380{
381 int moved = 0;
382
383 union {
384 struct {
385 unsigned int
386 selectionUp : 1,
387 selectionDown : 1,
388 scrollUp : 1,
389 scrollDown : 1;
390 } f;
391 unsigned int w;
392 } draw = {{0}};
393
394 if ( gMenuItems == NULL )
395return 0;
396
397if( bootArgs->Video.v_display != VGA_TEXT_MODE )
398{
399int res;
400
401// set navigation keys for horizontal layout as defaults
402int previous= 0x4B00;// left arrow
403int subsequent= 0x4D00;// right arrow
404int menu= 0x5000;// down arrow
405
406if ( gui.layout == VerticalLayout )
407{
408// set navigation keys for vertical layout
409previous= 0x4800;// up arrow
410subsequent= 0x5000;// down arrow
411menu= 0x4B00;// right arrow
412}
413
414if ( key == previous )
415{
416if ( gMenuSelection > gMenuTop )
417draw.f.selectionUp = 1;
418else if ( gMenuTop > 0 )
419draw.f.scrollDown = 1;
420
421}
422
423else if ( key == subsequent )
424{
425if ( gMenuSelection != gMenuBottom)
426draw.f.selectionDown = 1;
427else if ( gMenuBottom < ( gMenuItemCount - 1 ) )
428draw.f.scrollUp = 1;
429}
430
431else if ( key == menu )
432{
433if ( gui.menu.draw )
434updateInfoMenu(key);
435else
436drawInfoMenu();
437}
438
439else if ( gui.menu.draw )
440{
441res = updateInfoMenu(key);
442
443if ( res == CLOSE_INFO_MENU )
444gui.menu.draw = false;
445else
446{
447shouldboot = ( res != DO_NOT_BOOT );
448
449if ( shouldboot )
450gui.menu.draw = false;
451
452switch (res)
453{
454case BOOT_NORMAL:
455gVerboseMode = false;
456gBootMode = kBootModeNormal;
457break;
458
459case BOOT_VERBOSE:
460gVerboseMode = true;
461gBootMode = kBootModeNormal;
462addBootArg(kVerboseModeFlag);
463break;
464
465case BOOT_IGNORECACHE:
466gVerboseMode = false;
467gBootMode = kBootModeNormal;
468addBootArg(kIgnoreCachesFlag);
469break;
470
471case BOOT_SINGLEUSER:
472gVerboseMode = true;
473gBootMode = kBootModeNormal;
474addBootArg(kSingleUserModeFlag);
475break;
476}
477
478}
479
480}
481
482} else {
483switch ( key )
484{
485 case 0x4800: // Up Arrow
486if ( gMenuSelection != gMenuTop )
487draw.f.selectionUp = 1;
488else if ( gMenuTop > 0 )
489draw.f.scrollDown = 1;
490break;
491
492case 0x5000: // Down Arrow
493if ( gMenuSelection != gMenuBottom )
494draw.f.selectionDown = 1;
495else if ( gMenuBottom < (gMenuItemCount - 1) )
496draw.f.scrollUp = 1;
497break;
498}
499}
500
501 if ( draw.w )
502 {
503 if ( draw.f.scrollUp )
504 {
505 scollPage(0, gMenuRow, 40, gMenuRow + gMenuHeight - 1, 0x07, 1, 1);
506 gMenuTop++; gMenuBottom++;
507gMenuStart++; gMenuEnd++;
508 draw.f.selectionDown = 1;
509 }
510
511 if ( draw.f.scrollDown )
512 {
513 scollPage(0, gMenuRow, 40, gMenuRow + gMenuHeight - 1, 0x07, 1, -1);
514 gMenuTop--; gMenuBottom--;
515 gMenuStart--; gMenuEnd--;
516 draw.f.selectionUp = 1;
517 }
518
519 if ( draw.f.selectionUp || draw.f.selectionDown )
520 {
521
522CursorState cursorState;
523
524// Set cursor at current position, and clear inverse video.
525
526if( bootArgs->Video.v_display == VGA_TEXT_MODE )
527{
528changeCursor( 0, gMenuRow + gMenuSelection - gMenuTop, kCursorTypeHidden, &cursorState );
529printMenuItem( &gMenuItems[gMenuSelection], 0 );
530}
531
532if ( draw.f.selectionUp )
533{
534gMenuSelection--;
535if(( gMenuSelection - gMenuStart) == -1 )
536{
537gMenuStart--;
538gMenuEnd--;
539}
540
541} else {
542gMenuSelection++;
543if(( gMenuSelection - ( gui.maxdevices - 1) - gMenuStart) > 0 )
544{
545gMenuStart++;
546gMenuEnd++;
547}
548 }
549
550if( bootArgs->Video.v_display == VGA_TEXT_MODE )
551 {
552moveCursor( 0, gMenuRow + gMenuSelection - gMenuTop );
553printMenuItem( &gMenuItems[gMenuSelection], 1 );
554restoreCursor( &cursorState );
555
556 } else
557
558drawDeviceList (gMenuStart, gMenuEnd, gMenuSelection);
559
560}
561
562 *paramPtr = gMenuItems[gMenuSelection].param;
563 moved = 1;
564 }
565
566return moved;
567}
568
569//==========================================================================
570
571static void skipblanks( const char ** cpp )
572{
573 while ( **(cpp) == ' ' || **(cpp) == '\t' ) ++(*cpp);
574}
575
576//==========================================================================
577
578static const char * extractKernelName( char ** cpp )
579{
580 char * kn = *cpp;
581 char * cp = *cpp;
582 char c;
583
584 // Convert char to lower case.
585
586 c = *cp | 0x20;
587
588 // Must start with a letter or a '/'.
589
590 if ( (c < 'a' || c > 'z') && ( c != '/' ) )
591 return 0;
592
593 // Keep consuming characters until we hit a separator.
594
595 while ( *cp && (*cp != '=') && (*cp != ' ') && (*cp != '\t') )
596 cp++;
597
598 // Only SPACE or TAB separator is accepted.
599 // Reject everything else.
600
601 if (*cp == '=')
602 return 0;
603
604 // Overwrite the separator, and move the pointer past
605 // the kernel name.
606
607 if (*cp != '\0') *cp++ = '\0';
608 *cpp = cp;
609
610 return kn;
611}
612
613//==========================================================================
614
615static void
616printMemoryInfo(void)
617{
618 int line;
619 int i;
620 MemoryRange *mp = bootInfo->memoryMap;
621
622 // Activate and clear page 1
623 setActiveDisplayPage(1);
624 clearScreenRows(0, 24);
625 setCursorPosition( 0, 0, 1 );
626
627 printf("BIOS reported memory ranges:\n");
628 line = 1;
629 for (i=0; i<bootInfo->memoryMapCount; i++) {
630 printf("Base 0x%08x%08x, ",
631 (unsigned long)(mp->base >> 32),
632 (unsigned long)(mp->base));
633 printf("length 0x%08x%08x, type %d\n",
634 (unsigned long)(mp->length >> 32),
635 (unsigned long)(mp->length),
636 mp->type);
637 if (line++ > 20) {
638 pause();
639 line = 0;
640 }
641 mp++;
642 }
643 if (line > 0) {
644 pause();
645 }
646
647 setActiveDisplayPage(0);
648}
649
650char *getMemoryInfoString()
651{
652int i, bufflen;
653MemoryRange *mp = bootInfo->memoryMap;
654char *buff = malloc(sizeof(char)*1024);
655if(!buff) {
656return 0;
657}
658
659static const char info[] = "BIOS reported memory ranges:\n";
660bufflen = sprintf(buff, "%s", info);
661
662for (i = 0;
663(i < bootInfo->memoryMapCount) && (bufflen < 1024); /* prevent buffer overflow */
664i++) {
665bufflen += snprintf(buff+bufflen, 1024-bufflen, "Base 0x%08x%08x, ",
666 (unsigned long)(mp->base >> 32),
667 (unsigned long)(mp->base));
668bufflen += snprintf(buff+bufflen, 1024-bufflen, "length 0x%08x%08x, type %d\n",
669 (unsigned long)(mp->length >> 32),
670 (unsigned long)(mp->length),
671 mp->type);
672mp++;
673}
674return buff;
675}
676
677//==========================================================================
678
679void lspci(void)
680{
681if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
682setActiveDisplayPage(1);
683clearScreenRows(0, 24);
684setCursorPosition(0, 0, 1);
685}
686
687dump_pci_dt(root_pci_dev->children);
688
689pause();
690
691if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
692setActiveDisplayPage(0);
693}
694}
695
696//==========================================================================
697
698int getBootOptions(bool firstRun)
699{
700int i;
701int key;
702int nextRow;
703int timeout;
704int bvCount;
705BVRef bvr;
706BVRef menuBVR;
707bool showPrompt, newShowPrompt, isCDROM;
708
709// Initialize default menu selection entry.
710gBootVolume = menuBVR = selectBootVolume(bvChain);
711
712if (biosDevIsCDROM(gBIOSDev)) {
713isCDROM = true;
714} else {
715isCDROM = false;
716}
717
718// ensure we're in graphics mode if gui is setup
719if (firstRun && gui.initialised && bootArgs->Video.v_display == VGA_TEXT_MODE)
720{
721setVideoMode(GRAPHICS_MODE, 0);
722}
723
724// Clear command line boot arguments
725clearBootArgs();
726
727// Allow user to override default timeout.
728if (multiboot_timeout_set) {
729timeout = multiboot_timeout;
730} else if (!getIntForKey(kTimeoutKey, &timeout, &bootInfo->chameleonConfig)) {
731/* If there is no timeout key in the file use the default timeout
732 which is different for CDs vs. hard disks. However, if not booting
733 a CD and no config file could be loaded set the timeout
734 to zero which causes the menu to display immediately.
735 This way, if no partitions can be found, that is the disk is unpartitioned
736 or simply cannot be read) then an empty menu is displayed.
737 If some partitions are found, for example a Windows partition, then
738 these will be displayed in the menu as foreign partitions.
739 */
740if (isCDROM) {
741timeout = kCDBootTimeout;
742} else {
743timeout = sysConfigValid ? kBootTimeout : 0;
744}
745}
746
747if (timeout < 0) {
748gBootMode |= kBootModeQuiet;
749}
750
751// If the user is holding down a modifier key, enter safe mode.
752if ((readKeyboardShiftFlags() & 0x0F) != 0) {
753gBootMode |= kBootModeSafe;
754}
755
756// Checking user pressed keys
757bool f8press = false, spress = false, vpress = false;
758while (readKeyboardStatus()) {
759key = bgetc ();
760if (key == 0x4200) f8press = true;
761if ((key & 0xff) == 's' || (key & 0xff) == 'S') spress = true;
762if ((key & 0xff) == 'v' || (key & 0xff) == 'V') vpress = true;
763}
764// If user typed F8, abort quiet mode, and display the menu.
765if (f8press) {
766gBootMode &= ~kBootModeQuiet;
767timeout = 0;
768}
769// If user typed 'v' or 'V', boot in verbose mode.
770if ((gBootMode & kBootModeQuiet) && firstRun && vpress) {
771addBootArg(kVerboseModeFlag);
772}
773// If user typed 's' or 'S', boot in single user mode.
774if ((gBootMode & kBootModeQuiet) && firstRun && spress) {
775addBootArg(kSingleUserModeFlag);
776}
777
778if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
779setCursorPosition(0, 0, 0);
780clearScreenRows(0, kScreenLastRow);
781if (!(gBootMode & kBootModeQuiet)) {
782// Display banner and show hardware info.
783printf(bootBanner, (bootInfo->convmem + bootInfo->extmem) / 1024);
784printf(getVBEInfoString());
785}
786changeCursor(0, kMenuTopRow, kCursorTypeUnderline, 0);
787verbose("Scanning device %x...", gBIOSDev);
788}
789
790// When booting from CD, default to hard drive boot when possible.
791if (isCDROM && firstRun) {
792const char *val;
793char *prompt = NULL;
794char *name = NULL;
795int cnt;
796int optionKey;
797
798if (getValueForKey(kCDROMPromptKey, &val, &cnt, &bootInfo->chameleonConfig)) {
799prompt = malloc(cnt + 1);
800strncat(prompt, val, cnt);
801} else {
802name = malloc(80);
803getBootVolumeDescription(gBootVolume, name, 79, false);
804prompt = malloc(256);
805sprintf(prompt, "Press any key to start up from %s, or press F8 to enter startup options.", name);
806free(name);
807}
808
809if (getIntForKey( kCDROMOptionKey, &optionKey, &bootInfo->chameleonConfig )) {
810// The key specified is a special key.
811} else {
812// Default to F8.
813optionKey = 0x4200;
814}
815
816// If the timeout is zero then it must have been set above due to the
817// early catch of F8 which means the user wants to set boot options
818// which we ought to interpret as meaning he wants to boot the CD.
819if (timeout != 0) {
820key = countdown(prompt, kMenuTopRow, timeout);
821} else {
822key = optionKey;
823}
824
825if (prompt != NULL) {
826free(prompt);
827}
828
829clearScreenRows( kMenuTopRow, kMenuTopRow + 2 );
830
831// Hit the option key ?
832if (key == optionKey) {
833gBootMode &= ~kBootModeQuiet;
834timeout = 0;
835} else {
836key = key & 0xFF;
837
838// Try booting hard disk if user pressed 'h'
839if (biosDevIsCDROM(gBIOSDev) && key == 'h') {
840BVRef bvr;
841
842// Look at partitions hosting OS X other than the CD-ROM
843for (bvr = bvChain; bvr; bvr=bvr->next) {
844if ((bvr->flags & kBVFlagSystemVolume) && bvr->biosdev != gBIOSDev) {
845gBootVolume = bvr;
846}
847}
848}
849goto done;
850}
851}
852
853if (gBootMode & kBootModeQuiet) {
854// No input allowed from user.
855goto done;
856}
857
858if (firstRun && timeout > 0 && countdown("Press any key to enter startup options.", kMenuTopRow, timeout) == 0) {
859// If the user is holding down a modifier key,
860// enter safe mode.
861if ((readKeyboardShiftFlags() & 0x0F) != 0) {
862gBootMode |= kBootModeSafe;
863}
864goto done;
865}
866
867if (gDeviceCount > 0) {
868// Allocate memory for an array of menu items.
869menuItems = malloc(sizeof(MenuItem) * gDeviceCount);
870if (menuItems == NULL) {
871goto done;
872}
873
874// Associate a menu item for each BVRef.
875for (bvr=bvChain, i=gDeviceCount-1, selectIndex=-1; bvr; bvr=bvr->next) {
876if (bvr->visible) {
877getBootVolumeDescription(bvr, menuItems[i].name, sizeof(menuItems[i].name) - 1, true);
878menuItems[i].param = (void *) bvr;
879if (bvr == menuBVR) {
880selectIndex = i;
881}
882i--;
883}
884}
885// Jief : In case the default partition (returned by selectBootVolume) is not in the menu
886if ( selectIndex == -1 )
887{
888selectIndex = 0;
889
890// gDeviceCount is actually > 0, so menuItems[selectIndex] exists
891menuBVR = (BVRef)(menuItems[selectIndex].param);
892// what happen is bvChain is empty ?
893}
894}
895
896if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
897// redraw the background buffer
898gui.logo.draw = true;
899drawBackground();
900gui.devicelist.draw = true;
901gui.redraw = true;
902if (!(gBootMode & kBootModeQuiet)) {
903
904// Check if "Boot Banner"=N switch is present in config file.
905getBoolForKey(kBootBannerKey, &showBootBanner, &bootInfo->chameleonConfig);
906if (showBootBanner) {
907// Display banner and show hardware info.
908gprintf(&gui.screen, bootBanner + 1, (bootInfo->convmem + bootInfo->extmem) / 1024);
909}
910
911// redraw background
912memcpy(gui.backbuffer->pixels, gui.screen.pixmap->pixels, gui.backbuffer->width * gui.backbuffer->height * 4);
913}
914} else {
915// Clear screen and hide the blinking cursor.
916clearScreenRows(kMenuTopRow, kMenuTopRow + 2);
917changeCursor(0, kMenuTopRow, kCursorTypeHidden, 0);
918}
919
920nextRow = kMenuTopRow;
921showPrompt = true;
922
923if (gDeviceCount) {
924if( bootArgs->Video.v_display == VGA_TEXT_MODE ) {
925printf("Use \30\31 keys to select the startup volume.");
926}
927showMenu( menuItems, gDeviceCount, selectIndex, kMenuTopRow + 2, kMenuMaxItems );
928nextRow += MIN( gDeviceCount, kMenuMaxItems ) + 3;
929}
930
931// Show the boot prompt.
932showPrompt = (gDeviceCount == 0) || (menuBVR->flags & kBVFlagNativeBoot);
933showBootPrompt( nextRow, showPrompt );
934
935do {
936if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
937// redraw background
938memcpy( gui.backbuffer->pixels, gui.screen.pixmap->pixels, gui.backbuffer->width * gui.backbuffer->height * 4 );
939// reset cursor co-ords
940gui.debug.cursor = pos( gui.screen.width - 160 , 10 );
941}
942key = getchar();
943updateMenu( key, (void **) &menuBVR );
944newShowPrompt = (gDeviceCount == 0) || (menuBVR->flags & kBVFlagNativeBoot);
945
946if (newShowPrompt != showPrompt) {
947showPrompt = newShowPrompt;
948showBootPrompt( nextRow, showPrompt );
949}
950
951if (showPrompt) {
952updateBootArgs(key);
953}
954
955switch (key) {
956case KEY_ENTER:
957if (gui.menu.draw) {
958key=0;
959break;
960}
961if (*gBootArgs == '?') {
962char * argPtr = gBootArgs;
963
964// Skip the leading "?" character.
965argPtr++;
966getNextArg(&argPtr, booterCommand);
967getNextArg(&argPtr, booterParam);
968
969/*
970* TODO: this needs to be refactored.
971*/
972if (strcmp( booterCommand, "video" ) == 0) {
973if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
974showInfoBox(getVBEInfoString(), getVBEModeInfoString());
975} else {
976printVBEModeInfo();
977}
978} else if ( strcmp( booterCommand, "memory" ) == 0) {
979if (bootArgs->Video.v_display != VGA_TEXT_MODE ) {
980showInfoBox("Memory Map", getMemoryInfoString());
981} else {
982printMemoryInfo();
983}
984} else if (strcmp(booterCommand, "lspci") == 0) {
985lspci();
986} else if (strcmp(booterCommand, "more") == 0) {
987showTextFile(booterParam);
988} else if (strcmp(booterCommand, "rd") == 0) {
989processRAMDiskCommand(&argPtr, booterParam);
990} else if (strcmp(booterCommand, "norescan") == 0) {
991if (gEnableCDROMRescan) {
992gEnableCDROMRescan = false;
993break;
994}
995} else {
996showHelp();
997}
998key = 0;
999showBootPrompt(nextRow, showPrompt);
1000break;
1001}
1002gBootVolume = menuBVR;
1003setRootVolume(menuBVR);
1004gBIOSDev = menuBVR->biosdev;
1005break;
1006
1007case KEY_ESC:
1008clearBootArgs();
1009break;
1010
1011case KEY_F5:
1012// New behavior:
1013// Clear gBootVolume to restart the loop
1014// if the user enabled rescanning the optical drive.
1015// Otherwise boot the default boot volume.
1016if (gEnableCDROMRescan) {
1017gBootVolume = NULL;
1018clearBootArgs();
1019}
1020break;
1021
1022case KEY_F10:
1023gScanSingleDrive = false;
1024scanDisks(gBIOSDev, &bvCount);
1025gBootVolume = NULL;
1026clearBootArgs();
1027break;
1028
1029case KEY_TAB:
1030// New behavior:
1031// Switch between text & graphic interfaces
1032// Only Permitted if started in graphics interface
1033if (useGUI) {
1034if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
1035setVideoMode(VGA_TEXT_MODE, 0);
1036
1037setCursorPosition(0, 0, 0);
1038clearScreenRows(0, kScreenLastRow);
1039
1040// Display banner and show hardware info.
1041printf(bootBanner, (bootInfo->convmem + bootInfo->extmem) / 1024);
1042printf(getVBEInfoString());
1043
1044clearScreenRows(kMenuTopRow, kMenuTopRow + 2);
1045changeCursor(0, kMenuTopRow, kCursorTypeHidden, 0);
1046
1047nextRow = kMenuTopRow;
1048showPrompt = true;
1049
1050if (gDeviceCount) {
1051printf("Use \30\31 keys to select the startup volume.");
1052showMenu(menuItems, gDeviceCount, selectIndex, kMenuTopRow + 2, kMenuMaxItems);
1053nextRow += MIN(gDeviceCount, kMenuMaxItems) + 3;
1054}
1055
1056showPrompt = (gDeviceCount == 0) || (menuBVR->flags & kBVFlagNativeBoot);
1057showBootPrompt(nextRow, showPrompt);
1058//changeCursor( 0, kMenuTopRow, kCursorTypeUnderline, 0 );
1059} else {
1060gui.redraw = true;
1061setVideoMode(GRAPHICS_MODE, 0);
1062updateVRAM();
1063 updateGraphicBootPrompt();
1064}
1065}
1066key = 0;
1067break;
1068
1069default:
1070key = 0;
1071break;
1072}
1073} while (0 == key);
1074
1075done:
1076if (bootArgs->Video.v_display == VGA_TEXT_MODE) {
1077clearScreenRows(kMenuTopRow, kScreenLastRow);
1078changeCursor(0, kMenuTopRow, kCursorTypeUnderline, 0);
1079}
1080shouldboot = false;
1081gui.menu.draw = false;
1082if (menuItems) {
1083free(menuItems);
1084menuItems = NULL;
1085}
1086// MacMan commented out the next line to prevent writing the command line boot options to nvram
1087//execute_hook("BootOptions", gBootArgs, gBootArgsPtr, NULL, NULL);
1088return 0;
1089}
1090
1091//==========================================================================
1092
1093char gBootUUIDString[32+4+1] = ""; // UUID of the boot volume e.g. 5EB1869F-C4FA-3502-BDEB-3B8ED5D87292
1094extern unsigned char chainbootdev;
1095extern unsigned char chainbootflag;
1096
1097bool copyArgument(const char *argName, const char *val, int cnt, char **argP, int *cntRemainingP)
1098{
1099int argLen = argName ? strlen(argName) : 0;
1100int len = argLen + cnt + 1; // + 1 to account for space.
1101
1102if (argName)
1103{
1104len++; // +1 to account for '='
1105}
1106
1107if (len > *cntRemainingP) {
1108error("Warning: boot arguments too long, truncating\n");
1109return false;
1110}
1111
1112if (argName)
1113{
1114strncpy(*argP, argName, argLen);
1115*argP += argLen;
1116*argP[0] = '=';
1117(*argP)++;
1118}
1119
1120strncpy(*argP, val, cnt);
1121*argP += cnt;
1122*argP[0] = ' ';
1123(*argP)++;
1124*cntRemainingP -= len;
1125
1126return true;
1127}
1128
1129//
1130// Returns TRUE if an argument was copied, FALSE otherwise
1131bool
1132processBootArgument(
1133 const char *argName, // The argument to search for
1134 const char *userString, // Typed-in boot arguments
1135 const char *kernelFlags, // Kernel flags from config table
1136 const char *configTable,
1137 char **argP, // Output value
1138 int *cntRemainingP, // Output count
1139 char *foundVal, // found value
1140 int foundValSize // max found value size
1141 ) {
1142const char *val;
1143int cnt;
1144bool found = false;
1145
1146if (getValueForBootKey(userString, argName, &val, &cnt)) {
1147// Don't copy; these values will be copied at the end of argument processing.
1148found = true;
1149} else if (getValueForBootKey(kernelFlags, argName, &val, &cnt)) {
1150// Don't copy; these values will be copied at the end of argument processing.
1151found = true;
1152} else if (getValueForKey(argName, &val, &cnt, &bootInfo->chameleonConfig)) {
1153copyArgument(argName, val, cnt, argP, cntRemainingP);
1154found = true;
1155}
1156if (found && foundVal) {
1157strlcpy(foundVal, val, foundValSize);
1158}
1159return found;
1160}
1161
1162// Maximum config table value size
1163#define VALUE_SIZE 2048
1164
1165int
1166processBootOptions()
1167{
1168const char *cp = gBootArgs;
1169const char *val = 0;
1170const char *kernel;
1171int cnt;
1172int userCnt;
1173int cntRemaining;
1174char *argP;
1175char *configKernelFlags;
1176char *valueBuffer;
1177
1178valueBuffer = malloc(VALUE_SIZE);
1179
1180skipblanks( &cp );
1181
1182// Update the unit and partition number.
1183
1184if ( gBootVolume ) {
1185if (!( gBootVolume->flags & kBVFlagNativeBoot )) {
1186readBootSector( gBootVolume->biosdev, gBootVolume->part_boff, (void *) 0x7c00 );
1187//
1188// Setup edx, and signal intention to chain load the
1189// foreign booter.
1190//
1191
1192chainbootdev = gBootVolume->biosdev;
1193chainbootflag = 1;
1194
1195return 1;
1196}
1197
1198setRootVolume(gBootVolume);
1199
1200}
1201// If no boot volume fail immediately because we're just going to fail
1202// trying to load the config file anyway.
1203else {
1204return -1;
1205}
1206
1207 // Find out which version mac os we're booting.
1208 strlcpy(gMacOSVersion, gBootVolume->OSVersion, sizeof(gMacOSVersion));
1209// printf("OS Version Booted %s\n", gBootVolume->OSVersion);
1210
1211// Load config table specified by the user, or use the default.
1212
1213if (!getValueForBootKey(cp, "config", &val, &cnt)) {
1214val = 0;
1215cnt = 0;
1216}
1217
1218// Load com.apple.Boot.plist from the selected volume
1219// and use its contents to override default bootConfig.
1220
1221loadSystemConfig(&bootInfo->bootConfig);
1222loadChameleonConfig(&bootInfo->chameleonConfig, NULL);
1223
1224// Use the kernel name specified by the user, or fetch the name
1225// in the config table, or use the default if not specified.
1226// Specifying a kernel name on the command line, or specifying
1227// a non-default kernel name in the config file counts as
1228// overriding the kernel, which causes the kernelcache not
1229// to be used.
1230
1231gOverrideKernel = false;
1232if (( kernel = extractKernelName((char **)&cp) ))
1233{
1234strlcpy( bootInfo->bootFile, kernel, sizeof(bootInfo->bootFile) );
1235}
1236 else
1237 {
1238if ( getValueForKey( kKernelNameKey, &val, &cnt, &bootInfo->bootConfig ) )
1239 {
1240strlcpy( bootInfo->bootFile, val, cnt+1 );
1241}
1242 else
1243 {
1244 if ((checkOSVersion("10.10"))) {
1245 strlcpy( bootInfo->bootFile, kDefaultKernelYosemite, sizeof(bootInfo->bootFile) );
1246 }
1247 else
1248 {
1249 strlcpy( bootInfo->bootFile, kDefaultKernel, sizeof(bootInfo->bootFile) );
1250 }
1251}
1252}
1253if ((strcmp( bootInfo->bootFile, kDefaultKernel ) != 0) && (strcmp( bootInfo->bootFile, kDefaultKernelYosemite ) != 0))
1254{
1255gOverrideKernel = true;
1256}
1257
1258cntRemaining = BOOT_STRING_LEN - 2; // save 1 for NULL, 1 for space
1259argP = bootArgs->CommandLine;
1260
1261// Get config kernel flags, if not ignored.
1262if (getValueForBootKey(cp, kIgnoreBootFileFlag, &val, &cnt) ||
1263 !getValueForKey( kKernelFlagsKey, &val, &cnt, &bootInfo->bootConfig ))
1264 {
1265val = "";
1266cnt = 0;
1267}
1268configKernelFlags = malloc(cnt + 1);
1269strlcpy(configKernelFlags, val, cnt + 1);
1270
1271// boot-uuid can be set either on the command-line or in the config file
1272if (!processBootArgument(kBootUUIDKey, cp, configKernelFlags, bootInfo->config,
1273 &argP, &cntRemaining, gBootUUIDString, sizeof(gBootUUIDString)))
1274 {
1275//
1276// Try an alternate method for getting the root UUID on boot helper partitions.
1277//
1278if (gBootVolume->flags & kBVFlagBooter)
1279 {
1280// Load the configuration store in the boot helper partition
1281if (loadHelperConfig(&bootInfo->helperConfig) == 0)
1282 {
1283val = getStringForKey(kHelperRootUUIDKey, &bootInfo->helperConfig);
1284if (val != NULL)
1285 {
1286strlcpy(gBootUUIDString, val, sizeof(gBootUUIDString));
1287}
1288}
1289}
1290/*
1291// Try to get the volume uuid string
1292if (!strlen(gBootUUIDString) && gBootVolume->fs_getuuid)
1293 {
1294gBootVolume->fs_getuuid(gBootVolume, gBootUUIDString);
1295}
1296*/
1297// If we have the volume uuid add it to the commandline arguments
1298if (strlen(gBootUUIDString))
1299 {
1300copyArgument(kBootUUIDKey, gBootUUIDString, strlen(gBootUUIDString), &argP, &cntRemaining);
1301}
1302 // Try to get the volume uuid string
1303 if (!strlen(gBootUUIDString) && gBootVolume->fs_getuuid)
1304 {
1305 gBootVolume->fs_getuuid(gBootVolume, gBootUUIDString);
1306 DBG("boot-uuid: %s\n", gBootUUIDString);
1307 }
1308}
1309
1310if (!processBootArgument(kRootDeviceKey, cp, configKernelFlags, bootInfo->config,
1311 &argP, &cntRemaining, gRootDevice, ROOT_DEVICE_SIZE))
1312 {
1313cnt = 0;
1314if ( getValueForKey( kBootDeviceKey, &val, &cnt, &bootInfo->chameleonConfig))
1315 {
1316valueBuffer[0] = '*';
1317cnt++;
1318strlcpy(valueBuffer + 1, val, cnt);
1319val = valueBuffer;
1320}
1321 else
1322 { /*
1323 if (strlen(gBootUUIDString))
1324 {
1325val = "*uuid";
1326cnt = 5;
1327}
1328 else
1329 { */
1330// Don't set "rd=.." if there is no boot device key
1331// and no UUID.
1332val = "";
1333cnt = 0;
1334/*} */
1335}
1336
1337if (cnt > 0)
1338 {
1339copyArgument( kRootDeviceKey, val, cnt, &argP, &cntRemaining);
1340}
1341strlcpy( gRootDevice, val, (cnt + 1));
1342}
1343
1344/*
1345 * Removed. We don't need this anymore.
1346 *
1347if (!processBootArgument(kPlatformKey, cp, configKernelFlags, bootInfo->config,
1348 &argP, &cntRemaining, gPlatformName, sizeof(gCacheNameAdler)))
1349 {
1350getPlatformName(gPlatformName);
1351copyArgument(kPlatformKey, gPlatformName, strlen(gPlatformName), &argP, &cntRemaining);
1352}
1353*/
1354
1355if (!getValueForBootKey(cp, kSafeModeFlag, &val, &cnt) &&
1356 !getValueForBootKey(configKernelFlags, kSafeModeFlag, &val, &cnt))
1357 {
1358if (gBootMode & kBootModeSafe)
1359 {
1360copyArgument(0, kSafeModeFlag, strlen(kSafeModeFlag), &argP, &cntRemaining);
1361}
1362}
1363
1364// Store the merged kernel flags and boot args.
1365
1366cnt = strlen(configKernelFlags);
1367if (cnt)
1368 {
1369if (cnt > cntRemaining)
1370 {
1371error("Warning: boot arguments too long, truncating\n");
1372cnt = cntRemaining;
1373}
1374strncpy(argP, configKernelFlags, cnt);
1375argP[cnt++] = ' ';
1376cntRemaining -= cnt;
1377}
1378userCnt = strlen(cp);
1379if (userCnt > cntRemaining)
1380 {
1381error("Warning: boot arguments too long, truncating\n");
1382userCnt = cntRemaining;
1383}
1384strncpy(&argP[cnt], cp, userCnt);
1385argP[cnt+userCnt] = '\0';
1386
1387if(!shouldboot)
1388 {
1389gVerboseMode = getValueForKey( kVerboseModeFlag, &val, &cnt, &bootInfo->chameleonConfig ) ||
1390getValueForKey( kSingleUserModeFlag, &val, &cnt, &bootInfo->chameleonConfig );
1391
1392gBootMode = ( getValueForKey( kSafeModeFlag, &val, &cnt, &bootInfo->chameleonConfig ) ) ?
1393kBootModeSafe : kBootModeNormal;
1394
1395if ( getValueForKey( kIgnoreCachesFlag, &val, &cnt, &bootInfo->chameleonConfig ) )
1396 {
1397gBootMode = kBootModeSafe;
1398}
1399}
1400
1401if ( getValueForKey( kMKextCacheKey, &val, &cnt, &bootInfo->bootConfig ) )
1402 {
1403strlcpy(gMKextName, val, cnt + 1);
1404}
1405 else
1406 {
1407gMKextName[0]=0;
1408}
1409
1410free(configKernelFlags);
1411free(valueBuffer);
1412
1413return 0;
1414}
1415
1416
1417//==========================================================================
1418// Load the help file and display the file contents on the screen.
1419
1420void showTextBuffer(char *buf_orig, int size)
1421{
1422char*bp;
1423char* buf;
1424intline;
1425intline_offset;
1426intc;
1427
1428if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
1429showInfoBox( "Press q to continue, space for next page.\n",buf_orig );
1430return;
1431}
1432
1433// Create a copy so that we don't mangle the original
1434buf = malloc(size + 1);
1435memcpy(buf, buf_orig, size);
1436
1437
1438 bp = buf;
1439 while (size-- > 0) {
1440if (*bp == '\n') {
1441*bp = '\0';
1442}
1443bp++;
1444 }
1445 *bp = '\1';
1446 line_offset = 0;
1447
1448 setActiveDisplayPage(1);
1449
1450 while (1) {
1451clearScreenRows(0, 24);
1452setCursorPosition(0, 0, 1);
1453bp = buf;
1454for (line = 0; *bp != '\1' && line < line_offset; line++) {
1455while (*bp != '\0') {
1456bp++;
1457}
1458bp++;
1459}
1460for (line = 0; *bp != '\1' && line < 23; line++) {
1461setCursorPosition(0, line, 1);
1462printf("%s\n", bp);
1463while (*bp != '\0') {
1464bp++;
1465}
1466bp++;
1467}
1468
1469setCursorPosition(0, 23, 1);
1470if (*bp == '\1') {
1471printf("[Type %sq or space to quit viewer]", (line_offset > 0) ? "p for previous page, " : "");
1472} else {
1473printf("[Type %s%sq to quit viewer]", (line_offset > 0) ? "p for previous page, " : "", (*bp != '\1') ? "space for next page, " : "");
1474}
1475
1476c = getchar();
1477if (c == 'q' || c == 'Q') {
1478break;
1479}
1480if ((c == 'p' || c == 'P') && line_offset > 0) {
1481line_offset -= 23;
1482}
1483if (c == ' ') {
1484if (*bp == '\1') {
1485break;
1486} else {
1487line_offset += 23;
1488}
1489}
1490 }
1491 setActiveDisplayPage(0);
1492}
1493
1494void showHelp(void)
1495{
1496if (bootArgs->Video.v_display != VGA_TEXT_MODE) {
1497showInfoBox("Help. Press q to quit.\n", (char *)BootHelp_txt);
1498}
1499 else
1500 {
1501showTextBuffer((char *)BootHelp_txt, BootHelp_txt_len);
1502}
1503}
1504
1505void showTextFile(const char * filename)
1506{
1507#define MAX_TEXT_FILE_SIZE 65536
1508char*buf;
1509intfd;
1510intsize;
1511
1512if ((fd = open_bvdev("bt(0,0)", filename, 0)) < 0)
1513 {
1514printf("\nFile not found: %s\n", filename);
1515sleep(2);
1516return;
1517}
1518
1519 size = file_size(fd);
1520 if (size > MAX_TEXT_FILE_SIZE)
1521 {
1522size = MAX_TEXT_FILE_SIZE;
1523}
1524 buf = malloc(size);
1525 read(fd, buf, size);
1526 close(fd);
1527showTextBuffer(buf, size);
1528free(buf);
1529}
1530
1531// This is a very simplistic prompting scheme that just grabs two hex characters
1532// Eventually we need to do something more user-friendly like display a menu
1533// based off of the Multiboot device list
1534
1535int selectAlternateBootDevice(int bootdevice)
1536{
1537int key;
1538int newbootdevice;
1539int digitsI = 0;
1540char *end;
1541char digits[3] = {0,0,0};
1542
1543// We've already printed the current boot device so user knows what it is
1544printf("Typical boot devices are 80 (First HD), 81 (Second HD)\n");
1545printf("Enter two-digit hexadecimal boot device [%02x]: ", bootdevice);
1546do {
1547key = getchar();
1548switch (ASCII_KEY(key))
1549 {
1550case KEY_BKSP:
1551if (digitsI > 0)
1552 {
1553int x, y, t;
1554getCursorPositionAndType(&x, &y, &t);
1555// Assume x is not 0;
1556x--;
1557setCursorPosition(x,y,0); // back up one char
1558// Overwrite with space without moving cursor position
1559putca(' ', 0x07, 1);
1560digitsI--;
1561}
1562 else
1563 {
1564// TODO: Beep or something
1565}
1566break;
1567
1568case KEY_ENTER:
1569digits[digitsI] = '\0';
1570newbootdevice = strtol(digits, &end, 16);
1571if (end == digits && *end == '\0')
1572 {
1573// User entered empty string
1574printf("\nUsing default boot device %x\n", bootdevice);
1575key = 0;
1576} else if(end != digits && *end == '\0') {
1577bootdevice = newbootdevice;
1578printf("\n");
1579key = 0; // We gots da boot device
1580} else {
1581printf("\nCouldn't parse. try again: ");
1582digitsI = 0;
1583}
1584break;
1585
1586default:
1587if (isxdigit(ASCII_KEY(key)) && digitsI < 2)
1588 {
1589putchar(ASCII_KEY(key));
1590digits[digitsI++] = ASCII_KEY(key);
1591}
1592 else
1593 {
1594// TODO: Beep or something
1595}
1596break;
1597};
1598} while (key != 0);
1599
1600return bootdevice;
1601}
1602
1603bool promptForRescanOption(void)
1604{
1605printf("\nWould you like to enable media rescan option?\nPress ENTER to enable or any key to skip.\n");
1606if (getchar() == KEY_ENTER)
1607 {
1608return true;
1609}
1610 else
1611 {
1612return false;
1613}
1614}
1615

Archive Download this file

Revision: HEAD