// Game Boy emulator for 68k Macs // dialogs.c - open ROM, alerts, key mappings, about #include #include #include #include #include #include "emulator.h" #include "dialogs.h" #include "settings.h" #include "gb_palettes.h" #include "lcd_mac.h" // default mappings: up=W, down=S, left=A, right=D, a=L, b=K, select=N, start=M static unsigned char defaultMappings[8] = { 0x0d, 0x01, 0x00, 0x02, 0x25, 0x28, 0x2d, 0x2e }; static short gSelectedSlot = -1; int keyMappings[8]; int frame_skip; int screen_scale; const char *keyNames[128] = { "\pA", // 0x00 "\pS", // 0x01 "\pD", // 0x02 "\pF", // 0x03 "\pH", // 0x04 "\pG", // 0x05 "\pZ", // 0x06 "\pX", // 0x07 "\pC", // 0x08 "\pV", // 0x09 NULL, // 0x0a "\pB", // 0x0b "\pQ", // 0x0c "\pW", // 0x0d "\pE", // 0x0e "\pR", // 0x0f "\pY", // 0x10 "\pT", // 0x11 "\p1", // 0x12 "\p2", // 0x13 "\p3", // 0x14 "\p4", // 0x15 "\p6", // 0x16 "\p5", // 0x17 "\p=", // 0x18 "\p9", // 0x19 "\p7", // 0x1a "\p-", // 0x1b "\p8", // 0x1c "\p0", // 0x1d "\p]", // 0x1e "\pO", // 0x1f "\pU", // 0x20 "\p[", // 0x21 "\pI", // 0x22 "\pP", // 0x23 "\pReturn", // 0x24 "\pL", // 0x25 "\pJ", // 0x26 "\p'", // 0x27 "\pK", // 0x28 "\p;", // 0x29 "\p\\", // 0x2a "\p,", // 0x2b "\p/", // 0x2c "\pN", // 0x2d "\pM", // 0x2e "\p.", // 0x2f "\pTab", // 0x30 "\pSpace", // 0x31 "\p`", // 0x32 "\pDelete", // 0x33 NULL, // 0x34 "\pEsc", // 0x35 NULL, // 0x36 "\pCmd", // 0x37 "\pShift", // 0x38 "\pCaps", // 0x39 "\pOption", // 0x3a "\pCtrl", // 0x3b NULL, // 0x3c NULL, // 0x3d NULL, // 0x3e NULL, // 0x3f NULL, // 0x40 "\pKp .", // 0x41 NULL, // 0x42 "\pKp *", // 0x43 NULL, // 0x44 "\pKp +", // 0x45 NULL, // 0x46 "\pClear", // 0x47 NULL, // 0x48 NULL, // 0x49 NULL, // 0x4a "\pKp /", // 0x4b "\pEnter", // 0x4c NULL, // 0x4d "\pKp -", // 0x4e NULL, // 0x4f NULL, // 0x50 "\pKp =", // 0x51 "\pKp 0", // 0x52 "\pKp 1", // 0x53 "\pKp 2", // 0x54 "\pKp 3", // 0x55 "\pKp 4", // 0x56 "\pKp 5", // 0x57 "\pKp 6", // 0x58 "\pKp 7", // 0x59 NULL, // 0x5a "\pKp 8", // 0x5b "\pKp 9", // 0x5c NULL, // 0x5d NULL, // 0x5e NULL, // 0x5f "\pF5", // 0x60 "\pF6", // 0x61 "\pF7", // 0x62 "\pF3", // 0x63 "\pF8", // 0x64 "\pF9", // 0x65 NULL, // 0x66 "\pF11", // 0x67 NULL, // 0x68 "\pF13", // 0x69 NULL, // 0x6a "\pF14", // 0x6b NULL, // 0x6c "\pF10", // 0x6d NULL, // 0x6e "\pF12", // 0x6f NULL, // 0x70 "\pF15", // 0x71 "\pHelp", // 0x72 "\pHome", // 0x73 "\pPg Up", // 0x74 "\pFwd Del", // 0x75 "\pF4", // 0x76 "\pEnd", // 0x77 "\pF2", // 0x78 "\pPg Dn", // 0x79 "\pF1", // 0x7a "\pLeft", // 0x7b "\pRight", // 0x7c "\pDown", // 0x7d "\pUp", // 0x7e NULL // 0x7f }; static void CenterDialog(Handle dlog) { Rect *bounds; Rect screen; short width, height, dh, dv; bounds = (Rect *) *dlog; screen = qd.screenBits.bounds; screen.top += GetMBarHeight(); width = bounds->right - bounds->left; height = bounds->bottom - bounds->top; dh = ((screen.right - screen.left) - width) / 2 - bounds->left; dv = ((screen.bottom - screen.top) - height) / 4 - bounds->top + screen.top; OffsetRect(bounds, dh, dv); } // return true to hide, false to show static pascal Boolean RomFileFilter(CInfoPBRec *pb) { StringPtr name; unsigned char len; // always show 'GBRM' files if (pb->hFileInfo.ioFlFndrInfo.fdType == 'GBRM') { return false; } // show files ending in .gb or .gbc (case insensitive) name = pb->hFileInfo.ioNamePtr; len = name[0]; if (len >= 3) { char c1 = name[len - 2]; char c2 = name[len - 1]; char c3 = name[len]; // Check for .gb if (c1 == '.' && (c2 == 'g' || c2 == 'G') && (c3 == 'b' || c3 == 'B')) { return false; } } if (len >= 4) { char c1 = name[len - 3]; char c2 = name[len - 2]; char c3 = name[len - 1]; char c4 = name[len]; // Check for .gbc if (c1 == '.' && (c2 == 'g' || c2 == 'G') && (c3 == 'b' || c3 == 'B') && (c4 == 'c' || c4 == 'C')) { return false; } } return true; } int ShowOpenBox(void) { SFReply reply; Point pt = { 0, 0 }; const int stdWidth = 348; pt.h = qd.screenBits.bounds.right / 2 - stdWidth / 2; SFGetFile(pt, NULL, RomFileFilter, -1, NULL, NULL, &reply); if(reply.good) { FInfo fndrInfo; // need to verify here too because opening a file from Finder // while the app is already open skips RomFileFilter if (GetFInfo(reply.fName, reply.vRefNum, &fndrInfo) == noErr && fndrInfo.fdType != 'GBRM' && fndrInfo.fdCreator == 'MGBE') { ExplainNotARom(fndrInfo.fdType); return false; } return LoadRom(reply.fName, reply.vRefNum); } return false; } pascal Boolean AboutFilter(DialogPtr dlg, EventRecord *event, short *item) { Point pt; short type; Handle h; Rect r; if (event->what == mouseDown) { pt = event->where; SetPort(dlg); GlobalToLocal(&pt); GetDialogItem(dlg, 2, &type, &h, &r); if (PtInRect(pt, &r)) { *item = 2; return true; } } return false; } void ShowAboutBox(void) { DialogPtr dp; EventRecord e; DialogItemIndex itemHit; CenterDialog(GetResource('DLOG', DLOG_ABOUT)); dp = GetNewDialog(DLOG_ABOUT, 0L, (WindowPtr) -1L); ShowWindow(dp); do { ModalDialog(AboutFilter, &itemHit); if (itemHit == 2) { Rect rect; Handle handle; short type; GetDialogItem(dp, 2, &type, &handle, &rect); if (screen_depth > 1) { SetDialogItem(dp, 2, type, (Handle) GetCIcon(132), &rect); } else { SetDialogItem(dp, 2, type, GetIcon(132), &rect); } DrawDialog(dp); } } while (itemHit != ok); DisposeDialog(dp); } void LoadKeyMappings(void) { Handle h; int k; h = GetResource(RES_KEYS_TYPE, RES_KEYS_ID); if (h != nil && GetHandleSize(h) >= 8) { for (k = 0; k < 8; k++) { keyMappings[k] = ((unsigned char *)*h)[k]; } } else { for (k = 0; k < 8; k++) { keyMappings[k] = defaultMappings[k]; } } } void SaveKeyMappings(void) { Handle h; int k; h = GetResource(RES_KEYS_TYPE, RES_KEYS_ID); if (h == nil) { h = NewHandle(8); if (h == nil) return; for (k = 0; k < 8; k++) { ((unsigned char *)*h)[k] = keyMappings[k]; } AddResource(h, RES_KEYS_TYPE, RES_KEYS_ID, "\pKey Mappings"); } else { for (k = 0; k < 8; k++) { ((unsigned char *)*h)[k] = keyMappings[k]; } ChangedResource(h); } WriteResource(h); } void LoadPreferences(void) { Handle h; int *prefs; int incompatibleMode; h = GetResource(RES_PREFS_TYPE, RES_PREFS_ID); if (h != nil && GetHandleSize(h) >= sizeof(int) * 9) { prefs = (int *) *h; frame_skip = prefs[0]; screen_scale = prefs[1]; sound_enabled = prefs[2]; limit_fps = prefs[3]; current_palette = prefs[4]; if (current_palette < 0 || current_palette >= gb_palette_count) { current_palette = 0; } gbc_enabled = prefs[5]; ignore_double_speed = prefs[6]; stat_ints_enabled = prefs[7]; } else { frame_skip = 4; if (screen_depth > 1) { screen_scale = 1; } else { screen_scale = 2; } sound_enabled = 0; limit_fps = 0; current_palette = 0; gbc_enabled = 1; ignore_double_speed = 0; stat_ints_enabled = 1; } } void SavePreferences(void) { Handle h; int *prefs; Size needed = sizeof(int) * 10; h = GetResource(RES_PREFS_TYPE, RES_PREFS_ID); if (h == nil) { h = NewHandle(needed); if (h == nil) { return; } AddResource(h, RES_PREFS_TYPE, RES_PREFS_ID, "\pPreferences"); } else if (GetHandleSize(h) < needed) { SetHandleSize(h, needed); } prefs = (int *) *h; prefs[0] = frame_skip; prefs[1] = screen_scale; prefs[2] = sound_enabled; prefs[3] = limit_fps; prefs[4] = current_palette; prefs[5] = gbc_enabled; prefs[6] = ignore_double_speed; prefs[7] = stat_ints_enabled; ChangedResource(h); WriteResource(h); } pascal Boolean KeyMapFilter(DialogPtr dlg, EventRecord *event, short *item) { Point pt; Rect r; Handle h; short type; int k; if (event->what == mouseDown) { pt = event->where; SetPort(dlg); GlobalToLocal(&pt); for (k = 3; k <= 10; k++) { GetDialogItem(dlg, k, &type, &h, &r); if (PtInRect(pt, &r)) { gSelectedSlot = k; *item = k; return true; } } } else if (event->what == keyDown && gSelectedSlot != -1) { int keyNum = (event->message & keyCodeMask) >> 8; if (keyNum < 128 && keyNames[keyNum]) { keyMappings[gSelectedSlot - 3] = keyNum; *item = gSelectedSlot; gSelectedSlot = -1; return true; } } return false; } pascal void DrawKeySlot(DialogPtr dlg, short item) { Rect r; Handle h; short type; FontInfo fi; short oldFont, oldSize, oldMode; SetPort(dlg); GetDialogItem(dlg, item, &type, &h, &r); oldFont = dlg->txFont; oldSize = dlg->txSize; oldMode = dlg->txMode; TextFont(3); TextSize(9); TextMode(srcXor); GetFontInfo(&fi); if (gSelectedSlot == item) { PaintRect(&r); MoveTo(r.left + 3, r.top + fi.ascent + 1); DrawString("\p"); } else { EraseRect(&r); FrameRect(&r); MoveTo(r.left + 3, r.top + fi.ascent + 1); DrawString(keyNames[keyMappings[item - 3]]); } TextFont(oldFont); TextSize(oldSize); TextMode(oldMode); } pascal void FrameSaveButton(DialogPtr dlg, short item) { Rect rect; Handle handle; short type; GetDialogItem(dlg, 1, &type, &handle, &rect); PenSize(3, 3); InsetRect(&rect, -4, -4); FrameRoundRect(&rect, 16, 16); PenNormal(); } void ShowKeyMappingsDialog(void) { DialogPtr dp; EventRecord e; DialogItemIndex itemHit; int k; Rect rect; Handle handle; short type; // this can be left over from previous invocations if the user closed // the dialog box while a key mapping control is active gSelectedSlot = -1; CenterDialog(GetResource('DLOG', DLOG_KEY_MAPPINGS)); dp = GetNewDialog(DLOG_KEY_MAPPINGS, 0L, (WindowPtr) -1L); GetDialogItem(dp, 19, &type, &handle, &rect); SetDialogItem(dp, 19, type, (Handle) FrameSaveButton, &rect); for (k = 3; k <= 10; k++) { GetDialogItem(dp, k, &type, &handle, &rect); SetDialogItem(dp, k, type, (Handle) DrawKeySlot, &rect); } ShowWindow(dp); do { ModalDialog(KeyMapFilter, &itemHit); if (itemHit >= 3 && itemHit <= 10) { for (k = 3; k <= 10; k++) { DrawKeySlot(dp, k); } } } while (itemHit != ok && itemHit != cancel); if (itemHit == ok) { SaveKeyMappings(); } DisposeDialog(dp); } static void SetRadioGroup(DialogPtr dp, int first, int last, int selected) { int k; Rect rect; Handle handle; short type; for (k = first; k <= last; k++) { GetDialogItem(dp, k, &type, &handle, &rect); SetControlValue((ControlHandle) handle, k == selected ? 1 : 0); } } static short AlertWrapper(short alertID) { return Alert(alertID, NULL); } static short CautionAlertWrapper(short alertID) { return CautionAlert(alertID, NULL); } static short NoteAlertWrapper(short alertID) { return NoteAlert(alertID, NULL); } static short StopAlertWrapper(short alertID) { return StopAlert(alertID, NULL); } short ShowCenteredAlert( short alertID, const char *s0, const char *s1, const char *s2, const char *s3, int alertType ) { Handle alrt; Rect *bounds; Rect screen; short width, height, dh, dv; AlertProc funcs[] = { AlertWrapper, NoteAlertWrapper, CautionAlertWrapper, StopAlertWrapper }; ParamText(s0, s1, s2, s3); alrt = GetResource('ALRT', alertID); bounds = (Rect *) *alrt; screen = qd.screenBits.bounds; screen.top += GetMBarHeight(); width = bounds->right - bounds->left; height = bounds->bottom - bounds->top; // center horizontally, position 1/4 down vertically dh = ((screen.right - screen.left) - width) / 2 - bounds->left; dv = ((screen.bottom - screen.top) - height) / 4 - bounds->top + screen.top; OffsetRect(bounds, dh, dv); return funcs[alertType](alertID); } int SaveScreenshot(void) { SFReply reply; Point pt = { 0, 0 }; short refNum; long count; PicHandle pic; Rect picFrame; Rect srcRect; RgnHandle oldClip; char header[512]; int k; OSErr err; const int stdWidth = 348; pt.h = qd.screenBits.bounds.right / 2 - stdWidth / 2; SFPutFile(pt, "\pSave screenshot as:", "\pScreenshot", NULL, &reply); if (!reply.good) { return 0; } /* delete existing file if any, then create new one */ FSDelete(reply.fName, reply.vRefNum); err = Create(reply.fName, reply.vRefNum, 'ttxt', 'PICT'); if (err != noErr) { return 0; } err = FSOpen(reply.fName, reply.vRefNum, &refNum); if (err != noErr) { return 0; } /* write 512-byte header (all zeroes) */ for (k = 0; k < 512; k++) { header[k] = 0; } count = 512; FSWrite(refNum, &count, header); /* create picture by drawing offscreen bitmap */ SetPort(g_wp); picFrame = offscreen_rect; /* visible frame starts row_voff rows into the buffer */ srcRect = offscreen_rect; srcRect.top += lcd.row_voff * screen_scale; srcRect.bottom += lcd.row_voff * screen_scale; /* save and set clip to picture bounds */ oldClip = NewRgn(); GetClip(oldClip); ClipRect(&picFrame); pic = OpenPicture(&picFrame); if (screen_depth > 1) { CGrafPtr port = (CGrafPtr) g_wp; CopyBits( (BitMap *) lcd_active_pixmap(), (BitMap *) *port->portPixMap, &srcRect, &offscreen_rect, srcCopy, NULL ); } else { CopyBits(&offscreen_bmp, &g_wp->portBits, &srcRect, &offscreen_rect, srcCopy, NULL); } ClosePicture(); SetClip(oldClip); DisposeRgn(oldClip); /* write picture data */ HLock((Handle) pic); count = GetHandleSize((Handle) pic); FSWrite(refNum, &count, *pic); HUnlock((Handle) pic); KillPicture(pic); FSClose(refNum); return 1; }