// ===================================================================================== // Copyright (c) 2022-2025 Dave Bernazzani (wavemotion-dave) // // Copying and distribution of this emulator, it's source code and associated // readme files, with or without modification, are permitted in any medium without // royalty provided this copyright notice is used and wavemotion-dave (Phoenix-Edition), // Alekmaul (original port) and Greg Stanton (ProSystem Emulator) are thanked profusely. // // A7800DS emulator is offered as-is, without any warranty. // // The original GPL license: // // This program 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 2 of the License, or // (at your option) any later version. // // This program 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, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // ===================================================================================== #include #include #include #include #include #include #include "main.h" #include "config.h" #include "a7800utils.h" #include "emu/Database.h" #include "emu/ProSystem.h" #include "clickNoQuit_wav.h" #include "bgBottom.h" #include "bgTop.h" #include "bgFileSel.h" #include "printf.h" #include "soundbank.h" #include "soundbank_bin.h" u8 isDS_LITE __attribute__((section(".dtcm"))) = 0; u8 frameSkipMask __attribute__((section(".dtcm"))) = 1; u16 lastSample __attribute__((section(".dtcm"))) = 0; u16 gTotalAtariFrames __attribute__((section(".dtcm"))) = 0; int atari_frames __attribute__((section(".dtcm"))) = 0; u8 bRefreshXY __attribute__((section(".dtcm"))) = false; u16 dampen __attribute__((section(".dtcm"))) = 0; unsigned char keyboard_data[20] __attribute__((section(".dtcm"))) ALIGN(32); short int emu_state __attribute__((section(".dtcm"))) = 0; u8 full_speed __attribute__((section(".dtcm"))) = 0; u8 fpsDisplay __attribute__((section(".dtcm"))) = 0; u8 bEmulatorRun __attribute__((section(".dtcm"))) = 1; u8 bNoDatabase __attribute__((section(".dtcm"))) = 0; u8 bSkipBIOS __attribute__((section(".dtcm"))) = 0; u32 snes_adaptor __attribute__((section(".dtcm"))) = 0x0000FFFF; extern u32 tiaBufIdx; char fpsbuf[34]; char dbgbuf[40]; u8 bJustSavedHSC = 0; u8 spamHSC = 0; // ----------------------------------------------------------------- // Some vars for listing filenames of ROMs... 1K of ROMs is plenty // ----------------------------------------------------------------- FICA7800 proromlist[1024]; unsigned int countpro=0, countfiles=0, ucFicAct=0; int bg0; int bg1; int bg0b,bg1b; int bg2; int bg3; // BG pointers int bg0s, bg1s; // sub BG pointers #define MAX_DEBUG 6 int debug[MAX_DEBUG]={0}; u8 DEBUG_DUMP = 0; u16 *bufVideo; // Video flipping buffer gamecfg GameConf; // Game Config svg short cxBG __attribute__((section(".dtcm"))); short cyBG __attribute__((section(".dtcm"))); short xdxBG __attribute__((section(".dtcm"))); short ydyBG __attribute__((section(".dtcm"))); u32 myTiaBufIdx __attribute__((section(".dtcm"))) = 0; u8 soundEmuPause = 1; #define WAITVBL swiWaitForVBlank(); swiWaitForVBlank(); swiWaitForVBlank(); swiWaitForVBlank(); swiWaitForVBlank(); static u8 trace_x=0; static u8 trace_y=0; static u8 trace_done = 0; void Trace(word data) { if (trace_done) return; sprintf(fpsbuf, "%04X", data); dsPrintValue(trace_x,trace_y,0, fpsbuf); if (trace_y < 22) { trace_y++; } else { trace_y = 0; if (trace_x < 24) { trace_x += 5;} else {trace_x = 0; trace_done=1;} } } void Trace2(word addr, u8 data) { if (trace_done) return; sprintf(fpsbuf, "%04X=%02X", addr, data); dsPrintValue(trace_x,trace_y,0, fpsbuf); if (trace_y < 22) { trace_y++; } else { trace_y = 0; if (trace_x < 23) { trace_x += 8;} else {trace_x = 0; trace_done=1;} } } static void DumpDebugData(void) { if (DEBUG_DUMP) { for (int i=0; i 0) { sprintf(fpsbuf, "ILLEGAL OP=%02X", last_illegal_opcode); dsPrintValue(14,23,0, fpsbuf); } } } // ------------------------------------------------------------ // Utility function to pause the sound... // ------------------------------------------------------------ void SoundPause(void) { soundEmuPause = 1; } // ------------------------------------------------------------ // Utility function to un pause the sound... // ------------------------------------------------------------ void SoundUnPause(void) { myTiaBufIdx = 0; tiaBufIdx = 0; memset(tia_buffer, 0x00, SNDLENGTH); TIMER0_CR=0; TIMER0_DATA=0; TIMER0_CR=TIMER_ENABLE|TIMER_DIV_1024; atari_frames = 0; WAITVBL;WAITVBL; soundEmuPause = 0; } // Color fading effect void FadeToColor(unsigned char ucSens, unsigned short ucBG, unsigned char ucScr, unsigned char valEnd, unsigned char uWait) { unsigned short ucFade; unsigned char ucBcl; // Fade-out vers le noir if (ucScr & 0x01) REG_BLDCNT=ucBG; if (ucScr & 0x02) REG_BLDCNT_SUB=ucBG; if (ucSens == 1) { for(ucFade=0;ucFadevalEnd;ucFade--) { if (ucScr & 0x01) REG_BLDY=ucFade; if (ucScr & 0x02) REG_BLDY_SUB=ucFade; for (ucBcl=0;ucBcl 40) { if (temp_shift < 0) temp_shift++; else temp_shift--; if (temp_shift == 0) { shiftTime = 0; bRefreshXY = 1; // Force the next vBlank interrupt to put screen right } } } } } void dsInitScreenMain(void) { SetYtrigger(190); //trigger 2 lines before vsync irqSet(IRQ_VBLANK, vblankIntr); irqEnable(IRQ_VBLANK); if (isDSiMode()) isDS_LITE = false; else isDS_LITE = true; vramSetBankB(VRAM_B_LCD ); // Not using this for video but 128K of faster RAM always useful! Mapped at 0x06820000 - 96K for Cart Bankswitch cache and 32K for 2X Bankswitched RAM vramSetBankD(VRAM_D_LCD ); // Not using this for video but 128K of faster RAM always useful! Mapped at 0x06860000 - Used for Cart Bankswitch cache (272K contiguous below) vramSetBankE(VRAM_E_LCD ); // Not using this for video but 64K of faster RAM always useful! Mapped at 0x06880000 - Used for Cart Bankswitch cache vramSetBankF(VRAM_F_LCD ); // Not using this for video but 16K of faster RAM always useful! Mapped at 0x06890000 - Used for Cart Bankswitch cache vramSetBankG(VRAM_G_LCD ); // Not using this for video but 16K of faster RAM always useful! Mapped at 0x06894000 - Used for Cart Bankswitch cache vramSetBankH(VRAM_H_LCD ); // Not using this for video but 32K of faster RAM always useful! Mapped at 0x06898000 - Used for Cart Bankswitch cache vramSetBankI(VRAM_I_LCD ); // Not using this for video but 16K of faster RAM always useful! Mapped at 0x068A0000 - Used for Cart Bankswitch cache } void dsInitTimer(void) { TIMER0_DATA=0; TIMER0_CR=TIMER_ENABLE|TIMER_DIV_1024; } void dsShowScreenEmu(void) { // Change vram videoSetMode(MODE_5_2D | DISPLAY_BG2_ACTIVE | DISPLAY_BG3_ACTIVE); vramSetBankA(VRAM_A_MAIN_BG_0x06000000); vramSetBankB(VRAM_B_LCD ); // Not using this for video but 128K of faster RAM always useful! Mapped at 0x06820000 - 96K for Cart Bankswitch cache and 32K for 2X Bankswitched RAM vramSetBankD(VRAM_D_LCD ); // Not using this for video but 128K of faster RAM always useful! Mapped at 0x06860000 - Used for Cart Bankswitch cache (272K contiguous below) vramSetBankE(VRAM_E_LCD ); // Not using this for video but 64K of faster RAM always useful! Mapped at 0x06880000 - Used for Cart Bankswitch cache vramSetBankF(VRAM_F_LCD ); // Not using this for video but 16K of faster RAM always useful! Mapped at 0x06890000 - Used for Cart Bankswitch cache vramSetBankG(VRAM_G_LCD ); // Not using this for video but 16K of faster RAM always useful! Mapped at 0x06894000 - Used for Cart Bankswitch cache vramSetBankH(VRAM_H_LCD ); // Not using this for video but 32K of faster RAM always useful! Mapped at 0x06898000 - Used for Cart Bankswitch cache vramSetBankI(VRAM_I_LCD ); // Not using this for video but 16K of faster RAM always useful! Mapped at 0x068A0000 - Used for Cart Bankswitch cache bg0 = bgInit(3, BgType_Bmp8, BgSize_B8_512x512, 0,0); bg1 = bgInit(2, BgType_Bmp8, BgSize_B8_512x512, 0,0); REG_BLDCNT = BLEND_ALPHA | BLEND_SRC_BG2 | BLEND_DST_BG3; REG_BLDALPHA = (8 << 8) | 8; // 50% / 50% // Setup video scaling... bufVideo = BG_GFX; cxBG = (myCartInfo.xOffset << 8); cyBG = (myCartInfo.yOffset << 8); xdxBG = ((320 / myCartInfo.xScale) << 8) | (320 % myCartInfo.xScale) ; ydyBG = ((234 / myCartInfo.yScale) << 8) | (234 % myCartInfo.yScale); REG_BG2PB = 0; REG_BG2PC = 0; REG_BG3PB = 0; REG_BG3PC = 0; REG_BG2X = cxBG+myCartInfo.xJiggle; REG_BG2Y = cyBG+myCartInfo.yJiggle; REG_BG3X = cxBG; REG_BG3Y = cyBG; REG_BG2PA = xdxBG; REG_BG2PD = ydyBG; REG_BG3PA = xdxBG; REG_BG3PD = ydyBG; } void dsShowScreenMain(bool full) { if (full) { // Init BG mode for 16 bits colors videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE ); videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE); vramSetBankA(VRAM_A_MAIN_BG); vramSetBankC(VRAM_C_SUB_BG); bg0 = bgInit(0, BgType_Text8bpp, BgSize_T_256x256, 31,0); bg0b = bgInitSub(0, BgType_Text8bpp, BgSize_T_256x256, 31,0); bg1b = bgInitSub(1, BgType_Text8bpp, BgSize_T_256x256, 30,0); bgSetPriority(bg0b,1);bgSetPriority(bg1b,0); decompress(bgTopTiles, bgGetGfxPtr(bg0), LZ77Vram); decompress(bgTopMap, (void*) bgGetMapPtr(bg0), LZ77Vram); dmaCopy((void *) bgTopPal,(u16*) BG_PALETTE,256*2); } decompress(bgBottomTiles, bgGetGfxPtr(bg0b), LZ77Vram); decompress(bgBottomMap, (void*) bgGetMapPtr(bg0b), LZ77Vram); dmaCopy((void *) bgBottomPal,(u16*) BG_PALETTE_SUB,256*2); unsigned short dmaVal = *(bgGetMapPtr(bg1b) +31*32); dmaFillWords(dmaVal | (dmaVal<<16),(void*) bgGetMapPtr(bg1b),32*24*2); REG_BLDCNT=0; REG_BLDCNT_SUB=0; REG_BLDY=0; REG_BLDY_SUB=0; swiWaitForVBlank(); } void dsFreeEmu(void) { SoundPause(); } void dsLoadGame(char *filename) { u16 index; SoundPause(); // Clear out debug info... for (int i=0; i0 ? "<" : " ")); dsPrintValue(31,22,0,(char *) (NoDebGame+14" : " ")); sprintf(szName,"%s","A=SELECT, Y=HALT EMU, B=BACK"); dsPrintValue(16-strlen(szName)/2,23,0,szName); for (u8 ucBcl=0;ucBcl<17; ucBcl++) { ucGame= ucBcl+NoDebGame; if (ucGame < countpro) { maxLen=strlen(proromlist[ucGame].filename); strcpy(szName,proromlist[ucGame].filename); if (maxLen>29) szName[29]='\0'; if (proromlist[ucGame].directory) { sprintf(szName2,"%-29s",szName); dsPrintValue(0,5+ucBcl,(ucSel == ucBcl ? 1 : 0),szName2); } else { sprintf(szName2,"%-29s",strupr(szName)); dsPrintValue(1,5+ucBcl,(ucSel == ucBcl ? 1 : 0),szName2); } } } } unsigned int dsWaitForRom(void) { u8 bDone=false, bRet=false; u16 ucHaut=0x00, ucBas=0x00,ucSHaut=0x00, ucSBas=0x00,romSelected= 0, firstRomDisplay=0,nbRomPerPage, uNbRSPage, uLenFic=0; s16 ucFlip=0, ucFlop=0; decompress(bgFileSelTiles, bgGetGfxPtr(bg0b), LZ77Vram); decompress(bgFileSelMap, (void*) bgGetMapPtr(bg0b), LZ77Vram); dmaCopy((void *) bgFileSelPal,(u16*) BG_PALETTE_SUB,256*2); unsigned short dmaVal = *(bgGetMapPtr(bg1b) +31*32); dmaFillWords(dmaVal | (dmaVal<<16),(void*) bgGetMapPtr(bg1b),32*24*2); nbRomPerPage = (countpro>=17 ? 17 : countpro); uNbRSPage = (countpro>=5 ? 5 : countpro); if (ucFicAct>countpro-nbRomPerPage) { firstRomDisplay=countpro-nbRomPerPage; romSelected=ucFicAct-countpro+nbRomPerPage; } else { firstRomDisplay=ucFicAct; romSelected=0; } dsDisplayFiles(firstRomDisplay,romSelected); while (!bDone) { if (keysCurrent() & KEY_UP) { ucFlip = -50; ucFlop = 0; uLenFic = 0; if (!ucHaut) { ucFicAct = (ucFicAct>0 ? ucFicAct-1 : countpro-1); if (romSelected>uNbRSPage) { romSelected -= 1; } else { if (firstRomDisplay>0) { firstRomDisplay -= 1; } else { if (romSelected>0) { romSelected -= 1; } else { firstRomDisplay=countpro-nbRomPerPage; romSelected=nbRomPerPage-1; } } } ucHaut=0x01; dsDisplayFiles(firstRomDisplay,romSelected); } else { ucHaut++; if (ucHaut>10) ucHaut=0; } } else { ucHaut = 0; } if (keysCurrent() & KEY_DOWN) { ucFlip = -50; ucFlop = 0; uLenFic = 0; if (!ucBas) { ucFicAct = (ucFicAct< countpro-1 ? ucFicAct+1 : 0); if (romSelected10) ucBas=0; } } else { ucBas = 0; } if((keysCurrent() & KEY_R) || (keysCurrent() & KEY_RIGHT)) { if (!ucSBas) { ucFicAct = (ucFicAct< countpro-nbRomPerPage ? ucFicAct+nbRomPerPage : countpro-nbRomPerPage); if (firstRomDisplay10) ucSBas=0; } } else { ucSBas = 0; } if ((keysCurrent() & KEY_L) || (keysCurrent() & KEY_LEFT)) { if (!ucSHaut) { ucFicAct = (ucFicAct> nbRomPerPage ? ucFicAct-nbRomPerPage : 0); if (firstRomDisplay>nbRomPerPage) { firstRomDisplay -= nbRomPerPage; } else { firstRomDisplay = 0; } if (ucFicAct == 0) romSelected = 0; if (romSelected > ucFicAct) romSelected = ucFicAct; ucSHaut=0x01; dsDisplayFiles(firstRomDisplay,romSelected); } else { ucSHaut++; if (ucSHaut>10) ucSHaut=0; } } else { ucSHaut = 0; } if ( keysCurrent() & KEY_B ) { bDone=true; while (keysCurrent() & KEY_B); } if (keysCurrent() & (KEY_A | KEY_Y | KEY_SELECT | KEY_START)) // Select ROM { if (!proromlist[ucFicAct].directory) { if (keysCurrent() & KEY_Y) bEmulatorRun = false; else bEmulatorRun=true; if (keysCurrent() & KEY_SELECT) bNoDatabase = true; else bNoDatabase=false; if (keysCurrent() & KEY_START) bSkipBIOS = true; else bSkipBIOS=false; if (keysCurrent() & KEY_X) DEBUG_DUMP = 1; else DEBUG_DUMP=0; bRet=true; bDone=true; } else if (keysCurrent() & KEY_A) // Drill down into directory { chdir(proromlist[ucFicAct].filename); proFindFiles(); ucFicAct = 0; nbRomPerPage = (countpro>=16 ? 16 : countpro); uNbRSPage = (countpro>=5 ? 5 : countpro); if (ucFicAct>countpro-nbRomPerPage) { firstRomDisplay=countpro-nbRomPerPage; romSelected=ucFicAct-countpro+nbRomPerPage; } else { firstRomDisplay=ucFicAct; romSelected=0; } dsDisplayFiles(firstRomDisplay,romSelected); while (keysCurrent() & KEY_A); } } // Scroll la selection courante if (strlen(proromlist[ucFicAct].filename) > 29) { ucFlip++; if (ucFlip >= 20) { ucFlip = 0; uLenFic++; if ((uLenFic+29)>strlen(proromlist[ucFicAct].filename)) { ucFlop++; if (ucFlop >= 20) { uLenFic=0; ucFlop = 0; } else uLenFic--; } strncpy(szName,proromlist[ucFicAct].filename+uLenFic,29); szName[29] = '\0'; dsPrintValue(1,5+romSelected,1,szName); } } swiWaitForVBlank(); } decompress(bgBottomTiles, bgGetGfxPtr(bg0b), LZ77Vram); decompress(bgBottomMap, (void*) bgGetMapPtr(bg0b), LZ77Vram); dmaCopy((void *) bgBottomPal,(u16*) BG_PALETTE_SUB,256*2); dmaVal = *(bgGetMapPtr(bg1b) +31*32); dmaFillWords(dmaVal | (dmaVal<<16),(void*) bgGetMapPtr(bg1b),32*24*2); return bRet; } unsigned int dsWaitOnMenu(unsigned int actState) { unsigned int uState=A7800_PLAYINIT; bool bDone=false, romSel; short iTx,iTy; while (bDone==false) { // wait for stylus scanKeys(); if(keysHeld() & KEY_TOUCH) { touchPosition touch; touchRead(&touch); iTx = touch.px; iTy = touch.py; if ((iTx>31) && (iTx<65) && (iTy>159) && (iTy<169)) { // 32,160 -> 64,168 quit soundPlaySample(clickNoQuit_wav, SoundFormat_16Bit, clickNoQuit_wav_size, 22050, 127, 64, false, 0); bDone=dsWaitOnQuit(); if (bDone) uState=A7800_QUITSTDS; } if ((iTx>69) && (iTx<180) && (iTy>10) && (iTy<65)) { // 80,32 -> 179,61 cartridge slot bDone=true; // Find files in current directory and show it proFindFiles(); romSel=dsWaitForRom(); if (romSel) { uState=A7800_PLAYINIT; dsLoadGame(proromlist[ucFicAct].filename); } else { uState=actState; } } } swiWaitForVBlank(); } return uState; } void dsPrintValue(int x, int y, unsigned int isSelect, char *pchStr) { u16 *pusEcran,*pusMap; u16 usCharac; char *pTrTxt=pchStr; char ch; pusEcran=(u16*) (bgGetMapPtr(bg1b))+x+(y<<5); pusMap=(u16*) (bgGetMapPtr(bg0b)+(2*isSelect+24)*32); while((*pTrTxt)!='\0' ) { ch = *pTrTxt; if (ch >= 'a' && ch <= 'z') ch -= 32; // Faster than strcpy/strtoupper usCharac=0x0000; if ((ch) == '|') usCharac=*(pusMap); else if (((ch)<' ') || ((ch)>'_')) usCharac=*(pusMap); else if((ch)<'@') usCharac=*(pusMap+(ch)-' '); else usCharac=*(pusMap+32+(ch)-'@'); *pusEcran++=usCharac; pTrTxt++; } } // -------------------------------------------------------------------------------------------- // MAXMOD streaming setup and handling... // -------------------------------------------------------------------------------------------- #define sample_rate 31500 // To rough match the TIA driver for the Atari 7800 - we purposely undershoot slightly (263 x 60 x 2 = 31560) #define buffer_size (256) // Enough buffer that we don't have to fill it too often but not so big as to create lag mm_ds_system sys __attribute__((section(".dtcm"))); mm_stream myStream __attribute__((section(".dtcm"))); // ------------------------------------------------------------------------------------------- // maxmod will call this routine when the buffer is half-empty and requests that // we fill the sound buffer with more samples. They will request 'len' samples and // we will fill exactly that many. If the sound is paused, we fill with 'mute' samples. // ------------------------------------------------------------------------------------------- ITCM_CODE mm_word OurSoundMixer(mm_word len, mm_addr dest, mm_stream_formats format) { if (soundEmuPause) // If paused, just send same value - no amplitude... no sound { s32 *p = (s32*)dest; for (int i=0; i>1); while (new_len--) { if (tia_wait) tia_wait--; if (myTiaBufIdx != tiaBufIdx) { *p++ = tia_buffer[myTiaBufIdx++]; myTiaBufIdx &= (SNDLENGTH-1); } else // We're short some samples... this happens due to emualtor timing not being perfect. Just make up the samples. { *p++ = (myCartInfo.pokeyType ? pokey_ProcessNow() : tia_ProcessNow()); } } } return len; } // ------------------------------------------------------------------------------------------- // Setup the maxmod audio stream - this will be an 8-bit Mono output. // ------------------------------------------------------------------------------------------- void setupStream(void) { //---------------------------------------------------------------- // initialize maxmod with our small 3-effect soundbank //---------------------------------------------------------------- mmInitDefaultMem((mm_addr)soundbank_bin); mmLoadEffect(SFX_CLICKNOQUIT); mmLoadEffect(SFX_KEYCLICK); mmLoadEffect(SFX_MUS_INTRO); //---------------------------------------------------------------- // open stream //---------------------------------------------------------------- myStream.sampling_rate = sample_rate; // sampling rate = myStream.buffer_length = buffer_size; // buffer length = myStream.callback = OurSoundMixer; // set callback function myStream.format = MM_STREAM_8BIT_MONO; // format = mono 8-bit myStream.timer = MM_TIMER2; // use hardware timer 2 myStream.manual = false; // use automatic filling mmStreamOpen( &myStream ); //---------------------------------------------------------------- // when using 'automatic' filling, your callback will be triggered // every time half of the wave buffer is processed. // // so: // 25000 (rate) // ----- = ~21 Hz for a full pass, and ~42hz for half pass // 1200 (length) //---------------------------------------------------------------- // with 'manual' filling, you must call mmStreamUpdate // periodically (and often enough to avoid buffer underruns) //---------------------------------------------------------------- } //--------------------------------------------------------------------------------- void dsInstallSoundEmuFIFO(void) { setupStream(); } __attribute__ ((noinline)) void timer_reset(void) { // Timers are fed with 33.513982 MHz clock. // With DIV_1024 the clock is 32,728.5 ticks per sec... TIMER0_DATA=0; TIMER0_CR=TIMER_ENABLE|TIMER_DIV_1024; TIMER1_DATA=0; TIMER1_CR=TIMER_ENABLE | TIMER_DIV_1024; } static u8 lcd_swap_counter=0; static int scale_screen_dampen=0; __attribute__ ((noinline)) void handle_LR_keys(unsigned int keys_pressed) { if (myCartInfo.cardctrl1 != SNES) { if ((keys_pressed & KEY_R) && (keys_pressed & KEY_L)) { if (++lcd_swap_counter == 30) { if (keys_pressed & (KEY_X)) lcdSwap(); } } if (scale_screen_dampen > 5) { if ((keys_pressed & KEY_R) && (keys_pressed & KEY_UP)) { myCartInfo.yOffset++; bRefreshXY = true; } if ((keys_pressed & KEY_R) && (keys_pressed & KEY_DOWN)) { myCartInfo.yOffset--; bRefreshXY = true; } if ((keys_pressed & KEY_R) && (keys_pressed & KEY_LEFT)) { myCartInfo.xOffset++; bRefreshXY = true; } if ((keys_pressed & KEY_R) && (keys_pressed & KEY_RIGHT)) { myCartInfo.xOffset--; bRefreshXY = true; } if ((keys_pressed & KEY_L) && (keys_pressed & KEY_UP)) if (myCartInfo.yScale < 234) { myCartInfo.yScale++; bRefreshXY = true; } if ((keys_pressed & KEY_L) && (keys_pressed & KEY_DOWN)) if (myCartInfo.yScale > 192) { myCartInfo.yScale--; bRefreshXY = true; } if ((keys_pressed & KEY_L) && (keys_pressed & KEY_RIGHT)) if (myCartInfo.xScale < 320) { myCartInfo.xScale++; bRefreshXY = true; } if ((keys_pressed & KEY_L) && (keys_pressed & KEY_LEFT)) if (myCartInfo.xScale > 192) { myCartInfo.xScale--; bRefreshXY = true; } scale_screen_dampen=0; } else scale_screen_dampen++; } } // Toggle full 320x256 static s16 last_xScale = 0; static s16 last_yScale = 0; static s16 last_xOffset = 0; static s16 last_yOffset = 0; __attribute__ ((noinline)) void toggle_zoom(void) { if (last_xScale == 0) { last_xScale = myCartInfo.xScale; last_yScale = myCartInfo.yScale; last_xOffset = myCartInfo.xOffset; last_yOffset = myCartInfo.yOffset; myCartInfo.xScale = 320; myCartInfo.yScale = 234; myCartInfo.xOffset = 32; myCartInfo.yOffset = 21; } else { myCartInfo.xScale = last_xScale; myCartInfo.yScale = last_yScale; myCartInfo.xOffset = last_xOffset; myCartInfo.yOffset = last_yOffset; last_xScale = last_yScale = 0; last_xOffset = last_yOffset = 0; } bRefreshXY = true; } static u8 special_hsc_entry=0; u8 keys_touch __attribute__((section(".dtcm"))) = 0; void handle_touch_screen_input(void) { unsigned int romSel; short iTx,iTy; touchPosition touch; touchRead(&touch); iTx = touch.px; iTy = touch.py; if ((iTx>2) && (iTx<67) && (iTy>154) && (iTy<171)) { // POWER SoundPause(); mmEffect(SFX_KEYCLICK); // Play short key click for feedback... if (dsWaitOnQuit()) emu_state=A7800_QUITSTDS; else SoundUnPause(); } else if ((iTx>238) && (iTx<256) && (iTy>0) && (iTy<20)) { // Full Speed Toggle ... upper right corner... full_speed = 1-full_speed; if (full_speed) dsPrintValue(30,0,0,"FS"); else dsPrintValue(30,0,0," "); dampen=60; } else if ((iTx>=0) && (iTx<18) && (iTy>0) && (iTy<20)) { // FPS Toggle ... upper left corner... fpsDisplay = 1-fpsDisplay; gTotalAtariFrames=0; if (!fpsDisplay) dsPrintValue(0,0,0," "); dampen=60; } else if ((iTx>67) && (iTx<128) && (iTy>154) && (iTy<171)) { // PAUSE if (keys_touch == 0) mmEffect(SFX_KEYCLICK); // Play short key click for feedback... tchepres_value = 10; tchepres_delay = 5; tchepres(10); } else if ((iTx>128) && (iTx<193) && (iTy>154) && (iTy<171)) { // SELECT if (keys_touch == 0) mmEffect(SFX_KEYCLICK); // Play short key click for feedback... tchepres_value = 11; tchepres_delay = 5; tchepres(11); } else if ((iTx>193) && (iTx<254) && (iTy>154) && (iTy<171)) { // RESET if (keys_touch == 0) mmEffect(SFX_KEYCLICK); // Play short key click for feedback... tchepres_value = 6; tchepres_delay = 5; tchepres(6); } else if ((iTx>90) && (iTx<110) && (iTy>90) && (iTy<110)) { // Atari Logo - Activate HSC Maintenence Mode (only on High Score screen) special_hsc_entry=70; } else if ((iTx>69) && (iTx<180) && (iTy>21) && (iTy<62)) // Cartridge slot { SoundPause(); // Find files in current directory and show it proFindFiles(); romSel=dsWaitForRom(); if (romSel) {emu_state=A7800_PLAYINIT; dsLoadGame(proromlist[ucFicAct].filename); if (full_speed) dsPrintValue(30,0,0,"FS"); else dsPrintValue(30,0,0," ");} else SoundUnPause(); } else if ((iTx>190) && (iTx<230) && (iTy>22) && (iTy<62)) // Gear Icon (Settings) { SoundPause(); ShowConfig(); SoundUnPause(); } else if ((iTx>10) && (iTx<58) && (iTy>22) && (iTy<62)) // Magnifying Glass (zoom) { if (!keys_touch) toggle_zoom(); } } // ---------------------------------------------------------------------------------- // This is where the action happens! The main loop runs continually and clocks // out the 60 frames per second of the 7800 Prosystem // ---------------------------------------------------------------------------------- void dsMainLoop(void) { static short int last_keys_pressed = 999; unsigned int keys_pressed; timer_reset(); while(emu_state != A7800_QUITSTDS) { switch (emu_state) { case A7800_MENUINIT: dsShowScreenMain(true); emu_state = A7800_MENUSHOW; break; case A7800_MENUSHOW: emu_state = dsWaitOnMenu(A7800_MENUSHOW); break; case A7800_PLAYINIT: dsShowScreenEmu(); emu_state = A7800_PLAYGAME; last_xScale = 0; break; case A7800_PLAYGAME: // 32,728.5 ticks = 1 second // 1 frame = 1/50 or 1/60 (0.02 or 0.016) // 655 -> 50 fps and 546 -> 60 fps if (!full_speed) { while(TIMER0_DATA < (546*atari_frames)) // We are only supporting NTSC timing ; } if (bEmulatorRun) { // Execute one frame prosystem_ExecuteFrame(keyboard_data); if (++atari_frames == 60) { TIMER0_CR=0; TIMER0_DATA=0; TIMER0_CR=TIMER_ENABLE|TIMER_DIV_1024; atari_frames=0; } // Read keys if (special_hsc_entry > 0) { special_hsc_entry--; tchepres(10); if (special_hsc_entry < 10) { tchepres(11); } continue; } memset(keyboard_data, 0x00, 15); // Not the difficulty switches which are the two bytes after this... } // If we have recently pressed any of the console keys... keep it pressed for a minimum duration if (tchepres_delay) { tchepres_delay--; tchepres(tchepres_value); } scanKeys(); keys_pressed = keysCurrent(); if (dampen == 0) { // if touch screen pressed if (keys_pressed & KEY_TOUCH) { handle_touch_screen_input(); keys_touch=1; } else keys_touch=0; if (keys_pressed != last_keys_pressed) { last_keys_pressed = keys_pressed; if ((myCartInfo.cardctrl1 != SNES)) { if ( (keys_pressed & KEY_SELECT) ) { tchepres_value = 11; tchepres_delay = 5;tchepres(11); } // BUTTON SELECT } if ((myCartInfo.cardctrl1 != TWIN) && (myCartInfo.cardctrl1 != SNES)) { if ( (keys_pressed & KEY_START) ) { tchepres_value = 10; tchepres_delay = 5; tchepres(10); } // BUTTON PAUSE } if (myCartInfo.cardctrl1 == SOTA) { if ( (keys_pressed & (KEY_R | KEY_X)) ) { if (myCartInfo.xOffset < 50) myCartInfo.xOffset +=28; bRefreshXY = true; } if ( (keys_pressed & (KEY_L | KEY_Y)) ) { if (myCartInfo.xOffset > 28) myCartInfo.xOffset -=28; bRefreshXY = true; } } if (dampen < 6) dampen = 6; } } else dampen--; snes_adaptor = 0x0000FFFF; // Nothing pressed to start // manage a7800 joystick if ( (keys_pressed & KEY_UP) ) { tchepres(0); snes_adaptor &= 0xFFEF;} // UP if ( (keys_pressed & KEY_DOWN) ) { tchepres(1); snes_adaptor &= 0xFFDF;} // DOWN if ( (keys_pressed & KEY_LEFT) ) { tchepres(2); snes_adaptor &= 0xFFBF;} // LEFT if ( (keys_pressed & KEY_RIGHT) ) { tchepres(3); snes_adaptor &= 0xFF7F;} // RIGHT if (myCartInfo.cardctrl1 == TWIN) { if ( (keys_pressed & KEY_A) ) { tchepres(12); } // Left Joystick Right if ( (keys_pressed & KEY_B) ) { tchepres(13); } // Left Joystick Down if ( (keys_pressed & KEY_X) ) { tchepres(15); } // Left Joystick Up if ( (keys_pressed & KEY_Y) ) { tchepres(14); } // Left Joystick Left if ( (keys_pressed & KEY_START) ) { tchepres(4); } // Fire Button (mainly to enter high scores and start game) } else if (myCartInfo.cardctrl1 == SNES) { if ( (keys_pressed & KEY_A) ) { snes_adaptor &= 0xFEFF;} // SNES BUTTON A if ( (keys_pressed & KEY_B) ) { snes_adaptor &= 0xFFFE;} // SNES BUTTON B if ( (keys_pressed & KEY_Y) ) { snes_adaptor &= 0xFFFD;} // SNES BUTTON Y if ( (keys_pressed & KEY_X) ) { snes_adaptor &= 0xFDFF;} // SNES BUTTON X if ( (keys_pressed & KEY_L) ) {snes_adaptor &= 0xFBFF;} // SNES Left Shoulder if ( (keys_pressed & KEY_R) ) {snes_adaptor &= 0xF7FF;} // SNES Right Shoulder if ( (keys_pressed & KEY_START) ) {snes_adaptor &= 0xFFF7;} // SNES Start if ( (keys_pressed & KEY_SELECT) ) {snes_adaptor &= 0xFFFB;} // SNES Select } else // Just normal Proline Joystick handling { if ( (keys_pressed & KEY_A) ) { tchepres(4); snes_adaptor &= 0xFEFF;} // BUTTON #1 if ( (keys_pressed & KEY_B) ) { tchepres(5); snes_adaptor &= 0xFFFE;} // BUTTON #2 if (myCartInfo.cardctrl1 != SOTA) { if ( (keys_pressed & KEY_Y) ) { switch (myCartInfo.yButton) { case KEY_MAP_DEFAULT: { temp_shift = 16; shift_dampen = 1; break; } // Shift Screen Down case KEY_MAP_PANUP: { temp_shift = -16; shift_dampen = 1; break; } // Shift Screen Up case KEY_MAP_PANDN: { temp_shift = 16; shift_dampen = 1; break; } // Shift Screen Down case KEY_MAP_JOYUP: { tchepres(0); snes_adaptor &= 0xFFEF; break; } // Joystick Up case KEY_MAP_JOYDN: { tchepres(1); snes_adaptor &= 0xFFDF; break; } // Joystick Down case KEY_MAP_JOYLEFT: { tchepres(2); snes_adaptor &= 0xFFBF; break; } // Joystick Left case KEY_MAP_JOYRIGHT: { tchepres(3); snes_adaptor &= 0xFF7F; break; } // Joystick Right case KEY_MAP_JOYB1: { tchepres(4); snes_adaptor &= 0xFEFF; break; } // Joystick Button #1 case KEY_MAP_JOYB2: { tchepres(5); snes_adaptor &= 0xFFFE; break; } // Joystick Button #2 case KEY_MAP_PAUSE: { tchepres_value = 10; tchepres_delay = 5; tchepres(10); break; } // Console Pause Button } } if ( (keys_pressed & KEY_X) ) { switch (myCartInfo.xButton) { case KEY_MAP_DEFAULT: { temp_shift = -16; shift_dampen = 1; break; } // Shift Screen Up case KEY_MAP_PANUP: { temp_shift = -16; shift_dampen = 1; break; } // Shift Screen Up case KEY_MAP_PANDN: { temp_shift = 16; shift_dampen = 1; break; } // Shift Screen Down case KEY_MAP_JOYUP: { tchepres(0); snes_adaptor &= 0xFFEF; break; } // Joystick Up case KEY_MAP_JOYDN: { tchepres(1); snes_adaptor &= 0xFFDF; break; } // Joystick Down case KEY_MAP_JOYLEFT: { tchepres(2); snes_adaptor &= 0xFFBF; break; } // Joystick Left case KEY_MAP_JOYRIGHT: { tchepres(3); snes_adaptor &= 0xFF7F; break; } // Joystick Right case KEY_MAP_JOYB1: { tchepres(4); snes_adaptor &= 0xFEFF; break; } // Joystick Button #1 case KEY_MAP_JOYB2: { tchepres(5); snes_adaptor &= 0xFFFE; break; } // Joystick Button #2 case KEY_MAP_PAUSE: { tchepres_value = 10; tchepres_delay = 5; tchepres(10); break; } // Console Pause Button } } } } if ((keys_pressed & KEY_R) || (keys_pressed & KEY_L)) { handle_LR_keys(keys_pressed); } else lcd_swap_counter=0; // ------------------------------------------------------------- // Stuff to do once/second such as FPS display and Debug Data // ------------------------------------------------------------- if (TIMER1_DATA >= 32728) // 1000MS (1 sec) { if (bHSC_dirty) // Check to see if the High Score area has changed ... if so, snap out the .hsc file { if (high_score_cart_loaded && myCartInfo.hsc) // Check if HSC is enabled { if (++spamHSC <= 8) // Check that we haven't been spamming the HSC saving... { dsPrintValue(11,0,0, "HSC SAVING"); cartridge_SaveHighScoreSram(); bJustSavedHSC = 1; } } bHSC_dirty = 0; } else if (bJustSavedHSC) { bJustSavedHSC = 0; if (spamHSC <= 8) spamHSC = 0; dsPrintValue(11,0,0, " "); } if (fpsDisplay) { int fps = gTotalAtariFrames; if (fps == 61 && !full_speed) fps=60; gTotalAtariFrames = 0; static u8 lastFPS=99; if (fps != lastFPS) { lastFPS = fps; fpsbuf[0] = '0' + (int)fps/100; fps = fps % 100; fpsbuf[1] = '0' + (int)fps/10; fpsbuf[2] = '0' + (int)fps%10; fpsbuf[3] = 0; dsPrintValue(0,0,0, fpsbuf); } } DumpDebugData(); // And finally reset the frame timer... TIMER1_CR = 0; TIMER1_DATA = 0; TIMER1_CR=TIMER_ENABLE | TIMER_DIV_1024; } break; } } prosystem_Close(); } //---------------------------------------------------------------------------------- // Find files (a78 / bin) available int a78Filescmp (const void *c1, const void *c2) { FICA7800 *p1 = (FICA7800 *) c1; FICA7800 *p2 = (FICA7800 *) c2; if (p1->filename[0] == '.' && p2->filename[0] != '.') return -1; if (p2->filename[0] == '.' && p1->filename[0] != '.') return 1; if (p1->directory && !(p2->directory)) return -1; if (p2->directory && !(p1->directory)) return 1; return strcasecmp (p1->filename, p2->filename); } char filenametmp[255]; void proFindFiles(void) { DIR *pdir; struct dirent *pent; countpro = countfiles = 0; pdir = opendir("."); if (pdir) { while (((pent=readdir(pdir))!=NULL)) { strcpy(filenametmp,pent->d_name); if (pent->d_type == DT_DIR) { if (!( (filenametmp[0] == '.') && (strlen(filenametmp) == 1))) { if (strcasecmp(filenametmp, "sav") != 0) { proromlist[countpro].directory = true; strcpy(proromlist[countpro].filename,filenametmp); countpro++; } } } else { if (strlen(filenametmp)>4) { if ( (strcasecmp(strrchr(filenametmp, '.'), ".a78") == 0) ) { proromlist[countpro].directory = false; strcpy(proromlist[countpro].filename,filenametmp); countpro++;countfiles++; } if ( (strcasecmp(strrchr(filenametmp, '.'), ".bin") == 0) ) { proromlist[countpro].directory = false; strcpy(proromlist[countpro].filename,filenametmp); countpro++;countfiles++; } } } } closedir(pdir); } if (countpro) { qsort (proromlist, countpro, sizeof (FICA7800), a78Filescmp); } else // Failsafe... always provide a back directory... { proromlist[countpro].directory = true; strcpy(proromlist[countpro].filename,".."); countpro = 1; } } // End of file