diff --git a/desmume/src/GPU.cpp b/desmume/src/GPU.cpp index 13f177450..125fee8b2 100644 --- a/desmume/src/GPU.cpp +++ b/desmume/src/GPU.cpp @@ -2344,9 +2344,18 @@ static INLINE void GPU_ligne_MasterBrightness(NDS_Screen * screen, u16 l) // Bright up case 1: { - for(i16 = 0; i16 < 256; ++i16) + if(factor != 16) { - ((u16*)dst)[i16] = fadeInColors[factor][((u16*)dst)[i16]&0x7FFF]; + for(i16 = 0; i16 < 256; ++i16) + { + ((u16*)dst)[i16] = fadeInColors[factor][((u16*)dst)[i16]&0x7FFF]; + } + } + else + { + // all white (optimization) + for(i16 = 0; i16 < 256; ++i16) + ((u16*)dst)[i16] = 0x7FFF; } break; } @@ -2354,9 +2363,17 @@ static INLINE void GPU_ligne_MasterBrightness(NDS_Screen * screen, u16 l) // Bright down case 2: { - for(i16 = 0; i16 < 256; ++i16) + if(factor != 16) { - ((u16*)dst)[i16] = fadeOutColors[factor][((u16*)dst)[i16]&0x7FFF]; + for(i16 = 0; i16 < 256; ++i16) + { + ((u16*)dst)[i16] = fadeOutColors[factor][((u16*)dst)[i16]&0x7FFF]; + } + } + else + { + // all black (optimization) + memset(dst, 0, 512); } break; } @@ -2493,6 +2510,18 @@ void GPU_ligne(NDS_Screen * screen, u16 l, bool skip) return; } + // skip some work if master brightness makes the screen completely white or completely black + if(gpu->MasterBrightFactor >= 16 && (gpu->MasterBrightMode == 1 || gpu->MasterBrightMode == 2)) + { + // except if it could cause any side effects (for example if we're capturing), then don't skip anything + if(!(gpu->core == GPU_MAIN && (gpu->dispCapCnt.enabled || l == 0 || l == 191))) + { + gpu->currLine = l; + GPU_ligne_MasterBrightness(screen, l); + return; + } + } + //cache some parameters which are assumed to be stable throughout the rendering of the entire line gpu->currLine = l; u16 mosaic_control = T1ReadWord((u8 *)&gpu->dispx_st->dispx_MISC.MOSAIC, 0); diff --git a/desmume/src/GPU_osd.cpp b/desmume/src/GPU_osd.cpp index e232dac98..a52ed1588 100644 --- a/desmume/src/GPU_osd.cpp +++ b/desmume/src/GPU_osd.cpp @@ -70,7 +70,7 @@ void EditHud(s32 x, s32 y, HudStruct *hudstruct) { } if((x >= hud.x && x <= hud.x + hud.xsize) && - (y >= hud.y && y <= hud.y + hud.ysize) && !hud.clicked ) { + (y >= hud.y && y <= hud.y + hud.ysize) && !hudstruct->clicked ) { hud.clicked=1; hud.storedx = x - hud.x; @@ -89,7 +89,10 @@ void EditHud(s32 x, s32 y, HudStruct *hudstruct) { if(hud.y > 384-16)hud.y = 384-16; if(hud.clicked) + { + hudstruct->clicked = true; break;//prevent items from grouping together + } i++; } @@ -104,6 +107,8 @@ void HudClickRelease(HudStruct *hudstruct) { hud.clicked=0; i++; } + + hudstruct->clicked = false; } void HudStruct::reset() @@ -120,13 +125,13 @@ void HudStruct::reset() InputDisplay.x=0; InputDisplay.y=45; - InputDisplay.xsize=120; + InputDisplay.xsize=220; InputDisplay.ysize=10; GraphicalInputDisplay.x=8; GraphicalInputDisplay.y=328; - GraphicalInputDisplay.xsize=100; - GraphicalInputDisplay.ysize=40; + GraphicalInputDisplay.xsize=102; + GraphicalInputDisplay.ysize=50; LagFrameCounter.x=0; LagFrameCounter.y=65; @@ -144,12 +149,19 @@ void HudStruct::reset() SavestateSlots.ysize = 24; SetHudDummy(&Dummy); + clicked = false; } static void joyFill(int n) { - if(nds.pad & (1 << n)) + bool pressedForGame = NDS_getFinalUserInput().buttons.array[n]; + bool physicallyPressed = NDS_getRawUserInput().buttons.array[n]; + if(pressedForGame && physicallyPressed) aggDraw.hud->fillColor(0,0,0,255); + else if(pressedForGame) + aggDraw.hud->fillColor(255,0,0,255); + else if(physicallyPressed) + aggDraw.hud->fillColor(0,255,0,255); else aggDraw.hud->fillColor(255,255,255,255); } @@ -157,65 +169,140 @@ static void joyFill(int n) { static void joyEllipse(double ex, double ey, int xc, int yc, int x, int y, double ratio, double rad, int button) { joyFill(button); + aggDraw.hud->lineWidth(rad); aggDraw.hud->ellipse(x+((xc*ex)*ratio), y+((yc*ey)*ratio), rad*ratio, rad*ratio); } -static void gradientFill(double x1,double y1,double x2,double y2,AggColor c1,AggColor c2, int n) { - - if(nds.pad & (1 << n)) - aggDraw.hud->fillLinearGradient(x1,y1,x2,y2,c1,c2); +static void joyRoundedRect(double x1, double y1, int x2, int y2, int alpha1, int alpha2, int button) +{ + bool pressedForGame = NDS_getFinalUserInput().buttons.array[button]; + bool physicallyPressed = NDS_getRawUserInput().buttons.array[button]; + if(pressedForGame && physicallyPressed) + aggDraw.hud->fillLinearGradient(x1,y1,x2,y2,agg::rgba8(0,0,0,alpha1), agg::rgba8(0,0,0,alpha2)); + else if(pressedForGame) + aggDraw.hud->fillLinearGradient(x1,y1,x2,y2,agg::rgba8(255,0,0,alpha1), agg::rgba8(255,0,0,alpha2)); + else if(physicallyPressed) + aggDraw.hud->fillLinearGradient(x1,y1,x2,y2,agg::rgba8(0,255,0,alpha1), agg::rgba8(0,255,0,alpha2)); else - aggDraw.hud->fillColor(255,255,255,255); + return; //aggDraw.hud->fillLinearGradient(x1,y1,x2,y2,agg::rgba8(255,255,255,alpha1), agg::rgba8(255,255,255,alpha2)); + + aggDraw.hud->roundedRect(x1,y1,x2,y2,1); } -static void drawPad(int x, int y, double ratio) { - int xc = 41; - int yc = 20; +static void drawPad(double x, double y, double ratio) { + + // you might notice black/red/green colors used to show what buttons are pressed. + // the logic is roughly: + // RED == PAST (the button was held last frame) + // GREEN == FUTURE (the button is physically held now) + // BLACK == PRESENT (the button was held last frame and is still physically held now) + + // aligning to odd half-pixel boundaries prevents agg2d from blurring thin straight lines + x = floor(x) + 0.5; + y = floor(y) + 0.5; + double xc = 41 - 0.5; + double yc = 20 - 0.5; aggDraw.hud->lineColor(128,128,128,255); aggDraw.hud->fillLinearGradient(x, y, x+(xc*ratio), y+(yc*ratio), agg::rgba8(222,222,222,128), agg::rgba8(255,255,255,255)); - if(nds.pad & (1 << 2)) - aggDraw.hud->fillLinearGradient(x, y, x+(xc*ratio), y+(yc*ratio), agg::rgba8(0,0,0,128), agg::rgba8(255,255,255,255)); + aggDraw.hud->roundedRect (x, y, floor(x+(xc*ratio))+0.5, floor(y+(yc*ratio))+0.5, 1); - if(nds.pad & (1 << 1)) - aggDraw.hud->fillLinearGradient(x+(xc*ratio), y+(yc*ratio), x, y, agg::rgba8(0,0,0,128), agg::rgba8(255,255,255,255)); + double screenLeft = x+(xc*.25*ratio); + double screenTop = y+(yc*.1*ratio); + double screenRight = x+(xc*.745*ratio); + double screenBottom = y+(yc*.845*ratio); + aggDraw.hud->fillLinearGradient(screenLeft, screenTop, screenRight, screenBottom, agg::rgba8(128,128,128,128), agg::rgba8(255,255,255,255)); + aggDraw.hud->roundedRect (screenLeft, screenTop, screenRight, screenBottom, 1); - aggDraw.hud->roundedRect (x, y, x+(xc*ratio), y+(yc*ratio), 1); - aggDraw.hud->fillLinearGradient(x+(xc*.25*ratio), y+(yc*.1*ratio), x+(xc*.75*ratio), y+(yc*.85*ratio), agg::rgba8(128,128,128,128), agg::rgba8(255,255,255,255)); - - aggDraw.hud->roundedRect (x+(xc*.25*ratio), y+(yc*.1*ratio), x+(xc*.75*ratio),y+(yc*.85*ratio), 1); - joyEllipse(.89,.45,xc,yc,x,y,ratio,1,6);//B joyEllipse(.89,.22,xc,yc,x,y,ratio,1,3);//X joyEllipse(.83,.34,xc,yc,x,y,ratio,1,4);//Y joyEllipse(.95,.34,xc,yc,x,y,ratio,1,5);//A - joyEllipse(.82,.72,xc,yc,x,y,ratio,.5,7);//Start - joyEllipse(.82,.85,xc,yc,x,y,ratio,.5,8);//Select + joyEllipse(.82,.716,xc,yc,x,y,ratio,.5,7);//Start + joyEllipse(.82,.842,xc,yc,x,y,ratio,.5,8);//Select + + + double dpadPoints [][2] = { + {.04,.33}, // top-left corner of left button + {.08,.33}, + {.08,.24}, // top-left corner of up button + {.13,.24}, // top-right corner of up button + {.13,.33}, + {.17,.33}, // top-right corner of right button + {.17,.43}, // bottom-right corner of right button + {.13,.43}, + {.13,.516}, // bottom-right corner of down button + {.08,.516}, // bottom-left corner of down button + {.08,.43}, + {.04,.43}, // bottom-left corner of left button + }; + static const int numdpadPoints = sizeof(dpadPoints)/sizeof(dpadPoints[0]); + for(int i = 0; i < numdpadPoints; i++) + { + dpadPoints[i][0] = x+(xc*(dpadPoints[i][0]+.01)*ratio); + dpadPoints[i][1] = y+(yc*(dpadPoints[i][1]+.00)*ratio); + } + + // dpad outline + aggDraw.hud->fillColor(255,255,255,200); + aggDraw.hud->polygon((double*)dpadPoints, numdpadPoints); aggDraw.hud->noLine(); - aggDraw.hud->fillColor(255,255,255,200); - //left - gradientFill(x+(xc*.04*ratio), y+(yc*.33*ratio), x+(xc*.17*ratio), y+(yc*.43*ratio), agg::rgba8(0,0,0,255), agg::rgba8(255,255,255,255),11); + // left + joyRoundedRect(dpadPoints[0][0], dpadPoints[0][1], dpadPoints[7][0], dpadPoints[7][1], 255, 0, 11); + + // right + joyRoundedRect(dpadPoints[1][0], dpadPoints[1][1], dpadPoints[6][0], dpadPoints[6][1], 0, 255, 12); - //right - if(nds.pad & (1 << 12)) - aggDraw.hud->fillLinearGradient(x+(xc*.17*ratio), y+(yc*.43*ratio), x+(xc*.04*ratio), y+(yc*.33*ratio), agg::rgba8(0,0,0,255), agg::rgba8(255,255,255,255)); - - aggDraw.hud->roundedRect (x+(xc*.04*ratio), y+(yc*.33*ratio), x+(xc*.17*ratio), y+(yc*.43*ratio), 1); + // up + joyRoundedRect(dpadPoints[2][0], dpadPoints[2][1], dpadPoints[7][0], dpadPoints[7][1], 255, 0, 9); + + // right + joyRoundedRect(dpadPoints[1][0], dpadPoints[1][1], dpadPoints[8][0], dpadPoints[8][1], 0, 255, 10); - //down - gradientFill(x+(xc*.13*ratio), y+(yc*.52*ratio), x+(xc*.08*ratio), y+(yc*.23*ratio), agg::rgba8(0,0,0,255), agg::rgba8(255,255,255,255),10); + // left shoulder + joyRoundedRect(x+(xc*.00*ratio), y+(yc*.00*ratio), x+(xc*.15*ratio), y+(yc*.07*ratio), 255, 200, 2); - //up - if(nds.pad & (1<< 9)) - aggDraw.hud->fillLinearGradient(x+(xc*.08*ratio), y+(yc*.23*ratio), x+(xc*.13*ratio), y+(yc*.52*ratio), agg::rgba8(0,0,0,255), agg::rgba8(255,255,255,255)); + // right shoulder + joyRoundedRect(x+(xc*.85*ratio), y+(yc*.00*ratio), x+(xc*1.0*ratio), y+(yc*.07*ratio), 200, 255, 1); - aggDraw.hud->roundedRect (x+(xc*.08*ratio), y+(yc*.23*ratio), x+(xc*.13*ratio), y+(yc*.52*ratio), 1); + // lid... + joyRoundedRect(x+(xc*.4*ratio), y+(yc*.96*ratio), x+(xc*0.6*ratio), y+(yc*1.0*ratio), 200, 200, 13); + + // touch pad + { + bool gameTouchOn = nds.isTouch; + double gameTouchX = screenLeft+1 + (nds.touchX * 0.0625) * (screenRight - screenLeft - 2) / 256.0; + double gameTouchY = screenTop+1 + (nds.touchY * 0.0625) * (screenBottom - screenTop - 2) / 192.0; + bool physicalTouchOn = NDS_getRawUserInput().touch.isTouch; + double physicalTouchX = screenLeft+1 + (NDS_getRawUserInput().touch.touchX * 0.0625) * (screenRight - screenLeft - 2) / 256.0; + double physicalTouchY = screenTop+1 + (NDS_getRawUserInput().touch.touchY * 0.0625) * (screenBottom - screenTop - 2) / 192.0; + if(gameTouchOn && physicalTouchOn && gameTouchX == physicalTouchX && gameTouchY == physicalTouchY) + { + aggDraw.hud->fillColor(0,0,0,255); + aggDraw.hud->ellipse(gameTouchX, gameTouchY, ratio*0.37, ratio*0.37); + } + else + { + if(physicalTouchOn) + { + aggDraw.hud->fillColor(0,0,0,128); + aggDraw.hud->ellipse(physicalTouchX, physicalTouchY, ratio*0.5, ratio*0.5); + aggDraw.hud->fillColor(0,255,0,255); + aggDraw.hud->ellipse(physicalTouchX, physicalTouchY, ratio*0.37, ratio*0.37); + } + if(gameTouchOn) + { + aggDraw.hud->fillColor(255,0,0,255); + aggDraw.hud->ellipse(gameTouchX, gameTouchY, ratio*0.37, ratio*0.37); + } + } + } } @@ -225,20 +312,87 @@ struct TouchInfo{ }; static int touchalpha[8]= {31, 63, 95, 127, 159, 191, 223, 255}; static TouchInfo temptouch; -bool touchshadow = true; +static const bool touchshadow = false;//true; // sorry, it's cool but also distracting and looks cleaner with it off. maybe if it drew line segments between touch points instead of isolated crosses... static std::vector touch (8); +static void TextualInputDisplay() { + + // drawing the whole string at once looks ugly + // (because of variable width font and the "shadow" appearing over blank space) + // and can't give us the color-coded effects we want anyway (see drawPad for info) + + const UserButtons& gameButtons = NDS_getFinalUserInput().buttons; + const UserButtons& physicalButtons = NDS_getRawUserInput().buttons; + + double x = Hud.InputDisplay.x; + + // from order FRLDUTSBAYXWEG where G is 0 + static const char* buttonChars = "<^>vABXYLRSsgf"; + static const int buttonIndex [14] = {11,9,12,10,5,6,3,4,2,1,7,8,0,13}; + for(int i = 0; i < 14; i++, x+=11.0) + { + bool pressedForGame = gameButtons.array[buttonIndex[i]]; + bool physicallyPressed = physicalButtons.array[buttonIndex[i]]; + if(pressedForGame && physicallyPressed) + aggDraw.hud->lineColor(255,255,255,255); + else if(pressedForGame) + aggDraw.hud->lineColor(255,48,48,255); + else if(physicallyPressed) + aggDraw.hud->lineColor(0,255,0,255); + else + continue; + + // cast from char to std::string is a bit awkward + std::string str(buttonChars+i, 2); + str[1] = '\0'; + + aggDraw.hud->renderTextDropshadowed(x, Hud.InputDisplay.y, str); + } + + // touch pad + { + char str [32]; + bool gameTouchOn = nds.isTouch; + int gameTouchX = nds.touchX >> 4; + int gameTouchY = nds.touchY >> 4; + bool physicalTouchOn = NDS_getRawUserInput().touch.isTouch; + int physicalTouchX = NDS_getRawUserInput().touch.touchX >> 4; + int physicalTouchY = NDS_getRawUserInput().touch.touchY >> 4; + if(gameTouchOn && physicalTouchOn && gameTouchX == physicalTouchX && gameTouchY == physicalTouchY) + { + sprintf(str, "%d,%d", gameTouchX, gameTouchY); + aggDraw.hud->lineColor(255,255,255,255); + aggDraw.hud->renderTextDropshadowed(x, Hud.InputDisplay.y, str); + } + else + { + if(gameTouchOn) + { + sprintf(str, "%d,%d", gameTouchX, gameTouchY); + aggDraw.hud->lineColor(255,48,48,255); + aggDraw.hud->renderTextDropshadowed(x, Hud.InputDisplay.y-(physicalTouchOn?8:0), str); + } + if(physicalTouchOn) + { + sprintf(str, "%d,%d", physicalTouchX, physicalTouchY); + aggDraw.hud->lineColor(0,255,0,255); + aggDraw.hud->renderTextDropshadowed(x, Hud.InputDisplay.y+(gameTouchOn?8:0), str); + } + } + } +} static void TouchDisplay() { aggDraw.hud->lineWidth(1.0); - temptouch.X = nds.touchX >> 4; - temptouch.Y = nds.touchY >> 4; - touch.push_back(temptouch); - - if(touch.size() > 8) touch.erase(touch.begin()); + temptouch.X = NDS_getRawUserInput().touch.touchX >> 4; + temptouch.Y = NDS_getRawUserInput().touch.touchY >> 4; if(touchshadow) { + + touch.push_back(temptouch); + if(touch.size() > 8) touch.erase(touch.begin()); + for (int i = 0; i < 8; i++) { temptouch = touch[i]; if(temptouch.X != 0 || temptouch.Y != 0) { @@ -251,10 +405,20 @@ static void TouchDisplay() { } } else - if(nds.isTouch) { + if(NDS_getRawUserInput().touch.isTouch) { + aggDraw.hud->lineColor(0, 255, 0, 128); aggDraw.hud->line(temptouch.X - 256, temptouch.Y + 192, temptouch.X + 256, temptouch.Y + 192); //horiz aggDraw.hud->line(temptouch.X, temptouch.Y - 256, temptouch.X, temptouch.Y + 384); //vert } + + if(nds.isTouch) { + temptouch.X = nds.touchX >> 4; + temptouch.Y = nds.touchY >> 4; + aggDraw.hud->lineColor(255, 0, 0, 128); + aggDraw.hud->line(temptouch.X - 256, temptouch.Y + 192, temptouch.X + 256, temptouch.Y + 192); //horiz + aggDraw.hud->line(temptouch.X, temptouch.Y - 256, temptouch.X, temptouch.Y + 384); //vert + } + } static int previousslot = 0; @@ -304,19 +468,37 @@ static void DrawStateSlots(){ previousslot = lastSaveState; } +static void DrawEditableElementIndicators() +{ + u32 i = 0; + while (!IsHudDummy(&Hud.hud(i))) { + HudCoordinates &hud = Hud.hud(i); + aggDraw.hud->fillColor(0,0,0,0); + aggDraw.hud->lineColor(0,0,0,64); + aggDraw.hud->lineWidth(2.0); + aggDraw.hud->rectangle(hud.x,hud.y,hud.x+hud.xsize+1.0,hud.y+hud.ysize+1.0); + aggDraw.hud->lineColor(255,hud.clicked?127:255,0,255); + aggDraw.hud->lineWidth(1.0); + aggDraw.hud->rectangle(hud.x-0.5,hud.y-0.5,hud.x+hud.xsize+0.5,hud.y+hud.ysize+0.5); + i++; + } +} + + void DrawHUD() { GTimeVal time; g_get_current_time(&time); hudTimer = ((s64)time.tv_sec * 1000) + ((s64)time.tv_usec/1000); + if (HudEditorMode) + { + DrawEditableElementIndicators(); + } if (CommonSettings.hud.ShowInputDisplay) { - std::stringstream ss; - if(nds.isTouch) - ss << (nds.touchX >> 4) << " " << (nds.touchY >> 4); - osd->addFixed(Hud.InputDisplay.x, Hud.InputDisplay.y, "%s",(InputDisplayString + ss.str()).c_str()); + TextualInputDisplay(); TouchDisplay(); } @@ -341,7 +523,9 @@ void DrawHUD() } if (CommonSettings.hud.ShowGraphicalInputDisplay) + { drawPad(Hud.GraphicalInputDisplay.x, Hud.GraphicalInputDisplay.y, 2.5); + } #ifdef WIN32 if (CommonSettings.hud.ShowMicrophone) diff --git a/desmume/src/GPU_osd.h b/desmume/src/GPU_osd.h index a97343a83..7e19bd871 100644 --- a/desmume/src/GPU_osd.h +++ b/desmume/src/GPU_osd.h @@ -50,6 +50,7 @@ public: HudStruct() : fps(0) , fps3d(0) + , clicked(false) {} HudCoordinates SavestateSlots; @@ -65,6 +66,7 @@ public: void reset(); int fps, fps3d; + bool clicked; }; void EditHud(s32 x, s32 y, HudStruct *hudstruct); diff --git a/desmume/src/NDSSystem.cpp b/desmume/src/NDSSystem.cpp index b75148b06..880837e8a 100644 --- a/desmume/src/NDSSystem.cpp +++ b/desmume/src/NDSSystem.cpp @@ -541,12 +541,12 @@ int NDS_Init( void) { // Init calibration info TSCal.adc.x1 = 0x0200; TSCal.adc.y1 = 0x0200; - TSCal.scr.x1 = 0x20; - TSCal.scr.y1 = 0x20; + TSCal.scr.x1 = 0x20 + 1; // calibration screen coords are 1-based, + TSCal.scr.y1 = 0x20 + 1; // either that or NDS_getADCTouchPosX/Y are wrong. TSCal.adc.x2 = 0x0E00; TSCal.adc.y2 = 0x0800; - TSCal.scr.x2 = 0xE0; - TSCal.scr.y2 = 0x80; + TSCal.scr.x2 = 0xE0 + 1; + TSCal.scr.y2 = 0x80 + 1; return 0; } @@ -621,35 +621,6 @@ NDS_header * NDS_getROMHeader(void) } -INLINE u16 NDS_getADCTouchPosX(u16 scrX) -{ - return (scrX - TSCal.scr.x1 + 1) * (TSCal.adc.x2 - TSCal.adc.x1) / (TSCal.scr.x2 - TSCal.scr.x1) + TSCal.adc.x1; -} - -INLINE u16 NDS_getADCTouchPosY(u16 scrY) -{ - return (scrY - TSCal.scr.y1 + 1) * (TSCal.adc.y2 - TSCal.adc.y1) / (TSCal.scr.y2 - TSCal.scr.y1) + TSCal.adc.y1; -} - -void NDS_setTouchPos(u16 x, u16 y) -{ - //nds.touchX = (x <<4); - //nds.touchY = (y <<4); - nds.touchX = NDS_getADCTouchPosX(x); - nds.touchY = NDS_getADCTouchPosY(y); - nds.isTouch = 1; - - MMU.ARM7_REG[0x136] &= 0xBF; -} - -void NDS_releaseTouch(void) -{ - nds.touchX = 0; - nds.touchY = 0; - nds.isTouch = 0; - - MMU.ARM7_REG[0x136] |= 0x40; -} void debug() @@ -1446,13 +1417,13 @@ void NDS_FillDefaultFirmwareConfigData( struct NDS_fw_config_data *fw_config) { /* default touchscreen calibration */ fw_config->touch_cal[0].adc_x = 0x200; fw_config->touch_cal[0].adc_y = 0x200; - fw_config->touch_cal[0].screen_x = 0x20; - fw_config->touch_cal[0].screen_y = 0x20; + fw_config->touch_cal[0].screen_x = 0x20 + 1; // calibration screen coords are 1-based, + fw_config->touch_cal[0].screen_y = 0x20 + 1; // either that or NDS_getADCTouchPosX/Y are wrong. fw_config->touch_cal[1].adc_x = 0xe00; fw_config->touch_cal[1].adc_y = 0x800; - fw_config->touch_cal[1].screen_x = 0xe0; - fw_config->touch_cal[1].screen_y = 0x80; + fw_config->touch_cal[1].screen_x = 0xe0 + 1; + fw_config->touch_cal[1].screen_y = 0x80 + 1; } int NDS_LoadFirmware(const char *filename) @@ -1639,9 +1610,11 @@ template struct TSequenceItem_Timer : public TSequenceItem nds.timerCycle[procnum][i] += (remain << MMU.timerMODE[procnum][i]); ctr++; } +#if defined(DEBUG) || defined(_DEBUG) if(ctr>1) { printf("yikes!!!!! please report!\n"); } +#endif } if(over) @@ -2081,12 +2054,17 @@ void Sequencer::execHardware() void execHardware_interrupts(); +static void saveUserInput(std::ostream* os); +static bool loadUserInput(std::istream* is, int version); + void nds_savestate(std::ostream* os) { //version - write32le(1,os); + write32le(2,os); sequencer.save(os); + + saveUserInput(os); } bool nds_loadstate(std::istream* is, int size) @@ -2095,9 +2073,13 @@ bool nds_loadstate(std::istream* is, int size) int version; if(read32le(&version,is) != 1) return false; - if(version > 1) return false; + if(version > 2) return false; - return sequencer.load(is, version); + bool temp = true; + temp &= sequencer.load(is, version); + if(version <= 1 || !temp) return temp; + temp &= loadUserInput(is, version); + return temp; } //#define LOG_ARM9 @@ -2522,131 +2504,243 @@ static std::string MakeInputDisplayString(u16 pad, u16 padExt) { return s; } + +buttonstruct Turbo; +buttonstruct TurboTime; +buttonstruct AutoHold; + void ClearAutoHold(void) { - for (int i=0; i < 12; i++) { - AutoHold.hold(i)=false; + for (int i=0; i < ARRAY_SIZE(AutoHold.array); i++) { + AutoHold.array[i]=false; } } -void NDS_setPadFromMovie(u16 pad) + +INLINE u16 NDS_getADCTouchPosX(u16 scrX) { -#define FIX(b,n) (((pad>>n)&1)!=0) - NDS_setPad( - FIX(pad,12), //R - FIX(pad,11), //L - FIX(pad,10), //D - FIX(pad,9), //U - FIX(pad,7), //Select - FIX(pad,8), //Start - FIX(pad,6), //B - FIX(pad,5), //A - FIX(pad,4), //Y - FIX(pad,3), //X - FIX(pad,2), - FIX(pad,1), - FIX(pad,0), - movie_lid - ); -#undef FIX - + // this is a little iffy, + // we're basically adjusting the ADC results to + // compensate for how they will be interpreted. + // the actual system doesn't do this transformation. + int rv = (scrX - TSCal.scr.x1 + 1) * (TSCal.adc.x2 - TSCal.adc.x1) / (TSCal.scr.x2 - TSCal.scr.x1) + TSCal.adc.x1; + rv = min(0xFFF, max(0, rv)); + return (u16)rv; +} +INLINE u16 NDS_getADCTouchPosY(u16 scrY) +{ + int rv = (scrY - TSCal.scr.y1 + 1) * (TSCal.adc.y2 - TSCal.adc.y1) / (TSCal.scr.y2 - TSCal.scr.y1) + TSCal.adc.y1; + rv = min(0xFFF, max(0, rv)); + return (u16)rv; } -turbo Turbo; -turbotime TurboTime; +static UserInput rawUserInput = {}; // requested input, generally what the user is physically pressing +static UserInput intermediateUserInput = {}; // intermediate buffer for modifications (seperated from finalUserInput for safety reasons) +static UserInput finalUserInput = {}; // what gets sent to the game and possibly recorded +bool validToProcessInput = false; -static void SetTurbo(bool (&pad) [12]) { - - bool turbo[4] = {true, false, true, false}; - bool currentbutton; - - for (int i=0; i < 12; i++) { - currentbutton=Turbo.button(i); - - if(currentbutton && movieMode != MOVIEMODE_PLAY) { - pad[i]=turbo[TurboTime.time(i)-1]; - - if(TurboTime.time(i) >= (int)ARRAY_SIZE(turbo)) - TurboTime.time(i)=0; - } - else - TurboTime.time(i)=0; //reset timer if the button isn't pressed - } - for (int i=0; i<12; i++) - TurboTime.time(i)++; +const UserInput& NDS_getRawUserInput() +{ + return rawUserInput; +} +UserInput& NDS_getProcessingUserInput() +{ + assert(validToProcessInput); + return intermediateUserInput; +} +bool NDS_isProcessingUserInput() +{ + return validToProcessInput; +} +const UserInput& NDS_getFinalUserInput() +{ + return finalUserInput; } -autohold AutoHold; + +static void saveUserInput(std::ostream* os, UserInput& input) +{ + os->write((const char*)input.buttons.array, 14); + writebool(input.touch.isTouch, os); + write16le(input.touch.touchX, os); + write16le(input.touch.touchY, os); + write32le(input.mic.micButtonPressed, os); +} +static bool loadUserInput(std::istream* is, UserInput& input, int version) +{ + is->read((char*)input.buttons.array, 14); + readbool(&input.touch.isTouch, is); + read16le(&input.touch.touchX, is); + read16le(&input.touch.touchY, is); + read32le(&input.mic.micButtonPressed, is); + return true; +} +// (userinput is kind of a misnomer, e.g. finalUserInput has to mirror nds.pad, nds.touchX, etc.) +static void saveUserInput(std::ostream* os) +{ + saveUserInput(os, finalUserInput); + saveUserInput(os, intermediateUserInput); // saved in case a savestate is made during input processing (which Lua could do if nothing else) + writebool(validToProcessInput, os); + for(int i = 0; i < 14; i++) + write32le(TurboTime.array[i], os); // saved to make autofire more tolerable to use with re-recording +} +static bool loadUserInput(std::istream* is, int version) +{ + bool rv = true; + rv &= loadUserInput(is, finalUserInput, version); + rv &= loadUserInput(is, intermediateUserInput, version); + readbool(&validToProcessInput, is); + for(int i = 0; i < 14; i++) + read32le(&TurboTime.array[i], is); + return rv; +} + +static inline void gotInputRequest() +{ + // nobody should set the raw input while we're processing the input. + // it might not screw anything up but it would be completely useless. + assert(!validToProcessInput); +} void NDS_setPad(bool R,bool L,bool D,bool U,bool T,bool S,bool B,bool A,bool Y,bool X,bool W,bool E,bool G, bool F) { + gotInputRequest(); + UserButtons& rawButtons = rawUserInput.buttons; + rawButtons.R = R; + rawButtons.L = L; + rawButtons.D = D; + rawButtons.U = U; + rawButtons.T = T; + rawButtons.S = S; + rawButtons.B = B; + rawButtons.A = A; + rawButtons.Y = Y; + rawButtons.X = X; + rawButtons.W = W; + rawButtons.E = E; + rawButtons.G = G; + rawButtons.F = F; +} +void NDS_setTouchPos(u16 x, u16 y) +{ + gotInputRequest(); + rawUserInput.touch.touchX = NDS_getADCTouchPosX(x); + rawUserInput.touch.touchY = NDS_getADCTouchPosY(y); + rawUserInput.touch.isTouch = true; - bool padarray[12] = {R, L, D, U, T, S, B, A, Y, X, W, E}; - - SetTurbo(padarray); - - R=padarray[0]; - L=padarray[1]; - D=padarray[2]; - U=padarray[3]; - T=padarray[4]; - S=padarray[5]; - B=padarray[6]; - A=padarray[7]; - Y=padarray[8]; - X=padarray[9]; - W=padarray[10]; - E=padarray[11]; - - if (movieMode != MOVIEMODE_PLAY) { - if(AutoHold.Right) R=!padarray[0]; - if(AutoHold.Left) L=!padarray[1]; - if(AutoHold.Down) D=!padarray[2]; - if(AutoHold.Up) U=!padarray[3]; - if(AutoHold.Select)T=!padarray[4]; - if(AutoHold.Start) S=!padarray[5]; - if(AutoHold.B) B=!padarray[6]; - if(AutoHold.A) A=!padarray[7]; - if(AutoHold.Y) Y=!padarray[8]; - if(AutoHold.X) X=!padarray[9]; - if(AutoHold.L) W=!padarray[10]; - if(AutoHold.R) E=!padarray[11]; + if(movieMode != MOVIEMODE_INACTIVE) + { + // just in case, since the movie only stores 8 bits per touch coord + rawUserInput.touch.touchX &= 0x0FF0; + rawUserInput.touch.touchY &= 0x0FF0; } - //this macro is the opposite of what you would expect -#define FIX(b) (b?0:0x80) +#ifndef WIN32 + // FIXME: this code should be deleted from here, + // other platforms should call NDS_beginProcessingInput,NDS_endProcessingInput once per frame instead + // (see the function called "run" in src/windows/main.cpp), + // but I'm leaving this here for now since I can't test those other platforms myself. + nds.touchX = rawUserInput.touch.touchX; + nds.touchY = rawUserInput.touch.touchY; + nds.isTouch = 1; + MMU.ARM7_REG[0x136] &= 0xBF; +#endif +} +void NDS_releaseTouch(void) +{ + gotInputRequest(); + rawUserInput.touch.touchX = 0; + rawUserInput.touch.touchY = 0; + rawUserInput.touch.isTouch = false; - int r = FIX(R); - int l = FIX(L); - int d = FIX(D); - int u = FIX(U); - int t = FIX(T); - int s = FIX(S); - int b = FIX(B); - int a = FIX(A); - int y = FIX(Y); - int x = FIX(X); - int w = FIX(W); - int e = FIX(E); - int g = FIX(G); - int f = FIX(F); +#ifndef WIN32 + // FIXME: this code should be deleted from here, + // other platforms should call NDS_beginProcessingInput,NDS_endProcessingInput once per frame instead + // (see the function called "run" in src/windows/main.cpp), + // but I'm leaving this here for now since I can't test those other platforms myself. + nds.touchX = 0; + nds.touchY = 0; + nds.isTouch = 0; + MMU.ARM7_REG[0x136] |= 0x40; +#endif +} +void NDS_setMic(bool pressed) +{ + gotInputRequest(); + rawUserInput.mic.micButtonPressed = (pressed ? TRUE : FALSE); +} + + +static void NDS_applyFinalInput(); + + +void NDS_beginProcessingInput() +{ + // start off from the raw input + intermediateUserInput = rawUserInput; + + // processing is valid now + validToProcessInput = true; +} + +void NDS_endProcessingInput() +{ + // transfer the processed input + finalUserInput = intermediateUserInput; + + // processing is invalid now + validToProcessInput = false; + + // use the final input for a few things right away + NDS_applyFinalInput(); +} + + + + + + + + +static void NDS_applyFinalInput() +{ + const UserInput& input = NDS_getFinalUserInput(); u16 pad = (0 | - ((a) >> 7) | - ((b) >> 6) | - ((s) >> 5) | - ((t) >> 4) | - ((r) >> 3) | - ((l) >> 2) | - ((u) >> 1) | - ((d)) | - ((e) << 1) | - ((w) << 2)) ; + ((input.buttons.A ? 0 : 0x80) >> 7) | + ((input.buttons.B ? 0 : 0x80) >> 6) | + ((input.buttons.T ? 0 : 0x80) >> 5) | + ((input.buttons.S ? 0 : 0x80) >> 4) | + ((input.buttons.R ? 0 : 0x80) >> 3) | + ((input.buttons.L ? 0 : 0x80) >> 2) | + ((input.buttons.U ? 0 : 0x80) >> 1) | + ((input.buttons.D ? 0 : 0x80) ) | + ((input.buttons.E ? 0 : 0x80) << 1) | + ((input.buttons.W ? 0 : 0x80) << 2)) ; ((u16 *)MMU.ARM9_REG)[0x130>>1] = (u16)pad; ((u16 *)MMU.ARM7_REG)[0x130>>1] = (u16)pad; - if (!f && !countLid) + + if(input.touch.isTouch) + { + nds.touchX = input.touch.touchX; + nds.touchY = input.touch.touchY; + nds.isTouch = 1; + + MMU.ARM7_REG[0x136] &= 0xBF; + } + else + { + nds.touchX = 0; + nds.touchY = 0; + nds.isTouch = 0; + + MMU.ARM7_REG[0x136] |= 0x40; + } + + + if (input.buttons.F && !countLid) { LidClosed = (!LidClosed) & 0x01; if (!LidClosed) @@ -2667,9 +2761,9 @@ void NDS_setPad(bool R,bool L,bool D,bool U,bool T,bool S,bool B,bool A,bool Y,b } u16 padExt = (((u16 *)MMU.ARM7_REG)[0x136>>1] & 0x0070) | - ((x) >> 7) | - ((y) >> 6) | - ((g) >> 4) | + ((input.buttons.X ? 0 : 0x80) >> 7) | + ((input.buttons.Y ? 0 : 0x80) >> 6) | + ((input.buttons.G ? 0 : 0x80) >> 4) | ((LidClosed) << 7) | 0x0034; @@ -2678,41 +2772,22 @@ void NDS_setPad(bool R,bool L,bool D,bool U,bool T,bool S,bool B,bool A,bool Y,b InputDisplayString=MakeInputDisplayString(padExt, pad); //put into the format we want for the movie system - //RLDUTSBAYXWEGF -#undef FIX -#define FIX(b) (b?1:0) + //fRLDUTSBAYXWEg + //we don't really need nds.pad anymore, but removing it would be a pain - r = FIX(R); - l = FIX(L); - d = FIX(D); - u = FIX(U); - t = FIX(T); - s = FIX(S); - b = FIX(B); - a = FIX(A); - y = FIX(Y); - x = FIX(X); - w = FIX(W); - e = FIX(E); - g = FIX(G); - f = FIX(F); - - if(f) movie_lid=true; - else movie_lid=false; - - nds.pad = - (FIX(r)<<12)| - (FIX(l)<<11)| - (FIX(d)<<10)| - (FIX(u)<<9)| - (FIX(s)<<8)| - (FIX(t)<<7)| - (FIX(b)<<6)| - (FIX(a)<<5)| - (FIX(y)<<4)| - (FIX(x)<<3)| - (FIX(w)<<2)| - (FIX(e)<<1); + nds.pad = + ((input.buttons.R ? 1 : 0) << 12)| + ((input.buttons.L ? 1 : 0) << 11)| + ((input.buttons.D ? 1 : 0) << 10)| + ((input.buttons.U ? 1 : 0) << 9)| + ((input.buttons.T ? 1 : 0) << 8)| + ((input.buttons.S ? 1 : 0) << 7)| + ((input.buttons.B ? 1 : 0) << 6)| + ((input.buttons.A ? 1 : 0) << 5)| + ((input.buttons.Y ? 1 : 0) << 4)| + ((input.buttons.X ? 1 : 0) << 3)| + ((input.buttons.W ? 1 : 0) << 2)| + ((input.buttons.E ? 1 : 0) << 1); // TODO: low power IRQ } diff --git a/desmume/src/NDSSystem.h b/desmume/src/NDSSystem.h index 3e1d90500..23309d765 100644 --- a/desmume/src/NDSSystem.h +++ b/desmume/src/NDSSystem.h @@ -39,62 +39,34 @@ #include "pathsettings.h" #endif -struct turbo { - bool Right; - bool Left; - bool Down; - bool Up; - bool Start; - bool Select; - bool B; - bool A; - bool Y; - bool X; - bool L; - bool R; - - bool &button(int i) { return ((bool*)this)[i]; } +template +struct buttonstruct { + union { + struct { + // changing the order of these fields would break stuff + //fRLDUTSBAYXWEg + Type G; // debug + Type E; // right shoulder + Type W; // left shoulder + Type X; + Type Y; + Type A; + Type B; + Type S; // start + Type T; // select + Type U; // up + Type D; // down + Type L; // left + Type R; // right + Type F; // lid + }; + Type array[14]; + }; }; -extern turbo Turbo; - -struct turbotime { - int Right; - int Left; - int Down; - int Up; - int Start; - int Select; - int B; - int A; - int Y; - int X; - int L; - int R; - - int &time(int i) { return ((int*)this)[i]; } -}; - -extern turbotime TurboTime; - -struct autohold { - bool Right; - bool Left; - bool Down; - bool Up; - bool Start; - bool Select; - bool B; - bool A; - bool Y; - bool X; - bool L; - bool R; - - bool &hold(int i) { return ((bool*)this)[i]; } -}; - -extern autohold AutoHold; +extern buttonstruct Turbo; +extern buttonstruct TurboTime; +extern buttonstruct AutoHold; int NDS_WritePNG(const char *fname); @@ -310,10 +282,60 @@ typedef struct TSCalInfo } TSCalInfo; extern GameInfo gameInfo; + + +struct UserButtons : buttonstruct +{ +}; +struct UserTouch +{ + u16 touchX; + u16 touchY; + bool isTouch; +}; +struct UserMicrophone +{ + BOOL micButtonPressed; +}; +struct UserInput +{ + UserButtons buttons; + UserTouch touch; + UserMicrophone mic; +}; + +// set physical user input +// these functions merely request the input to be changed. +// the actual change happens later at a specific time during the frame. +// this is to minimize the risk of desyncs. void NDS_setTouchPos(u16 x, u16 y); void NDS_releaseTouch(void); -void NDS_setPad(bool R,bool L,bool D,bool U,bool T,bool S,bool B,bool A,bool Y,bool X,bool W,bool E,bool G, bool F); -void NDS_setPadFromMovie(u16 pad); +void NDS_setPad(bool right,bool left,bool down,bool up,bool select,bool start,bool B,bool A,bool Y,bool X,bool leftShoulder,bool rightShoulder,bool debug, bool lid); +void NDS_setMic(bool pressed); + +// get physical user input +// not including the results of autofire/etc. +// the effects of calls to "set physical user input" functions will be immediately reflected here though. +const UserInput& NDS_getRawUserInput(); +const UserInput& NDS_getPrevRawUserInput(); + +// get final (fully processed) user input +// this should match whatever was or would be sent to the game +const UserInput& NDS_getFinalUserInput(); + +// set/get to-be-processed or in-the-middle-of-being-processed user input +// to process input, simply call this function and edit the return value. +// (applying autofire is one example of processing the input.) +// (movie playback is another example.) +// this must be done after the raw user input is set +// and before that input is sent to the game's memory. +UserInput& NDS_getProcessingUserInput(); +bool NDS_isProcessingUserInput(); +// call once per frame to prepare input for processing +void NDS_beginProcessingInput(); +// call once per frame to copy the processed input to the final input +void NDS_endProcessingInput(); + int NDS_LoadROM(const char *filename, const char* logicalFilename=0); void NDS_FreeROM(void); diff --git a/desmume/src/aggdraw.h b/desmume/src/aggdraw.h index 377a2e817..9052b057d 100644 --- a/desmume/src/aggdraw.h +++ b/desmume/src/aggdraw.h @@ -405,7 +405,10 @@ public: virtual void renderTextDropshadowed(double dstX, double dstY, const std::string& str) { AggColor lineColorOld = lineColor(); - lineColor(255-lineColorOld.r,255-lineColorOld.g,255-lineColorOld.b); + if(lineColorOld.r+lineColorOld.g+lineColorOld.b<192) + lineColor(255-lineColorOld.r,255-lineColorOld.g,255-lineColorOld.b); + else + lineColor(0,0,0); renderText(dstX-1,dstY-1,str); renderText(dstX,dstY-1,str); renderText(dstX+1,dstY-1,str); diff --git a/desmume/src/lua-engine.cpp b/desmume/src/lua-engine.cpp index 2468a99fd..3bf4442ec 100644 --- a/desmume/src/lua-engine.cpp +++ b/desmume/src/lua-engine.cpp @@ -1923,251 +1923,154 @@ DEFINE_LUA_FUNCTION(state_load, "location[,option]") //joypad lib static const char *button_mappings[] = { -"debug","r","l","x","y","a","b","start","select","up","down","left","right" +"debug","R","L","X","Y","A","B","start","select","up","down","left","right","lid" }; -// table joypad.read(int which = 1) -// -// Reads the joypads as inputted by the user. -static int joy_get(lua_State *L) { - - uint16 buttons = nds.pad; - - lua_newtable(L); - - int i; - for (i = 0; i < 16; i++) { - if (buttons & (1< pressed +// false -> unpressed +// nil -> no change +DEFINE_LUA_FUNCTION(joy_set, "buttonTable") { - unsigned short controllerNum; - unsigned short bit; - const char* name; -} -s_buttonDescs [] = -{ - {1, 0, "up"}, - {1, 1, "down"}, - {1, 2, "left"}, - {1, 3, "right"}, - {1, 4, "A"}, - {1, 5, "B"}, - {1, 6, "C"}, - {1, 7, "start"}, - {1, 32, "X"}, - {1, 33, "Y"}, - {1, 34, "Z"}, - {1, 35, "mode"}, - {2, 24, "up"}, - {2, 25, "down"}, - {2, 26, "left"}, - {2, 27, "right"}, - {2, 28, "A"}, - {2, 29, "B"}, - {2, 30, "C"}, - {2, 31, "start"}, - {2, 36, "X"}, - {2, 37, "Y"}, - {2, 38, "Z"}, - {2, 39, "mode"}, - {0x1B, 8, "up"}, - {0x1B, 9, "down"}, - {0x1B, 10, "left"}, - {0x1B, 11, "right"}, - {0x1B, 12, "A"}, - {0x1B, 13, "B"}, - {0x1B, 14, "C"}, - {0x1B, 15, "start"}, - {0x1C, 16, "up"}, - {0x1C, 17, "down"}, - {0x1C, 18, "left"}, - {0x1C, 19, "right"}, - {0x1C, 20, "A"}, - {0x1C, 21, "B"}, - {0x1C, 22, "C"}, - {0x1C, 23, "start"}, -}; + if(movieMode == MOVIEMODE_PLAY) // don't allow tampering with a playing movie's input + return 0; // (although it might be useful sometimes...) -int joy_getArgControllerNum(lua_State* L, int& index) -{ - int controllerNumber; - int type = lua_type(L,index); - if(type == LUA_TSTRING || type == LUA_TNUMBER) - { - controllerNumber = 0; - if(type == LUA_TSTRING) - { - const char* str = lua_tostring(L,index); - if(!stricmp(str, "1C")) - controllerNumber = 0x1C; - else if(!stricmp(str, "1B")) - controllerNumber = 0x1B; - else if(!stricmp(str, "1A")) - controllerNumber = 0x1A; - } - if(!controllerNumber) - controllerNumber = luaL_checkinteger(L,index); - index++; - } - else - { - // argument omitted; default to controller 1 - controllerNumber = 1; - } - - if(controllerNumber == 0x1A) - controllerNumber = 1; - if(controllerNumber != 1 && controllerNumber != 2 && controllerNumber != 0x1B && controllerNumber != 0x1C) - luaL_error(L, "controller number must be 1, 2, '1B', or '1C'"); - - return controllerNumber; -} - - -// joypad.set(controllerNum = 1, inputTable) -// controllerNum can be 1, 2, '1B', or '1C' -DEFINE_LUA_FUNCTION(joy_set, "[controller=1,]inputtable") -{ int index = 1; - int controllerNumber = joy_getArgControllerNum(L, index); - + (void)joy_getArgControllerNum(L, index); luaL_checktype(L, index, LUA_TTABLE); - int input = ~0; - int mask = 0; + UserButtons buttons = NDS_isProcessingUserInput() ? NDS_getProcessingUserInput().buttons : NDS_getRawUserInput().buttons; - for(int i = 0; i < sizeof(s_buttonDescs)/sizeof(*s_buttonDescs); i++) + for(int i = 0; i < sizeof(button_mappings)/sizeof(*button_mappings); i++) { - const ButtonDesc& bd = s_buttonDescs[i]; - if(bd.controllerNum == controllerNumber) + const char* name = button_mappings[i]; + lua_getfield(L, index, name); + if (!lua_isnil(L,-1)) { - lua_getfield(L, index, bd.name); - if (!lua_isnil(L,-1)) - { - bool pressed = lua_toboolean(L,-1) != 0; - int bitmask = ((long long)1 << bd.bit); - if(pressed) - input &= ~bitmask; - else - input |= bitmask; - mask |= bitmask; - } - lua_pop(L,1); + bool pressed = lua_toboolean(L,-1) != 0; + buttons.array[i] = pressed; } + lua_pop(L,1); } - SetNextInputCondensed(input, mask); + if(NDS_isProcessingUserInput()) + NDS_getProcessingUserInput().buttons = buttons; + else + NDS_setPad(buttons.R, buttons.L, buttons.D, buttons.U, buttons.T, buttons.S, buttons.B, buttons.A, buttons.Y, buttons.X, buttons.W, buttons.E, buttons.G, buttons.F); return 0; } -// joypad.get(controllerNum = 1) -// controllerNum can be 1, 2, '1B', or '1C' +// table joypad.read() +// +// Reads the joypad state (what the game sees) int joy_get_internal(lua_State* L, bool reportUp, bool reportDown) { int index = 1; - int controllerNumber = joy_getArgControllerNum(L, index); + (void)joy_getArgControllerNum(L, index); lua_newtable(L); - long long input = GetCurrentInputCondensed(); + const UserButtons& buttons = NDS_getFinalUserInput().buttons; - for(int i = 0; i < sizeof(s_buttonDescs)/sizeof(*s_buttonDescs); i++) + for(int i = 0; i < sizeof(button_mappings)/sizeof(*button_mappings); i++) { - const ButtonDesc& bd = s_buttonDescs[i]; - if(bd.controllerNum == controllerNumber) + const char* name = button_mappings[i]; + bool pressed = buttons.array[i]; + if((pressed && reportDown) || (!pressed && reportUp)) { - bool pressed = (input & ((long long)1<> 4); + lua_setfield(L, -2, "x"); + lua_pushinteger(L, NDS_getRawUserInput().touch.touchY >> 4); + lua_setfield(L, -2, "y"); + lua_pushinteger(L, NDS_getRawUserInput().touch.isTouch?1:0); + lua_setfield(L, -2, "touch"); + + return 1; +} static const struct luaL_reg styluslib [] = { {"read", stylus_read}, + {"peek", stylus_peek}, {NULL, NULL} }; @@ -3560,17 +3477,17 @@ static const struct luaL_reg memorylib [] = static const struct luaL_reg joylib [] = { {"get", joy_get}, -// {"getdown", joy_getdown}, -// {"getup", joy_getup}, -// {"peek", joy_peek}, -// {"peekdown", joy_peekdown}, -// {"peekup", joy_peekup}, -// {"set", joy_set}, + {"getdown", joy_getdown}, + {"getup", joy_getup}, + {"peek", joy_peek}, + {"peekdown", joy_peekdown}, + {"peekup", joy_peekup}, + {"set", joy_set}, // alternative names {"read", joy_get}, -// {"write", joy_set}, -// {"readdown", joy_getdown}, -// {"readup", joy_getup}, + {"write", joy_set}, + {"readdown", joy_getdown}, + {"readup", joy_getup}, {NULL, NULL} }; static const struct luaL_reg inputlib [] = diff --git a/desmume/src/mic.h b/desmume/src/mic.h index 4bbdea231..c5cf1a87d 100644 --- a/desmume/src/mic.h +++ b/desmume/src/mic.h @@ -21,8 +21,6 @@ #ifndef MIC_H #define MIC_H -extern int MicButtonPressed; - #ifdef WIN32 static char MicSampleName[256]; bool LoadSample(const char *name); @@ -39,4 +37,7 @@ void Mic_Reset(); void Mic_DeInit(); u8 Mic_ReadSample(); +void mic_savestate(std::ostream* os); +bool mic_loadstate(std::istream* is, int size); + #endif diff --git a/desmume/src/movie.cpp b/desmume/src/movie.cpp index c728a04ea..a9948030c 100644 --- a/desmume/src/movie.cpp +++ b/desmume/src/movie.cpp @@ -60,7 +60,6 @@ char curMovieFilename[512] = {0}; MovieData currMovieData; int currRerecordCount; bool movie_reset_command = false; -bool movie_lid = false; //-------------- @@ -434,10 +433,23 @@ void _CDECL_ FCEUI_LoadMovie(const char *fname, bool _read_only, bool tasedit, i //LoadFM2(currMovieData, fp->stream, INT_MAX, false); - - fstream fs (fname); - LoadFM2(currMovieData, &fs, INT_MAX, false); - fs.close(); + bool opened = false; + { + fstream fs (fname); + if(fs.is_open()) + { + LoadFM2(currMovieData, &fs, INT_MAX, false); + opened = true; + } + fs.close(); + } + if(!opened) + { + // for some reason fs.open doesn't work, it has to be a whole new fstream object + fstream fs (fname, std::ios_base::in); + LoadFM2(currMovieData, &fs, INT_MAX, false); + fs.close(); + } //TODO //fully reload the game to reinitialize everything before playing any movie @@ -589,33 +601,17 @@ void _CDECL_ FCEUI_SaveMovie(const char *fname, std::wstring author, int flag, s driver->USR_InfoMessage("Movie recording started."); } - void NDS_setTouchFromMovie(void) { - - if(movieMode == MOVIEMODE_PLAY) - { - - MovieRecord* mr = &currMovieData.records[currFrameCounter]; - nds.touchX=mr->touch.x << 4; - nds.touchY=mr->touch.y << 4; - - if(mr->touch.touch) { - nds.isTouch=mr->touch.touch; - MMU.ARM7_REG[0x136] &= 0xBF; - } - else { - nds.touchX=0; - nds.touchY=0; - nds.isTouch=0; - - MMU.ARM7_REG[0x136] |= 0x40; - } - //osd->addFixed(mr->touch.x, mr->touch.y, "%s", "X"); - } - } //the main interaction point between the emulator and the movie system. - //either dumps the current joystick state or loads one state from the movie + //either dumps the current joystick state or loads one state from the movie. + //deprecated, should use the two functions it has been split into directly void FCEUMOV_AddInputState() + { + FCEUMOV_HandlePlayback(); + FCEUMOV_HandleRecording(); + } + + void FCEUMOV_HandlePlayback() { if(movieMode == MOVIEMODE_PLAY) { @@ -626,18 +622,36 @@ void _CDECL_ FCEUI_SaveMovie(const char *fname, std::wstring author, int flag, s } else { + UserInput& input = NDS_getProcessingUserInput(); + MovieRecord* mr = &currMovieData.records[currFrameCounter]; - if(mr->command_microphone()) MicButtonPressed=1; - else MicButtonPressed=0; + if(mr->command_microphone()) input.mic.micButtonPressed = 1; + else input.mic.micButtonPressed = 0; if(mr->command_reset()) NDS_Reset(); - if(mr->command_lid()) movie_lid = true; - else movie_lid = false; + if(mr->command_lid()) input.buttons.F = true; + else input.buttons.F = false; - NDS_setPadFromMovie(mr->pad); - NDS_setTouchFromMovie(); + u16 pad = mr->pad; + input.buttons.R = (((pad>>12)&1)!=0); + input.buttons.L = (((pad>>11)&1)!=0); + input.buttons.D = (((pad>>10)&1)!=0); + input.buttons.U = (((pad>>9)&1)!=0); + input.buttons.T = (((pad>>8)&1)!=0); + input.buttons.S = (((pad>>7)&1)!=0); + input.buttons.B = (((pad>>6)&1)!=0); + input.buttons.A = (((pad>>5)&1)!=0); + input.buttons.Y = (((pad>>4)&1)!=0); + input.buttons.X = (((pad>>3)&1)!=0); + input.buttons.W = (((pad>>2)&1)!=0); + input.buttons.E = (((pad>>1)&1)!=0); + input.buttons.G = (((pad>>0)&1)!=0); + + input.touch.touchX = mr->touch.x << 4; + input.touch.touchY = mr->touch.y << 4; + input.touch.isTouch = mr->touch.touch; } //if we are on the last frame, then pause the emulator if the player requested it @@ -655,43 +669,57 @@ void _CDECL_ FCEUI_SaveMovie(const char *fname, std::wstring author, int flag, s // FCEUI_ToggleEmulationPause(); // FCEU_DispMessage("Paused at specified movie frame"); //} - osd->addFixed(180, 176, "%s", "Playback"); + + // it's apparently un-threadsafe to do this here + // (causes crazy flickering in other OSD elements, at least) + // and it's also pretty annoying, + // and the framecounter display already conveys this info as well. + // so, I'm disabling this, at least for now. +// osd->addFixed(180, 176, "%s", "Playback"); } - else if(movieMode == MOVIEMODE_RECORD) + } + + void FCEUMOV_HandleRecording() + { + if(movieMode == MOVIEMODE_RECORD) { + const UserInput& input = NDS_getFinalUserInput(); + MovieRecord mr; mr.commands = 0; - if(MicButtonPressed == 1) - mr.commands=1; + if(input.mic.micButtonPressed == 1) + mr.commands = MOVIECMD_MIC; mr.pad = nds.pad; - if(movie_lid) { - mr.commands=4; - movie_lid = false; - } + if(input.buttons.F) + mr.commands = MOVIECMD_LID; if(movie_reset_command) { - mr.commands=2; + mr.commands = MOVIECMD_RESET; movie_reset_command = false; } - if(nds.isTouch) { - mr.touch.x = nds.touchX >> 4; - mr.touch.y = nds.touchY >> 4; - mr.touch.touch = 1; - } else { - mr.touch.x = 0; - mr.touch.y = 0; - mr.touch.touch = 0; - } + mr.touch.touch = input.touch.isTouch ? 1 : 0; + mr.touch.x = input.touch.isTouch ? input.touch.touchX >> 4 : 0; + mr.touch.y = input.touch.isTouch ? input.touch.touchY >> 4 : 0; + + assert(mr.touch.touch || (!mr.touch.x && !mr.touch.y)); + assert(nds.touchX == input.touch.touchX && nds.touchY == input.touch.touchY); + assert((mr.touch.x << 4) == nds.touchX && (mr.touch.y << 4) == nds.touchY); mr.dump(&currMovieData, osRecordingMovie,currMovieData.records.size()); currMovieData.records.push_back(mr); - osd->addFixed(180, 176, "%s", "Recording"); + + // it's apparently un-threadsafe to do this here + // (causes crazy flickering in other OSD elements, at least) + // and it's also pretty annoying, + // and the framecounter display already conveys this info as well. + // so, I'm disabling this, at least for now. +// osd->addFixed(180, 176, "%s", "Recording"); } /*extern uint8 joy[4]; @@ -859,6 +887,12 @@ bool mov_loadstate(std::istream* is, int size) currMovieData.rerecordCount = currRerecordCount; openRecordingMovie(curMovieFilename); + if(!osRecordingMovie->is_open()) + { + osd->setLineColor(255, 0, 0); + osd->addLine("Can't save movie file!"); + } + //printf("DUMPING MOVIE: %d FRAMES\n",currMovieData.records.size()); currMovieData.dump(osRecordingMovie, false); movieMode = MOVIEMODE_RECORD; diff --git a/desmume/src/movie.h b/desmume/src/movie.h index 432a6226b..6b45db44b 100644 --- a/desmume/src/movie.h +++ b/desmume/src/movie.h @@ -34,7 +34,9 @@ enum EMOVIEMODE enum EMOVIECMD { + MOVIECMD_MIC = 1, MOVIECMD_RESET = 2, + MOVIECMD_LID = 4, }; //RLDUTSBAYXWEG @@ -60,8 +62,8 @@ public: //the disk format will support up to 64bit if necessary uint8 commands; bool command_reset() { return (commands&MOVIECMD_RESET)!=0; } - bool command_microphone() { return (commands&1)!=0; } - bool command_lid() { return (commands&4)!=0; } + bool command_microphone() { return (commands&MOVIECMD_MIC)!=0; } + bool command_lid() { return (commands&MOVIECMD_LID)!=0; } void toggleBit(int bit) { @@ -189,20 +191,19 @@ extern EMOVIEMODE movieMode; //adelikat: main needs this for frame counter disp extern MovieData currMovieData; //adelikat: main needs this for frame counter display extern bool movie_reset_command; -extern bool movie_lid; bool FCEUI_MovieGetInfo(std::istream* fp, MOVIE_INFO& info, bool skipFrameCount); void _CDECL_ FCEUI_SaveMovie(const char *fname, std::wstring author, int flag, std::string sramfname); void _CDECL_ FCEUI_LoadMovie(const char *fname, bool _read_only, bool tasedit, int _pauseframe); void FCEUI_StopMovie(); void FCEUMOV_AddInputState(); -void NDS_setTouchFromMovie(void); +void FCEUMOV_HandlePlayback(); +void FCEUMOV_HandleRecording(); void mov_savestate(std::ostream* os); bool mov_loadstate(std::istream* is, int size); void LoadFM2_binarychunk(MovieData& movieData, std::istream* fp, int size); bool LoadFM2(MovieData& movieData, std::istream* fp, int size, bool stopAfterHeader); extern bool movie_readonly; extern bool ShowInputDisplay; -extern int MicButtonPressed; void FCEUI_MakeBackupMovie(bool dispMessage); #endif diff --git a/desmume/src/rasterize.cpp b/desmume/src/rasterize.cpp index 2fda13f04..7154f9551 100644 --- a/desmume/src/rasterize.cpp +++ b/desmume/src/rasterize.cpp @@ -564,16 +564,16 @@ static FORCEINLINE void pixel(int adr,float r, float g, float b, float invu, flo FragmentColor shaderOutput; shader.shade(shaderOutput); - //alpha test (don't have any test cases for this...? is it in the right place...?) - if(gfx3d.enableAlphaTest) - { - if(shaderOutput.a < gfx3d.alphaTestRef) - goto rejected_fragment; - } - //we shouldnt do any of this if we generated a totally transparent pixel if(shaderOutput.a != 0) { + //alpha test (don't have any test cases for this...? is it in the right place...?) + if(gfx3d.enableAlphaTest) + { + if(shaderOutput.a < gfx3d.alphaTestRef) + goto rejected_fragment; + } + //handle polyids bool isOpaquePixel = shaderOutput.a == 31; if(isOpaquePixel) @@ -970,12 +970,13 @@ static void SoftRastVramReconfigureSignal() { static void SoftRastFramebufferProcess() { - // lack of edge marking was bugging me so I took a stab at - // making it more accurate so it could be reenabled. - // the best test case I know of for this feature is Sonic Rush: + // this looks ok although it's still pretty much a hack, + // it needs to be redone with low-level accuracy at some point, + // but that should probably wait until the shape renderer is more accurate. + // a good test case for edge marking is Sonic Rush: // - the edges are completely sharp/opaque on the very brief title screen intro, // - the level-start intro gets a pseudo-antialiasing effect around the silhouette, - // - the character edges in-level are clearly smoothed/transparent, but show well through shield powerups + // - the character edges in-level are clearly transparent, and also show well through shield powerups. if(gfx3d.enableEdgeMarking) { //TODO - need to test and find out whether these get grabbed at flush time, or at render time @@ -1003,7 +1004,7 @@ static void SoftRastFramebufferProcess() // > is used instead of != to prevent double edges // between overlapping polys of different IDs. - // also note that the edge generally goes on the outside, not the inside, + // also note that the edge generally goes on the outside, not the inside, (maybe needs to change later) // and that polys with the same edge color can make edges against each other. FragmentColor edgeColor = edgeMarkColors[self>>3]; diff --git a/desmume/src/saves.cpp b/desmume/src/saves.cpp index 52d47926d..aaff69698 100644 --- a/desmume/src/saves.cpp +++ b/desmume/src/saves.cpp @@ -40,6 +40,7 @@ #include "readwrite.h" #include "gfx3d.h" #include "movie.h" +#include "mic.h" #include "path.h" @@ -594,7 +595,7 @@ void savestate_slot(int num) else { osd->setLineColor(255, 0, 0); - osd->addLine("Error save to %i slot", num); + osd->addLine("Error saving %i slot", num); return; } @@ -627,7 +628,7 @@ void loadstate_slot(int num) else { osd->setLineColor(255, 0, 0); - osd->addLine("Error from load %i slot", num); + osd->addLine("Error loading %i slot", num); } } @@ -995,6 +996,7 @@ static void writechunks(std::ostream* os) { savestate_WriteChunk(os,61,mmu_savestate); savestate_WriteChunk(os,7,gpu_savestate); savestate_WriteChunk(os,8,spu_savestate); + savestate_WriteChunk(os,81,mic_savestate); savestate_WriteChunk(os,90,SF_GFX3D); savestate_WriteChunk(os,91,gfx3d_savestate); savestate_WriteChunk(os,100,SF_MOVIE); @@ -1026,6 +1028,7 @@ static bool ReadStateChunks(std::istream* is, s32 totalsize) case 61: if(!mmu_loadstate(is,size)) ret=false; break; case 7: if(!gpu_loadstate(is,size)) ret=false; break; case 8: if(!spu_loadstate(is,size)) ret=false; break; + case 81: if(!mic_loadstate(is,size)) ret=false; break; case 90: if(!ReadStateChunk(is,SF_GFX3D,size)) ret=false; break; case 91: if(!gfx3d_loadstate(is,size)) ret=false; break; case 100: if(!ReadStateChunk(is,SF_MOVIE, size)) ret=false; break; diff --git a/desmume/src/windows/CWindow.cpp b/desmume/src/windows/CWindow.cpp index 0874a7f62..000160fd6 100644 --- a/desmume/src/windows/CWindow.cpp +++ b/desmume/src/windows/CWindow.cpp @@ -192,6 +192,18 @@ void WINCLASS::sizingMsg(WPARAM wParam, LPARAM lParam, LONG keepRatio) rect->top -= rect->bottom - prevBottom; rect->bottom = prevBottom; } + + // windows screws up the window size if the top of the window goes too high above the top of the screen + if(keepRatio & KEEPY) + { + int titleBarHeight = GetSystemMetrics(SM_CYSIZE); + int topExceeded = -(titleBarHeight + rect->top); + if(topExceeded > 0) + { + rect->top += topExceeded; + rect->bottom += topExceeded; + } + } } void WINCLASS::setClientSize(int width, int height) diff --git a/desmume/src/windows/hotkey.cpp b/desmume/src/windows/hotkey.cpp index c20ea5d8d..00cdb49fc 100644 --- a/desmume/src/windows/hotkey.cpp +++ b/desmume/src/windows/hotkey.cpp @@ -182,8 +182,8 @@ void HK_StateQuickLoadSlot(int) HK_StateLoadSlot(lastSaveState); } -void HK_MicrophoneKeyDown(int) {MicButtonPressed =1;} -void HK_MicrophoneKeyUp(int) {MicButtonPressed =0;} +void HK_MicrophoneKeyDown(int) { NDS_setMic(1); } +void HK_MicrophoneKeyUp(int) { NDS_setMic(0); } void HK_AutoHoldClearKeyDown(int) { @@ -247,23 +247,23 @@ void HK_StopMovie(int) void HK_AutoHoldKeyDown(int) {AutoHoldPressed = true;} void HK_AutoHoldKeyUp(int) {AutoHoldPressed = false;} -void HK_TurboRightKeyDown(int) { Turbo.Right = true; } -void HK_TurboRightKeyUp(int) { Turbo.Right = false; } +void HK_TurboRightKeyDown(int) { Turbo.R = true; } +void HK_TurboRightKeyUp(int) { Turbo.R = false; } -void HK_TurboLeftKeyDown(int) { Turbo.Left = true; } -void HK_TurboLeftKeyUp(int) { Turbo.Left = false; } +void HK_TurboLeftKeyDown(int) { Turbo.L = true; } +void HK_TurboLeftKeyUp(int) { Turbo.L = false; } -void HK_TurboRKeyDown(int) { Turbo.R = true; } -void HK_TurboRKeyUp(int) { Turbo.R = false; } +void HK_TurboRKeyDown(int) { Turbo.E = true; } +void HK_TurboRKeyUp(int) { Turbo.E = false; } -void HK_TurboLKeyDown(int) { Turbo.L = true; } -void HK_TurboLKeyUp(int) { Turbo.L = false; } +void HK_TurboLKeyDown(int) { Turbo.W = true; } +void HK_TurboLKeyUp(int) { Turbo.W = false; } -void HK_TurboDownKeyDown(int) { Turbo.Down = true; } -void HK_TurboDownKeyUp(int) { Turbo.Down = false; } +void HK_TurboDownKeyDown(int) { Turbo.D = true; } +void HK_TurboDownKeyUp(int) { Turbo.D = false; } -void HK_TurboUpKeyDown(int) { Turbo.Up = true; } -void HK_TurboUpKeyUp(int) { Turbo.Up = false; } +void HK_TurboUpKeyDown(int) { Turbo.U = true; } +void HK_TurboUpKeyUp(int) { Turbo.U = false; } void HK_TurboBKeyDown(int) { Turbo.B = true; } void HK_TurboBKeyUp(int) { Turbo.B = false; } @@ -277,11 +277,11 @@ void HK_TurboXKeyUp(int) { Turbo.X = false; } void HK_TurboYKeyDown(int) { Turbo.Y = true; } void HK_TurboYKeyUp(int) { Turbo.Y = false; } -void HK_TurboStartKeyDown(int) { Turbo.Start = true; } -void HK_TurboStartKeyUp(int) { Turbo.Start = false; } +void HK_TurboStartKeyDown(int) { Turbo.S = true; } +void HK_TurboStartKeyUp(int) { Turbo.S = false; } -void HK_TurboSelectKeyDown(int) { Turbo.Select = true; } -void HK_TurboSelectKeyUp(int) { Turbo.Select = false; } +void HK_TurboSelectKeyDown(int) { Turbo.T = true; } +void HK_TurboSelectKeyUp(int) { Turbo.T = false; } void HK_NextSaveSlot(int) { lastSaveState++; diff --git a/desmume/src/windows/inputdx.cpp b/desmume/src/windows/inputdx.cpp index 3bf380d6b..a44e18726 100644 --- a/desmume/src/windows/inputdx.cpp +++ b/desmume/src/windows/inputdx.cpp @@ -246,6 +246,8 @@ SGuitar DefaultGuitar = { false, 'E', 'R', 'T', 'Y' }; SGuitar Guitar; u8 guitarState = 0; +bool allowUpAndDown = false; + extern volatile bool paused; #define MAXKEYPAD 15 @@ -412,6 +414,8 @@ static void LoadInputConfig() DO(A); DO(B); DO(X); DO(Y); DO(L); DO(R); #undef DO + + allowUpAndDown = GetPrivateProfileInt("Controls","AllowUpAndDown",0,IniName) != 0; } static void WriteControl(char* name, WORD val) @@ -429,6 +433,8 @@ static void SaveInputConfig() DO(A); DO(B); DO(X); DO(Y); DO(L); DO(R); #undef DO + + WritePrivateProfileInt("Controls","AllowUpAndDown",allowUpAndDown?1:0,IniName); } BOOL di_init() @@ -1879,10 +1885,10 @@ void EnableDisableKeyFields (int index, HWND hDlg) enableUnTurboable = false; } - EnableWindow(GetDlgItem(hDlg,IDC_UPLEFT), false); - EnableWindow(GetDlgItem(hDlg,IDC_UPRIGHT), false); - EnableWindow(GetDlgItem(hDlg,IDC_DWNRIGHT), false); - EnableWindow(GetDlgItem(hDlg,IDC_DWNLEFT), false); + //EnableWindow(GetDlgItem(hDlg,IDC_UPLEFT), false); + //EnableWindow(GetDlgItem(hDlg,IDC_UPRIGHT), false); + //EnableWindow(GetDlgItem(hDlg,IDC_DWNRIGHT), false); + //EnableWindow(GetDlgItem(hDlg,IDC_DWNLEFT), false); EnableWindow(GetDlgItem(hDlg,IDC_DEBUG), false); EnableWindow(GetDlgItem(hDlg,IDC_LID), true); } @@ -1954,7 +1960,8 @@ switch(msg) //SendDlgItemMessage(hDlg,IDC_JPCOMBO,CB_SETCURSEL,(WPARAM)0,0); //SendDlgItemMessage(hDlg,IDC_JPTOGGLE,BM_SETCHECK, Joypad[index].Enabled ? (WPARAM)BST_CHECKED : (WPARAM)BST_UNCHECKED, 0); - //SendDlgItemMessage(hDlg,IDC_ALLOWLEFTRIGHT,BM_SETCHECK, Settings.UpAndDown ? (WPARAM)BST_CHECKED : (WPARAM)BST_UNCHECKED, 0); + + SendDlgItemMessage(hDlg,IDC_ALLOWLEFTRIGHT,BM_SETCHECK, allowUpAndDown ? (WPARAM)BST_CHECKED : (WPARAM)BST_UNCHECKED, 0); set_buttoninfo(index,hDlg); @@ -2068,7 +2075,7 @@ switch(msg) break; case IDOK: - //Settings.UpAndDown = IsDlgButtonChecked(hDlg, IDC_ALLOWLEFTRIGHT); + allowUpAndDown = IsDlgButtonChecked(hDlg, IDC_ALLOWLEFTRIGHT) != 0; SaveInputConfig(); EndDialog(hDlg,0); break; @@ -2181,158 +2188,189 @@ void S9xWinScanJoypads () for (int J = 0; J < 8; J++) { if (Joypad [J].Enabled) - { - // toggle checks - { - PadState = 0; - PadState |= ToggleJoypadStorage[J].Left||TurboToggleJoypadStorage[J].Left ? LEFT_MASK : 0; - PadState |= ToggleJoypadStorage[J].Right||TurboToggleJoypadStorage[J].Right ? RIGHT_MASK : 0; - PadState |= ToggleJoypadStorage[J].Up||TurboToggleJoypadStorage[J].Up ? UP_MASK : 0; - PadState |= ToggleJoypadStorage[J].Down||TurboToggleJoypadStorage[J].Down ? DOWN_MASK : 0; - PadState |= ToggleJoypadStorage[J].Start||TurboToggleJoypadStorage[J].Start ? START_MASK : 0; - PadState |= ToggleJoypadStorage[J].Select||TurboToggleJoypadStorage[J].Select ? SELECT_MASK : 0; - PadState |= ToggleJoypadStorage[J].Lid||TurboToggleJoypadStorage[J].Lid ? LID_MASK : 0; - PadState |= ToggleJoypadStorage[J].Debug||TurboToggleJoypadStorage[J].Debug ? DEBUG_MASK : 0; - PadState |= ToggleJoypadStorage[J].A||TurboToggleJoypadStorage[J].A ? A_MASK : 0; - PadState |= ToggleJoypadStorage[J].B||TurboToggleJoypadStorage[J].B ? B_MASK : 0; - PadState |= ToggleJoypadStorage[J].X||TurboToggleJoypadStorage[J].X ? X_MASK : 0; - PadState |= ToggleJoypadStorage[J].Y||TurboToggleJoypadStorage[J].Y ? Y_MASK : 0; - PadState |= ToggleJoypadStorage[J].L||TurboToggleJoypadStorage[J].L ? L_MASK : 0; - PadState |= ToggleJoypadStorage[J].R||TurboToggleJoypadStorage[J].R ? R_MASK : 0; - } - // auto-hold AND regular key/joystick presses - if(S9xGetState(Joypad[J+8].Left)) - { - PadState ^= (!S9xGetState(Joypad[J].R)||!S9xGetState(Joypad[J+8].R)) ? R_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].L)||!S9xGetState(Joypad[J+8].L)) ? L_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].X)||!S9xGetState(Joypad[J+8].X)) ? X_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].A)||!S9xGetState(Joypad[J+8].A)) ? A_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Right)) ? RIGHT_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Right_Up)) ? RIGHT_MASK + UP_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Right_Down)) ? RIGHT_MASK + DOWN_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Left)) ? LEFT_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Left_Up)) ? LEFT_MASK + UP_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Left_Down)) ? LEFT_MASK + DOWN_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Down)) ? DOWN_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Up)) ? UP_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Start)||!S9xGetState(Joypad[J+8].Start)) ? START_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Select)||!S9xGetState(Joypad[J+8].Select)) ? SELECT_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Y)||!S9xGetState(Joypad[J+8].Y)) ? Y_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].B)||!S9xGetState(Joypad[J+8].B)) ? B_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Lid)||!S9xGetState(Joypad[J+8].Lid)) ? LID_MASK : 0; - PadState ^= (!S9xGetState(Joypad[J].Debug)||!S9xGetState(Joypad[J+8].Debug)) ? DEBUG_MASK : 0; - } - - bool turbofy = !S9xGetState(Joypad[J+8].Up); // All Mod for turbo - - u32 TurboMask = 0; - - //handle turbo case! (autofire / auto-fire) - if(turbofy || ((TurboMask&A_MASK))&&(PadState&A_MASK) || !S9xGetState(Joypad[J+8].A )) PadState^=(joypads[J]&A_MASK); - if(turbofy || ((TurboMask&B_MASK))&&(PadState&B_MASK) || !S9xGetState(Joypad[J+8].B )) PadState^=(joypads[J]&B_MASK); - if(turbofy || ((TurboMask&Y_MASK))&&(PadState&Y_MASK) || !S9xGetState(Joypad[J+8].Y )) PadState^=(joypads[J]&Y_MASK); - if(turbofy || ((TurboMask&X_MASK))&&(PadState&X_MASK) || !S9xGetState(Joypad[J+8].X )) PadState^=(joypads[J]&X_MASK); - if(turbofy || ((TurboMask&L_MASK))&&(PadState&L_MASK) || !S9xGetState(Joypad[J+8].L )) PadState^=(joypads[J]&L_MASK); - if(turbofy || ((TurboMask&R_MASK))&&(PadState&R_MASK) || !S9xGetState(Joypad[J+8].R )) PadState^=(joypads[J]&R_MASK); - if(turbofy || ((TurboMask&START_MASK))&&(PadState&START_MASK) || !S9xGetState(Joypad[J+8].Start )) PadState^=(joypads[J]&START_MASK); - if(turbofy || ((TurboMask&SELECT_MASK))&&(PadState&SELECT_MASK) || !S9xGetState(Joypad[J+8].Select)) PadState^=(joypads[J]&SELECT_MASK); - if(turbofy || ((TurboMask&DEBUG_MASK))&&(PadState&DEBUG_MASK) || !S9xGetState(Joypad[J+8].Debug)) PadState^=(joypads[J]&DEBUG_MASK); - if( ((TurboMask&LEFT_MASK))&&(PadState&LEFT_MASK) ) PadState^=(joypads[J]&LEFT_MASK); - if( ((TurboMask&UP_MASK))&&(PadState&UP_MASK) ) PadState^=(joypads[J]&UP_MASK); - if( ((TurboMask&RIGHT_MASK))&&(PadState&RIGHT_MASK) ) PadState^=(joypads[J]&RIGHT_MASK); - if( ((TurboMask&DOWN_MASK))&&(PadState&DOWN_MASK) ) PadState^=(joypads[J]&DOWN_MASK); - if( ((TurboMask&LID_MASK))&&(PadState&LID_MASK) ) PadState^=(joypads[J]&LID_MASK); - - if(TurboToggleJoypadStorage[J].A ) PadState^=(joypads[J]&A_MASK); - if(TurboToggleJoypadStorage[J].B ) PadState^=(joypads[J]&B_MASK); - if(TurboToggleJoypadStorage[J].Y ) PadState^=(joypads[J]&Y_MASK); - if(TurboToggleJoypadStorage[J].X ) PadState^=(joypads[J]&X_MASK); - if(TurboToggleJoypadStorage[J].L ) PadState^=(joypads[J]&L_MASK); - if(TurboToggleJoypadStorage[J].R ) PadState^=(joypads[J]&R_MASK); - if(TurboToggleJoypadStorage[J].Start ) PadState^=(joypads[J]&START_MASK); - if(TurboToggleJoypadStorage[J].Select) PadState^=(joypads[J]&SELECT_MASK); - if(TurboToggleJoypadStorage[J].Left ) PadState^=(joypads[J]&LEFT_MASK); - if(TurboToggleJoypadStorage[J].Up ) PadState^=(joypads[J]&UP_MASK); - if(TurboToggleJoypadStorage[J].Right ) PadState^=(joypads[J]&RIGHT_MASK); - if(TurboToggleJoypadStorage[J].Down ) PadState^=(joypads[J]&DOWN_MASK); - if(TurboToggleJoypadStorage[J].Lid ) PadState^=(joypads[J]&LID_MASK); - if(TurboToggleJoypadStorage[J].Debug ) PadState^=(joypads[J]&DEBUG_MASK); - //end turbo case... - - - // enforce left+right/up+down disallowance here to - // avoid recording unused l+r/u+d that will cause desyncs - // when played back with l+r/u+d is allowed - //if(!Settings.UpAndDown) - //{ - // if((PadState[1] & 2) != 0) - // PadState[1] &= ~(1); - // if((PadState[1] & 8) != 0) - // PadState[1] &= ~(4); - //} - + { + int PadState = 0; + PadState |= (!S9xGetState(Joypad[J].R)) ? R_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].L)) ? L_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].X)) ? X_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].A)) ? A_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Right)) ? RIGHT_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Right_Up)) ? RIGHT_MASK|UP_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Right_Down)) ? RIGHT_MASK|DOWN_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Left)) ? LEFT_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Left_Up)) ? LEFT_MASK|UP_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Left_Down)) ? LEFT_MASK|DOWN_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Down)) ? DOWN_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Up)) ? UP_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Start)) ? START_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Select)) ? SELECT_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Y)) ? Y_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].B)) ? B_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Lid)) ? LID_MASK : 0; + PadState |= (!S9xGetState(Joypad[J].Debug)) ? DEBUG_MASK : 0; joypads [J] = PadState | 0x80000000; - } - else - joypads [J] = 0; - } - - // input from macro - //for (int J = 0; J < 8; J++) - //{ - // if(MacroIsEnabled(J)) - // { - // uint16 userPadState = joypads[J] & 0xFFFF; - // uint16 macroPadState = MacroInput(J); - // uint16 newPadState; - - // switch(GUI.MacroInputMode) - // { - // case MACRO_INPUT_MOV: - // newPadState = macroPadState; - // break; - // case MACRO_INPUT_OR: - // newPadState = macroPadState | userPadState; - // break; - // case MACRO_INPUT_XOR: - // newPadState = macroPadState ^ userPadState; - // break; - // default: - // newPadState = userPadState; - // break; - // } - - // PadState[0] = (uint8) ( newPadState & 0xFF); - // PadState[1] = (uint8) ((newPadState >> 8) & 0xFF); - - // // enforce left+right/up+down disallowance here to - // // avoid recording unused l+r/u+d that will cause desyncs - // // when played back with l+r/u+d is allowed - // if(!Settings.UpAndDown) - // { - // if((PadState[1] & 2) != 0) - // PadState[1] &= ~(1); - // if((PadState[1] & 8) != 0) - // PadState[1] &= ~(4); - // } - - // joypads [J] = PadState [0] | (PadState [1] << 8) | 0x80000000; - // } - //} - -//#ifdef NETPLAY_SUPPORT -// if (Settings.NetPlay) -// { -// // Send joypad position update to server -// S9xNPSendJoypadUpdate (joypads [GUI.NetplayUseJoypad1 ? 0 : NetPlay.Player-1]); -// -// // set input from network -// for (int J = 0; J < NP_MAX_CLIENTS; J++) -// joypads[J] = S9xNPGetJoypad (J); -// } -//#endif + } + } } +//void S9xOldAutofireAndStuff () +//{ +// // stuff ripped out of Snes9x that's no longer functional, at least for now +// for (int J = 0; J < 8; J++) +// { +// if (Joypad [J].Enabled) +// { +// // toggle checks +// { +// PadState = 0; +// PadState |= ToggleJoypadStorage[J].Left||TurboToggleJoypadStorage[J].Left ? LEFT_MASK : 0; +// PadState |= ToggleJoypadStorage[J].Right||TurboToggleJoypadStorage[J].Right ? RIGHT_MASK : 0; +// PadState |= ToggleJoypadStorage[J].Up||TurboToggleJoypadStorage[J].Up ? UP_MASK : 0; +// PadState |= ToggleJoypadStorage[J].Down||TurboToggleJoypadStorage[J].Down ? DOWN_MASK : 0; +// PadState |= ToggleJoypadStorage[J].Start||TurboToggleJoypadStorage[J].Start ? START_MASK : 0; +// PadState |= ToggleJoypadStorage[J].Select||TurboToggleJoypadStorage[J].Select ? SELECT_MASK : 0; +// PadState |= ToggleJoypadStorage[J].Lid||TurboToggleJoypadStorage[J].Lid ? LID_MASK : 0; +// PadState |= ToggleJoypadStorage[J].Debug||TurboToggleJoypadStorage[J].Debug ? DEBUG_MASK : 0; +// PadState |= ToggleJoypadStorage[J].A||TurboToggleJoypadStorage[J].A ? A_MASK : 0; +// PadState |= ToggleJoypadStorage[J].B||TurboToggleJoypadStorage[J].B ? B_MASK : 0; +// PadState |= ToggleJoypadStorage[J].X||TurboToggleJoypadStorage[J].X ? X_MASK : 0; +// PadState |= ToggleJoypadStorage[J].Y||TurboToggleJoypadStorage[J].Y ? Y_MASK : 0; +// PadState |= ToggleJoypadStorage[J].L||TurboToggleJoypadStorage[J].L ? L_MASK : 0; +// PadState |= ToggleJoypadStorage[J].R||TurboToggleJoypadStorage[J].R ? R_MASK : 0; +// } +// // auto-hold AND regular key/joystick presses +// if(S9xGetState(Joypad[J+8].Left)) +// { +// PadState ^= (!S9xGetState(Joypad[J].R)||!S9xGetState(Joypad[J+8].R)) ? R_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].L)||!S9xGetState(Joypad[J+8].L)) ? L_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].X)||!S9xGetState(Joypad[J+8].X)) ? X_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].A)||!S9xGetState(Joypad[J+8].A)) ? A_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Right)) ? RIGHT_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Right_Up)) ? RIGHT_MASK + UP_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Right_Down)) ? RIGHT_MASK + DOWN_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Left)) ? LEFT_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Left_Up)) ? LEFT_MASK + UP_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Left_Down)) ? LEFT_MASK + DOWN_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Down)) ? DOWN_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Up)) ? UP_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Start)||!S9xGetState(Joypad[J+8].Start)) ? START_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Select)||!S9xGetState(Joypad[J+8].Select)) ? SELECT_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Y)||!S9xGetState(Joypad[J+8].Y)) ? Y_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].B)||!S9xGetState(Joypad[J+8].B)) ? B_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Lid)||!S9xGetState(Joypad[J+8].Lid)) ? LID_MASK : 0; +// PadState ^= (!S9xGetState(Joypad[J].Debug)||!S9xGetState(Joypad[J+8].Debug)) ? DEBUG_MASK : 0; +// } +// +// bool turbofy = !S9xGetState(Joypad[J+8].Up); // All Mod for turbo +// +// u32 TurboMask = 0; +// +// //handle turbo case! (autofire / auto-fire) +// if(turbofy || ((TurboMask&A_MASK))&&(PadState&A_MASK) || !S9xGetState(Joypad[J+8].A )) PadState^=(joypads[J]&A_MASK); +// if(turbofy || ((TurboMask&B_MASK))&&(PadState&B_MASK) || !S9xGetState(Joypad[J+8].B )) PadState^=(joypads[J]&B_MASK); +// if(turbofy || ((TurboMask&Y_MASK))&&(PadState&Y_MASK) || !S9xGetState(Joypad[J+8].Y )) PadState^=(joypads[J]&Y_MASK); +// if(turbofy || ((TurboMask&X_MASK))&&(PadState&X_MASK) || !S9xGetState(Joypad[J+8].X )) PadState^=(joypads[J]&X_MASK); +// if(turbofy || ((TurboMask&L_MASK))&&(PadState&L_MASK) || !S9xGetState(Joypad[J+8].L )) PadState^=(joypads[J]&L_MASK); +// if(turbofy || ((TurboMask&R_MASK))&&(PadState&R_MASK) || !S9xGetState(Joypad[J+8].R )) PadState^=(joypads[J]&R_MASK); +// if(turbofy || ((TurboMask&START_MASK))&&(PadState&START_MASK) || !S9xGetState(Joypad[J+8].Start )) PadState^=(joypads[J]&START_MASK); +// if(turbofy || ((TurboMask&SELECT_MASK))&&(PadState&SELECT_MASK) || !S9xGetState(Joypad[J+8].Select)) PadState^=(joypads[J]&SELECT_MASK); +// if(turbofy || ((TurboMask&DEBUG_MASK))&&(PadState&DEBUG_MASK) || !S9xGetState(Joypad[J+8].Debug)) PadState^=(joypads[J]&DEBUG_MASK); +// if( ((TurboMask&LEFT_MASK))&&(PadState&LEFT_MASK) ) PadState^=(joypads[J]&LEFT_MASK); +// if( ((TurboMask&UP_MASK))&&(PadState&UP_MASK) ) PadState^=(joypads[J]&UP_MASK); +// if( ((TurboMask&RIGHT_MASK))&&(PadState&RIGHT_MASK) ) PadState^=(joypads[J]&RIGHT_MASK); +// if( ((TurboMask&DOWN_MASK))&&(PadState&DOWN_MASK) ) PadState^=(joypads[J]&DOWN_MASK); +// if( ((TurboMask&LID_MASK))&&(PadState&LID_MASK) ) PadState^=(joypads[J]&LID_MASK); +// +// if(TurboToggleJoypadStorage[J].A ) PadState^=(joypads[J]&A_MASK); +// if(TurboToggleJoypadStorage[J].B ) PadState^=(joypads[J]&B_MASK); +// if(TurboToggleJoypadStorage[J].Y ) PadState^=(joypads[J]&Y_MASK); +// if(TurboToggleJoypadStorage[J].X ) PadState^=(joypads[J]&X_MASK); +// if(TurboToggleJoypadStorage[J].L ) PadState^=(joypads[J]&L_MASK); +// if(TurboToggleJoypadStorage[J].R ) PadState^=(joypads[J]&R_MASK); +// if(TurboToggleJoypadStorage[J].Start ) PadState^=(joypads[J]&START_MASK); +// if(TurboToggleJoypadStorage[J].Select) PadState^=(joypads[J]&SELECT_MASK); +// if(TurboToggleJoypadStorage[J].Left ) PadState^=(joypads[J]&LEFT_MASK); +// if(TurboToggleJoypadStorage[J].Up ) PadState^=(joypads[J]&UP_MASK); +// if(TurboToggleJoypadStorage[J].Right ) PadState^=(joypads[J]&RIGHT_MASK); +// if(TurboToggleJoypadStorage[J].Down ) PadState^=(joypads[J]&DOWN_MASK); +// if(TurboToggleJoypadStorage[J].Lid ) PadState^=(joypads[J]&LID_MASK); +// if(TurboToggleJoypadStorage[J].Debug ) PadState^=(joypads[J]&DEBUG_MASK); +// //end turbo case... +// +// +// // enforce left+right/up+down disallowance here to +// // avoid recording unused l+r/u+d that will cause desyncs +// // when played back with l+r/u+d is allowed +// //if(!allowUpAndDown) +// //{ +// // if((PadState[1] & 2) != 0) +// // PadState[1] &= ~(1); +// // if((PadState[1] & 8) != 0) +// // PadState[1] &= ~(4); +// //} +// +// joypads [J] = PadState | 0x80000000; +// } +// else +// joypads [J] = 0; +// } +// +// // input from macro +// //for (int J = 0; J < 8; J++) +// //{ +// // if(MacroIsEnabled(J)) +// // { +// // uint16 userPadState = joypads[J] & 0xFFFF; +// // uint16 macroPadState = MacroInput(J); +// // uint16 newPadState; +// +// // switch(GUI.MacroInputMode) +// // { +// // case MACRO_INPUT_MOV: +// // newPadState = macroPadState; +// // break; +// // case MACRO_INPUT_OR: +// // newPadState = macroPadState | userPadState; +// // break; +// // case MACRO_INPUT_XOR: +// // newPadState = macroPadState ^ userPadState; +// // break; +// // default: +// // newPadState = userPadState; +// // break; +// // } +// +// // PadState[0] = (uint8) ( newPadState & 0xFF); +// // PadState[1] = (uint8) ((newPadState >> 8) & 0xFF); +// +// // // enforce left+right/up+down disallowance here to +// // // avoid recording unused l+r/u+d that will cause desyncs +// // // when played back with l+r/u+d is allowed +// // if(!allowUpAndDown) +// // { +// // if((PadState[1] & 2) != 0) +// // PadState[1] &= ~(1); +// // if((PadState[1] & 8) != 0) +// // PadState[1] &= ~(4); +// // } +// +// // joypads [J] = PadState [0] | (PadState [1] << 8) | 0x80000000; +// // } +// //} +// +////#ifdef NETPLAY_SUPPORT +//// if (Settings.NetPlay) +//// { +//// // Send joypad position update to server +//// S9xNPSendJoypadUpdate (joypads [GUI.NetplayUseJoypad1 ? 0 : NetPlay.Player-1]); +//// +//// // set input from network +//// for (int J = 0; J < NP_MAX_CLIENTS; J++) +//// joypads[J] = S9xNPGetJoypad (J); +//// } +////#endif +//} + void input_feedback(BOOL enable) { if (!Feedback) return; @@ -2357,43 +2395,82 @@ void input_init() FeedbackON = input_feedback; } -void input_process() +// TODO: maybe some of this stuff should move back to NDSSystem.cpp? +// I don't know which is the better place for it. + +static void StepManualTurbo(); +static void ApplyAntipodalRestriction(buttonstruct& pad); +static void SetManualTurbo(buttonstruct& pad); +static void RunAntipodalRestriction(const buttonstruct& pad); + +// may run multiple times per frame. +// gets the user input and puts in a request with NDSSystem about it, +// and updates input-related state that needs to update even while paused. +void input_acquire() { + u32 oldInput = joypads[0]; + S9xWinScanJoypads(); - //not appropriate right now in desmume - //if (paused) return; + buttonstruct buttons = {}; + buttons.R = (joypads[0] & RIGHT_MASK)!=0; + buttons.L = (joypads[0] & LEFT_MASK)!=0; + buttons.D = (joypads[0] & DOWN_MASK)!=0; + buttons.U = (joypads[0] & UP_MASK)!=0; + buttons.S = (joypads[0] & START_MASK)!=0; + buttons.T = (joypads[0] & SELECT_MASK)!=0; + buttons.B = (joypads[0] & B_MASK)!=0; + buttons.A = (joypads[0] & A_MASK)!=0; + buttons.Y = (joypads[0] & Y_MASK)!=0; + buttons.X = (joypads[0] & X_MASK)!=0; + buttons.W = (joypads[0] & L_MASK)!=0; + buttons.E = (joypads[0] & R_MASK)!=0; + buttons.G = (joypads[0] & DEBUG_MASK)!=0; + buttons.F = (joypads[0] & LID_MASK)!=0; - bool R = (joypads[0] & RIGHT_MASK)!=0; - bool L = (joypads[0] & LEFT_MASK)!=0; - bool D = (joypads[0] & DOWN_MASK)!=0; - bool U = (joypads[0] & UP_MASK)!=0; - bool T = (joypads[0] & START_MASK)!=0; - bool S = (joypads[0] & SELECT_MASK)!=0; - bool B = (joypads[0] & B_MASK)!=0; - bool A = (joypads[0] & A_MASK)!=0; - bool Y = (joypads[0] & Y_MASK)!=0; - bool X = (joypads[0] & X_MASK)!=0; - bool W = (joypads[0] & L_MASK)!=0; - bool E = (joypads[0] & R_MASK)!=0; - bool G = (joypads[0] & DEBUG_MASK)!=0; - bool F = (joypads[0] & LID_MASK)!=0; + // take care of toggling the auto-hold flags. + if(AutoHoldPressed) + { + if(buttons.R && !(oldInput & RIGHT_MASK)) AutoHold.R ^= true; + if(buttons.L && !(oldInput & LEFT_MASK)) AutoHold.L ^= true; + if(buttons.D && !(oldInput & DOWN_MASK)) AutoHold.D ^= true; + if(buttons.U && !(oldInput & UP_MASK)) AutoHold.U ^= true; + if(buttons.S && !(oldInput & START_MASK)) AutoHold.S ^= true; + if(buttons.T && !(oldInput & SELECT_MASK)) AutoHold.T ^= true; + if(buttons.B && !(oldInput & B_MASK)) AutoHold.B ^= true; + if(buttons.A && !(oldInput & A_MASK)) AutoHold.A ^= true; + if(buttons.Y && !(oldInput & Y_MASK)) AutoHold.Y ^= true; + if(buttons.X && !(oldInput & X_MASK)) AutoHold.X ^= true; + if(buttons.W && !(oldInput & L_MASK)) AutoHold.W ^= true; + if(buttons.E && !(oldInput & R_MASK)) AutoHold.E ^= true; + } - if(AutoHoldPressed && R) AutoHold.Right ^= true; - if(AutoHoldPressed && L) AutoHold.Left ^= true; - if(AutoHoldPressed && D) AutoHold.Down ^= true; - if(AutoHoldPressed && U) AutoHold.Up ^= true; - if(AutoHoldPressed && T) AutoHold.Select ^= true; - if(AutoHoldPressed && S) AutoHold.Start ^= true; - if(AutoHoldPressed && B) AutoHold.B ^= true; - if(AutoHoldPressed && A) AutoHold.A ^= true; - if(AutoHoldPressed && Y) AutoHold.Y ^= true; - if(AutoHoldPressed && X) AutoHold.X ^= true; - if(AutoHoldPressed && W) AutoHold.L ^= true; - if(AutoHoldPressed && E) AutoHold.R ^= true; + // update upAndDown timers + RunAntipodalRestriction(buttons); - NDS_setPad( R, L, D, U, T, S, B, A, Y, X, W, E, G, F); + // apply any autofire that requires the user to be + // actively holding a button in order to trigger it + SetManualTurbo(buttons); + // let's actually apply auto-hold here too, + // even though this is supposed to be the "raw" user input, + // since things seem to work out better this way (more useful input display, for one thing), + // and it kind of makes sense to think of auto-held keys as + // a direct extension of what the user is physically trying to press. + for(int i = 0; i < ARRAY_SIZE(buttons.array); i++) + buttons.array[i] ^= AutoHold.array[i]; + + + // set initial input request + NDS_setPad( + buttons.R, buttons.L, buttons.D, buttons.U, + buttons.T, buttons.S, buttons.B, buttons.A, + buttons.Y, buttons.X, buttons.W, buttons.E, + buttons.G, buttons.F); + + + // TODO: this part hasn't been revised yet, + // but guitarGrip_setKey should only request a change (like NDS_setPad does) if (Guitar.Enabled) { bool gG=!S9xGetState(Guitar.GREEN); @@ -2404,6 +2481,74 @@ void input_process() } } +// only runs once per frame (always after input_acquire has been called at least once). +// applies transformations to the user's input, +// and updates input-related state that needs to update once per frame. +void input_process() +{ + UserButtons& input = NDS_getProcessingUserInput().buttons; + + // prevent left+right/up+down if that option is set to not allow it + ApplyAntipodalRestriction(input); + + // step turbo frame timers + StepManualTurbo(); + + // TODO: things like macros or "hands free" turbo/autofire + // should probably be applied here. +} + +static bool turbo[4] = {true, false, true, false}; + +static void StepManualTurbo() +{ + for (int i = 0; i < ARRAY_SIZE(TurboTime.array); i++) + { + TurboTime.array[i]++; + if(!Turbo.array[i] || TurboTime.array[i] >= (int)ARRAY_SIZE(turbo)) + TurboTime.array[i] = 0; // reset timer if the button isn't pressed or we overran + } +} + +static void SetManualTurbo(buttonstruct& pad) +{ + for (int i = 0; i < ARRAY_SIZE(pad.array); i++) + if(Turbo.array[i]) + pad.array[i] = turbo[TurboTime.array[i]]; +} + +static buttonstruct cardinalHeldTime = {0}; + +static void RunAntipodalRestriction(const buttonstruct& pad) +{ + if(allowUpAndDown) + return; + + pad.U ? (cardinalHeldTime.U++) : (cardinalHeldTime.U=0); + pad.D ? (cardinalHeldTime.D++) : (cardinalHeldTime.D=0); + pad.L ? (cardinalHeldTime.L++) : (cardinalHeldTime.L=0); + pad.R ? (cardinalHeldTime.R++) : (cardinalHeldTime.R=0); +} +static void ApplyAntipodalRestriction(buttonstruct& pad) +{ + if(allowUpAndDown) + return; + + // give preference to whichever direction was most recently pressed + if(pad.U && pad.D) + if(cardinalHeldTime.U < cardinalHeldTime.D) + pad.D = false; + else + pad.U = false; + if(pad.L && pad.R) + if(cardinalHeldTime.L < cardinalHeldTime.R) + pad.R = false; + else + pad.L = false; +} + + + static void set_hotkeyinfo(HWND hDlg) { HotkeyPage page = (HotkeyPage) SendDlgItemMessage(hDlg,IDC_HKCOMBO,CB_GETCURSEL,0,0); diff --git a/desmume/src/windows/inputdx.h b/desmume/src/windows/inputdx.h index 2aa559e98..567e66fac 100644 --- a/desmume/src/windows/inputdx.h +++ b/desmume/src/windows/inputdx.h @@ -110,6 +110,7 @@ extern SJoypad TurboToggleJoypadStorage[8]; void RunInputConfig(); void RunHotkeyConfig(); +void input_acquire(); void input_process(); struct SGuitar { diff --git a/desmume/src/windows/main.cpp b/desmume/src/windows/main.cpp index 238d67c5b..32e211b74 100644 --- a/desmume/src/windows/main.cpp +++ b/desmume/src/windows/main.cpp @@ -439,26 +439,44 @@ void SetMinWindowSize() MainWindow->setMinSize(video.rotatedwidthgap(), video.rotatedheightgap()); } -void translateXY(s32& x, s32& y) +// input x,y should be windows client-space coords already at 1x scaling. +// output is in pixels relative to the top-left of the chosen screen. +// the gap between screens (if any) is subtracted away from the output y. +void ToDSScreenRelativeCoords(s32& x, s32& y, bool bottomScreen) { s32 tx=x, ty=y; + int gapSize = video.screengap / video.ratio(); + + // first deal with rotation switch(video.rotation) { case 90: x = ty; - y = 191-tx; + y = (383+gapSize)-tx; break; case 180: x = 255-tx; - y = 383-ty; - y -= 192; + y = (383+gapSize)-ty; break; case 270: x = 255-ty; - y = (tx-192-(video.screengap/video.ratio())); + y = tx; break; } + + // then deal with screen gap + if(y > 191 + gapSize) + y -= gapSize; + else if(y > 191 + gapSize/2) + y = 192; + else if(y > 191) + y = 191; + + // finally, make it relative to the correct screen + if(bottomScreen) + y -= 192; } + // END Rotation definitions void UpdateRecentRomsMenu() @@ -784,7 +802,7 @@ static void DD_DoDisplay() memset(&ddsd, 0, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags=DDSD_ALL; - res = lpBackSurface->Lock(NULL, &ddsd, DDLOCK_WAIT, NULL); + res = lpBackSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_WRITEONLY, NULL); if (res==DDERR_SURFACELOST) { @@ -1056,9 +1074,16 @@ DWORD WINAPI run() { while(execute) { - input_process(); - CallRegisteredLuaFunctions(LUACALL_BEFOREEMULATION); - FCEUMOV_AddInputState(); + // the order of these function calls is very important + input_acquire(); + NDS_beginProcessingInput(); + { + input_process(); + FCEUMOV_HandlePlayback(); + CallRegisteredLuaFunctions(LUACALL_BEFOREEMULATION); + } + NDS_endProcessingInput(); + FCEUMOV_HandleRecording(); { Lock lock; @@ -2511,6 +2536,8 @@ bool first; void FrameAdvance(bool state) { + if(!romloaded) + return; if(state) { if(first) { execute = TRUE; @@ -2656,8 +2683,8 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM DesEnableMenuItem(mainMenu, IDM_CHEATS_SEARCH, romloaded); //DesEnableMenuItem(mainMenu, IDM_WIFISETTINGS, romloaded); - DesEnableMenuItem(mainMenu, IDM_RECORD_MOVIE, (romloaded && movieMode == MOVIEMODE_INACTIVE)); - DesEnableMenuItem(mainMenu, IDM_PLAY_MOVIE, (romloaded && movieMode == MOVIEMODE_INACTIVE)); + DesEnableMenuItem(mainMenu, IDM_RECORD_MOVIE, (romloaded /*&& movieMode == MOVIEMODE_INACTIVE*/)); + DesEnableMenuItem(mainMenu, IDM_PLAY_MOVIE, (romloaded /*&& movieMode == MOVIEMODE_INACTIVE*/)); DesEnableMenuItem(mainMenu, IDM_STOPMOVIE, (romloaded && movieMode != MOVIEMODE_INACTIVE)); DesEnableMenuItem(mainMenu, ID_RAM_WATCH, romloaded); @@ -2929,6 +2956,8 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM break; case WM_KEYDOWN: + if(paused) + input_acquire(); if(wParam != VK_PAUSE) break; case WM_SYSKEYDOWN: @@ -2940,6 +2969,8 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM break; } case WM_KEYUP: + if(paused) + input_acquire(); if(wParam != VK_PAUSE) break; case WM_SYSKEYUP: @@ -3060,16 +3091,14 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM x = x/video.ratio(); y = y/video.ratio(); - if(HudEditorMode) { + if(HudEditorMode) + { + ToDSScreenRelativeCoords(x,y,false); EditHud(x,y, &Hud); } - else { - //translate for rotation - if (video.rotation != 0) - translateXY(x,y); - else - y-=192+(video.screengap/video.ratio()); - + else + { + ToDSScreenRelativeCoords(x,y,true); if(x<0) x = 0; else if(x>255) x = 255; if(y<0) y = 0; else if(y>192) y = 192; NDS_setTouchPos(x, y); diff --git a/desmume/src/windows/mic.cpp b/desmume/src/windows/mic.cpp index 5d037eb20..3aec52f16 100644 --- a/desmume/src/windows/mic.cpp +++ b/desmume/src/windows/mic.cpp @@ -8,15 +8,16 @@ Note : I added these notes because the microphone isn't documented on GBATek. */ -#include +#include "NDSSystem.h" #include "../types.h" #include "../debug.h" #include "../mic.h" +#include "../movie.h" +#include "readwrite.h" #include #include int MicDisplay; -int MicButtonPressed=0; int SampleLoaded=0; #define MIC_CHECKERR(hr) if(hr != MMSYSERR_NOERROR) return FALSE; @@ -31,6 +32,8 @@ static u16 Mic_BufPos; static u8 Mic_WriteBuf; static u8 Mic_PlayBuf; +static int micReadSamplePos=0; + static HWAVEIN waveIn; static WAVEHDR waveHdr; @@ -150,6 +153,8 @@ void Mic_Reset() Mic_WriteBuf = 0; Mic_PlayBuf = 1; + + micReadSamplePos = 0; } void Mic_DeInit() @@ -167,7 +172,6 @@ static const int random[32] = {0xB1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9}; -static int x=0; u8 Mic_ReadSample() { @@ -177,28 +181,40 @@ u8 Mic_ReadSample() u8 ret; u8 tmp; - if(MicButtonPressed) { + if(NDS_getFinalUserInput().mic.micButtonPressed) { if(SampleLoaded) { //use a sample - tmp = samplebuffer[x >> 1]; - x++; - if(x == samplebuffersize*2) - x=0; + //TODO: what if a movie is active? + // for now I'm going to hope that if anybody records a movie with a sample loaded, + // either they know what they're doing and plan to distribute the sample, + // or they're playing a game where it doesn't even matter or they never press the mic button. + tmp = samplebuffer[micReadSamplePos >> 1]; + micReadSamplePos++; + if(micReadSamplePos == samplebuffersize*2) + micReadSamplePos=0; } else { //use the "random" values - tmp = random[x >> 1]; + tmp = random[micReadSamplePos >> 1]; //tmp = rand()&0xFF; - x++; - if(x == ARRAY_SIZE(random)*2) - x=0; + micReadSamplePos++; + if(micReadSamplePos == ARRAY_SIZE(random)*2) + micReadSamplePos=0; } } else { - //normal mic behavior - tmp = (u8)Mic_Buffer[Mic_PlayBuf][Mic_BufPos >> 1]; + if(movieMode != MOVIEMODE_INACTIVE) + { + //normal mic behavior + tmp = (u8)Mic_Buffer[Mic_PlayBuf][Mic_BufPos >> 1]; + } + else + { + //since we're not recording Mic_Buffer to the movie, use silence + tmp = 0; + } //reset mic button buffer pos if not pressed - x=0; + micReadSamplePos=0; } if(Mic_BufPos & 0x1) @@ -220,4 +236,34 @@ u8 Mic_ReadSample() } return ret; -} \ No newline at end of file +} + +// maybe a bit paranoid... +void mic_savestate(std::ostream* os) +{ + //version + write32le(0,os); + assert(MIC_BUFSIZE == 4096); // else needs new version + + os->write((char*)Mic_Buffer[0], MIC_BUFSIZE); + os->write((char*)Mic_Buffer[1], MIC_BUFSIZE); + write16le(Mic_BufPos,os); + write8le(Mic_WriteBuf,os); // seems OK to save... + write8le(Mic_PlayBuf,os); + write32le(micReadSamplePos,os); +} +bool mic_loadstate(std::istream* is, int size) +{ + int version; + if(read32le(&version,is) != 1) return false; + if(version > 0) { is->seekg(size-4, std::ios::cur); return true; } + + is->read((char*)Mic_Buffer[0], MIC_BUFSIZE); + is->read((char*)Mic_Buffer[1], MIC_BUFSIZE); + read16le(&Mic_BufPos,is); + read8le(&Mic_WriteBuf,is); + read8le(&Mic_PlayBuf,is); + read32le(&micReadSamplePos,is); + return true; +} + diff --git a/desmume/src/windows/replay.cpp b/desmume/src/windows/replay.cpp index e4f4033f7..cd90d4e49 100644 --- a/desmume/src/windows/replay.cpp +++ b/desmume/src/windows/replay.cpp @@ -25,11 +25,80 @@ inline std::string GetDlgItemText(HWND hDlg, int nIDDlgItem) { } +// tests if a file is readable at the given location +// without changing whatever is there +static BOOL IsFileReadable(char* filePath) +{ + if(!filePath || GetFileAttributes(filePath) == 0xFFFFFFFF) + return false; + FILE* file = fopen(filePath, "rb"); + if(file) + fclose(file); + return file != 0; +} + +// tests if a file is writable at the given location +// without changing whatever is there +static BOOL IsFileWritable(char* filePath) +{ + if(!filePath) + return false; + bool didNotExist = GetFileAttributes(filePath) == 0xFFFFFFFF; + FILE* file = fopen(filePath, "ab"); + if(file) + { + fclose(file); + if(didNotExist) + unlink(filePath); + return true; + } + return false; +} + +// if there's no directory (only a filename), make it an absolute path, +// otherwise the user won't have any clue where the file actually is... +void FixRelativeMovieFilename(HWND hwndDlg, DWORD dlgItem) +{ + char tempfname [MAX_PATH]; + char fullfname [MAX_PATH]; + GetDlgItemText(hwndDlg,dlgItem,tempfname,MAX_PATH); + + // NOTE: if this puts the wrong path in, then the solution is to call SetCurrentDirectory beforehand, + // since if it's wrong then even without this code that's the wrong place it would get saved to. + // also, single-letter filenames are passed by here in case the user is deleting the path from the right and goes from "c:" to "c" + if(tempfname[0] && tempfname[1] && !strchr(tempfname, '/') && !strchr(tempfname, '\\') && !strchr(tempfname, ':') + && GetFullPathName(tempfname,MAX_PATH-4,fullfname,NULL)) + { + // store cursor position + int sel1=-1, sel2=0; + SendMessage(GetDlgItem(hwndDlg, dlgItem), EM_GETSEL, (WPARAM)&sel1, (LPARAM)&sel2); + + // add dsm if no extension + if(!strchr(fullfname,'.')) + strcat(fullfname, ".dsm"); + + // replace text with the absolute path + extension + SetDlgItemText(hwndDlg, dlgItem, fullfname); + + // keep cursor where user was typing + char* match = fullfname; + while(strstr(match+1, tempfname)) match = strstr(match+1, tempfname); // strrstr + if(match > fullfname) + { + sel1 += match - fullfname; + sel2 += match - fullfname; + SendMessage(GetDlgItem(hwndDlg, dlgItem), EM_SETSEL, sel1, sel2); + } + } +} + + + static char playfilename[MAX_PATH] = ""; void Describe(HWND hwndDlg) { - std::fstream fs (playfilename); + std::fstream fs (playfilename, std::ios_base::in); MovieData md; LoadFM2(md, &fs, INT_MAX, false); fs.close(); @@ -47,7 +116,6 @@ void Describe(HWND hwndDlg) SetDlgItemText(hwndDlg,IDC_MLENGTH,tmp); SetDlgItemInt(hwndDlg,IDC_MFRAMES,num_frames,FALSE); - SetDlgItemText(hwndDlg, PM_FILENAME, playfilename); SetDlgItemInt(hwndDlg,IDC_MRERECORDCOUNT,md.rerecordCount,FALSE); SetDlgItemText(hwndDlg,IDC_MROM,md.romSerial.c_str()); } @@ -62,6 +130,7 @@ INT_PTR CALLBACK ReplayDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM switch(uMsg) { case WM_INITDIALOG: + { SendDlgItemMessage(hwndDlg, IDC_CHECK_READONLY, BM_SETCHECK, replayreadonly?BST_CHECKED:BST_UNCHECKED, 0); //Clear fields @@ -69,7 +138,16 @@ INT_PTR CALLBACK ReplayDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM SetWindowText(GetDlgItem(hwndDlg, IDC_MFRAMES), ""); SetWindowText(GetDlgItem(hwndDlg, IDC_MRERECORDCOUNT), ""); SetWindowText(GetDlgItem(hwndDlg, IDC_MROM), ""); - return FALSE; + + extern char curMovieFilename[512]; + strncpy(playfilename, curMovieFilename, MAX_PATH); + playfilename[MAX_PATH-1] = '\0'; + + SetWindowText(GetDlgItem(hwndDlg, PM_FILENAME), playfilename); + SetFocus(GetDlgItem(hwndDlg, PM_FILENAME)); + SendMessage(GetDlgItem(hwndDlg, PM_FILENAME), EM_SETSEL, 0, -1); // select all + + } return FALSE; case WM_COMMAND: int wID = LOWORD(wParam); @@ -79,7 +157,7 @@ INT_PTR CALLBACK ReplayDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = MainWindow->getHWnd(); + ofn.hwndOwner = hwndDlg; ofn.lpstrFilter = "Desmume Movie File (*.dsm)\0*.dsm\0All files(*.*)\0*.*\0\0"; ofn.nFilterIndex = 1; ofn.lpstrFile = filename; @@ -87,13 +165,12 @@ INT_PTR CALLBACK ReplayDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM ofn.nMaxFile = MAX_PATH; ofn.lpstrDefExt = "dsm"; ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST; - GetOpenFileName(&ofn); - strcpy(playfilename, filename); - Describe(hwndDlg); + if(GetOpenFileName(&ofn)) + SetDlgItemText(hwndDlg, PM_FILENAME, filename); return true; case IDC_CHECK_READONLY: - replayreadonly ^= 1; + replayreadonly = IsDlgButtonChecked(hwndDlg, IDC_CHECK_READONLY) != 0; return true; case IDOK: @@ -106,11 +183,43 @@ INT_PTR CALLBACK ReplayDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM ZeroMemory(&playfilename, sizeof(playfilename)); EndDialog(hwndDlg, 0); return true; + + case PM_FILENAME: + switch(HIWORD(wParam)) + { + case EN_CHANGE: + { + FixRelativeMovieFilename(hwndDlg, PM_FILENAME); + + // disable the OK button if we can't read the file + char filename [MAX_PATH]; + GetDlgItemText(hwndDlg,PM_FILENAME,filename,MAX_PATH); + EnableWindow(GetDlgItem(hwndDlg, IDOK), IsFileReadable(filename)); + strcpy(playfilename, filename); + Describe(hwndDlg); + + // force read-only to be checked if we can't write the file + if(!IsFileWritable(filename)) + { + CheckDlgButton(hwndDlg, IDC_CHECK_READONLY, BST_CHECKED); + EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK_READONLY), FALSE); + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_CHECK_READONLY), TRUE); + } + } + break; + } + break; + } } return false; } + + int flag=0; std::string sramfname; @@ -123,10 +232,11 @@ static INT_PTR CALLBACK RecordDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, int x; //temp vairable switch(uMsg) { - case WM_INITDIALOG: - CheckDlgButton(hwndDlg, IDC_START_FROM_SRAM, ((flag == 1) ? BST_CHECKED : BST_UNCHECKED)); + case WM_INITDIALOG: + CheckDlgButton(hwndDlg, IDC_START_FROM_SRAM, ((flag == 1) ? BST_CHECKED : BST_UNCHECKED)); + SetFocus(GetDlgItem(hwndDlg, IDC_EDIT_FILENAME)); + return false; - return false; case WM_COMMAND: switch(LOWORD(wParam)) { @@ -139,7 +249,7 @@ static INT_PTR CALLBACK RecordDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, EndDialog(hwndDlg, 0); } return true; - } + } case IDCANCEL: EndDialog(hwndDlg, 0); @@ -149,26 +259,29 @@ static INT_PTR CALLBACK RecordDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, { OPENFILENAME ofn; char szChoice[MAX_PATH]={0}; + GetDlgItemText(hwndDlg,IDC_EDIT_FILENAME,szChoice,MAX_PATH); // browse button ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = MainWindow->getHWnd(); + ofn.hwndOwner = hwndDlg; ofn.lpstrFilter = "Desmume Movie File (*.dsm)\0*.dsm\0All files(*.*)\0*.*\0\0"; ofn.lpstrFile = szChoice; ofn.lpstrTitle = "Record a new movie"; ofn.lpstrDefExt = "dsm"; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN | OFN_PATHMUSTEXIST; - GetSaveFileName(&ofn); - - //If user did not specify an extension, add .dsm for them - fname = szChoice; - x = fname.find_last_of("."); - if (x < 0) - fname.append(".dsm"); - - SetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, fname.c_str()); + if(GetSaveFileName(&ofn)) + { + fname = szChoice; +/* // windows does this automatically, since lpstrDefExt is set + //If user did not specify an extension, add .dsm for them + x = fname.find_last_of("."); + if (x < 0) + fname.append(".dsm"); +*/ + SetDlgItemText(hwndDlg, IDC_EDIT_FILENAME, fname.c_str()); + } //if(GetSaveFileName(&ofn)) // UpdateRecordDialogPath(hwndDlg,szChoice); @@ -182,28 +295,46 @@ static INT_PTR CALLBACK RecordDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, // browse button ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = MainWindow->getHWnd(); + ofn.hwndOwner = hwndDlg; ofn.lpstrFilter = "Desmume SRAM File (*.dsv)\0*.dsv\0All files(*.*)\0*.*\0\0"; ofn.lpstrFile = szChoice; ofn.lpstrTitle = "Choose SRAM"; ofn.lpstrDefExt = "dsv"; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; - GetOpenFileName(&ofn); - - //If user did not specify an extension, add .dsm for them - fname = szChoice; + if(GetOpenFileName(&ofn)) + { + fname = szChoice; +/* // windows does this automatically, since lpstrDefExt is set + //If user did not specify an extension, add .dsv for them x = fname.find_last_of("."); if (x < 0) fname.append(".dsv"); - - SetDlgItemText(hwndDlg, IDC_EDIT_SRAMFILENAME, fname.c_str()); - sramfname=(std::string)fname; +*/ + SetDlgItemText(hwndDlg, IDC_EDIT_SRAMFILENAME, fname.c_str()); + sramfname=(std::string)fname; + } //if(GetSaveFileName(&ofn)) // UpdateRecordDialogPath(hwndDlg,szChoice); return true; } + + case IDC_EDIT_FILENAME: + switch(HIWORD(wParam)) + { + case EN_CHANGE: + { + FixRelativeMovieFilename(hwndDlg, IDC_EDIT_FILENAME); + + // disable the OK button if we can't write to the file + char filename [MAX_PATH]; + GetDlgItemText(hwndDlg,IDC_EDIT_FILENAME,filename,MAX_PATH); + EnableWindow(GetDlgItem(hwndDlg, IDOK), IsFileWritable(filename)); + } + break; + } + break; } } diff --git a/desmume/src/windows/resources.rc b/desmume/src/windows/resources.rc index 9fa633f14..0d216d793 100644 Binary files a/desmume/src/windows/resources.rc and b/desmume/src/windows/resources.rc differ