AppleWin/source/Windows/AppleWin.cpp
TomCh b371e3436f
Support HDC SmartPort firmware for Enhanced //e (#1207, PR #1314)
Added new SmartPort Harddisk Controller (HDC) firmware for Enhanced //e. (@burniouf)
. remove the old DOSMaster entrypoints.
. firmware ID byte (at $FE) defaults to 4 ProDOS block devices (was 2 with older v2 f/w).
Enhanced //e defaults to using SP f/w, and older Apple II's still use the older v2 f/w.
Support up to 8 units per HDC (currently limited to 4GiB capacity for each unit).
Save-state: save HDC firmware in state file.
New slot-specific switches to configure each HDC's firmware: -s<N> -hdc-[sp|bm2|bm4].
New switch to select old non-SP firmware for all HDCs: -hdc-firmware-v2.
2024-07-28 16:12:36 +01:00

1032 lines
32 KiB
C++

/*
AppleWin : An Apple //e emulator for Windows
Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2014, Tom Charlesworth, Michael Pohoreski
AppleWin is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
AppleWin is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with AppleWin; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* Description: main
*
* Author: Various
*/
#include "StdAfx.h"
#include "Windows/AppleWin.h"
#include "Windows/HookFilter.h"
#include "Interface.h"
#include "Utilities.h"
#include "CmdLine.h"
#include "Debug.h"
#include "Log.h"
#include "Memory.h"
#include "Mockingboard.h"
#include "MouseInterface.h"
#include "ParallelPrinter.h"
#include "Registry.h"
#include "Riff.h"
#include "SaveState.h"
#include "SerialComms.h"
#include "SoundCore.h"
#include "Speaker.h"
#include "LanguageCard.h"
#include "CardManager.h"
#ifdef USE_SPEECH_API
#include "Speech.h"
#endif
#include "Windows/Win32Frame.h"
#include "RGBMonitor.h"
#include "NTSC.h"
#include "Configuration/About.h"
#include "Configuration/PropertySheet.h"
//=================================================
static bool g_bLoadedSaveState = false;
static bool g_bSysClkOK = false;
bool g_bRestartFullScreen = false;
static bool g_fullScreenResolutionChangedByUser = false;
//===========================================================================
bool GetLoadedSaveStateFlag(void)
{
return g_bLoadedSaveState;
}
void Win32Frame::SetLoadedSaveStateFlag(const bool bFlag)
{
g_bLoadedSaveState = bFlag;
}
bool GetHookAltTab(void)
{
return g_bHookAltTab;
}
bool GetHookAltGrControl(void)
{
return g_bHookAltGrControl;
}
bool GetFullScreenResolutionChangedByUser(void)
{
return g_fullScreenResolutionChangedByUser;
}
static void ResetToLogoMode(void)
{
g_nAppMode = MODE_LOGO;
GetFrame().SetLoadedSaveStateFlag(false);
}
//---------------------------------------------------------------------------
static bool g_bPriorityNormal = true;
// Make APPLEWIN process higher priority
void SetPriorityAboveNormal(void)
{
if (!g_bPriorityNormal)
return;
if ( SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS) )
{
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
g_bPriorityNormal = false;
}
}
// Make APPLEWIN process normal priority
void SetPriorityNormal(void)
{
if (g_bPriorityNormal)
return;
if ( SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS) )
{
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
g_bPriorityNormal = true;
}
}
//---------------------------------------------------------------------------
static UINT g_uModeStepping_Cycles = 0;
static bool g_uModeStepping_LastGetKey_ScrollLock = false;
static void ContinueExecution(void)
{
#ifdef LOG_PERF_TIMINGS
PerfMarker* pPerfMarkerTotal = new PerfMarker(g_timeTotal);
#endif
_ASSERT(g_nAppMode == MODE_RUNNING || g_nAppMode == MODE_STEPPING);
const double fUsecPerSec = 1.e6;
#if 1
const UINT nExecutionPeriodUsec = 1000; // 1.0ms
// const UINT nExecutionPeriodUsec = 100; // 0.1ms
const double fExecutionPeriodClks = g_fCurrentCLK6502 * ((double)nExecutionPeriodUsec / fUsecPerSec);
#else
const double fExecutionPeriodClks = 1800.0;
const UINT nExecutionPeriodUsec = (UINT) (fUsecPerSec * (fExecutionPeriodClks / g_fCurrentCLK6502));
#endif
//
bool bScrollLock_FullSpeed = false;
if (GetPropertySheet().GetScrollLockToggle())
{
bScrollLock_FullSpeed = Win32Frame::GetWin32Frame().g_bScrollLock_FullSpeed;
}
else
{
if (g_nAppMode == MODE_RUNNING)
{
bScrollLock_FullSpeed = GetKeyState(VK_SCROLL) < 0;
}
else if (!IsDebugSteppingAtFullSpeed()) // Implicitly: MODE_STEPPING
{
// NB. For MODE_STEPPING: GetKeyState() is slow, so only call periodically
// . 0x3FFF is roughly the number of cycles in a video frame, which seems a reasonable rate to call GetKeyState()
if ((g_uModeStepping_Cycles & 0x3FFF) == 0)
g_uModeStepping_LastGetKey_ScrollLock = GetKeyState(VK_SCROLL) < 0;
bScrollLock_FullSpeed = g_uModeStepping_LastGetKey_ScrollLock;
}
}
const bool bWasFullSpeed = g_bFullSpeed;
g_bFullSpeed = (g_dwSpeed == SPEED_MAX) ||
bScrollLock_FullSpeed ||
(GetCardMgr().GetDisk2CardMgr().IsConditionForFullSpeed() && !Spkr_IsActive() && !GetCardMgr().GetMockingboardCardMgr().IsActive()) ||
IsDebugSteppingAtFullSpeed();
if (g_bFullSpeed)
{
if (!bWasFullSpeed)
GetFrame().VideoRedrawScreenDuringFullSpeed(0, true); // Init for full-speed mode
// Don't call Spkr_Mute() - will get speaker clicks
GetCardMgr().GetMockingboardCardMgr().MuteControl(true);
SysClk_StopTimer();
#ifdef USE_SPEECH_API
g_Speech.Reset(); // TODO: Put this on a timer (in emulated cycles)... otherwise CATALOG cuts out
#endif
g_nCpuCyclesFeedback = 0; // For the case when this is a big -ve number
// Switch to normal priority so that APPLEWIN process doesn't hog machine!
//. EG: No disk in Drive-1, and boot Apple: Windows will start to crawl!
SetPriorityNormal();
}
else
{
if (bWasFullSpeed)
GetFrame().VideoRedrawScreenAfterFullSpeed(g_dwCyclesThisFrame);
// Don't call Spkr_Unmute()
GetCardMgr().GetMockingboardCardMgr().MuteControl(false);
SysClk_StartTimerUsec(nExecutionPeriodUsec);
// Switch to higher priority, eg. for audio (BUG #015394)
SetPriorityAboveNormal();
}
//
int nCyclesWithFeedback = (int) fExecutionPeriodClks + g_nCpuCyclesFeedback;
const UINT uCyclesToExecuteWithFeedback = (nCyclesWithFeedback >= 0) ? nCyclesWithFeedback
: 0;
const DWORD uCyclesToExecute = (g_nAppMode == MODE_RUNNING) ? uCyclesToExecuteWithFeedback
/* MODE_STEPPING */ : 0;
const bool bVideoUpdate = !g_bFullSpeed;
const DWORD uActualCyclesExecuted = CpuExecute(uCyclesToExecute, bVideoUpdate);
g_dwCyclesThisFrame += uActualCyclesExecuted;
GetCardMgr().Update(uActualCyclesExecuted);
//
DWORD uSpkrActualCyclesExecuted = uActualCyclesExecuted;
bool bModeStepping_WaitTimer = false;
if (g_nAppMode == MODE_STEPPING && !IsDebugSteppingAtFullSpeed())
{
g_uModeStepping_Cycles += uActualCyclesExecuted;
if (g_uModeStepping_Cycles >= uCyclesToExecuteWithFeedback)
{
uSpkrActualCyclesExecuted = g_uModeStepping_Cycles;
g_uModeStepping_Cycles -= uCyclesToExecuteWithFeedback;
bModeStepping_WaitTimer = true;
}
}
// For MODE_STEPPING: do this speaker update periodically
// - Otherwise kills performance due to sound-buffer lock/unlock for every 6502 opcode!
if (g_nAppMode == MODE_RUNNING || bModeStepping_WaitTimer)
SpkrUpdate(uSpkrActualCyclesExecuted);
//
const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame();
if (g_dwCyclesThisFrame >= dwClksPerFrame && !GetVideo().VideoGetVblBarEx(g_dwCyclesThisFrame))
{
#ifdef LOG_PERF_TIMINGS
PerfMarker perfMarkerVideoRefresh(g_timeVideoRefresh);
#endif
g_dwCyclesThisFrame -= dwClksPerFrame;
if (g_bFullSpeed)
GetFrame().VideoRedrawScreenDuringFullSpeed(g_dwCyclesThisFrame);
else
GetFrame().VideoPresentScreen(); // Just copy the output of our Apple framebuffer to the system Back Buffer
}
#ifdef LOG_PERF_TIMINGS
delete pPerfMarkerTotal; // Explicitly call dtor *before* SysClk_WaitTimer()
#endif
if ((g_nAppMode == MODE_RUNNING && !g_bFullSpeed) || bModeStepping_WaitTimer)
{
SysClk_WaitTimer();
}
}
void SingleStep(bool bReinit)
{
if (bReinit)
{
g_uModeStepping_Cycles = 0;
g_uModeStepping_LastGetKey_ScrollLock = false;
}
ContinueExecution();
}
//===========================================================================
void EnterMessageLoop(void)
{
MSG message;
PeekMessage(&message, NULL, 0, 0, PM_NOREMOVE);
while (message.message!=WM_QUIT)
{
if (PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&message);
DispatchMessage(&message);
while ((g_nAppMode == MODE_RUNNING) || (g_nAppMode == MODE_STEPPING))
{
if (PeekMessage(&message,0,0,0,PM_REMOVE))
{
if (message.message == WM_QUIT)
return;
TranslateMessage(&message);
DispatchMessage(&message);
}
else if (g_nAppMode == MODE_STEPPING)
{
DebugContinueStepping();
}
else
{
ContinueExecution();
if (g_nAppMode != MODE_DEBUG)
{
if (g_bFullSpeed)
ContinueExecution();
}
}
}
}
else
{
if (g_nAppMode == MODE_DEBUG)
DebuggerUpdate();
else if (g_nAppMode == MODE_PAUSED)
Sleep(1); // Stop process hogging CPU - 1ms, as need to fade-out speaker sound buffer
else if (g_nAppMode == MODE_LOGO)
Sleep(1); // Stop process hogging CPU (NB. don't delay for too long otherwise key input can be slow in other apps - GH#569)
}
}
}
//===========================================================================
static void GetProgramDirectory(void)
{
TCHAR programDir[MAX_PATH];
GetModuleFileName((HINSTANCE)0, programDir, MAX_PATH);
programDir[MAX_PATH-1] = 0;
g_sProgramDir = programDir;
int loop = g_sProgramDir.size();
while (loop--)
{
if ((g_sProgramDir[loop] == TEXT(PATH_SEPARATOR)) || (g_sProgramDir[loop] == TEXT(':')))
{
g_sProgramDir.resize(loop + 1); // this reduces the size
break;
}
}
}
//===========================================================================
void RegisterExtensions(void)
{
char szModuleFileName[MAX_PATH];
GetModuleFileName(static_cast<HMODULE>(NULL), szModuleFileName, sizeof(szModuleFileName));
// Wrap path & filename in quotes & null terminate
std::string command = std::string("\"") + szModuleFileName + '"';
std::string const icon = command + ",1";
command += " \"%1\""; // Append ' "%1"'
// command += " -d1 \"%1\""; // Append ' -d1 "%1"'
// command += " \"-d1 %1\""; // Append ' "-d1 %1"'
// NB. Registry access to HKLM typically results in ErrorCode 5(ACCESS DENIED), as UAC requires elevated permissions (Run as administrator).
// . HKEY_CLASSES_ROOT\CLSID is a merged view of HKLM\SOFTWARE\Classes and HKCU\SOFTWARE\Classes
// NB. Reflect extensions in DELREG.INF
// RegSetValue(HKEY_CLASSES_ROOT,".bin",REG_SZ,"DiskImage",0); // Removed as .bin is too generic
const char* pValueName = ".bin";
LSTATUS res = RegDeleteValue(HKEY_CLASSES_ROOT, pValueName);
if (res != NOERROR && res != ERROR_FILE_NOT_FOUND) LogFileOutput("RegDeleteValue(%s) failed (0x%08X)\n", pValueName, res);
pValueName = ".do";
res = RegSetValue(HKEY_CLASSES_ROOT, pValueName ,REG_SZ,"DiskImage",0);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
pValueName = ".dsk";
res = RegSetValue(HKEY_CLASSES_ROOT, pValueName, REG_SZ,"DiskImage",0);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
pValueName = ".nib";
res = RegSetValue(HKEY_CLASSES_ROOT, pValueName, REG_SZ,"DiskImage",0);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
pValueName = ".po";
res = RegSetValue(HKEY_CLASSES_ROOT, pValueName, REG_SZ,"DiskImage",0);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
pValueName = ".woz";
res = RegSetValue(HKEY_CLASSES_ROOT, pValueName, REG_SZ,"DiskImage",0);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
// RegSetValue(HKEY_CLASSES_ROOT,".2mg",REG_SZ,"DiskImage",0); // Don't grab this, as not all .2mg images are supported (so defer to CiderPress)
// RegSetValue(HKEY_CLASSES_ROOT,".2img",REG_SZ,"DiskImage",0); // Don't grab this, as not all .2mg images are supported (so defer to CiderPress)
// RegSetValue(HKEY_CLASSES_ROOT,".aws.yaml",REG_SZ,"DiskImage",0); // NB. Can't grab this extension (even though it returns 0!) with embedded period (and .yaml is too generic) - GH#548
// RegSetValue(HKEY_CLASSES_ROOT,".hdv",REG_SZ,"DiskImage",0); // TO DO
pValueName = "DiskImage";
res = RegSetValue(HKEY_CLASSES_ROOT,
pValueName,
REG_SZ, "Disk Image", 0);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
pValueName = "DiskImage\\DefaultIcon";
res = RegSetValue(HKEY_CLASSES_ROOT,
pValueName,
REG_SZ, icon.c_str(), 0);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
// This key can interfere....
// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExt\.dsk
pValueName = "DiskImage\\shell\\open\\command";
res = RegSetValue(HKEY_CLASSES_ROOT,
pValueName,
REG_SZ, command.c_str(), command.length() + 1);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
pValueName = "DiskImage\\shell\\open\\ddeexec";
res = RegSetValue(HKEY_CLASSES_ROOT,
pValueName,
REG_SZ, "%1", 3);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
pValueName = "DiskImage\\shell\\open\\ddeexec\\application";
res = RegSetValue(HKEY_CLASSES_ROOT,
pValueName,
REG_SZ, "applewin", strlen("applewin") + 1);
// REG_SZ, szModuleFileName, strlen(szModuleFileName)+1);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
pValueName = "DiskImage\\shell\\open\\ddeexec\\topic";
res = RegSetValue(HKEY_CLASSES_ROOT,
pValueName,
REG_SZ, "system", strlen("system") + 1);
if (res != NOERROR) LogFileOutput("RegSetValue(%s) failed (0x%08X)\n", pValueName, res);
}
//===========================================================================
// NB. On a restart, it's OK to call RegisterHotKey() again since the old g_hFrameWindow has been destroyed
static void RegisterHotKeys(void)
{
BOOL bStatus[3] = {0,0,0};
bStatus[0] = RegisterHotKey(
GetFrame().g_hFrameWindow , // HWND hWnd
VK_SNAPSHOT_560, // int id (user/custom id)
0 , // UINT fsModifiers
VK_SNAPSHOT // UINT vk = PrintScreen
);
bStatus[1] = RegisterHotKey(
GetFrame().g_hFrameWindow , // HWND hWnd
VK_SNAPSHOT_280, // int id (user/custom id)
MOD_SHIFT , // UINT fsModifiers
VK_SNAPSHOT // UINT vk = PrintScreen
);
bStatus[2] = RegisterHotKey(
GetFrame().g_hFrameWindow , // HWND hWnd
VK_SNAPSHOT_TEXT, // int id (user/custom id)
MOD_CONTROL , // UINT fsModifiers
VK_SNAPSHOT // UINT vk = PrintScreen
);
if ((!bStatus[0] || !bStatus[1] || !bStatus[2]))
{
std::string msg("Unable to register for PrintScreen key(s):\n");
if (!bStatus[0])
msg += "\n. PrintScreen";
if (!bStatus[1])
msg += "\n. Shift+PrintScreen";
if (!bStatus[2])
msg += "\n. Ctrl+PrintScreen";
if (GetFrame().GetShowPrintScreenWarningDialog())
SHMessageBoxCheck( GetFrame().g_hFrameWindow, msg.c_str(), "Warning", MB_ICONASTERISK | MB_OK, MB_OK, "AppleWin-75097740-8e59-444c-bc94-2d4915132599" );
msg += "\n";
LogFileOutput(msg.c_str());
}
}
//---------------------------------------------------------------------------
static void ExceptionHandler(const char* pError)
{
GetFrame().FrameMessageBox(
pError,
TEXT("Runtime Exception"),
MB_ICONEXCLAMATION | MB_SETFOREGROUND);
LogFileOutput("Runtime Exception: %s\n", pError);
}
//---------------------------------------------------------------------------
static void GetAppleWinVersion(void);
static void OneTimeInitialization(HINSTANCE passinstance);
static void RepeatInitialization(void);
static void Shutdown(void);
int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int)
{
char startDir[_MAX_PATH];
GetCurrentDirectory(sizeof(startDir), startDir);
g_sStartDir = startDir;
if (*(g_sStartDir.end()-1) != PATH_SEPARATOR) g_sStartDir += PATH_SEPARATOR;
if (!ProcessCmdLine(lpCmdLine))
return 0;
LogFileOutput("g_sStartDir = %s\n", g_sStartDir.c_str());
GetAppleWinVersion();
OneTimeInitialization(passinstance);
try
{
do
{
g_bRestart = false;
RepeatInitialization();
// ENTER THE MAIN MESSAGE LOOP
LogFileOutput("Main: EnterMessageLoop()\n");
EnterMessageLoop();
LogFileOutput("Main: LeaveMessageLoop()\n");
if (g_bRestart)
{
g_cmdLine.setFullScreen = g_bRestartFullScreen ? 1 : 0;
g_bRestartFullScreen = false;
CMouseInterface* pMouseCard = GetCardMgr().GetMouseCard();
if (pMouseCard)
{
// dtor removes event from g_SynchronousEventMgr - do before g_SynchronousEventMgr.Reset()
GetCardMgr().Remove( pMouseCard->GetSlot(), false );
LogFileOutput("Main: CMouseInterface::dtor\n");
}
_ASSERT(g_SynchronousEventMgr.GetHead() == NULL);
g_SynchronousEventMgr.Reset();
}
DSUninit();
LogFileOutput("Main: DSUninit()\n");
if (g_bHookSystemKey)
{
GetHookFilter().UninitHookThread();
LogFileOutput("Main: UnhookFilterForKeyboard()\n");
}
}
while (g_bRestart);
Shutdown();
}
catch (const std::exception& exception)
{
ExceptionHandler(exception.what());
}
return 0;
}
static void GetAppleWinVersion(void)
{
char szPath[_MAX_PATH];
if (0 == GetModuleFileName(NULL, szPath, sizeof(szPath)))
strcpy_s(szPath, sizeof(szPath), __argv[0]);
// Extract application version and store in a global variable
DWORD dwHandle, dwVerInfoSize;
dwVerInfoSize = GetFileVersionInfoSize(szPath, &dwHandle);
if (dwVerInfoSize > 0)
{
char* pVerInfoBlock = new char[dwVerInfoSize];
if (GetFileVersionInfo(szPath, NULL, dwVerInfoSize, pVerInfoBlock))
{
VS_FIXEDFILEINFO* pFixedFileInfo;
UINT pFixedFileInfoLen;
VerQueryValue(pVerInfoBlock, TEXT("\\"), (LPVOID*) &pFixedFileInfo, (PUINT) &pFixedFileInfoLen);
// Construct version string from fixed file info block
UINT16 major = pFixedFileInfo->dwFileVersionMS >> 16;
UINT16 minor = pFixedFileInfo->dwFileVersionMS & 0xffff;
UINT16 fix = pFixedFileInfo->dwFileVersionLS >> 16;
UINT16 fix_minor = pFixedFileInfo->dwFileVersionLS & 0xffff;
SetAppleWinVersion(major, minor, fix, fix_minor);
}
delete [] pVerInfoBlock;
}
LogFileOutput("AppleWin version: %s\n", g_VERSIONSTRING.c_str());
}
// DO ONE-TIME INITIALIZATION
static void OneTimeInitialization(HINSTANCE passinstance)
{
// Currently only support one RIFF file
if (!g_cmdLine.wavFileSpeaker.empty())
{
if (RiffInitWriteFile(g_cmdLine.wavFileSpeaker.c_str(), SPKR_SAMPLE_RATE, Spkr_GetNumChannels()))
Spkr_OutputToRiff();
}
else if (!g_cmdLine.wavFileMockingboard.empty())
{
if (RiffInitWriteFile(g_cmdLine.wavFileMockingboard.c_str(), MockingboardCard::SAMPLE_RATE, MockingboardCard::NUM_MB_CHANNELS))
GetCardMgr().GetMockingboardCardMgr().OutputToRiff();
}
// Initialize COM - so we can use CoCreateInstance
// . DSInit() & DIMouse::DirectInputInit are done when g_hFrameWindow is created (WM_CREATE)
// . DDInit() is done in RepeatInitialization() by GetVideo().Initialize()
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
LogFileOutput("Init: CoInitializeEx(), hr=0x%08X\n", hr);
g_bSysClkOK = SysClk_InitTimer();
LogFileOutput("Init: SysClk_InitTimer(), res=%d\n", g_bSysClkOK ? 1:0);
#ifdef USE_SPEECH_API
if (g_bEnableSpeech)
{
const bool bSpeechOK = g_Speech.Init();
LogFileOutput("Init: SysClk_InitTimer(), res=%d\n", bSpeechOK ? 1:0);
}
#endif
GetFrame().g_hInstance = passinstance;
GdiSetBatchLimit(512);
LogFileOutput("Init: GdiSetBatchLimit()\n");
GetProgramDirectory();
LogFileOutput("Init: GetProgramDirectory()\n");
if (g_bRegisterFileTypes)
{
RegisterExtensions();
LogFileOutput("Init: RegisterExtensions()\n");
}
Win32Frame::GetWin32Frame().FrameRegisterClass();
LogFileOutput("Init: FrameRegisterClass()\n");
}
// DO INITIALIZATION THAT MUST BE REPEATED FOR A RESTART
static void RepeatInitialization(void)
{
GetVideo().SetVidHD(false); // Set true later only if VidHDCard is instantiated
ResetToLogoMode();
// NB. g_OldAppleWinVersion needed by LoadConfiguration() -> Config_Load_Video()
const bool bShowAboutDlg = CheckOldAppleWinVersion(); // Post: g_OldAppleWinVersion
// Load configuration from Registry (+ will insert cards)
{
bool loadImages = g_cmdLine.szSnapshotName == NULL; // don't load floppy/harddisk images if a snapshot is to be loaded later on
LoadConfiguration(loadImages);
LogFileOutput("Main: LoadConfiguration()\n");
}
if (g_cmdLine.model != A2TYPE_MAX)
SetApple2Type(g_cmdLine.model);
RGB_SetVideocard(g_cmdLine.rgbCard, g_cmdLine.rgbCardForegroundColor, g_cmdLine.rgbCardBackgroundColor);
if (g_cmdLine.newVideoType >= 0)
{
GetVideo().SetVideoType( (VideoType_e)g_cmdLine.newVideoType );
g_cmdLine.newVideoType = -1; // Don't reapply after a restart
}
GetVideo().SetVideoStyle( (VideoStyle_e) ((GetVideo().GetVideoStyle() | g_cmdLine.newVideoStyleEnableMask) & ~g_cmdLine.newVideoStyleDisableMask) );
if (g_cmdLine.newVideoRefreshRate != VR_NONE)
{
GetVideo().SetVideoRefreshRate(g_cmdLine.newVideoRefreshRate);
g_cmdLine.newVideoRefreshRate = VR_NONE; // Don't reapply after a restart
SetCurrentCLK6502();
}
UseClockMultiplier(g_cmdLine.clockMultiplier);
g_cmdLine.clockMultiplier = 0.0;
// Apply the memory expansion switches after loading the Apple II machine type
#ifdef RAMWORKS
if (g_cmdLine.uRamWorksExPages)
{
SetRamWorksMemorySize(g_cmdLine.uRamWorksExPages);
SetExpansionMemType(CT_RamWorksIII);
g_cmdLine.uRamWorksExPages = 0; // Don't reapply after a restart
}
#endif
if (g_cmdLine.uSaturnBanks)
{
Saturn128K::SetSaturnMemorySize(g_cmdLine.uSaturnBanks); // Set number of banks before constructing Saturn card
SetExpansionMemType(CT_Saturn128K);
g_cmdLine.uSaturnBanks = 0; // Don't reapply after a restart
}
if (g_cmdLine.bSlot0LanguageCard)
{
SetExpansionMemType(CT_LanguageCard);
g_cmdLine.bSlot0LanguageCard = false; // Don't reapply after a restart
}
if (g_cmdLine.bSwapButtons0and1)
{
GetPropertySheet().SetButtonsSwapState(true);
// Reapply after a restart - TODO: grey-out the Config UI for "Swap 0/1" when this cmd line is passed in
}
JoyInitialize();
LogFileOutput("Main: JoyInitialize()\n");
// Init palette color
VideoSwitchVideocardPalette(RGB_GetVideocard(), GetVideo().GetVideoType());
// Allow the 4 hardcoded slots to be configurated as empty
// NB. this state *is* persisted to the Registry/conf.ini (just like '-s7 empty' is)
// TODO: support bSlotEmpty[] for slots: 0
for (UINT i = SLOT1; i < NUM_SLOTS; i++)
{
if (g_cmdLine.bSlotEmpty[i])
GetCardMgr().Remove(i);
}
if (g_cmdLine.supportDCD && GetCardMgr().IsSSCInstalled())
{
GetCardMgr().GetSSC()->SupportDCD(true);
}
if (g_cmdLine.slotInsert[SLOT1] != CT_Empty && g_cmdLine.slotInsert[SLOT1] == CT_GenericPrinter) // For now just support Printer card in slot 1
{
GetCardMgr().Insert(SLOT1, g_cmdLine.slotInsert[SLOT1]);
}
if (g_cmdLine.slotInsert[SLOT2] != CT_Empty && g_cmdLine.slotInsert[SLOT2] == CT_SSC) // For now just support SSC in slot 2
{
GetCardMgr().Insert(SLOT2, g_cmdLine.slotInsert[SLOT2]);
}
if (g_cmdLine.enableDumpToRealPrinter && GetCardMgr().IsParallelPrinterCardInstalled())
{
GetCardMgr().GetParallelPrinterCard()->SetEnableDumpToRealPrinter(true);
}
if (g_cmdLine.slotInsert[SLOT3] != CT_Empty)
{
// NB. Only support Saturn in slot 3, otherwise there's more Config UI to change
if (g_cmdLine.slotInsert[SLOT3] == CT_VidHD || g_cmdLine.slotInsert[SLOT3] == CT_Saturn128K) // For now just support VidHD and Saturn128 in slot 3)
GetCardMgr().Insert(SLOT3, g_cmdLine.slotInsert[SLOT3]);
}
if (g_cmdLine.slotInsert[SLOT4] != CT_Empty)
{
GetCardMgr().Insert(SLOT4, g_cmdLine.slotInsert[SLOT4]);
}
if (g_cmdLine.slotInsert[SLOT5] != CT_Empty)
{
if (GetCardMgr().QuerySlot(SLOT5) != g_cmdLine.slotInsert[SLOT5]) // Ignore if already got this card type in slot 5
GetCardMgr().Insert(SLOT5, g_cmdLine.slotInsert[SLOT5]);
}
if (g_cmdLine.slotInsert[SLOT6] == CT_Disk2) // For now just support Disk2 in slot 6
{
if (GetCardMgr().QuerySlot(SLOT6) != g_cmdLine.slotInsert[SLOT6]) // Ignore if already got this card type in slot 6
GetCardMgr().Insert(SLOT6, g_cmdLine.slotInsert[SLOT6]);
}
if (g_cmdLine.slotInsert[SLOT7] != CT_Empty)
{
if (GetCardMgr().QuerySlot(SLOT7) != g_cmdLine.slotInsert[SLOT7]) // Ignore if already got this card type in slot 7
GetCardMgr().Insert(SLOT7, g_cmdLine.slotInsert[SLOT7]);
}
for (UINT i = SLOT0; i < NUM_SLOTS; i++)
{
if (GetCardMgr().QuerySlot(i) == CT_Disk2 && g_cmdLine.slotInfo[i].isDiskII13)
dynamic_cast<Disk2InterfaceCard&>(GetCardMgr().GetRef(i)).SetFirmware13Sector();
if (GetCardMgr().QuerySlot(i) == CT_GenericHDD)
{
dynamic_cast<HarddiskInterfaceCard&>(GetCardMgr().GetRef(i)).SetUserNumBlocks(g_cmdLine.uHarddiskNumBlocks);
if (g_cmdLine.useHdcFirmwareV1)
dynamic_cast<HarddiskInterfaceCard&>(GetCardMgr().GetRef(i)).UseHdcFirmwareV1();
if (g_cmdLine.useHdcFirmwareV2)
dynamic_cast<HarddiskInterfaceCard&>(GetCardMgr().GetRef(i)).UseHdcFirmwareV2();
dynamic_cast<HarddiskInterfaceCard&>(GetCardMgr().GetRef(i)).SetHdcFirmwareMode(g_cmdLine.slotInfo[i].useHdcFirmwareMode);
}
}
// Create window after inserting/removing VidHD card (as it affects width & height)
{
Win32Frame::GetWin32Frame().SetViewportScale(Win32Frame::GetWin32Frame().GetViewportScale(), true);
GetFrame().Initialize(true); // g_pFramebufferinfo been created now & COM init'ed
LogFileOutput("Main: VideoInitialize()\n");
LogFileOutput("Main: FrameCreateWindow() - pre\n");
Win32Frame::GetWin32Frame().FrameCreateWindow(); // GetFrame().g_hFrameWindow is now valid
LogFileOutput("Main: FrameCreateWindow() - post\n");
}
// Set best W,H resolution after inserting/removing VidHD card
if (g_cmdLine.bestFullScreenResolution || g_cmdLine.userSpecifiedWidth || g_cmdLine.userSpecifiedHeight)
{
bool res = false;
UINT bestWidth = 0, bestHeight = 0;
if (g_cmdLine.bestFullScreenResolution)
res = GetFrame().GetBestDisplayResolutionForFullScreen(bestWidth, bestHeight);
else
res = GetFrame().GetBestDisplayResolutionForFullScreen(bestWidth, bestHeight, g_cmdLine.userSpecifiedWidth, g_cmdLine.userSpecifiedHeight);
if (res)
LogFileOutput("Best resolution for -fs-width/height=x switch(es): Width=%d, Height=%d\n", bestWidth, bestHeight);
else
LogFileOutput("Failed to set parameter for -fs-width/height=x switch(es)\n");
if (res)
g_fullScreenResolutionChangedByUser = true;
}
// Pre: may need g_hFrameWindow for MessageBox errors
// Post: may enable HDD, required for MemInitialize()->MemInitializeIO()
{
bool temp = false;
InsertFloppyDisks(SLOT5, g_cmdLine.szImageName_drive[SLOT5], g_cmdLine.driveConnected[SLOT5], temp);
g_cmdLine.szImageName_drive[SLOT5][DRIVE_1] = g_cmdLine.szImageName_drive[SLOT5][DRIVE_2] = NULL; // Don't insert on a restart
InsertFloppyDisks(SLOT6, g_cmdLine.szImageName_drive[SLOT6], g_cmdLine.driveConnected[SLOT6], g_cmdLine.bBoot);
g_cmdLine.szImageName_drive[SLOT6][DRIVE_1] = g_cmdLine.szImageName_drive[SLOT6][DRIVE_2] = NULL; // Don't insert on a restart
InsertHardDisks(SLOT5, g_cmdLine.szImageName_harddisk[SLOT5], temp);
for (UINT i = 0; i < NUM_HARDDISKS; i++)
g_cmdLine.szImageName_harddisk[SLOT5][i] = NULL; // Don't insert on a restart
InsertHardDisks(SLOT7, g_cmdLine.szImageName_harddisk[SLOT7], g_cmdLine.bBoot);
for (UINT i = 0; i < NUM_HARDDISKS; i++)
g_cmdLine.szImageName_harddisk[SLOT7][i] = NULL; // Don't insert on a restart
if (g_cmdLine.bSlotEmpty[SLOT7])
{
GetCardMgr().Remove(SLOT7); // Disable HDD controller, and persist this to Registry/conf.ini (consistent with other '-sn empty' cmds)
Snapshot_UpdatePath(); // If save-state's filename is a harddisk, and the floppy is in the same path, then the filename won't be updated
}
}
// Set *after* InsertFloppyDisks() & InsertHardDisks(), which both update g_sCurrentDir
if (!g_cmdLine.strCurrentDir.empty())
SetCurrentImageDir(g_cmdLine.strCurrentDir);
if (g_cmdLine.bRemoveNoSlotClock)
MemRemoveNoSlotClock();
if (g_cmdLine.supportExtraMBCardTypes)
GetCardMgr().GetMockingboardCardMgr().SetEnableExtraCardTypes(true);
if (g_cmdLine.noDisk2StepperDefer)
GetCardMgr().GetDisk2CardMgr().SetStepperDefer(false);
// Call DebugInitialize() after SetCurrentImageDir()
DebugInitialize();
LogFileOutput("Main: DebugInitialize()\n");
MemInitialize();
LogFileOutput("Main: MemInitialize()\n");
// Show About dialog after creating main window (need g_hFrameWindow)
if (bShowAboutDlg)
{
if (!AboutDlg())
g_cmdLine.bShutdown = true; // Close everything down
else
RegSaveString(REG_CONFIG, REGVALUE_VERSION, TRUE, g_VERSIONSTRING); // Only save version after user accepts license
}
if (g_bCapturePrintScreenKey)
{
RegisterHotKeys(); // needs valid g_hFrameWindow
LogFileOutput("Main: RegisterHotKeys()\n");
}
if (g_bHookSystemKey)
{
if (GetHookFilter().InitHookThread()) // needs valid g_hFrameWindow (for message pump)
LogFileOutput("Main: HookFilterForKeyboard()\n");
}
// Need to test if it's safe to call ResetMachineState(). In the meantime, just call Disk2Card's Reset():
GetCardMgr().GetDisk2CardMgr().Reset(true); // Switch from a booting A][+ to a non-autostart A][, so need to turn off floppy motor
LogFileOutput("Main: DiskReset()\n");
if (GetCardMgr().QuerySlot(SLOT7) == CT_GenericHDD)
GetCardMgr().GetRef(SLOT7).Reset(true); // GH#515
LogFileOutput("Main: HDDReset()\n");
if (!g_bSysClkOK)
{
GetFrame().FrameMessageBox("DirectX failed to create SystemClock instance", TEXT("AppleWin Error"), MB_OK);
g_cmdLine.bShutdown = true;
}
if (g_bCustomRomF8Failed || g_bCustomRomFailed || (g_hCustomRomF8 != INVALID_HANDLE_VALUE && g_hCustomRom != INVALID_HANDLE_VALUE))
{
std::string msg = g_bCustomRomF8Failed ? "Failed to load custom F8 rom (not found or not exactly 2KiB)\n"
: g_bCustomRomFailed ? "Failed to load custom rom (not found or not exactly 12KiB or 16KiB)\n"
: "Unsupported -rom and -f8rom being used at the same time\n";
LogFileOutput("%s", msg.c_str());
GetFrame().FrameMessageBox(msg.c_str(), TEXT("AppleWin Error"), MB_OK);
g_cmdLine.bShutdown = true;
}
if (g_cmdLine.szSnapshotName)
{
std::string strPathname(g_cmdLine.szSnapshotName);
int nIdx = strPathname.find_last_of(PATH_SEPARATOR);
if (nIdx >= 0 && nIdx+1 < (int)strPathname.length()) // path exists?
{
const std::string strPath = strPathname.substr(0, nIdx+1);
SetCurrentImageDir(strPath);
}
// Override value just loaded from Registry by LoadConfiguration()
// . NB. Registry value is not updated with this cmd-line value
Snapshot_SetFilename(g_cmdLine.szSnapshotName);
Snapshot_LoadState();
g_cmdLine.bBoot = true;
g_cmdLine.szSnapshotName = NULL;
}
else
{
Snapshot_Startup(); // Do this after everything has been init'ed
LogFileOutput("Main: Snapshot_Startup()\n");
}
if (g_cmdLine.szScreenshotFilename)
{
GetFrame().Video_RedrawAndTakeScreenShot(g_cmdLine.szScreenshotFilename);
g_cmdLine.bShutdown = true;
}
if (g_cmdLine.bShutdown)
{
PostMessage(GetFrame().g_hFrameWindow, WM_DESTROY, 0, 0); // Close everything down
// NB. If shutting down, then don't post any other messages (GH#286)
}
else
{
if (g_cmdLine.setFullScreen > 0)
{
PostMessage(GetFrame().g_hFrameWindow, WM_USER_FULLSCREEN, 0, 0);
g_cmdLine.setFullScreen = 0;
}
if (g_cmdLine.bBoot)
{
PostMessage(GetFrame().g_hFrameWindow, WM_USER_BOOT, 0, 0);
g_cmdLine.bBoot = false;
}
}
}
static void Shutdown(void)
{
// NB. WM_CLOSE has already called SetNormalMode() to exit full screen mode & restore default resolution
// Release COM
SysClk_UninitTimer();
LogFileOutput("Exit: SysClk_UninitTimer()\n");
CoUninitialize();
LogFileOutput("Exit: CoUninitialize()\n");
LogDone();
RiffFinishWriteFile();
if (g_hCustomRomF8 != INVALID_HANDLE_VALUE)
CloseHandle(g_hCustomRomF8);
if (g_hCustomRom != INVALID_HANDLE_VALUE)
CloseHandle(g_hCustomRom);
if (g_cmdLine.bSlot7EmptyOnExit)
GetCardMgr().Remove(SLOT7);
}
IPropertySheet& GetPropertySheet(void)
{
static CPropertySheet sg_PropertySheet;
return sg_PropertySheet;
}
FrameBase& GetFrame(void)
{
static Win32Frame sg_Win32Frame;
return sg_Win32Frame;
}
Video& GetVideo(void)
{
static Video video;
return video;
}