/* Copyright 2005, 2006, 2007 Dennis van Weeren Copyright 2008, 2009 Jakub Bednarski This file is part of Minimig Minimig is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Minimig is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // 2009-11-14 - OSD labels changed // 2009-12-15 - added display of directory name extensions // 2010-01-09 - support for variable number of tracks // 2016-06-01 - improvements to 8-bit menu //#include "AT91SAM7S256.h" //#include "stdbool.h" #include #include #include "stdio.h" #include "string.h" #include "errors.h" #include "mmc.h" #include "fat.h" #include "osd.h" #include "state.h" #include "fpga.h" #include "fdd.h" #include "hdd.h" #include "hardware.h" #include "firmware.h" #include "config.h" #include "menu.h" #include "user_io.h" #include "data_io.h" #include "tos.h" #include "cdc_control.h" #include "debug.h" #include "boot.h" #include "archie.h" #include "arc_file.h" #include "usb/joymapping.h" // test features (not used right now) // #define ALLOW_TEST_MENU 0 //remove to disable in prod version // other constants #define DIRSIZE 8 // number of items in directory display window // TODO! #define SPIN() asm volatile ( "mov r0, r0\n\t" \ "mov r0, r0\n\t" \ "mov r0, r0\n\t" \ "mov r0, r0"); unsigned char menustate = MENU_NONE1; unsigned char first_displayed_8bit = 0; unsigned char selected_drive_slot; unsigned char parentstate; unsigned char menusub = 0; unsigned char menusub_last = 0; //for when we allocate it dynamically and need to know last row unsigned int menumask = 0; // Used to determine which rows are selectable... unsigned long menu_timer = 0; extern unsigned char drives; extern adfTYPE df[4]; extern configTYPE config; extern fileTYPE file; extern char s[40]; extern unsigned char fat32; extern DIRENTRY DirEntry[MAXDIRENTRIES]; extern unsigned char sort_table[MAXDIRENTRIES]; extern unsigned char nDirEntries; extern unsigned char iSelectedEntry; extern unsigned long iCurrentDirectory; extern char DirEntryLFN[MAXDIRENTRIES][261]; char DirEntryInfo[MAXDIRENTRIES][5]; // disk number info of dir entries char DiskInfo[5]; // disk number info of selected entry extern const char version[]; const char *config_tos_mem[] = {"512 kB", "1 MB", "2 MB", "4 MB", "8 MB", "14 MB", "--", "--" }; const char *config_tos_wrprot[] = {"none", "A:", "B:", "A: and B:"}; const char *config_tos_usb[] = {"none", "control", "debug", "serial", "parallel", "midi"}; const char *config_filter_msg[] = {"none", "HORIZONTAL", "VERTICAL", "H+V"}; const char *config_memory_chip_msg[] = {"0.5 MB", "1.0 MB", "1.5 MB", "2.0 MB"}; const char *config_memory_slow_msg[] = {"none ", "0.5 MB", "1.0 MB", "1.5 MB"}; const char *config_scanlines_msg[] = {"off", "dim", "black"}; const char *config_dither_msg[] = {"off", "SPT", "RND", "S+R"}; const char *config_memory_fast_msg[] = {"none ", "2.0 MB", "4.0 MB","8.0 MB","24.0 MB"}; const char *config_cpu_msg[] = {"68000 ", "68010", "-----","68020"}; const char *config_hdf_msg[] = {"Disabled", "Hardfile (disk img)", "MMC/SD card", "MMC/SD partition 1", "MMC/SD partition 2", "MMC/SD partition 3", "MMC/SD partition 4"}; const char *config_chipset_msg[] = {"OCS-A500", "OCS-A1000", "ECS", "---", "---", "---", "AGA", "---"}; const char *config_turbo_msg[] = {"none", "CHIPRAM", "KICK", "BOTH"}; char *config_autofire_msg[] = {" AUTOFIRE OFF", " AUTOFIRE FAST", " AUTOFIRE MEDIUM", " AUTOFIRE SLOW"}; const char *config_cd32pad_msg[] = {"OFF", "ON"}; char *config_button_turbo_msg[] = {"OFF", "FAST", "MEDIUM", "SLOW"}; char *config_button_turbo_choice_msg[] = {"A only", "B only", "A & B"}; enum HelpText_Message {HELPTEXT_NONE,HELPTEXT_MAIN,HELPTEXT_HARDFILE,HELPTEXT_CHIPSET,HELPTEXT_MEMORY,HELPTEXT_VIDEO}; const char *helptexts[]={ 0, " Welcome to MiST! Use the cursor keys to navigate the menus. Use space bar or enter to select an item. Press Esc or F12 to exit the menus. Joystick emulation on the numeric keypad can be toggled with the numlock key, while pressing Ctrl-Alt-0 (numeric keypad) toggles autofire mode.", " Minimig can emulate an A600 IDE harddisk interface. The emulation can make use of Minimig-style hardfiles (complete disk images) or UAE-style hardfiles (filesystem images with no partition table). It is also possible to use either the entire SD card or an individual partition as an emulated harddisk.", " Minimig's processor core can emulate a 68000 or 68020 processor (though the 68020 mode is still experimental.) If you're running software built for 68000, there's no advantage to using the 68020 mode, since the 68000 emulation runs just as fast.", " Minimig can make use of up to 2 megabytes of Chip RAM, up to 1.5 megabytes of Slow RAM (A500 Trapdoor RAM), and up to 8 megabytes (68000/68010) / 24 megabytes (68020) of true Fast RAM. To use the HRTmon feature you will need a file on the SD card named hrtmon.rom.", " Minimig's video features include a blur filter, to simulate the poorer picture quality on older monitors, and also scanline generation to simulate the appearance of a screen with low vertical resolution.", 0 }; // one screen width const char* HELPTEXT_SPACER= " "; char helptext_custom[320]; const char* scanlines[]={"Off","25%","50%","75%"}; const char* stereo[]={"Mono","Stereo"}; const char* atari_chipset[]={"ST","STE","MegaSTE","STEroids"}; // file selection menu variables char fs_pFileExt[13] = "xxx"; unsigned char fs_ShowExt = 0; unsigned char fs_Options; unsigned char fs_MenuSelect; unsigned char fs_MenuCancel; char* GetExt(char *ext) { static char extlist[32]; char *p = extlist; while(*ext) { strcpy(p, ","); strncat(p, ext, 3); if(strlen(ext)<=3) break; ext +=3; p += strlen(p); } return extlist+1; } void SelectFile(char* pFileExt, unsigned char Options, unsigned char MenuSelect, unsigned char MenuCancel, char chdir) { // this function displays file selection menu iprintf("%s - %s\n", pFileExt, fs_pFileExt); if (strncmp(pFileExt, fs_pFileExt, 12) != 0) // check desired file extension { // if different from the current one go to the root directory and init entry buffer ChangeDirectory(DIRECTORY_ROOT); // for 8 bit cores try to if(((user_io_core_type() == CORE_TYPE_8BIT) || (user_io_core_type() == CORE_TYPE_ARCHIE)) && chdir) user_io_change_into_core_dir(); ScanDirectory(SCAN_INIT, pFileExt, Options); } iprintf("pFileExt = %3s\n", pFileExt); strcpy(fs_pFileExt, pFileExt); fs_ShowExt = ((strlen(fs_pFileExt)>3 && strncmp(fs_pFileExt, "RBFARC", 6)) || strchr(fs_pFileExt, '*') || strchr(fs_pFileExt, '?')); fs_Options = Options; fs_MenuSelect = MenuSelect; fs_MenuCancel = MenuCancel; menustate = MENU_FILE_SELECT1; } static void substrcpy(char *d, char *s, char idx) { char p = 0; while(*s) { if((p == idx) && *s && (*s != ',')) *d++ = *s; if(*s == ',') p++; s++; } *d = 0; } #define STD_EXIT " exit" #define STD_SPACE_EXIT " SPACE to exit" #define STD_COMBO_EXIT " Hold ESC then SPACE to exit" #define HELPTEXT_DELAY 10000 #define FRAME_DELAY 150 // prints input as a string of binary (on/off) values // assumes big endian, returns using special characters (checked box/unchecked box) void siprintbinary(char* buffer, size_t const size, void const * const ptr) { unsigned char *b = (unsigned char*) ptr; unsigned char byte; int i, j; memset(buffer, '\0', sizeof(buffer)); for (i=size-1;i>=0;i--) { for (j=0;j<8;j++) { byte = (b[i] >> j) & 1; buffer[j]=byte?'\x1a':'\x19'; } } return; } void get_joystick_state( char *joy_string, char *joy_string2, uint8_t joy_num ) { // helper to get joystick status (both USB or DB9) uint16_t vjoy; memset(joy_string, '\0', sizeof(joy_string)); memset(joy_string2, '\0', sizeof(joy_string2)); vjoy = StateJoyGet(joy_num); vjoy |= StateJoyGetExtra(joy_num) << 8; if (vjoy==0) { strcpy(joy_string2, " "); memset(joy_string2, ' ', 8); memset(joy_string2+8, '\x14', 1); memset(joy_string2+9, ' ', 1); strcat(joy_string2, "\0"); return; } strcpy(joy_string, " \x12 X Y L R L2 R2 L3"); strcpy(joy_string2, " < \x13 > A B Sel Sta R3"); if(!(vjoy & JOY_UP)) memset(joy_string+8, ' ', 1); if(!(vjoy & JOY_X)) memset(joy_string+12, ' ', 1); if(!(vjoy & JOY_Y)) memset(joy_string+14, ' ', 1); if(!(vjoy & JOY_L)) memset(joy_string+16, ' ', 1); if(!(vjoy & JOY_R)) memset(joy_string+18, ' ', 1); if(!(vjoy & JOY_L2)) memset(joy_string+20, ' ', 2); if(!(vjoy & JOY_R2)) memset(joy_string+23, ' ', 2); if(!(vjoy & JOY_L3)) memset(joy_string+26, ' ', 2); if(!(vjoy & JOY_LEFT)) memset(joy_string2+6, ' ', 1); if(!(vjoy & JOY_DOWN)) memset(joy_string2+8, '\x14', 1); if(!(vjoy & JOY_RIGHT)) memset(joy_string2+10, ' ', 1); if(!(vjoy & JOY_A)) memset(joy_string2+12, ' ', 1); if(!(vjoy & JOY_B)) memset(joy_string2+14, ' ', 1); if(!(vjoy & JOY_SELECT))memset(joy_string2+16, ' ', 3); if(!(vjoy & JOY_START)) memset(joy_string2+20, ' ', 3); if(!(vjoy & JOY_R3)) memset(joy_string2+24, ' ', 2); return; } void get_joystick_state_usb( char *s, unsigned char joy_num ) { /* USB specific - current "raw" state (in reverse binary format to correspont to MIST.INI mapping entries) */ char buffer[5]; unsigned short i; char binary_string[9]="00000000"; unsigned char joy = 0; unsigned int max_btn = 1; if (StateNumJoysticks() <= joy_num) { strcpy( s, " "); return; } max_btn = StateUsbGetNumButtons(joy_num); joy = StateUsbJoyGet(joy_num); siprintf(s, " USB: ---- 0000 0000 0000"); siprintbinary(binary_string, sizeof(joy), &joy); s[7] = binary_string[0]=='\x1a'?'>':'\x1b'; s[8] = binary_string[1]=='\x1a'?'<':'\x1b'; s[9] = binary_string[2]=='\x1a'?'\x13':'\x1b'; s[10] = binary_string[3]=='\x1a'?'\x12':'\x1b'; s[12] = binary_string[4]; s[13] = max_btn>1 ? binary_string[5] : ' '; s[14] = max_btn>2 ? binary_string[6] : ' '; s[15] = max_btn>3 ? binary_string[7] : ' '; joy = StateUsbJoyGetExtra(joy_num); siprintbinary(binary_string, sizeof(joy), &joy); s[17] = max_btn>4 ? binary_string[0] : ' '; s[18] = max_btn>5 ? binary_string[1] : ' '; s[19] = max_btn>6 ? binary_string[2] : ' '; s[20] = max_btn>7 ? binary_string[3] : ' '; s[22] = max_btn>8 ? binary_string[4] : ' '; s[23] = max_btn>9 ? binary_string[5] : ' '; s[24] = max_btn>10 ? binary_string[6] : ' '; s[25] = max_btn>11 ? binary_string[7] : ' '; return; } void append_joystick_usbid ( char *usb_id, unsigned int usb_vid, unsigned int usb_pid ) { siprintf(usb_id, "VID:%04X PID:%04X", usb_vid, usb_pid); } void get_joystick_id ( char *usb_id, unsigned char joy_num, short raw_id ) { /* Builds a string containing the USB VID/PID information of a joystick */ char buffer[32]=""; if (raw_id==0) { if (joy_num >= StateNumJoysticks()) { strcpy( usb_id, " "); if (joy_num < StateNumJoysticks()+2) { strcat( usb_id, "Atari DB9 Joystick"); } else { strcat( usb_id, "None"); } return; } } //hack populate from outside int vid = StateUsbVidGet(joy_num); int pid = StateUsbPidGet(joy_num); memset(usb_id, '\0', sizeof(usb_id)); if (vid>0) { if (raw_id == 0) { strcpy(buffer, get_joystick_alias( vid, pid )); } if(strlen(buffer)==0) { append_joystick_usbid( buffer, vid, pid ); } } else { if (joy_num >= StateNumJoysticks()) { if (joy_num < StateNumJoysticks()+2) { strcpy( buffer, "Atari DB9 Joystick"); } else { strcpy( buffer, "None"); } } } if(raw_id == 0) siprintf(usb_id, "%*s", (28-strlen(buffer))/2, " "); else strcpy(usb_id, ""); strcat(usb_id, buffer); return; } unsigned char getIdx(char *opt) { if((opt[1]>='0') && (opt[1]<='9')) return opt[1]-'0'; if((opt[1]>='A') && (opt[1]<='V')) return opt[1]-'A'+10; return 0; // basically 0 cannot be valid because used as a reset. Thus can be used as a error. } unsigned long getStatus(char *opt, unsigned long status) { char idx1 = getIdx(opt); char idx2 = getIdx(opt+1); unsigned long x = (status & (1<idx1) { x = status >> idx1; x = x & ~(0xffffffff << (idx2 - idx1 + 1)); } return x; } unsigned long setStatus(char *opt, unsigned long status, unsigned long value) { unsigned char idx1 = getIdx(opt); unsigned char idx2 = getIdx(opt+1); unsigned long x = 1; if(idx2>idx1) x = ~(0xffffffff << (idx2 - idx1 + 1)); x = x << idx1; return (status & ~x) | ((value << idx1) & x); } unsigned long getStatusMask(char *opt) { char idx1 = getIdx(opt); char idx2 = getIdx(opt+1); unsigned long x = 1; if(idx2>idx1) x = ~(0xffffffff << (idx2 - idx1 + 1)); //iprintf("grtStatusMask %d %d %x\n", idx1, idx2, x); return x << idx1; } char* get_keycode_table() { switch(user_io_core_type()) { case CORE_TYPE_MINIMIG: case CORE_TYPE_MINIMIG2: return "Amiga"; case CORE_TYPE_MIST: case CORE_TYPE_MIST2: return " ST"; case CORE_TYPE_ARCHIE: return "Archie"; } return " PS/2"; } void HandleUI(void) { char *p; unsigned char i, c, m, up, down, select, menu, right, left, plus, minus; uint8_t mod; unsigned long len; static hardfileTYPE t_hardfile[2]; // temporary copy of former hardfile configuration static unsigned char t_enable_ide; // temporary copy of former IDE configuration static unsigned char ctrl = false; static unsigned char lalt = false; char enable; static long helptext_timer; static const char *helptext; static char helpstate=0; uint8_t keys[6] = {0,0,0,0,0,0}; uint16_t keys_ps2[6] = {0,0,0,0,0,0}; mist_joystick_t joy0, joy1; static unsigned char joytest_num; /* check joystick status */ char joy_string[32]; char joy_string2[32]; char usb_id[64]; // get user control codes c = OsdGetCtrl(); // decode and set events menu = false; select = false; up = false; down = false; left = false; right = false; plus=false; minus=false; switch (c) { case KEY_CTRL : ctrl = true; break; case KEY_CTRL | KEY_UPSTROKE : ctrl = false; break; case KEY_LALT : lalt = true; break; case KEY_LALT | KEY_UPSTROKE : lalt = false; break; case KEY_KP0 : // Only sent by Minimig if (ctrl && lalt) { if (menustate == MENU_NONE2 || menustate == MENU_INFO) { char autofire_tmp = config.autofire & 3; autofire_tmp++; config.autofire=(config.autofire & 4) | (autofire_tmp & 3); ConfigAutofire(config.autofire); if (menustate == MENU_NONE2 || menustate == MENU_INFO) InfoMessage(config_autofire_msg[config.autofire & 3]); } } break; case KEY_MENU: menu = true; OsdKeySet(KEY_MENU | KEY_UPSTROKE); break; // Within the menu the esc key acts as the menu key. problem: // if the menu is left with a press of ESC, then the follwing // break code for the ESC key when the key is released will // reach the core which never saw the make code. Simple solution: // react on break code instead of make code case KEY_ESC | KEY_UPSTROKE : if (menustate != MENU_NONE2) menu = true; break; case KEY_ENTER : case KEY_SPACE : select = true; break; case KEY_UP: up = true; break; case KEY_DOWN: down = true; break; case KEY_LEFT : left = true; break; case KEY_RIGHT : right = true; break; case KEY_KPPLUS : plus=true; break; case KEY_KPMINUS : minus=true; break; } if(menu || select || up || down || left || right ) { if(helpstate) OsdWrite(7,STD_EXIT,(menumask-((1<<(menusub+1))-1))<=0,0); // Redraw the Exit line... helpstate=0; helptext_timer=GetTimer(HELPTEXT_DELAY); } if(helptext) { if(helpstate<9) { if(CheckTimer(helptext_timer)) { helptext_timer=GetTimer(FRAME_DELAY); OsdWriteOffset(7,STD_EXIT,0,0,helpstate); ++helpstate; } } else if(helpstate==9) { ScrollReset(); ++helpstate; } else ScrollText(7,helptext,0,0,0,0); } // Standardised menu up/down. // The screen should set menumask, bit 0 to make the top line selectable, bit 1 for the 2nd line, etc. // (Lines in this context don't have to correspond to rows on the OSD.) // Also set parentstate to the appropriate menustate. if(menumask) { if (down && (menumask>=(1<<(menusub+1)))) // Any active entries left? { do menusub++; while((menumask & (1< 0 && (menumask<<(8-menusub))) { do --menusub; while((menumask & (1<='0' && p[1]<='9') selected_drive_slot = p[1]-'0'; substrcpy(ext, p, 1); while(strlen(ext) < 3) strcat(ext, " "); SelectFile(ext, SCAN_DIR | SCAN_LFN, (p[0] == 'F')?MENU_8BIT_MAIN_FILE_SELECTED:MENU_8BIT_MAIN_IMAGE_SELECTED, MENU_8BIT_MAIN1, 1); } else if(p[0] == 'O') { unsigned long status = user_io_8bit_set_status(0,0); // 0,0 gets status unsigned long x = getStatus(p, status) + 1; //unsigned long mask = getStatusMask(p); //unsigned long x2 = x; // check if next value available substrcpy(s, p, 2+x); if(!strlen(s)) x = 0; //iprintf("Option %s %x %x %x %x\n", p, status, mask, x2, x); user_io_8bit_set_status(setStatus(p, status, x), 0xffffffff); menustate = MENU_8BIT_MAIN1; } else { // 'T' option // determine which status bit is affected unsigned long mask = 1<