diff --git a/Base/Actions.cpp b/Base/Actions.cpp index e964d5cc..086188f3 100644 --- a/Base/Actions.cpp +++ b/Base/Actions.cpp @@ -167,8 +167,12 @@ bool Do(Action action, bool pressed/*=true*/) } break; - case Action::SaveScreenshot: - Frame::SaveScreenshot(); + case Action::SavePNG: + Frame::SavePNG(); + break; + + case Action::SaveSSX: + Frame::SaveSSX(); break; case Action::Debugger: @@ -394,7 +398,7 @@ std::string to_string(Action action) { Action::Debugger, "Debugger" }, { Action::ImportData, "Import data" }, { Action::ExportData, "Export data" }, - { Action::SaveScreenshot, "Save screenshot" }, + { Action::SavePNG, "Save screenshot (PNG)" }, { Action::ResetButton, "Reset button" }, { Action::NmiButton, "NMI button" }, { Action::Pause, "Pause" }, @@ -429,6 +433,7 @@ std::string to_string(Action action) { Action::TapeInsert, "Insert Tape" }, { Action::TapeEject, "Eject Tape" }, { Action::TapeBrowser, "Tape Browser" }, + { Action::SaveSSX, "Save screenshot (SSX)" }, }; auto it = action_descs.find(action); diff --git a/Base/Actions.h b/Base/Actions.h index 171cd574..16fa8522 100644 --- a/Base/Actions.h +++ b/Base/Actions.h @@ -24,14 +24,14 @@ enum class Action { NewDisk1, InsertFloppy1, EjectFloppy1, SaveFloppy1, NewDisk2, InsertFloppy2, EjectFloppy2, SaveFloppy2, ExitApplication, Options, Debugger, ImportData, - ExportData, SaveScreenshot, ChangeProfiler_REMOVED, ResetButton, NmiButton, + ExportData, SavePNG, ChangeProfiler_REMOVED, ResetButton, NmiButton, Pause, FrameStep, ToggleTurbo, TempTurbo, ToggleScanHiRes, ToggleFullscreen, ChangeWindowSize_REMOVED, ChangeBorders_REMOVED, Toggle5_4, ToggleFilter, ToggleScanlines, ToggleGreyscale, ToggleMute, ReleaseMouse, PrinterOnline, FlushPrinter, About, Minimise, RecordGif, RecordGifLoop, RecordGifStop, RecordWav, RecordWavSegment, RecordWavStop, RecordAvi, RecordAviHalf, RecordAviStop, SpeedFaster, SpeedSlower, SpeedNormal, Paste, TapeInsert, - TapeEject, TapeBrowser + TapeEject, TapeBrowser, SaveSSX }; namespace Actions diff --git a/Base/Frame.cpp b/Base/Frame.cpp index 00fac4ca..1a577858 100644 --- a/Base/Frame.cpp +++ b/Base/Frame.cpp @@ -45,6 +45,7 @@ #include "OSD.h" #include "PNG.h" #include "Sound.h" +#include "SSX.h" #include "Util.h" #include "UI.h" @@ -64,7 +65,7 @@ int s_nViewLeft, s_nViewRight; CScreen* pScreen, * pLastScreen, * pGuiScreen, * pLastGuiScreen, * pDisplayScreen; CFrame* pFrame; -bool fDrawFrame, g_fFlashPhase, fSaveScreen; +bool fDrawFrame, g_fFlashPhase, fSavePNG, fSaveSSX; int nFrame; int nLastLine, nLastBlock; // Line and block we've drawn up to so far this frame @@ -346,6 +347,8 @@ static void DrawRaster(CScreen* pScreen_) // Begin the frame by copying from the previous frame, up to the last cange void Begin() { + display_changed = false; + // Return if we're skipping this frame if (!fDrawFrame) return; @@ -392,10 +395,18 @@ void End() else { // Screenshot required? - if (fSaveScreen) + if (fSavePNG) { PNG::Save(pScreen); - fSaveScreen = false; + fSavePNG = false; + } + + if (fSaveSSX) + { + auto main_x = (BORDER_BLOCKS - s_nViewLeft) << 4; + auto main_y = (TOP_BORDER_LINES - s_nViewTop); + SSX::Save(pScreen, main_x, main_y); + fSaveSSX = false; } // Add the frame to any recordings @@ -583,12 +594,15 @@ void DrawOSD(CScreen* pScreen_) } } -// Screenshot save request -void SaveScreenshot() +void SavePNG() { - fSaveScreen = true; + fSavePNG = true; } +void SaveSSX() +{ + fSaveSSX = true; +} // Set a status message, which will remain on screen for a few seconds void SetStatus(const char* pcszFormat_, ...) diff --git a/Base/Frame.h b/Base/Frame.h index a1d48f8c..d309f13c 100644 --- a/Base/Frame.h +++ b/Base/Frame.h @@ -50,7 +50,8 @@ void ChangeScreen(BYTE bNewBorder_); void Sync(); void Redraw(); -void SaveScreenshot(); +void SavePNG(); +void SaveSSX(); int GetWidth(); int GetHeight(); diff --git a/Base/PNG.cpp b/Base/PNG.cpp index ecdecbc8..bb421f8c 100644 --- a/Base/PNG.cpp +++ b/Base/PNG.cpp @@ -241,7 +241,7 @@ bool Save(CScreen* pScreen_) // Report what happened if (fRet) - Frame::SetStatus("Saved %s", pszFile); + Frame::SetStatus("Saved %s", szPath); else Frame::SetStatus("PNG save failed!?"); #else diff --git a/Base/SAMIO.cpp b/Base/SAMIO.cpp index e690cd85..8f9c9ea2 100644 --- a/Base/SAMIO.cpp +++ b/Base/SAMIO.cpp @@ -95,6 +95,7 @@ BYTE keyports[9]; // 8 rows of keys (+ 1 row for unscanned keys) BYTE keybuffer[9]; // working buffer for key changed, activated mid-frame bool fASICStartup; // If set, the ASIC will be unresponsive shortly after first power-on +bool display_changed; // Mid-frame main display change using VMPR or CLUT int g_nAutoLoad = AUTOLOAD_NONE; // don't auto-load on startup @@ -323,6 +324,14 @@ void OutVmpr(BYTE bVal_) // The ASIC changes mode before page, so consider an on-screen artifact from the mode change Frame::ChangeMode(bVal_); + if ((vmpr ^ bVal_) & (VMPR_MODE_MASK | VMPR_PAGE_MASK)) + { + int line; + Frame::GetRasterPos(&line); + if (IsScreenLine(line)) + display_changed = true; + } + vmpr = bVal_ & (VMPR_MODE_MASK | VMPR_PAGE_MASK); vmpr_mode = VMPR_MODE; @@ -354,6 +363,11 @@ void OutClut(WORD wPort_, BYTE bVal_) // Has the clut value actually changed? if (clut[wPort_] != bVal_) { + int line; + Frame::GetRasterPos(&line); + if (IsScreenLine(line)) + display_changed = true; + // Draw up to the current point with the previous settings Frame::Update(); @@ -434,13 +448,13 @@ BYTE In(WORD wPort_) bRet = lmpr; break; - // High memory page register + // High memory page register case HMPR_PORT: bRet = hmpr; break; - // Video memory page register + // Video memory page register case VMPR_PORT: bRet = vmpr; bRet |= 0x80; // RXMIDI bit always one for now diff --git a/Base/SAMIO.h b/Base/SAMIO.h index de697b8a..4f24af05 100644 --- a/Base/SAMIO.h +++ b/Base/SAMIO.h @@ -250,3 +250,4 @@ extern CDiskDevice* pFloppy1, * pFloppy2, * pBootDrive; extern CIoDevice* pParallel1, * pParallel2; extern int g_nAutoLoad; +extern bool display_changed; diff --git a/Base/SSX.cpp b/Base/SSX.cpp new file mode 100644 index 00000000..97fe5d72 --- /dev/null +++ b/Base/SSX.cpp @@ -0,0 +1,101 @@ +// Part of SimCoupe - A SAM Coupe emulator +// +// SSX.cpp: SAM main screen data saving in raw formats +// +// These files hold the display memory data for the main screen area, +// followed by the CLUT indices into the 128 SAM palette colours. +// +// MODE 1 = 6144 data + 768 attrs + 16 CLUT = 6928 bytes. +// MODE 2 = 6144 data + 6144 attrs + 16 CLUT = 12304 bytes. +// MODE 3 = 24576 data + 4 CLUT = 24580 bytes. +// MODE 4 = 24576 data + 16 CLUT = 24592 bytes. +// +// Mid-display changes to VMPR or CLUT will give the wrong result for +// the dumps above. If detected the file is written in a different format: +// +// 512x192 pixels, each holding palette index (0-127) = 98304 bytes. +// +// The extra horizontal resolution is required for MODE 3. In other modes +// each native pixel is represented by a pair of thin pixels. + +#include "SimCoupe.h" +#include "SAMIO.h" +#include "Memory.h" + +namespace SSX +{ + +bool Save(CScreen* screen, int main_x, int main_y) +{ + char szPath[MAX_PATH]{}; + Util::GetUniqueFile("ssx", szPath, sizeof(szPath)); + + auto file = fopen(szPath, "wb"); + if (!file) + { + Frame::SetStatus("Failed to open %s for writing!", szPath); + return false; + } + + if (display_changed) + { + for (auto y = 0; y < SCREEN_LINES; ++y) + fwrite(screen->GetLine(main_y + y) + main_x, 2, SCREEN_PIXELS, file); + } + else + { + auto vmpr_page = VMPR_PAGE; + auto screen_mode = 1 + (VMPR_MODE >> VMPR_MODE_SHIFT); + + const void* ptr0 = PageReadPtr(vmpr_page); + const void* ptr1 = nullptr; + size_t size0 = 0; + size_t size1 = 0; + + switch (screen_mode) + { + case 1: + ptr0 = PageReadPtr(vmpr_page); + size0 = 32*192 + 32*24; + break; + case 2: + ptr0 = PageReadPtr(vmpr_page); + ptr1 = PageReadPtr(vmpr_page) + 0x2000; + size0 = size1 = 32*192; + break; + case 3: + case 4: + { + vmpr_page &= ~1; + ptr0 = PageReadPtr(vmpr_page); + ptr1 = PageReadPtr((vmpr_page + 1) & (MEM_PAGE_SIZE - 1)); + size0 = 128*128; + size1 = 128*64; + break; + } + } + + if (ptr0 && size0) + fwrite(ptr0, 1, size0, file); + if (ptr1 && size1) + fwrite(ptr1, 1, size1, file); + + if (screen_mode == 3) + { + for (int i = 0; i < 4; ++i) + fputc(mode3clut[i], file); + } + else + { + for (int i = 0; i < N_CLUT_REGS; ++i) + fputc(clut[i], file); + } + } + + fclose(file); + Frame::SetStatus("Saved %s", szPath); + + return true; +} + +} diff --git a/Base/SSX.h b/Base/SSX.h new file mode 100644 index 00000000..4d53f8ce --- /dev/null +++ b/Base/SSX.h @@ -0,0 +1,11 @@ +// Part of SimCoupe - A SAM Coupe emulator +// +// SSX.h: SAM main screen data saving in raw formats + +#pragma once +#include "Screen.h" + +namespace SSX +{ + bool Save(CScreen* screen, int main_x, int main_y); +} diff --git a/SDL/Input.cpp b/SDL/Input.cpp index 9365cb63..597b4618 100644 --- a/SDL/Input.cpp +++ b/SDL/Input.cpp @@ -363,7 +363,7 @@ bool Input::FilterEvent(SDL_Event* pEvent_) case HK_KPPLUS: Actions::Do(fCtrl ? Action::TempTurbo : Action::SpeedFaster, fPress); break; case HK_KPMINUS: Actions::Do(fCtrl ? Action::SpeedNormal : Action::SpeedSlower, fPress); break; - case HK_PRINT: Actions::Do(Action::SaveScreenshot, fPress); break; + case HK_PRINT: Actions::Do(Action::SavePNG, fPress); break; case HK_SCROLL: case HK_PAUSE: Actions::Do(fCtrl ? Action::ResetButton : fShift ? Action::FrameStep : Action::Pause, fPress); break; diff --git a/SDL/UI.cpp b/SDL/UI.cpp index 2b294e2f..a03f9f0a 100644 --- a/SDL/UI.cpp +++ b/SDL/UI.cpp @@ -174,7 +174,7 @@ bool UI::CheckEvents() case UE_TOGGLESCANLINES: Actions::Do(Action::ToggleScanlines); break; case UE_TOGGLE54: Actions::Do(Action::Toggle5_4); break; case UE_DEBUGGER: Actions::Do(Action::Debugger); break; - case UE_SAVESCREENSHOT: Actions::Do(Action::SaveScreenshot); break; + case UE_SAVESCREENSHOT: Actions::Do(Action::SavePNG); break; case UE_PAUSE: Actions::Do(Action::Pause); break; case UE_TOGGLETURBO: Actions::Do(Action::ToggleTurbo); break; case UE_TOGGLEMUTE: Actions::Do(Action::ToggleMute); break; diff --git a/Win32/SimCoupe.rc b/Win32/SimCoupe.rc index 206ff200..b1a34d9a 100644 --- a/Win32/SimCoupe.rc +++ b/Win32/SimCoupe.rc @@ -573,6 +573,9 @@ BEGIN MENUITEM SEPARATOR MENUITEM "&Stop Recording", IDM_RECORD_WAV_STOP END + MENUITEM SEPARATOR + MENUITEM "Save Screenshot (&PNG)" IDM_RECORD_SCREEN_PNG + MENUITEM "Save Screenshot (SS&X)" IDM_RECORD_SCREEN_SSX END POPUP "&System" BEGIN diff --git a/Win32/UI.cpp b/Win32/UI.cpp index 8d4ddca3..8e149d8d 100644 --- a/Win32/UI.cpp +++ b/Win32/UI.cpp @@ -1824,7 +1824,7 @@ LRESULT CALLBACK WindowProc(HWND hwnd_, UINT uMsg_, WPARAM wParam_, LPARAM lPara case VK_SNAPSHOT: case VK_SCROLL: if (!fPress) - Actions::Do(Action::SaveScreenshot); + Actions::Do(Action::SavePNG); break; // Use the default behaviour for anything we're not using @@ -1881,6 +1881,9 @@ LRESULT CALLBACK WindowProc(HWND hwnd_, UINT uMsg_, WPARAM wParam_, LPARAM lPara case IDM_RECORD_WAV_SEGMENT: Actions::Do(Action::RecordWavSegment); break; case IDM_RECORD_WAV_STOP: Actions::Do(Action::RecordWavStop); break; + case IDM_RECORD_SCREEN_PNG: Actions::Do(Action::SavePNG); break; + case IDM_RECORD_SCREEN_SSX: Actions::Do(Action::SaveSSX); break; + case IDM_TOOLS_OPTIONS: Actions::Do(Action::Options); break; case IDM_TOOLS_PASTE_CLIPBOARD: Actions::Do(Action::Paste); break; case IDM_TOOLS_PRINTER_ONLINE: Actions::Do(Action::PrinterOnline); break; diff --git a/Win32/resource.h b/Win32/resource.h index dc6e5376..24a513b9 100644 --- a/Win32/resource.h +++ b/Win32/resource.h @@ -229,6 +229,8 @@ #define IDM_RECORD_GIF_START 40231 #define IDM_RECORD_WAV_START 40232 #define IDM_RECORD_AVI_HALF 40235 +#define IDM_RECORD_SCREEN_PNG 40236 +#define IDM_RECORD_SCREEN_SSX 40237 #define IDM_SYSTEM_SPEED_50 40250 #define IDM_SYSTEM_SPEED_100 40251 #define IDM_SYSTEM_SPEED_200 40252