mirror of
https://github.com/AppleWin/AppleWin.git
synced 2026-10-25 14:03:18 +00:00
1. Registry functions The set of functions are changed to consistently use bool arguments. The macros are changed to inline functions to improve type checking. RegSaveValue() are overloaded with help of templates to reduce typecasting at call sites. For instance, bool value would be converted consistently instead of relying on call sites to convert bool to int values. 2. Memory functions BOOL usages were unnecessary. Mostly internal usages which can simply be bool. 3. FrameBase Some member variables are changed to bool. BOOL was used because of those Registry functions. Additional changes: - Some expressions are forced to be bool when assigning to bool variables.
3158 lines
98 KiB
C++
3158 lines
98 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-2007, 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: Memory emulation
|
|
*
|
|
* Author: Various
|
|
*
|
|
* In comments, UTAIIe is an abbreviation for a reference to "Understanding the Apple //e" by James Sather
|
|
*/
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "Memory.h"
|
|
#include "Interface.h"
|
|
#include "Core.h"
|
|
#include "CardManager.h"
|
|
#include "CopyProtectionDongles.h"
|
|
#include "CPU.h"
|
|
#include "Joystick.h"
|
|
#include "Keyboard.h"
|
|
#include "LanguageCard.h"
|
|
#include "Log.h"
|
|
#include "NTSC.h"
|
|
#include "NoSlotClock.h"
|
|
#include "Pravets.h"
|
|
#include "Registry.h"
|
|
#include "Speaker.h"
|
|
#include "Tape.h"
|
|
#include "RGBMonitor.h"
|
|
#include "VidHD.h"
|
|
|
|
#include "z80emu.h"
|
|
#include "Z80VICE/z80.h"
|
|
#include "../resource/resource.h"
|
|
#include "Configuration/IPropertySheet.h"
|
|
#include "YamlHelper.h"
|
|
|
|
// In this file allocate the 64KB of RAM with aligned memory allocations (0x10000)
|
|
// to ease mapping between Apple ][ and host memory space (while debugging) & also to fix GH#1285.
|
|
// This is not available in Windows CRT:
|
|
// https://en.cppreference.com/w/c/memory/aligned_alloc
|
|
|
|
#ifdef _WIN32
|
|
// VirtualAlloc is aligned
|
|
#define ALIGNED_ALLOC(size) (LPBYTE)VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE)
|
|
#define ALIGNED_FREE(ptr) VirtualFree(ptr, 0, MEM_RELEASE)
|
|
#else
|
|
#include <unistd.h>
|
|
#include <sys/mman.h>
|
|
// use plain "new" in gcc (where debugging needs are less important)
|
|
#define ALIGNED_ALLOC(size) new BYTE[size]
|
|
#define ALIGNED_FREE(ptr) delete [] ptr
|
|
#endif
|
|
|
|
|
|
// UTAIIe:5-28 (GH#419)
|
|
// . Sather uses INTCXROM instead of SLOTCXROM' (used by the Apple//e Tech Ref Manual), so keep to this
|
|
// convention too since UTAIIe is the reference for most of the logic that we implement in the emulator.
|
|
|
|
#define SW_80STORE (g_memmode & MF_80STORE)
|
|
#define SW_ALTZP (g_memmode & MF_ALTZP)
|
|
#define SW_AUXREAD (g_memmode & MF_AUXREAD)
|
|
#define SW_AUXWRITE (g_memmode & MF_AUXWRITE)
|
|
#define SW_BANK2 (g_memmode & MF_BANK2)
|
|
#define SW_HIGHRAM (g_memmode & MF_HIGHRAM)
|
|
#define SW_HIRES (g_memmode & MF_HIRES)
|
|
#define SW_PAGE2 (g_memmode & MF_PAGE2)
|
|
#define SW_SLOTC3ROM (g_memmode & MF_SLOTC3ROM)
|
|
#define SW_INTCXROM (g_memmode & MF_INTCXROM)
|
|
#define SW_WRITERAM (g_memmode & MF_WRITERAM)
|
|
#define SW_IOUDIS (g_memmode & MF_IOUDIS)
|
|
#define SW_ALTROM0 (g_memmode & MF_ALTROM0) // For Copam Base64A
|
|
#define SW_ALTROM1 (g_memmode & MF_ALTROM1) // For Copam Base64A
|
|
|
|
/*
|
|
MEMORY MANAGEMENT SOFT SWITCHES
|
|
$C000 W 80STOREOFF Allow page2 to switch video page1 page2
|
|
$C001 W 80STOREON Allow page2 to switch main & aux video memory
|
|
$C002 W RAMRDOFF Read enable main memory from $0200-$BFFF
|
|
$C003 W RAMRDON Read enable aux memory from $0200-$BFFF
|
|
$C004 W RAMWRTOFF Write enable main memory from $0200-$BFFF
|
|
$C005 W RAMWRTON Write enable aux memory from $0200-$BFFF
|
|
$C006 W INTCXROMOFF Enable slot ROM from $C100-$C7FF (but $C800-$CFFF depends on INTC8ROM)
|
|
$C007 W INTCXROMON Enable main ROM from $C100-$CFFF
|
|
$C008 W ALTZPOFF Enable main memory from $0000-$01FF & avl BSR
|
|
$C009 W ALTZPON Enable aux memory from $0000-$01FF & avl BSR
|
|
$C00A W SLOTC3ROMOFF Enable main ROM from $C300-$C3FF
|
|
$C00B W SLOTC3ROMON Enable slot ROM from $C300-$C3FF
|
|
$C07E W IOUDIS [//c] On: disable IOU access for addresses $C058 to $C05F; enable access to DHIRES switch
|
|
$C07F W IOUDIS [//c] Off: enable IOU access for addresses $C058 to $C05F; disable access to DHIRES switch
|
|
|
|
VIDEO SOFT SWITCHES
|
|
$C00C W 80COLOFF Turn off 80 column display
|
|
$C00D W 80COLON Turn on 80 column display
|
|
$C00E W ALTCHARSETOFF Turn off alternate characters
|
|
$C00F W ALTCHARSETON Turn on alternate characters
|
|
$C022 R/W SCREENCOLOR [IIgs] text foreground and background colors (also VidHD)
|
|
$C029 R/W NEWVIDEO [IIgs] Select new video modes (also VidHD)
|
|
$C034 R/W BORDERCOLOR [IIgs] b3:0 are border color (also VidHD)
|
|
$C035 R/W SHADOW [IIgs] auxmem-to-bank-E1 shadowing (also VidHD)
|
|
$C050 R/W TEXTOFF Select graphics mode
|
|
$C051 R/W TEXTON Select text mode
|
|
$C052 R/W MIXEDOFF Use full screen for graphics
|
|
$C053 R/W MIXEDON Use graphics with 4 lines of text
|
|
$C054 R/W PAGE2OFF Select panel display (or main video memory)
|
|
$C055 R/W PAGE2ON Select page2 display (or aux video memory)
|
|
$C056 R/W HIRESOFF Select low resolution graphics
|
|
$C057 R/W HIRESON Select high resolution graphics
|
|
$C05E R/W DHIRESON Select double (14M) resolution graphics
|
|
$C05F R/W DHIRESOFF Select single (7M) resolution graphics
|
|
|
|
SOFT SWITCH STATUS FLAGS
|
|
$C010 R7 AKD 1=key pressed 0=keys free (clears strobe)
|
|
$C011 R7 BSRBANK2 1=bank2 available 0=bank1 available
|
|
$C012 R7 BSRREADRAM 1=BSR active for read 0=$D000-$FFFF active (BSR = Bank Switch RAM)
|
|
$C013 R7 RAMRD 0=main $0200-$BFFF active reads 1=aux active
|
|
$C014 R7 RAMWRT 0=main $0200-$BFFF active writes 1=aux active
|
|
$C015 R7 INTCXROM 1=main $C100-$CFFF ROM active 0=slot active
|
|
$C016 R7 ALTZP 1=aux $0000-$1FF+auxBSR 0=main available
|
|
$C017 R7 SLOTC3ROM 1=slot $C3 ROM active 0=main $C3 ROM active
|
|
$C018 R7 80STORE 1=page2 switches main/aux 0=page2 video
|
|
$C019 R7 VERTBLANK 1=vertical retrace on 0=vertical retrace off
|
|
$C01A R7 TEXT 1=text mode is active 0=graphics mode active
|
|
$C01B R7 MIXED 1=mixed graphics & text 0=full screen
|
|
$C01C R7 PAGE2 1=video page2 selected or aux
|
|
$C01D R7 HIRES 1=high resolution graphics 0=low resolution
|
|
$C01E R7 ALTCHARSET 1=alt character set on 0=alt char set off
|
|
$C01F R7 80COL 1=80 col display on 0=80 col display off
|
|
$C07E R7 RDIOUDIS [//c] 1=IOUDIS off 0=IOUDIS on
|
|
$C07F R7 RDDHIRES [Enhanced //e? or //c] 1=DHIRES on 0=DHIRES off
|
|
*/
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Notes
|
|
// -----
|
|
//
|
|
// memimage
|
|
// - 64KB
|
|
//
|
|
// mem
|
|
// (a pointer to memimage 64KB)
|
|
// - a 64KB r/w memory cache
|
|
// - reflects the current readable memory in the 6502's 64K address space
|
|
// . excludes $Cxxx I/O memory
|
|
// . could be a mix of RAM/ROM, main/aux, etc
|
|
// - may also reflect the current writeable memory (on a 256-page granularity) if the write page addr == read page addr
|
|
// . for this case, the memdirty flag(s) are valid
|
|
// . when writes instead occur to backing-store, then memdirty flag(s) can be ignored
|
|
//
|
|
// memmain, memaux
|
|
// - physical contiguous 64KB "backing-store" for main & aux respectively
|
|
// - NB. 4K bank1 BSR is at $C000-$CFFF
|
|
//
|
|
// memwrite
|
|
// - 1 pointer entry per 256-byte page
|
|
// - used to write to a page
|
|
// - if RD & WR point to the same 256-byte RAM page, then memwrite will point to the page in 'mem'
|
|
// . ie. when SW_AUXREAD==SW_AUXWRITE, or 4K-BSR is r/w, or 8K BSR is r/w, or SW_80STORE=1
|
|
// . So 'mem' remains correct for both r&w operations, but the physical 64K mem block becomes stale
|
|
// - if RD & WR point to different 256-byte pages, then memwrite will point to the page in the physical 64K mem block
|
|
// . writes will still set the dirty flag (but can be ignored)
|
|
// . UpdatePaging() ignores this, as it only copies back to the physical 64K mem block when memshadow changes (for that 256-byte page)
|
|
//
|
|
// memdirty
|
|
// - 1 byte entry per 256-byte page
|
|
// - set when a write occurs to a 256-byte page
|
|
// - indicates that 'mem' (ie. the cache) is out-of-sync with the "physical" 64K backing-store memory
|
|
// - NB. a page's dirty flag is only useful(valid) when 'mem' is used for both read & write for the corresponding page
|
|
// When they differ, then writes go directly to the backing-store.
|
|
// . In this case, the dirty flag will just force a memcpy() to the same address in backing-store.
|
|
//
|
|
// memshadow
|
|
// - 1 pointer entry per 256-byte page
|
|
// - reflects how 'mem' is setup for read operations (at a 256-byte granularity)
|
|
// . EG: if ALTZP=1, then:
|
|
// . mem will have copies of memaux's ZP & stack
|
|
// . memshadow[0] = &memaux[0x0000]
|
|
// . memshadow[1] = &memaux[0x0100]
|
|
//
|
|
// memreadPageType (used by _READ_ALT for CPU emulation)
|
|
// - 1 byte entry per 256-byte page
|
|
// - Required specifically for when:
|
|
// . the aux slot is empty, so that it can return floating-bus
|
|
// . the aux slot has an 80-col(1KiB) card, so reads are restricted to this mem space
|
|
//
|
|
// memwriteDirtyPage (used by _WRITE_ALT for CPU emulation)
|
|
// - 1 byte entry per 256-byte page
|
|
// - Required specifically for the 80-Col(1KiB) card so that writes *outside* the 1KiB area only set dirty pages *inside* the 1KiB area!
|
|
//
|
|
// Apple //e 64K (aux slot is empty or has 80-Col(1KiB) card) - GH#1341
|
|
// . MMU still supports RAMRDOFF/RAMRDON/RAMWRTOFF/RAMWRTON/ALTZPOFF/ALTZPON
|
|
// . DHIRESON: no affect, as the Ext 80-col card enables this (so no DHGR or DGR - although a full VidHD would support this)
|
|
// . NB. With a VidHD card, then SHR video works correctly to HDMI-out
|
|
// . Apple //e 64K (aux slot is empty)
|
|
// . aux writes still get written to memaux (write-only memory! Used by VidHD card)
|
|
// . aux reads are from floating bus
|
|
// . Apple //e 64K (w/ 80-Col(1KiB) card in aux slot)
|
|
// . aux reads & writes are to the 1KiB of aux mem
|
|
// . aux writes outside of the aux TEXT1 get written to memaux (if there's a VidHD card)
|
|
//
|
|
|
|
LPBYTE memshadow[_6502_NUM_PAGES];
|
|
LPBYTE memwrite[_6502_NUM_PAGES];
|
|
BYTE memreadPageType[_6502_NUM_PAGES];
|
|
|
|
static const UINT kNumIOFunctionPointers = APPLE_TOTAL_IO_SIZE / 16; // Split into 16-byte units
|
|
iofunction IORead[kNumIOFunctionPointers];
|
|
iofunction IOWrite[kNumIOFunctionPointers];
|
|
|
|
LPBYTE mem = NULL;
|
|
|
|
//
|
|
|
|
static LPBYTE memaux = NULL;
|
|
static LPBYTE memmain = NULL;
|
|
|
|
LPBYTE memdirty = NULL;
|
|
static LPBYTE memrom = NULL;
|
|
|
|
static LPBYTE memimage = NULL;
|
|
|
|
static LPBYTE pCxRomInternal = NULL;
|
|
static LPBYTE pCxRomPeripheral = NULL;
|
|
|
|
static LPBYTE g_pMemMainLanguageCard = NULL;
|
|
|
|
static uint32_t g_memmode = LanguageCardUnit::kMemModeInitialState;
|
|
static bool modechanging = false; // An Optimisation: means delay calling UpdatePaging() for 1 instruction
|
|
|
|
static UINT memrompages = 1;
|
|
|
|
LPBYTE memVidHD = NULL; // For Apple II/II+ writes to aux mem (on VidHD card). memVidHD = memaux or NULL (depends on //e soft-switches)
|
|
|
|
static CNoSlotClock* g_NoSlotClock = new CNoSlotClock;
|
|
|
|
#ifdef RAMWORKS
|
|
static UINT g_uMaxExBanks = 1; // user requested ram banks (default to 1 aux bank: so total = 128KB)
|
|
static UINT g_uActiveBank = 0; // 0 = aux 64K for: //e extended 80 Col card, or //c -- also RamWorks III aux card
|
|
static LPBYTE RWpages[kMaxExMemoryBanks]; // pointers to RW memory banks
|
|
#endif
|
|
|
|
static const UINT kNumAnnunciators = 4;
|
|
static bool g_Annunciator[kNumAnnunciators] = {};
|
|
|
|
static const UINT num64KPages = 2; // number of 64K pages used to create hardware circular buffer
|
|
#ifdef _WIN32
|
|
static HANDLE g_hMemImage = NULL; // NB. When not initialised, this handle is NULL (not INVALID_HANDLE_VALUE)
|
|
#else
|
|
static FILE * g_hMemTempFile = NULL;
|
|
#endif
|
|
|
|
BYTE __stdcall IO_Annunciator(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles);
|
|
static void FreeMemImage(void);
|
|
static bool g_isMemCacheValid = true; // flag for is 'mem' valid - set in UpdatePaging() and valid for regular (not alternate) CPU emulation
|
|
static bool g_forceAltCpuEmulation = false; // set by cmd line
|
|
|
|
//=============================================================================
|
|
|
|
// Default memory types on a VM restart
|
|
// - can be overwritten by cmd-line or loading a save-state
|
|
static SS_CARDTYPE g_MemTypeAppleII = CT_Empty;
|
|
static SS_CARDTYPE g_MemTypeAppleIIPlus = CT_LanguageCard; // Keep a copy so it's not lost if machine type changes, eg: A][ -> A//e -> A][
|
|
static SS_CARDTYPE g_MemTypeAppleIIe = CT_Extended80Col; // Keep a copy so it's not lost if machine type changes, eg: A//e -> A][ -> A//e
|
|
|
|
|
|
const UINT CxRomSize = 4 * 1024;
|
|
const UINT Apple2RomSize = 12 * 1024;
|
|
const UINT Apple2eRomSize = Apple2RomSize + CxRomSize;
|
|
//const UINT Pravets82RomSize = 12*1024;
|
|
//const UINT Pravets8ARomSize = Pravets82RomSize+CxRomSize;
|
|
const UINT MaxRomPages = 4; // For Copam Base64A
|
|
const UINT Base64ARomSize = MaxRomPages * Apple2RomSize;
|
|
|
|
// Called from MemLoadSnapshot()
|
|
static void ResetDefaultMachineMemTypes(void)
|
|
{
|
|
g_MemTypeAppleII = CT_Empty;
|
|
g_MemTypeAppleIIPlus = CT_LanguageCard;
|
|
g_MemTypeAppleIIe = CT_Extended80Col;
|
|
}
|
|
|
|
// Called from MemLoadSnapshot()
|
|
static void SetExpansionMemTypeDefault(void)
|
|
{
|
|
SS_CARDTYPE defaultType = IsApple2Original(GetApple2Type()) ? g_MemTypeAppleII
|
|
: IsApple2PlusOrClone(GetApple2Type()) ? g_MemTypeAppleIIPlus
|
|
: g_MemTypeAppleIIe;
|
|
|
|
SetExpansionMemType(defaultType);
|
|
}
|
|
|
|
// Called from SetExpansionMemTypeDefault(), LoadConfiguration(), RepeatInitialization(), MemLoadSnapshotAux(), SaveState.cpp_ParseSlots(), cmd-line switch
|
|
void SetExpansionMemType(const SS_CARDTYPE type, bool updateRegistry/*=true*/)
|
|
{
|
|
SS_CARDTYPE newSlot0Card;
|
|
SS_CARDTYPE newSlotAuxCard;
|
|
|
|
// Set defaults:
|
|
if (IsApple2Original(GetApple2Type()))
|
|
{
|
|
newSlot0Card = CT_Empty;
|
|
newSlotAuxCard = CT_Empty;
|
|
}
|
|
else if (IsApple2PlusOrClone(GetApple2Type()))
|
|
{
|
|
newSlot0Card = CT_LanguageCard;
|
|
newSlotAuxCard = CT_Empty;
|
|
}
|
|
else // Apple //e or above
|
|
{
|
|
newSlot0Card = CT_LanguageCardIIe; // NB. No slot0 for //e
|
|
newSlotAuxCard = CT_Extended80Col;
|
|
}
|
|
|
|
if (type == CT_LanguageCard || type == CT_Saturn128K)
|
|
{
|
|
g_MemTypeAppleII = type;
|
|
g_MemTypeAppleIIPlus = type;
|
|
if (IsApple2PlusOrClone(GetApple2Type()))
|
|
newSlot0Card = type;
|
|
else
|
|
newSlot0Card = CT_Empty; // NB. No slot0 for //e
|
|
}
|
|
else if (type == CT_80Col || type == CT_Extended80Col || type == CT_RamWorksIII)
|
|
{
|
|
g_MemTypeAppleIIe = type;
|
|
if (IsApple2PlusOrClone(GetApple2Type()))
|
|
newSlotAuxCard = CT_Empty; // NB. No aux slot for ][ or ][+
|
|
else
|
|
newSlotAuxCard = type;
|
|
}
|
|
else if (type == CT_Empty) // NB. This sets global state depending on machine type
|
|
{
|
|
if (IsApple2PlusOrClone(GetApple2Type()))
|
|
{
|
|
g_MemTypeAppleII = CT_Empty;
|
|
g_MemTypeAppleIIPlus = CT_Empty;
|
|
newSlot0Card = CT_Empty;
|
|
}
|
|
else
|
|
{
|
|
g_MemTypeAppleIIe = CT_Empty;
|
|
newSlotAuxCard = CT_Empty;
|
|
}
|
|
}
|
|
|
|
GetCardMgr().Insert(SLOT0, newSlot0Card, updateRegistry);
|
|
GetCardMgr().InsertAux(newSlotAuxCard, updateRegistry);
|
|
}
|
|
|
|
void CreateLanguageCard(void)
|
|
{
|
|
SS_CARDTYPE slot0CardType = GetCardMgr().QuerySlot(SLOT0);
|
|
if (IsApple2PlusOrClone(GetApple2Type()))
|
|
{
|
|
switch (slot0CardType) {
|
|
case CT_Empty: // OK
|
|
case CT_Saturn128K: // OK
|
|
case CT_LanguageCard: // OK
|
|
break;
|
|
default: // Anything else is invalid
|
|
GetCardMgr().Remove(SLOT0);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// only ever a CT_LanguageCardIIe for a //e
|
|
if (slot0CardType != CT_LanguageCardIIe)
|
|
GetCardMgr().Insert(SLOT0, CT_LanguageCardIIe);
|
|
|
|
SetMemMainLanguageCard(nullptr, SLOT0, true); // Force this, as LoadConfiguration() calls this twice when memmain==0 (for //e)
|
|
}
|
|
}
|
|
|
|
SS_CARDTYPE GetCurrentExpansionMemType(void)
|
|
{
|
|
if (IsApple2PlusOrClone(GetApple2Type()))
|
|
return GetCardMgr().QuerySlot(SLOT0);
|
|
else
|
|
return GetCardMgr().QueryAux();
|
|
}
|
|
|
|
//
|
|
|
|
UINT GetRamWorksMemorySize()
|
|
{
|
|
return g_uMaxExBanks;
|
|
}
|
|
|
|
void SetRamWorksMemorySize(UINT banks, bool updateRegistry/*=true*/)
|
|
{
|
|
_ASSERT(banks <= kMaxExMemoryBanks);
|
|
if (banks > kMaxExMemoryBanks)
|
|
banks = kMaxExMemoryBanks;
|
|
|
|
g_uMaxExBanks = banks;
|
|
|
|
if (updateRegistry)
|
|
SetRegistryAuxNumberOfBanks();
|
|
}
|
|
|
|
void SetRegistryAuxNumberOfBanks(void)
|
|
{
|
|
std::string regSection = RegGetConfigSlotSection(SLOT_AUX);
|
|
RegSaveValue(regSection.c_str(), REGVALUE_AUX_NUM_BANKS, true, g_uMaxExBanks);
|
|
}
|
|
|
|
UINT GetRamWorksActiveBank(void)
|
|
{
|
|
return g_uActiveBank;
|
|
}
|
|
|
|
//
|
|
|
|
static BOOL GetLastRamWrite(void)
|
|
{
|
|
if (GetCardMgr().GetLanguageCardMgr().GetLanguageCard())
|
|
return GetCardMgr().GetLanguageCardMgr().GetLanguageCard()->GetLastRamWrite();
|
|
return 0;
|
|
}
|
|
|
|
static void SetLastRamWrite(BOOL count)
|
|
{
|
|
if (GetCardMgr().GetLanguageCardMgr().GetLanguageCard())
|
|
GetCardMgr().GetLanguageCardMgr().GetLanguageCard()->SetLastRamWrite(count);
|
|
}
|
|
|
|
//
|
|
|
|
void SetMemMainLanguageCard(LPBYTE ptr, UINT slot, bool bMemMain /*=false*/)
|
|
{
|
|
if (bMemMain)
|
|
g_pMemMainLanguageCard = memmain+0xC000;
|
|
else
|
|
g_pMemMainLanguageCard = ptr;
|
|
|
|
GetCardMgr().GetLanguageCardMgr().SetLastSlotToSetMainMemLC(slot);
|
|
}
|
|
|
|
LPBYTE GetCxRomPeripheral(void)
|
|
{
|
|
return pCxRomPeripheral; // Can be NULL if at MODE_LOGO
|
|
}
|
|
|
|
bool GetIsMemCacheValid(void)
|
|
{
|
|
return g_isMemCacheValid;
|
|
}
|
|
|
|
uint8_t ReadByteFromMemory(uint16_t addr)
|
|
{
|
|
if (GetIsMemCacheValid())
|
|
return mem[addr];
|
|
|
|
if (memreadPageType[addr >> 8] != MEM_FloatingBus) // MEM_Normal or MEM_IORead
|
|
{
|
|
_ASSERT(memshadow[addr >> 8]); // Should never be NULL
|
|
if (memshadow[addr >> 8] == NULL)
|
|
return 0x00;
|
|
|
|
return *(memshadow[addr >> 8] + (addr & 0xff));
|
|
}
|
|
|
|
// Read floating-bus
|
|
// NB. Can't call MemReadFloatingBus(), as don't have 'uExecutedCycles' - needed when g_FullSpeed == true
|
|
return 0;
|
|
}
|
|
|
|
uint16_t ReadWordFromMemory(uint16_t addr)
|
|
{
|
|
return ReadByteFromMemory(addr) | (ReadByteFromMemory(addr + 1) << 8);
|
|
}
|
|
|
|
void WriteByteToMemory(uint16_t addr, uint8_t data)
|
|
{
|
|
if (GetIsMemCacheValid())
|
|
{
|
|
mem[addr] = data;
|
|
memdirty[addr >> 8] = 1;
|
|
return;
|
|
}
|
|
|
|
if (memwrite[addr >> 8] == NULL) // Can be NULL (eg. ROM)
|
|
return;
|
|
|
|
*(memwrite[addr >> 8] + (addr & 0xff)) = data;
|
|
}
|
|
|
|
void CopyBytesFromMemoryPage(uint8_t* pDst, uint16_t srcAddr, size_t size)
|
|
{
|
|
_ASSERT(((srcAddr & 0xff) + size) <= _6502_PAGE_SIZE);
|
|
|
|
uint8_t* pSrc = &mem[srcAddr];
|
|
|
|
if (!GetIsMemCacheValid())
|
|
{
|
|
_ASSERT(memshadow[srcAddr >> 8]); // Should never be NULL
|
|
if (memshadow[srcAddr >> 8] == NULL) return;
|
|
|
|
pSrc = memshadow[srcAddr >> 8] + (srcAddr & 0xff);
|
|
}
|
|
|
|
memcpy(pDst, pSrc, size);
|
|
}
|
|
|
|
// //e aux slot is empty & ALTZP=1
|
|
bool IsZeroPageFloatingBus(void)
|
|
{
|
|
if (GetIsMemCacheValid())
|
|
return false;
|
|
|
|
return memreadPageType[0x0000 >> 8] == MEM_FloatingBus;
|
|
}
|
|
|
|
void ForceAltCpuEmulation(void)
|
|
{
|
|
g_forceAltCpuEmulation = true;
|
|
}
|
|
|
|
uint8_t ReadByteFromROM(uint16_t addr)
|
|
{
|
|
if (addr < APPLE_IO_BEGIN) // $0000-BFFF
|
|
return ReadByteFromMemory(addr);
|
|
|
|
if (addr < APPLE_SLOT_BEGIN) // $C000-C0FF
|
|
return ReadByteFromMemory(addr);
|
|
|
|
if (addr < (FIRMWARE_EXPANSION_END + 1)) // $C100-CFFF
|
|
{
|
|
if (IsApple2PlusOrClone(GetApple2Type()))
|
|
return ReadByteFromMemory(addr);
|
|
return pCxRomInternal[addr - APPLE_IO_BEGIN];
|
|
}
|
|
|
|
return memrom[addr - APPLE_ROM_BEGIN]; // $D000-FFFF
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
static BYTE __stdcall IORead_C00x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
return KeybReadData();
|
|
}
|
|
|
|
static BYTE __stdcall IOWrite_C00x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
if ((addr & 0xf) <= 0xB)
|
|
return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles);
|
|
else
|
|
return GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
static BYTE __stdcall IORead_C01x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
if (IS_APPLE2) // Include Pravets machines too?
|
|
{
|
|
KeybClearStrobe();
|
|
return IO_Null(pc, addr, bWrite, d, nExecutedCycles); // GH#1261
|
|
}
|
|
|
|
bool res = false;
|
|
switch (addr & 0xf)
|
|
{
|
|
case 0x0: return KeybReadFlag();
|
|
case 0x1: res = SW_BANK2 ? true : false; break;
|
|
case 0x2: res = SW_HIGHRAM ? true : false; break;
|
|
case 0x3: res = SW_AUXREAD ? true : false; break;
|
|
case 0x4: res = SW_AUXWRITE ? true : false; break;
|
|
case 0x5: res = SW_INTCXROM ? true : false; break;
|
|
case 0x6: res = SW_ALTZP ? true : false; break;
|
|
case 0x7: res = SW_SLOTC3ROM ? true : false; break;
|
|
case 0x8: res = SW_80STORE ? true : false; break;
|
|
case 0x9: res = GetVideo().VideoGetVblBar(nExecutedCycles); break;
|
|
case 0xA: res = GetVideo().VideoGetSWTEXT(); break;
|
|
case 0xB: res = GetVideo().VideoGetSWMIXED(); break;
|
|
case 0xC: res = SW_PAGE2 ? true : false; break;
|
|
case 0xD: res = GetVideo().VideoGetSWHIRES(); break;
|
|
case 0xE: res = GetVideo().VideoGetSWAltCharSet(); break;
|
|
case 0xF: res = GetVideo().VideoGetSW80COL(); break;
|
|
}
|
|
|
|
return KeybGetKeycode() | (res ? 0x80 : 0);
|
|
}
|
|
|
|
static BYTE __stdcall IOWrite_C01x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
return KeybClearStrobe();
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
static BYTE __stdcall IORead_C02x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
static BYTE __stdcall IOWrite_C02x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
if (GetCardMgr().GetVidHDCard())
|
|
{
|
|
if (addr == 0xC022 || addr == 0xC029)
|
|
GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
return TapeWrite(pc, addr, bWrite, d, nExecutedCycles); // $C020 TAPEOUT
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
static BYTE __stdcall IORead_C03x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
return SpkrToggle(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
static BYTE __stdcall IOWrite_C03x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
if (GetCardMgr().GetVidHDCard())
|
|
{
|
|
// NB. Writes to $C03x addresses will still toggle the speaker, even with a VidHD present
|
|
if (addr == 0xC034 || addr == 0xC035)
|
|
GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
return SpkrToggle(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
static BYTE __stdcall IORead_C04x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
static BYTE __stdcall IOWrite_C04x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
static BYTE __stdcall IOReadWrite_ANx(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
// $C058..C05F
|
|
_ASSERT((addr & 0xf) >= 8);
|
|
|
|
if (IsAppleIIeOrAbove(GetApple2Type()))
|
|
{
|
|
if (!IsAppleIIc(GetApple2Type()) || /* IsIIc && */ SW_IOUDIS)
|
|
GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles); // Apple //e or (//c && IOUDIS)
|
|
}
|
|
|
|
if (IsAppleIIc(GetApple2Type())) // No ANx lines for //c
|
|
return 0;
|
|
|
|
// GH#1018 - AN3 line is still present on //e (with $C05E/F also toggling DHIRES)
|
|
return IO_Annunciator(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
static BYTE __stdcall IORead_C05x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
switch (addr & 0xf)
|
|
{
|
|
case 0x0: return GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x1: return GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x2: return GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x3: return GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x4: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x5: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x6: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x7: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles);
|
|
default: return IOReadWrite_ANx(pc, addr, bWrite, d, nExecutedCycles); // $C058..C05F
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static BYTE __stdcall IOWrite_C05x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
switch (addr & 0xf)
|
|
{
|
|
case 0x0: return GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x1: return GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x2: return GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x3: return GetVideo().VideoSetMode(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x4: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x5: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x6: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x7: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles);
|
|
default: return IOReadWrite_ANx(pc, addr, bWrite, d, nExecutedCycles); // $C058..C05F
|
|
}
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
static BYTE __stdcall IORead_C06x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
switch (addr & 0x7) // address bit 4 is ignored (UTAIIe:7-5)
|
|
{
|
|
case 0x0: return TapeRead(pc, addr, bWrite, d, nExecutedCycles); //$C060 TAPEIN
|
|
case 0x1: return JoyReadButton(pc, addr, bWrite, d, nExecutedCycles); //$C061 Digital input 0 (If bit 7=1 then JoyButton 0 or OpenApple is pressed)
|
|
case 0x2: return JoyReadButton(pc, addr, bWrite, d, nExecutedCycles); //$C062 Digital input 1 (If bit 7=1 then JoyButton 1 or ClosedApple is pressed)
|
|
case 0x3: return JoyReadButton(pc, addr, bWrite, d, nExecutedCycles); //$C063 Digital input 2
|
|
case 0x4: return JoyReadPosition(pc, addr, bWrite, d, nExecutedCycles); //$C064 Analog input 0
|
|
case 0x5: return JoyReadPosition(pc, addr, bWrite, d, nExecutedCycles); //$C065 Analog input 1
|
|
case 0x6: return JoyReadPosition(pc, addr, bWrite, d, nExecutedCycles); //$C066 Analog input 2
|
|
case 0x7: return JoyReadPosition(pc, addr, bWrite, d, nExecutedCycles); //$C067 Analog input 3
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static BYTE __stdcall IOWrite_C06x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
switch (addr & 0xf)
|
|
{
|
|
case 0x0:
|
|
if (g_Apple2Type == A2TYPE_PRAVETS8A)
|
|
return GetPravets().SetCapsLockAllowed(d);
|
|
else
|
|
return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
static BYTE __stdcall IORead_C07x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
// Apple//e TRM, pg-258: "Reading or writing any address in the range $C070-$C07F also triggers the paddle timer and resets the VBLINT(*)." (*) //c only!
|
|
JoyResetPosition(nExecutedCycles); //$C07X Analog input reset
|
|
|
|
switch (addr & 0xf)
|
|
{
|
|
case 0x0: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x1: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x2: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x3: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x4: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x5: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x6: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x7: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x8: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x9: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xA: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xB: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xC: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xD: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xE: return IS_APPLE2C() ? MemReadFloatingBus(SW_IOUDIS ? true : false, nExecutedCycles) // GH#636
|
|
: IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xF: return IsEnhancedIIEorIIC() ? MemReadFloatingBus(GetVideo().VideoGetSWDHIRES(), nExecutedCycles) // GH#636
|
|
: IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static BYTE __stdcall IOWrite_C07x(WORD pc, WORD addr, BYTE bWrite, BYTE d, ULONG nExecutedCycles)
|
|
{
|
|
// Apple//e TRM, pg-258: "Reading or writing any address in the range $C070-$C07F also triggers the paddle timer and resets the VBLINT(*)." (*) //c only!
|
|
JoyResetPosition(nExecutedCycles); //$C07X Analog input reset
|
|
|
|
switch (addr & 0xf)
|
|
{
|
|
case 0x0: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
#ifdef RAMWORKS
|
|
case 0x1: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles); // extended memory card set page
|
|
case 0x2: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x3: return MemSetPaging(pc, addr, bWrite, d, nExecutedCycles); // Ramworks III set page
|
|
#else
|
|
case 0x1: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x2: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x3: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
#endif
|
|
case 0x4: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x5: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x6: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x7: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x8: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0x9: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xA: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xB: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xC: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xD: return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
case 0xE: if (IS_APPLE2C())
|
|
SetMemMode(g_memmode | MF_IOUDIS); // On: disable IOU access for addresses $C058 to $C05F; enable access to DHIRES switch
|
|
else
|
|
return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
break;
|
|
case 0xF: if (IS_APPLE2C())
|
|
SetMemMode(g_memmode & ~MF_IOUDIS); // Off: enable IOU access for addresses $C058 to $C05F; disable access to DHIRES switch
|
|
else
|
|
return IO_Null(pc, addr, bWrite, d, nExecutedCycles);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static iofunction IORead_C0xx[8] =
|
|
{
|
|
IORead_C00x, // Keyboard
|
|
IORead_C01x, // Memory/Video
|
|
IORead_C02x, // Cassette
|
|
IORead_C03x, // Speaker
|
|
IORead_C04x,
|
|
IORead_C05x, // Video
|
|
IORead_C06x, // Joystick
|
|
IORead_C07x, // Joystick/Video
|
|
};
|
|
|
|
static iofunction IOWrite_C0xx[8] =
|
|
{
|
|
IOWrite_C00x, // Memory/Video
|
|
IOWrite_C01x, // Keyboard
|
|
IOWrite_C02x, // Cassette
|
|
IOWrite_C03x, // Speaker
|
|
IOWrite_C04x,
|
|
IOWrite_C05x, // Video/Memory
|
|
IOWrite_C06x,
|
|
IOWrite_C07x, // Joystick/Ramworks
|
|
};
|
|
|
|
static BYTE IO_SELECT = 0;
|
|
static bool INTC8ROM = false; // UTAIIe:5-28
|
|
|
|
enum eExpansionRomType {eExpRomNull=0, eExpRomInternal, eExpRomPeripheral};
|
|
static eExpansionRomType g_eExpansionRomType = eExpRomNull;
|
|
static UINT g_uPeripheralRomSlot = 0;
|
|
|
|
static struct SlotInfo
|
|
{
|
|
iofunction IOReadCx;
|
|
iofunction IOWriteCx;
|
|
LPVOID parameters;
|
|
BYTE* expansionRom;
|
|
} g_SlotInfo[NUM_SLOTS] = { 0 };
|
|
|
|
//=============================================================================
|
|
|
|
BYTE __stdcall IO_Null(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nExecutedCycles)
|
|
{
|
|
if (!write)
|
|
return MemReadFloatingBus(nExecutedCycles);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
BYTE __stdcall IO_Annunciator(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nExecutedCycles)
|
|
{
|
|
// Apple//e ROM:
|
|
// . $FA6F: LDA $C058 (SETAN0) ; AN0 = TTL LO
|
|
// . $FA72: LDA $C05A (SETAN1) ; AN1 = TTL LO
|
|
// . $C2B5: LDA $C05D (CLRAN2) ;SETUP
|
|
// . $C2B8: LDA $C05F (CLRAN3) ; ANNUNCIATORS
|
|
|
|
DongleControl(address); // do before setting g_Annunciator[] as may need to access old MemGetAnnunciator() state
|
|
|
|
g_Annunciator[(address>>1) & 3] = (address&1) ? true : false;
|
|
|
|
if (address >= 0xC058 && address <= 0xC05B)
|
|
JoyportControl(address & 0x3); // AN0 and AN1 control
|
|
|
|
if (address >= 0xC058 && address <= 0xC05B && IsCopamBase64A(GetApple2Type()))
|
|
MemSetPaging(programcounter, address, write, value, nExecutedCycles);
|
|
|
|
if (address >= 0xC05C && address <= 0xC05D && IsApple2JPlus(GetApple2Type()))
|
|
NTSC_VideoInitAppleType(); // AN2 switches between Katakana & ASCII video rom chars (GH#773)
|
|
|
|
if (!write)
|
|
return MemReadFloatingBus(nExecutedCycles);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
inline bool IsPotentialNoSlotClockAccess(const WORD address)
|
|
{
|
|
// UAIIe:5-28
|
|
const BYTE AddrHi = address >> 8;
|
|
return ( ((SW_INTCXROM || !SW_SLOTC3ROM) && (AddrHi == 0xC3)) || // Internal ROM at [$C100-CFFF or $C300-C3FF] && AddrHi == $C3
|
|
(SW_INTCXROM && (AddrHi == 0xC8)) ); // Internal ROM at [$C100-CFFF] && AddrHi == $C8
|
|
}
|
|
|
|
static bool IsCardInSlot(UINT slot);
|
|
|
|
// Enabling expansion ROM ($C800..$CFFF]:
|
|
// . Enable if: Enable1 && Enable2
|
|
// . Enable1 = I/O SELECT' (6502 accesses $Csxx)
|
|
// - Reset when 6502 accesses $CFFF
|
|
// . Enable2 = I/O STROBE' (6502 accesses [$C800..$CFFF])
|
|
|
|
// TODO:
|
|
// . IO_SELECT and INTC8ROM are sticky - they only getting reset by $CFFF and MemReset()
|
|
// . Check Sather UAIIe, but I assume that a 6502 access to a non-$Csxx (and non-expansion ROM) location will clear IO_SELECT
|
|
|
|
// NB. ProDOS boot sets IO_SELECT=0x04 (its scan for boot devices?), as slot2 contains a card (ie. SSC) with an expansion ROM.
|
|
|
|
//
|
|
// -----------
|
|
// UTAIIe:5-28
|
|
// $C100-C2FF
|
|
// INTCXROM SLOTC3ROM $C400-CFFF $C300-C3FF
|
|
// 0 0 slot internal
|
|
// 0 1 slot slot
|
|
// 1 0 internal internal
|
|
// 1 1 internal internal
|
|
//
|
|
// NB. if (INTCXROM || INTC8ROM) == true then internal ROM
|
|
//
|
|
// -----------
|
|
//
|
|
// INTC8ROM: Unreadable soft switch (UTAIIe:5-28)
|
|
// . Set: On access to $C3XX with SLOTC3ROM reset
|
|
// - "From this point, $C800-$CFFF will stay assigned to motherboard ROM until
|
|
// an access is made to $CFFF or until the MMU detects a system reset."
|
|
// . Reset: On access to $CFFF or an MMU reset
|
|
//
|
|
|
|
static BYTE __stdcall IO_Cxxx(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nExecutedCycles)
|
|
{
|
|
if (address == 0xCFFF)
|
|
{
|
|
// Disable expansion ROM at [$C800..$CFFF]
|
|
// . SSC will disable on an access to $CFxx - but ROM only accesses $CFFF, so it doesn't matter
|
|
IO_SELECT = 0;
|
|
INTC8ROM = false;
|
|
g_uPeripheralRomSlot = 0;
|
|
|
|
if (!SW_INTCXROM)
|
|
{
|
|
// NB. SW_INTCXROM==1 ensures that internal rom stays switched in
|
|
memset(pCxRomPeripheral+0x800, 0, FIRMWARE_EXPANSION_SIZE);
|
|
if (GetIsMemCacheValid())
|
|
memset(mem+FIRMWARE_EXPANSION_BEGIN, 0, FIRMWARE_EXPANSION_SIZE);
|
|
g_eExpansionRomType = eExpRomNull;
|
|
}
|
|
|
|
// NB. IO_SELECT won't get set, so ROM won't be switched back in...
|
|
}
|
|
|
|
//
|
|
|
|
BYTE IO_STROBE = 0;
|
|
|
|
if (IS_APPLE2 || !SW_INTCXROM)
|
|
{
|
|
if ((address >= APPLE_SLOT_BEGIN) && (address <= APPLE_SLOT_END))
|
|
{
|
|
const UINT uSlot = (address>>8)&0x7;
|
|
if (IS_APPLE2 || uSlot != SLOT3)
|
|
{
|
|
if (g_SlotInfo[uSlot].expansionRom)
|
|
IO_SELECT |= 1<<uSlot;
|
|
}
|
|
else // slot3
|
|
{
|
|
if ((SW_SLOTC3ROM) && g_SlotInfo[uSlot].expansionRom)
|
|
IO_SELECT |= 1<<uSlot; // Slot3 & Peripheral ROM
|
|
else if (!SW_SLOTC3ROM)
|
|
INTC8ROM = true; // Slot3 & Internal ROM
|
|
}
|
|
}
|
|
else if ((address >= FIRMWARE_EXPANSION_BEGIN) && (address <= FIRMWARE_EXPANSION_END))
|
|
{
|
|
if (!INTC8ROM) // [GH#423] UTAIIe:5-28: if INTCXROM or INTC8ROM is configured for internal response,
|
|
// then access to $C800-$CFFF results in ROMEN1' low (active) and I/O STROBE' high (inactive)
|
|
IO_STROBE = 1;
|
|
}
|
|
|
|
//
|
|
|
|
if (IO_SELECT && IO_STROBE)
|
|
{
|
|
// Enable Peripheral Expansion ROM
|
|
UINT uSlot = SLOT1;
|
|
for (; uSlot<NUM_SLOTS; uSlot++)
|
|
{
|
|
if (IO_SELECT & (1<<uSlot))
|
|
{
|
|
BYTE RemainingSelected = IO_SELECT & ~(1<<uSlot);
|
|
_ASSERT(RemainingSelected == 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (g_SlotInfo[uSlot].expansionRom && (g_uPeripheralRomSlot != uSlot))
|
|
{
|
|
memcpy(pCxRomPeripheral+0x800, g_SlotInfo[uSlot].expansionRom, FIRMWARE_EXPANSION_SIZE);
|
|
if (GetIsMemCacheValid())
|
|
memcpy(mem+FIRMWARE_EXPANSION_BEGIN, g_SlotInfo[uSlot].expansionRom, FIRMWARE_EXPANSION_SIZE);
|
|
g_eExpansionRomType = eExpRomPeripheral;
|
|
g_uPeripheralRomSlot = uSlot;
|
|
}
|
|
}
|
|
else if (INTC8ROM && (g_eExpansionRomType != eExpRomInternal))
|
|
{
|
|
// Enable Internal ROM
|
|
// . Get this for PR#3
|
|
memcpy(pCxRomPeripheral+0x800, pCxRomInternal+0x800, FIRMWARE_EXPANSION_SIZE);
|
|
if (GetIsMemCacheValid())
|
|
memcpy(mem+FIRMWARE_EXPANSION_BEGIN, pCxRomInternal+0x800, FIRMWARE_EXPANSION_SIZE);
|
|
g_eExpansionRomType = eExpRomInternal;
|
|
g_uPeripheralRomSlot = 0;
|
|
}
|
|
}
|
|
|
|
// NSC only for //e at internal C3/C8 ROMs, as II/II+ has no internal ROM here! (GH#827)
|
|
if (!IS_APPLE2 && g_NoSlotClock && IsPotentialNoSlotClockAccess(address))
|
|
{
|
|
if (g_NoSlotClock->ReadWrite(address, value, write))
|
|
return value;
|
|
}
|
|
|
|
if (!IS_APPLE2 && SW_INTCXROM)
|
|
{
|
|
// !SW_SLOTC3ROM = Internal ROM: $C300-C3FF
|
|
// SW_INTCXROM = Internal ROM: $C100-CFFF
|
|
|
|
if ((address >= 0xC300) && (address <= 0xC3FF))
|
|
{
|
|
if (!SW_SLOTC3ROM) // GH#423
|
|
INTC8ROM = true;
|
|
}
|
|
else if ((address >= FIRMWARE_EXPANSION_BEGIN) && (address <= FIRMWARE_EXPANSION_END))
|
|
{
|
|
if (!INTC8ROM) // GH#423
|
|
IO_STROBE = 1;
|
|
}
|
|
|
|
if (INTC8ROM && (g_eExpansionRomType != eExpRomInternal))
|
|
{
|
|
// Enable Internal ROM
|
|
memcpy(pCxRomPeripheral+0x800, pCxRomInternal+0x800, FIRMWARE_EXPANSION_SIZE);
|
|
if (GetIsMemCacheValid())
|
|
memcpy(mem+FIRMWARE_EXPANSION_BEGIN, pCxRomInternal+0x800, FIRMWARE_EXPANSION_SIZE);
|
|
g_eExpansionRomType = eExpRomInternal;
|
|
g_uPeripheralRomSlot = 0;
|
|
}
|
|
}
|
|
|
|
if (address >= APPLE_SLOT_BEGIN && address <= APPLE_SLOT_END)
|
|
{
|
|
const UINT uSlot = (address>>8)&0x7;
|
|
const bool bPeripheralSlotRomEnabled = IS_APPLE2 ? true // A][
|
|
: // A//e or above
|
|
( !SW_INTCXROM && // Peripheral (card) ROMs enabled in $C100..$C7FF
|
|
!(!SW_SLOTC3ROM && uSlot == SLOT3) ); // Internal C3 ROM disabled in $C300 when slot == 3
|
|
|
|
// Fix for GH#149 and GH#164
|
|
if (bPeripheralSlotRomEnabled && !IsCardInSlot(uSlot)) // Slot is empty
|
|
{
|
|
return IO_Null(programcounter, address, write, value, nExecutedCycles);
|
|
}
|
|
}
|
|
|
|
if ((g_eExpansionRomType == eExpRomNull) && (address >= FIRMWARE_EXPANSION_BEGIN))
|
|
return IO_Null(programcounter, address, write, value, nExecutedCycles);
|
|
|
|
return ReadByteFromMemory(address);
|
|
}
|
|
|
|
BYTE __stdcall IO_F8xx(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nCycles) // NSC for Apple II/II+ (GH#827)
|
|
{
|
|
if (IS_APPLE2 && g_NoSlotClock && !SW_HIGHRAM && !SW_WRITERAM)
|
|
{
|
|
if (g_NoSlotClock->ReadWrite(address, value, write))
|
|
return value;
|
|
}
|
|
|
|
//
|
|
|
|
if (!write)
|
|
{
|
|
return *(mem+address);
|
|
}
|
|
else
|
|
{
|
|
memdirty[address >> 8] = 0xFF;
|
|
LPBYTE page = memwrite[address >> 8];
|
|
if (page)
|
|
*(page+(address & 0xFF)) = value;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
static void InitIoHandlers()
|
|
{
|
|
UINT i=0;
|
|
|
|
for (; i<8; i++) // C00x..C07x
|
|
{
|
|
IORead[i] = IORead_C0xx[i];
|
|
IOWrite[i] = IOWrite_C0xx[i];
|
|
}
|
|
|
|
for (; i<16; i++) // C08x..C0Fx
|
|
{
|
|
IORead[i] = IO_Null;
|
|
IOWrite[i] = IO_Null;
|
|
}
|
|
|
|
//
|
|
|
|
for (; i<256; i++) // C10x..CFFx
|
|
{
|
|
IORead[i] = IO_Cxxx;
|
|
IOWrite[i] = IO_Cxxx;
|
|
}
|
|
|
|
//
|
|
|
|
for (i=0; i<NUM_SLOTS; i++)
|
|
{
|
|
g_SlotInfo[i].IOReadCx = IO_Cxxx;
|
|
g_SlotInfo[i].IOWriteCx = IO_Cxxx;
|
|
g_SlotInfo[i].parameters = NULL;
|
|
g_SlotInfo[i].expansionRom = NULL;
|
|
}
|
|
}
|
|
|
|
// All slots [0..7] must register their handlers
|
|
void RegisterIoHandler(UINT uSlot, iofunction IOReadC0, iofunction IOWriteC0, iofunction IOReadCx, iofunction IOWriteCx, LPVOID lpSlotParameter, BYTE* pExpansionRom)
|
|
{
|
|
_ASSERT(uSlot < NUM_SLOTS);
|
|
g_SlotInfo[uSlot].parameters = lpSlotParameter;
|
|
|
|
// What about [$C80x..$CFEx]? - Do any cards use this as I/O memory?
|
|
g_SlotInfo[uSlot].expansionRom = pExpansionRom;
|
|
|
|
if (IOReadC0 == NULL) IOReadC0 = IO_Null;
|
|
if (IOWriteC0 == NULL) IOWriteC0 = IO_Null;
|
|
|
|
// Setup the r/w function pointers for I/O in the 16-byte range at: $C080+s*16
|
|
IORead[uSlot+8] = IOReadC0;
|
|
IOWrite[uSlot+8] = IOWriteC0;
|
|
|
|
if (uSlot == SLOT0) // Don't trash C0xx handlers
|
|
return;
|
|
|
|
//
|
|
|
|
if (IOReadCx == NULL) IOReadCx = IO_Cxxx;
|
|
if (IOWriteCx == NULL) IOWriteCx = IO_Cxxx;
|
|
|
|
for (UINT i=0; i<16; i++) // Setup the r/w function pointers for I/O in the 256-byte range: $Cs00..CsFF
|
|
{
|
|
IORead[uSlot*16+i] = IOReadCx;
|
|
IOWrite[uSlot*16+i] = IOWriteCx;
|
|
}
|
|
|
|
// Setup the r/w function pointers for I/O in the range: $C800..CFFF
|
|
g_SlotInfo[uSlot].IOReadCx = IOReadCx;
|
|
g_SlotInfo[uSlot].IOWriteCx = IOWriteCx;
|
|
}
|
|
|
|
void UnregisterIoHandler(UINT uSlot)
|
|
{
|
|
RegisterIoHandler(uSlot, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
LPVOID MemGetSlotParameters(UINT uSlot)
|
|
{
|
|
_ASSERT(uSlot < NUM_SLOTS);
|
|
return g_SlotInfo[uSlot].parameters;
|
|
}
|
|
|
|
// From UTAIIe:5-28: Since INTCXROM==1 then state of SLOTC3ROM is not important
|
|
static void IoHandlerCardsOut(void)
|
|
{
|
|
_ASSERT( SW_INTCXROM );
|
|
|
|
for (UINT uSlot=SLOT1; uSlot<NUM_SLOTS; uSlot++)
|
|
{
|
|
for (UINT i=0; i<16; i++)
|
|
{
|
|
IORead[uSlot*16+i] = IO_Cxxx;
|
|
IOWrite[uSlot*16+i] = IO_Cxxx;
|
|
}
|
|
}
|
|
}
|
|
|
|
// From UTAIIe:5-28: If INTCXROM==0 && SLOTC3ROM==0 Then $C300-C3FF is internal ROM
|
|
static void IoHandlerSlot3CardOut(void)
|
|
{
|
|
_ASSERT(!SW_INTCXROM && !SW_SLOTC3ROM);
|
|
|
|
for (UINT i = 0; i < 16; i++)
|
|
{
|
|
IORead[SLOT3 * 16 + i] = IO_Cxxx;
|
|
IOWrite[SLOT3 * 16 + i] = IO_Cxxx;
|
|
}
|
|
}
|
|
|
|
static void IoHandlerCardsIn(void)
|
|
{
|
|
_ASSERT( !SW_INTCXROM );
|
|
|
|
for (UINT uSlot=SLOT1; uSlot<NUM_SLOTS; uSlot++)
|
|
{
|
|
if (uSlot == SLOT3 && !SW_SLOTC3ROM)
|
|
{
|
|
IoHandlerSlot3CardOut();
|
|
}
|
|
else
|
|
{
|
|
iofunction ioreadcx = g_SlotInfo[uSlot].IOReadCx;
|
|
iofunction iowritecx = g_SlotInfo[uSlot].IOWriteCx;
|
|
|
|
for (UINT i = 0; i < 16; i++)
|
|
{
|
|
IORead[uSlot * 16 + i] = ioreadcx;
|
|
IOWrite[uSlot * 16 + i] = iowritecx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool IsCardInSlot(UINT slot)
|
|
{
|
|
return GetCardMgr().QuerySlot(slot) != CT_Empty;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
uint32_t GetMemMode(void)
|
|
{
|
|
return g_memmode;
|
|
}
|
|
|
|
void SetMemMode(uint32_t uNewMemMode)
|
|
{
|
|
#if defined(_DEBUG) && 0
|
|
static uint32_t dwOldDiff = 0;
|
|
uint32_t dwDiff = g_memmode ^ uNewMemMode;
|
|
dwDiff &= ~(MF_SLOTC3ROM | MF_INTCXROM);
|
|
if (dwOldDiff != dwDiff)
|
|
{
|
|
dwOldDiff = dwDiff;
|
|
std::string str = StrFormat(
|
|
/*01*/ "diff = %08X "
|
|
/*02*/ "80=%d "
|
|
/*03*/ "ALTZP=%d "
|
|
/*04*/ "AUXR=%d "
|
|
/*05*/ "AUXW=%d "
|
|
/*06*/ "BANK2=%d "
|
|
/*07*/ "HIRAM=%d "
|
|
/*08*/ "HIRES=%d "
|
|
/*09*/ "PAGE2=%d "
|
|
/*10*/ "C3=%d "
|
|
/*11*/ "CX=%d "
|
|
/*12*/ "WRAM=%d "
|
|
"\n",
|
|
/*01*/ dwDiff,
|
|
/*02*/ SW_80STORE ? 1 : 0,
|
|
/*03*/ SW_ALTZP ? 1 : 0,
|
|
/*04*/ SW_AUXREAD ? 1 : 0,
|
|
/*05*/ SW_AUXWRITE ? 1 : 0,
|
|
/*06*/ SW_BANK2 ? 1 : 0,
|
|
/*07*/ SW_HIGHRAM ? 1 : 0,
|
|
/*08*/ SW_HIRES ? 1 : 0,
|
|
/*09*/ SW_PAGE2 ? 1 : 0,
|
|
/*10*/ SW_SLOTC3ROM ? 1 : 0,
|
|
/*11*/ SW_INTCXROM ? 1 : 0,
|
|
/*12*/ SW_WRITERAM ? 1 : 0
|
|
);
|
|
OutputDebugString(str.c_str());
|
|
}
|
|
#endif
|
|
g_memmode = uNewMemMode;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
static void ResetPaging(bool initialize);
|
|
static void UpdatePaging(bool initialize);
|
|
|
|
// Call by:
|
|
// . CtrlReset() Soft-reset (Ctrl+Reset) for //e
|
|
void MemResetPaging()
|
|
{
|
|
ResetPaging(false /*Initialize*/);
|
|
}
|
|
|
|
// Call by:
|
|
// . MemResetPaging() -> ResetPaging(false)
|
|
// . MemReset() -> ResetPaging(true)
|
|
static void ResetPaging(bool initialize)
|
|
{
|
|
GetCardMgr().GetLanguageCardMgr().Reset(initialize);
|
|
UpdatePaging(initialize);
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
static void UpdatePagingForAltRW();
|
|
|
|
void MemUpdatePaging(bool initialize)
|
|
{
|
|
UpdatePaging(initialize);
|
|
}
|
|
|
|
static void UpdatePaging(bool initialize)
|
|
{
|
|
if (initialize)
|
|
{
|
|
// Importantly from:
|
|
// . MemReset() -> ResetPaging(true)
|
|
// . MemInitializeFromSnapshot() -> MemUpdatePaging(true);
|
|
g_isMemCacheValid = !(IsAppleIIe(GetApple2Type()) && (GetCardMgr().QueryAux() == CT_Empty || GetCardMgr().QueryAux() == CT_80Col));
|
|
if (g_forceAltCpuEmulation)
|
|
g_isMemCacheValid = false;
|
|
}
|
|
|
|
modechanging = false;
|
|
|
|
// SAVE THE CURRENT PAGING SHADOW TABLE
|
|
LPBYTE oldshadow[256];
|
|
if (!initialize)
|
|
memcpy(oldshadow,memshadow,256*sizeof(LPBYTE));
|
|
|
|
// UPDATE THE PAGING TABLES BASED ON THE NEW PAGING SWITCH VALUES
|
|
UINT loop;
|
|
if (initialize)
|
|
{
|
|
for (loop = 0x00; loop < 0xC0; loop++)
|
|
memwrite[loop] = mem+(loop << 8);
|
|
|
|
for (loop = 0xC0; loop < 0xD0; loop++)
|
|
memwrite[loop] = NULL;
|
|
}
|
|
|
|
for (loop = 0x00; loop < 0x02; loop++)
|
|
{
|
|
memshadow[loop] = SW_ALTZP ? memaux + (loop << 8) : memmain + (loop << 8);
|
|
|
|
// re-init this, since for //e aux slot 80-col(1KiB) card, this can change
|
|
memwrite[loop] = mem + (loop << 8);
|
|
}
|
|
|
|
for (loop = 0x02; loop < 0xC0; loop++)
|
|
{
|
|
memshadow[loop] = SW_AUXREAD ? memaux+(loop << 8)
|
|
: memmain+(loop << 8);
|
|
|
|
memwrite[loop] = ((SW_AUXREAD != 0) == (SW_AUXWRITE != 0))
|
|
? mem+(loop << 8)
|
|
: SW_AUXWRITE ? memaux+(loop << 8)
|
|
: memmain+(loop << 8);
|
|
}
|
|
|
|
for (loop = 0xC0; loop < 0xC8; loop++)
|
|
{
|
|
memdirty[loop] = 0; // mem(cache) can't be dirty for ROM (but STA $Csnn will set the dirty flag)
|
|
const UINT uSlotOffset = (loop & 0x0f) * 0x100;
|
|
if (loop == 0xC3)
|
|
memshadow[loop] = (SW_SLOTC3ROM && !SW_INTCXROM) ? pCxRomPeripheral+uSlotOffset // C300..C3FF - Slot 3 ROM (all 0x00's)
|
|
: pCxRomInternal+uSlotOffset; // C300..C3FF - Internal ROM
|
|
else
|
|
memshadow[loop] = !SW_INTCXROM ? pCxRomPeripheral+uSlotOffset // C000..C7FF - SSC/Disk][/etc
|
|
: pCxRomInternal+uSlotOffset; // C000..C7FF - Internal ROM
|
|
}
|
|
|
|
for (loop = 0xC8; loop < 0xD0; loop++)
|
|
{
|
|
memdirty[loop] = 0; // mem(cache) can't be dirty for ROM (but STA $Cnnn will set the dirty flag)
|
|
const UINT uRomOffset = (loop & 0x0f) * 0x100;
|
|
memshadow[loop] = (!SW_INTCXROM && !INTC8ROM) ? pCxRomPeripheral+uRomOffset // C800..CFFF - Peripheral ROM (GH#486)
|
|
: pCxRomInternal+uRomOffset; // C800..CFFF - Internal ROM
|
|
}
|
|
|
|
const int selectedrompage = (SW_ALTROM0 ? 1 : 0) | (SW_ALTROM1 ? 2 : 0);
|
|
#ifdef _DEBUG
|
|
if (selectedrompage) { _ASSERT(IsCopamBase64A(GetApple2Type())); }
|
|
#endif
|
|
const int romoffset = (selectedrompage % memrompages) * Apple2RomSize; // Only Copam Base64A has a non-zero romoffset
|
|
for (loop = 0xD0; loop < 0xE0; loop++)
|
|
{
|
|
const int bankoffset = (SW_BANK2 ? 0 : 0x1000);
|
|
memshadow[loop] = SW_HIGHRAM ? SW_ALTZP ? memaux+(loop << 8)-bankoffset
|
|
: g_pMemMainLanguageCard+((loop-0xC0)<<8)-bankoffset
|
|
: memrom+((loop-0xD0) * 0x100)+romoffset;
|
|
|
|
memwrite[loop] = SW_WRITERAM ? SW_HIGHRAM ? mem+(loop << 8)
|
|
: SW_ALTZP ? memaux+(loop << 8)-bankoffset
|
|
: g_pMemMainLanguageCard+((loop-0xC0)<<8)-bankoffset
|
|
: NULL;
|
|
}
|
|
|
|
for (loop = 0xE0; loop < 0x100; loop++)
|
|
{
|
|
memshadow[loop] = SW_HIGHRAM ? SW_ALTZP ? memaux+(loop << 8)
|
|
: g_pMemMainLanguageCard+((loop-0xC0)<<8)
|
|
: memrom+((loop-0xD0) * 0x100)+romoffset;
|
|
|
|
memwrite[loop] = SW_WRITERAM ? SW_HIGHRAM ? mem+(loop << 8)
|
|
: SW_ALTZP ? memaux+(loop << 8)
|
|
: g_pMemMainLanguageCard+((loop-0xC0)<<8)
|
|
: NULL;
|
|
}
|
|
|
|
if (SW_80STORE)
|
|
{
|
|
for (loop = 0x04; loop < 0x08; loop++)
|
|
{
|
|
memshadow[loop] = SW_PAGE2 ? memaux+(loop << 8)
|
|
: memmain+(loop << 8);
|
|
memwrite[loop] = mem+(loop << 8);
|
|
}
|
|
|
|
if (SW_HIRES)
|
|
{
|
|
for (loop = 0x20; loop < 0x40; loop++)
|
|
{
|
|
memshadow[loop] = SW_PAGE2 ? memaux+(loop << 8)
|
|
: memmain+(loop << 8);
|
|
memwrite[loop] = mem+(loop << 8);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g_isMemCacheValid)
|
|
{
|
|
// MOVE MEMORY BACK AND FORTH AS NECESSARY BETWEEN THE SHADOW AREAS AND
|
|
// THE MAIN RAM IMAGE TO KEEP BOTH SETS OF MEMORY CONSISTENT WITH THE NEW
|
|
// PAGING SHADOW TABLE
|
|
//
|
|
// NB. the condition 'loop <= 1' is there because:
|
|
// . Page0 (ZP) and Page1 (stack) are written to so often that it's almost certain that they'll be dirty every time this function is called.
|
|
// Note also that:
|
|
// . Page0 (ZP) : memdirty[0] is set when the 6502 CPU writes to ZP.
|
|
// . Page1 (stack) : memdirty[1] is NOT set when the 6502 CPU writes to this page with JSR, PHA, etc.
|
|
// Ultimately this is an optimisation (due to Page1 writes not setting memdirty[1]) and Page0 could be optimised to also not set memdirty[0].
|
|
|
|
for (UINT page = _6502_ZERO_PAGE; page < _6502_NUM_PAGES; page++)
|
|
{
|
|
if (initialize || (oldshadow[page] != memshadow[page]))
|
|
{
|
|
if (!initialize &&
|
|
((*(memdirty+page) & 1) || (page <= _6502_STACK_PAGE)))
|
|
{
|
|
*(memdirty+page) &= ~1;
|
|
memcpy(oldshadow[page],mem+(page << 8),_6502_PAGE_SIZE);
|
|
}
|
|
|
|
memcpy(mem+(page << 8),memshadow[page],_6502_PAGE_SIZE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UpdatePagingForAltRW();
|
|
}
|
|
}
|
|
|
|
// For Cpu6502_altRW() & Cpu65C02_altRW()
|
|
static void UpdatePagingForAltRW(void)
|
|
{
|
|
UINT page;
|
|
|
|
const BYTE memType = (GetCardMgr().QueryAux() == CT_Empty) ? MEM_FloatingBus : MEM_Normal;
|
|
|
|
for (page = 0x00; page < 0x02; page++)
|
|
memreadPageType[page] = SW_ALTZP ? memType : MEM_Normal;
|
|
|
|
for (page = 0x02; page < 0xC0; page++)
|
|
memreadPageType[page] = SW_AUXREAD ? memType : MEM_Normal;
|
|
|
|
for (page = 0xC0; page < 0xD0; page++)
|
|
{
|
|
// NB. I/O SELECT' set on $C100-C7FF access
|
|
// NB. I/O STROBE' set on $C800-CFFF access
|
|
// So Cx ROM reads (both internal and slot/expansion) go via IO_Cxxx() - to set I/O SELECT/STROBE correctly
|
|
// . and then read from memshadow[$Cx]
|
|
memreadPageType[page] = MEM_IORead;
|
|
}
|
|
|
|
for (page = 0xD0; page < 0x100; page++)
|
|
memreadPageType[page] = (SW_HIGHRAM && SW_ALTZP) ? memType : MEM_Normal;
|
|
|
|
if (SW_80STORE)
|
|
{
|
|
for (page = 0x04; page < 0x08; page++)
|
|
memreadPageType[page] = SW_PAGE2 ? memType : MEM_Normal;
|
|
|
|
for (page = 0x20; page < 0x40; page++)
|
|
memreadPageType[page] = (SW_PAGE2 && SW_HIRES) ? memType : MEM_Normal;
|
|
}
|
|
|
|
//
|
|
|
|
for (page = 0x00; page < 0x02; page++)
|
|
memwrite[page] = memshadow[page];
|
|
|
|
if ((SW_AUXREAD != 0) == (SW_AUXWRITE != 0))
|
|
{
|
|
for (page = 0x02; page < 0xC0; page++)
|
|
memwrite[page] = memshadow[page];
|
|
}
|
|
|
|
if (SW_WRITERAM && SW_HIGHRAM)
|
|
{
|
|
for (page = 0xD0; page < 0x100; page++)
|
|
memwrite[page] = memshadow[page];
|
|
}
|
|
|
|
if (SW_80STORE)
|
|
{
|
|
for (page = 0x04; page < 0x08; page++)
|
|
memwrite[page] = memshadow[page];
|
|
|
|
if (SW_HIRES)
|
|
{
|
|
for (page = 0x20; page < 0x40; page++)
|
|
memwrite[page] = memshadow[page];
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
if (GetCardMgr().QueryAux() == CT_80Col)
|
|
{
|
|
// Map all aux read/writes into the 1K memory
|
|
|
|
const uint32_t kBase = TEXT_PAGE1_BEGIN;
|
|
|
|
if (SW_ALTZP)
|
|
for (page = 0x00; page < 0x02; page++)
|
|
memshadow[page] = memwrite[page] = memaux + kBase + ((page & 3) << 8);
|
|
|
|
if (SW_AUXREAD)
|
|
for (page = 0x02; page < 0xC0; page++)
|
|
memshadow[page] = memaux + kBase + ((page & 3) << 8);
|
|
|
|
if (SW_AUXWRITE)
|
|
for (page = 0x02; page < 0xC0; page++)
|
|
memwrite[page] = memaux + kBase + ((page & 3) << 8);
|
|
|
|
if (SW_HIGHRAM && SW_ALTZP)
|
|
{
|
|
for (page = 0xD0; page < 0x100; page++)
|
|
{
|
|
memshadow[page] = memaux + kBase + ((page & 3) << 8);
|
|
if (SW_WRITERAM)
|
|
memwrite[page] = memshadow[page];
|
|
}
|
|
}
|
|
|
|
if (SW_80STORE && SW_PAGE2)
|
|
{
|
|
for (page = 0x04; page < 0x08; page++)
|
|
memshadow[page] = memwrite[page] = memaux + kBase + ((page & 3) << 8);
|
|
|
|
if (SW_HIRES)
|
|
for (page = 0x20; page < 0x40; page++)
|
|
memshadow[page] = memwrite[page] = memaux + kBase + ((page & 3) << 8);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
void MemDestroy()
|
|
{
|
|
ALIGNED_FREE(memaux);
|
|
ALIGNED_FREE(memmain);
|
|
FreeMemImage();
|
|
|
|
delete [] memdirty;
|
|
delete [] memrom;
|
|
|
|
delete [] pCxRomInternal;
|
|
delete [] pCxRomPeripheral;
|
|
|
|
#ifdef RAMWORKS
|
|
for (UINT i=1; i<kMaxExMemoryBanks; i++)
|
|
{
|
|
if (RWpages[i])
|
|
{
|
|
ALIGNED_FREE(RWpages[i]);
|
|
RWpages[i] = NULL;
|
|
}
|
|
}
|
|
RWpages[0]=NULL;
|
|
#endif
|
|
|
|
memaux = NULL;
|
|
memmain = NULL;
|
|
memdirty = NULL;
|
|
memrom = NULL;
|
|
memimage = NULL;
|
|
|
|
pCxRomInternal = NULL;
|
|
pCxRomPeripheral = NULL;
|
|
|
|
mem = NULL;
|
|
|
|
memset(memwrite, 0, sizeof(memwrite));
|
|
memset(memshadow, 0, sizeof(memshadow));
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
bool MemCheckSLOTC3ROM()
|
|
{
|
|
return SW_SLOTC3ROM ? true : false;
|
|
}
|
|
|
|
bool MemCheckINTCXROM()
|
|
{
|
|
return SW_INTCXROM ? true : false;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
static LPBYTE MemGetPtrBANK1(const WORD offset, const LPBYTE pMemBase)
|
|
{
|
|
if ((offset & 0xF000) != 0xC000) // Requesting RAM at physical addr $Cxxx (ie. 4K RAM BANK1)
|
|
return NULL;
|
|
|
|
// NB. This works for memaux when set to any RWpages[] value, ie. RamWork III "just works"
|
|
const BYTE bank1page = (offset >> 8) & 0xF;
|
|
return (g_isMemCacheValid && (memshadow[0xD0+bank1page] == pMemBase+(0xC0+bank1page)*_6502_PAGE_SIZE))
|
|
? mem+offset+0x1000 // Return ptr to $Dxxx address - 'mem' has (a potentially dirty) 4K RAM BANK1 mapped in at $D000
|
|
: pMemBase+offset; // Else return ptr to $Cxxx address
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
LPBYTE MemGetAuxPtrWithLC(const WORD offset)
|
|
{
|
|
LPBYTE lpMem = MemGetPtrBANK1(offset, memaux);
|
|
if (lpMem)
|
|
return lpMem;
|
|
|
|
return MemGetAuxPtr(offset);
|
|
}
|
|
|
|
LPBYTE MemGetAuxPtr(const WORD offset)
|
|
{
|
|
LPBYTE lpMem = g_isMemCacheValid && (memshadow[(offset >> 8)] == (memaux+(offset & 0xFF00)))
|
|
? mem+offset // Return 'mem' copy if possible, as page could be dirty
|
|
: memaux+offset;
|
|
|
|
#ifdef RAMWORKS
|
|
// Video scanner (for 14M video modes) always fetches from 1st 64K aux bank (RamWorks manual ref?)
|
|
if (((SW_PAGE2 && SW_80STORE) || GetVideo().VideoGetSW80COL()) &&
|
|
(
|
|
( ((offset & 0xFF00)>=0x0400) && ((offset & 0xFF00)<=0x0700) ) ||
|
|
( SW_HIRES && ((offset & 0xFF00)>=0x2000) && ((offset & 0xFF00)<=0x3F00) )
|
|
)
|
|
)
|
|
{
|
|
lpMem = g_isMemCacheValid && (memshadow[(offset >> 8)] == (RWpages[0]+(offset & 0xFF00)))
|
|
? mem+offset
|
|
: RWpages[0]+offset;
|
|
}
|
|
#endif
|
|
|
|
return lpMem;
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
// if memshadow == memmain
|
|
// so RD memmain
|
|
// case: RD == WR
|
|
// so RD(mem),WR(mem)
|
|
// so 64K memmain could be incorrect
|
|
// *therefore mem is correct
|
|
// case: RD != WR
|
|
// so RD(mem),WR(memaux)
|
|
// doesn't matter since RD != WR, then it's guaranteed that memaux is correct
|
|
// *therefore either mem or memmain is correct
|
|
// else ; memshadow != memmain
|
|
// so RD memaux (or ROM)
|
|
// case: RD == WR
|
|
// so RD(mem),WR(mem)
|
|
// so 64K memaux could be incorrect
|
|
// *therefore memmain is correct
|
|
// case: RD != WR
|
|
// so RD(mem),WR(memmain)
|
|
// doesn't matter since RD != WR, then it's guaranteed that memmain is correct
|
|
// *therefore memmain is correct
|
|
//
|
|
// *OR*
|
|
//
|
|
// Is the mem(cache) setup to read (via memshadow) from memmain?
|
|
// . if yes, then return the mem(cache) address as writes (via memwrite) may've made the mem(cache) dirty.
|
|
// . if no, then return memmain, as the mem(cache) isn't involved in memmain (any writes will go directly to this backing-store).
|
|
//
|
|
|
|
LPBYTE MemGetMainPtrWithLC(const WORD offset)
|
|
{
|
|
LPBYTE lpMem = MemGetPtrBANK1(offset, memmain);
|
|
if (lpMem)
|
|
return lpMem;
|
|
|
|
return MemGetMainPtr(offset);
|
|
}
|
|
|
|
LPBYTE MemGetMainPtr(const WORD offset)
|
|
{
|
|
#if 1
|
|
return g_isMemCacheValid && (memshadow[(offset >> 8)] == (memmain + (offset & 0xFF00)))
|
|
? mem + offset // Return 'mem' copy if possible, as page could be dirty
|
|
: memmain + offset;
|
|
#else
|
|
// TODO: GH#1426
|
|
if (!g_isMemCacheValid)
|
|
return memmain + offset;
|
|
|
|
if (offset < 0xC000)
|
|
{
|
|
return memshadow[(offset >> 8)] == (memmain + (offset & 0xFF00))
|
|
? mem + offset // Return 'mem' copy if possible, as page could be dirty
|
|
: memmain + offset;
|
|
}
|
|
|
|
// Required for a II+ w/Saturn in SLOT0 or //e w/Saturn (in another slot)
|
|
return memshadow[(offset >> 8)] == (g_pMemMainLanguageCard + (offset & 0xFF00) - 0xC000)
|
|
? mem + offset // Return 'mem' copy if possible, as page could be dirty
|
|
: memmain + offset;
|
|
#endif
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
static void BackMainImage(void)
|
|
{
|
|
if (!g_isMemCacheValid)
|
|
return;
|
|
|
|
for (UINT loop = 0; loop < 256; loop++)
|
|
{
|
|
if (memshadow[loop] && ((*(memdirty + loop) & 1) || (loop <= 1)))
|
|
memcpy(memshadow[loop], mem + (loop << 8), _6502_PAGE_SIZE);
|
|
|
|
*(memdirty + loop) &= ~1;
|
|
}
|
|
}
|
|
|
|
//-------------------------------------
|
|
|
|
// Used by:
|
|
// . Savestate: MemSaveSnapshotMemory(), MemLoadSnapshotAux()
|
|
// . VidHD : SaveSnapshot(), LoadSnapshot()
|
|
// . Debugger : CmdMemorySave(), CmdMemoryLoad()
|
|
LPBYTE MemGetBankPtr(const UINT nBank, const bool isSaveSnapshotOrDebugging/*=true*/)
|
|
{
|
|
// Only call BackMainImage() when a consistent 64K bank is needed, eg. for saving snapshot or debugging
|
|
// - for snapshot *loads* it's redundant, and worse it can corrupt pages 0 & 1 for aux banks, so must be avoided (GH#1262)
|
|
if (isSaveSnapshotOrDebugging)
|
|
BackMainImage(); // Flush any dirty pages to back-buffer
|
|
|
|
#ifdef RAMWORKS
|
|
if (nBank > g_uMaxExBanks)
|
|
return NULL;
|
|
|
|
if (nBank == 0)
|
|
return memmain;
|
|
|
|
return RWpages[nBank-1];
|
|
#else
|
|
return (nBank == 0) ? memmain :
|
|
(nBank == 1) ? memaux :
|
|
NULL;
|
|
#endif
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
LPBYTE MemGetCxRomPeripheral()
|
|
{
|
|
return pCxRomPeripheral;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// Post:
|
|
// . true: code memory
|
|
// . false: I/O memory or floating bus
|
|
bool MemIsAddrCodeMemory(const USHORT addr)
|
|
{
|
|
if (addr < APPLE_IO_BEGIN || addr > FIRMWARE_EXPANSION_END) // Assume all A][ types have at least 48K
|
|
return true;
|
|
|
|
if (addr < APPLE_SLOT_BEGIN) // [$C000..C0FF]
|
|
return false;
|
|
|
|
if (!IS_APPLE2 && SW_INTCXROM) // [$C100..C7FF] //e or Enhanced //e internal ROM
|
|
return true;
|
|
|
|
if (!IS_APPLE2 && !SW_SLOTC3ROM && (addr >> 8) == 0xC3) // [$C300..C3FF] //e or Enhanced //e internal ROM
|
|
return true;
|
|
|
|
if (addr <= APPLE_SLOT_END) // [$C100..C7FF]
|
|
{
|
|
UINT slot = (addr >> 8) & 0x7;
|
|
return IsCardInSlot(slot);
|
|
}
|
|
|
|
// [$C800..CFFF]
|
|
|
|
if (g_eExpansionRomType == eExpRomNull)
|
|
{
|
|
if (IO_SELECT || INTC8ROM) // Was at $Csxx and now in [$C800..$CFFF]
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
static void FreeMemImage(void)
|
|
{
|
|
#ifdef _WIN32
|
|
if (g_hMemImage)
|
|
{
|
|
for (UINT i = 0; i < num64KPages; i++)
|
|
UnmapViewOfFile(memimage + i * _6502_MEM_LEN);
|
|
|
|
CloseHandle(g_hMemImage);
|
|
g_hMemImage = NULL;
|
|
}
|
|
else
|
|
{
|
|
ALIGNED_FREE(memimage);
|
|
}
|
|
#else
|
|
if (g_hMemTempFile)
|
|
{
|
|
// unmap the whole region, everything inside will be unmapped too
|
|
munmap(memimage, num64KPages * _6502_MEM_LEN);
|
|
|
|
fclose(g_hMemTempFile);
|
|
g_hMemTempFile = NULL;
|
|
}
|
|
else
|
|
{
|
|
ALIGNED_FREE(memimage);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static LPBYTE AllocMemImage(void)
|
|
{
|
|
#ifdef _WIN32
|
|
LPBYTE baseAddr = NULL;
|
|
|
|
// Allocate memory for 'memimage' (and the alias 'mem')
|
|
// . Setup so we have 2 consecutive virtual 64K regions pointing to the same physical 64K region.
|
|
// . This is a fix (and optimisation) for 6502 opcodes that do a 16-bit read at 6502 address $FFFF. (GH#1285)
|
|
SYSTEM_INFO info;
|
|
GetSystemInfo(&info);
|
|
bool res = (info.dwAllocationGranularity == _6502_MEM_LEN);
|
|
|
|
if (res)
|
|
{
|
|
UINT retry = 10;
|
|
do
|
|
{
|
|
res = false;
|
|
const SIZE_T totalVirtualSize = _6502_MEM_LEN * num64KPages;
|
|
baseAddr = (LPBYTE)VirtualAlloc(0, totalVirtualSize, MEM_RESERVE, PAGE_NOACCESS);
|
|
if (baseAddr == NULL)
|
|
break;
|
|
VirtualFree(baseAddr, 0, MEM_RELEASE);
|
|
|
|
// Create a file mapping object of [64K] size that is backed by the system paging file.
|
|
g_hMemImage = CreateFileMapping(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0, _6502_MEM_LEN, NULL);
|
|
// NB. Returns NULL on failure (not INVALID_HANDLE_VALUE)
|
|
if (g_hMemImage == NULL)
|
|
break;
|
|
|
|
UINT count = 0;
|
|
while (count < num64KPages)
|
|
{
|
|
// MSDN: "To specify a suggested base address for the view, use the MapViewOfFileEx function. However, this practice is not recommended."
|
|
// The OS (ie. another process) may've beaten us to this suggested baseAddr. This is why we retry multiple times.
|
|
if (!MapViewOfFileEx(g_hMemImage, FILE_MAP_ALL_ACCESS, 0, 0, _6502_MEM_LEN, baseAddr + count * _6502_MEM_LEN))
|
|
break;
|
|
count++;
|
|
}
|
|
|
|
res = (count == num64KPages);
|
|
if (res)
|
|
break;
|
|
|
|
// Failed this time, so clean-up and retry...
|
|
FreeMemImage();
|
|
}
|
|
while (retry--);
|
|
|
|
#if 1
|
|
if (res) // test
|
|
{
|
|
baseAddr[0x0000] = 0x11;
|
|
baseAddr[0xffff] = 0x22;
|
|
USHORT value = *((USHORT*)(baseAddr + 0xffff));
|
|
_ASSERT(value == 0x1122);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
LogFileOutput("MemInitialize: SYSETEM_INFO.wAllocationGranularity = 0x%08X.\n", info.dwAllocationGranularity);
|
|
}
|
|
|
|
if (!res)
|
|
{
|
|
LogFileOutput("MemInitialize: Failed to map 2 adjacent virtual 64K pages (reverting to old method).\n");
|
|
baseAddr = ALIGNED_ALLOC(_6502_MEM_LEN);
|
|
}
|
|
|
|
return baseAddr;
|
|
#else
|
|
g_hMemTempFile = tmpfile();
|
|
if (g_hMemTempFile)
|
|
{
|
|
const int fd = fileno(g_hMemTempFile);
|
|
if (!ftruncate(fd, _6502_MEM_LEN))
|
|
{
|
|
LPBYTE baseAddr = static_cast<LPBYTE>(mmap(NULL, num64KPages * _6502_MEM_LEN, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
|
|
if (baseAddr)
|
|
{
|
|
bool ok = true;
|
|
for (UINT i = 0; i < num64KPages; i++)
|
|
{
|
|
void * target = baseAddr + i * _6502_MEM_LEN;
|
|
void * addr = mmap(target, _6502_MEM_LEN, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0);
|
|
ok = ok && (target == addr);
|
|
}
|
|
// we could fclose the file here
|
|
// but we keep it as a reminder of how to free the memory later
|
|
if (ok)
|
|
{
|
|
return baseAddr;
|
|
}
|
|
// revert to ALIGNED_ALLOC
|
|
munmap(baseAddr, num64KPages * _6502_MEM_LEN);
|
|
}
|
|
}
|
|
|
|
fclose(g_hMemTempFile);
|
|
g_hMemTempFile = NULL;
|
|
}
|
|
|
|
LogFileOutput("MemInitialize: Failed to map 2 adjacent virtual 64K pages (reverting to old method).\n");
|
|
return ALIGNED_ALLOC(_6502_MEM_LEN);
|
|
#endif
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void MemInitialize()
|
|
{
|
|
// ALLOCATE MEMORY FOR THE APPLE MEMORY IMAGE AND ASSOCIATED DATA STRUCTURES
|
|
// NB. alloc memaux even if a IIe with an empty aux slot - writes still go to memaux, but reads are from floating bus
|
|
memaux = ALIGNED_ALLOC(_6502_MEM_LEN); // NB. alloc even if model is Apple II/II+, since it's used by VidHD card
|
|
memmain = ALIGNED_ALLOC(_6502_MEM_LEN);
|
|
memimage = AllocMemImage();
|
|
|
|
memdirty = new BYTE[0x100];
|
|
memrom = new BYTE[0x3000 * MaxRomPages];
|
|
|
|
pCxRomInternal = new BYTE[CxRomSize];
|
|
pCxRomPeripheral = new BYTE[CxRomSize];
|
|
|
|
if (!memaux || !memdirty || !memimage || !memmain || !memrom || !pCxRomInternal || !pCxRomPeripheral)
|
|
{
|
|
GetFrame().FrameMessageBox(
|
|
"The emulator was unable to allocate the memory it "
|
|
"requires. Further execution is not possible.",
|
|
g_pAppTitle.c_str(),
|
|
MB_ICONSTOP | MB_SETFOREGROUND);
|
|
ExitProcess(1);
|
|
}
|
|
|
|
RWpages[0] = memaux;
|
|
|
|
#ifdef RAMWORKS
|
|
if (GetCardMgr().QueryAux() == CT_RamWorksIII)
|
|
{
|
|
// allocate memory for RamWorks III - up to 16MB
|
|
UINT i = 1;
|
|
while ((i < g_uMaxExBanks) && (RWpages[i] = ALIGNED_ALLOC(_6502_MEM_LEN)))
|
|
i++;
|
|
while (i < kMaxExMemoryBanks)
|
|
RWpages[i++] = NULL;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
|
|
// Load the No-Slot clock state
|
|
uint32_t hasNoSlotClock;
|
|
REGLOAD_DEFAULT(REGVALUE_NO_SLOT_CLOCK, &hasNoSlotClock, 1);
|
|
hasNoSlotClock ? MemInsertNoSlotClock() : MemRemoveNoSlotClock();
|
|
|
|
//
|
|
|
|
CreateLanguageCard();
|
|
|
|
MemInitializeROM();
|
|
MemInitializeCustomROM();
|
|
MemInitializeCustomF8ROM();
|
|
MemInitializeIO();
|
|
MemReset();
|
|
}
|
|
|
|
void MemInitializeROM(void)
|
|
{
|
|
// READ THE APPLE FIRMWARE ROMS INTO THE ROM IMAGE
|
|
UINT ROM_SIZE = 0;
|
|
WORD resourceId = 0;
|
|
switch (g_Apple2Type)
|
|
{
|
|
case A2TYPE_APPLE2: resourceId = IDR_APPLE2_ROM ; ROM_SIZE = Apple2RomSize ; break;
|
|
case A2TYPE_APPLE2PLUS: resourceId = IDR_APPLE2_PLUS_ROM ; ROM_SIZE = Apple2RomSize ; break;
|
|
case A2TYPE_APPLE2JPLUS: resourceId = IDR_APPLE2_JPLUS_ROM ; ROM_SIZE = Apple2RomSize ; break;
|
|
case A2TYPE_APPLE2E: resourceId = IDR_APPLE2E_ROM ; ROM_SIZE = Apple2eRomSize; break;
|
|
case A2TYPE_APPLE2EENHANCED:resourceId = IDR_APPLE2E_ENHANCED_ROM; ROM_SIZE = Apple2eRomSize; break;
|
|
case A2TYPE_PRAVETS82: resourceId = IDR_PRAVETS_82_ROM ; ROM_SIZE = Apple2RomSize ; break;
|
|
case A2TYPE_PRAVETS8M: resourceId = IDR_PRAVETS_8M_ROM ; ROM_SIZE = Apple2RomSize ; break;
|
|
case A2TYPE_PRAVETS8A: resourceId = IDR_PRAVETS_8C_ROM ; ROM_SIZE = Apple2eRomSize; break;
|
|
case A2TYPE_TK30002E: resourceId = IDR_TK3000_2E_ROM ; ROM_SIZE = Apple2eRomSize; break;
|
|
case A2TYPE_BASE64A: resourceId = IDR_BASE_64A_ROM ; ROM_SIZE = Base64ARomSize; break;
|
|
}
|
|
|
|
BYTE* pData = NULL;
|
|
if (resourceId)
|
|
{
|
|
pData = GetFrame().GetResource(resourceId, "ROM", ROM_SIZE);
|
|
}
|
|
|
|
if (pData == NULL)
|
|
{
|
|
char sRomFileName[ MAX_PATH ];
|
|
switch (g_Apple2Type)
|
|
{
|
|
case A2TYPE_APPLE2: strcpy(sRomFileName, "APPLE2.ROM"); break;
|
|
case A2TYPE_APPLE2PLUS: strcpy(sRomFileName, "APPLE2_PLUS.ROM"); break;
|
|
case A2TYPE_APPLE2JPLUS: strcpy(sRomFileName, "APPLE2_JPLUS.ROM"); break;
|
|
case A2TYPE_APPLE2E: strcpy(sRomFileName, "APPLE2E.ROM"); break;
|
|
case A2TYPE_APPLE2EENHANCED:strcpy(sRomFileName, "APPLE2E_ENHANCED.ROM"); break;
|
|
case A2TYPE_PRAVETS82: strcpy(sRomFileName, "PRAVETS82.ROM"); break;
|
|
case A2TYPE_PRAVETS8M: strcpy(sRomFileName, "PRAVETS8M.ROM"); break;
|
|
case A2TYPE_PRAVETS8A: strcpy(sRomFileName, "PRAVETS8C.ROM"); break;
|
|
case A2TYPE_TK30002E: strcpy(sRomFileName, "TK3000e.ROM"); break;
|
|
case A2TYPE_BASE64A: strcpy(sRomFileName, "BASE64A.ROM"); break;
|
|
default:
|
|
{
|
|
strcpy(sRomFileName, "Unknown type!");
|
|
GetPropertySheet().ConfigSaveApple2Type(A2TYPE_APPLE2EENHANCED);
|
|
}
|
|
}
|
|
|
|
std::string strText = StrFormat("Unable to open the required firmware ROM data file.\n\nFile: %s", sRomFileName);
|
|
|
|
LogFileOutput("%s\n", strText.c_str());
|
|
|
|
GetFrame().FrameMessageBox(strText.c_str(),
|
|
g_pAppTitle.c_str(),
|
|
MB_ICONSTOP | MB_SETFOREGROUND);
|
|
|
|
ExitProcess(1);
|
|
}
|
|
|
|
memset(pCxRomInternal,0,CxRomSize);
|
|
memset(pCxRomPeripheral,0,CxRomSize);
|
|
|
|
if (ROM_SIZE == Apple2eRomSize)
|
|
{
|
|
memcpy(pCxRomInternal, pData, CxRomSize);
|
|
pData += CxRomSize;
|
|
ROM_SIZE -= CxRomSize;
|
|
}
|
|
|
|
memrompages = MAX(MaxRomPages, ROM_SIZE / Apple2RomSize);
|
|
_ASSERT(ROM_SIZE % Apple2RomSize == 0);
|
|
memcpy(memrom, pData, ROM_SIZE); // ROM at $D000...$FFFF, one or several pages
|
|
}
|
|
|
|
void MemInitializeCustomF8ROM(void)
|
|
{
|
|
const UINT F8RomSize = 0x800;
|
|
const UINT F8RomOffset = Apple2RomSize-F8RomSize;
|
|
FrameBase& frame = GetFrame();
|
|
|
|
if (IsApple2Original(GetApple2Type()) && GetCardMgr().QuerySlot(SLOT0) == CT_LanguageCard)
|
|
{
|
|
BYTE* pData = frame.GetResource(IDR_APPLE2_PLUS_ROM, "ROM", Apple2RomSize);
|
|
if (pData == NULL)
|
|
{
|
|
frame.FrameMessageBox("Failed to read F8 (auto-start) ROM for language card in original Apple][", "AppleWin Error", MB_OK);
|
|
}
|
|
else
|
|
{
|
|
memcpy(memrom+F8RomOffset, pData+F8RomOffset, F8RomSize);
|
|
}
|
|
}
|
|
|
|
if (g_hCustomRomF8 != INVALID_HANDLE_VALUE)
|
|
{
|
|
std::vector<BYTE> oldRom(memrom, memrom+Apple2RomSize); // range ctor: [first,last)
|
|
|
|
SetFilePointer(g_hCustomRomF8, 0, NULL, FILE_BEGIN);
|
|
DWORD uNumBytesRead;
|
|
BOOL bRes = ReadFile(g_hCustomRomF8, memrom+F8RomOffset, F8RomSize, &uNumBytesRead, NULL);
|
|
if (uNumBytesRead != F8RomSize)
|
|
{
|
|
memcpy(memrom, &oldRom[0], Apple2RomSize); // ROM at $D000...$FFFF
|
|
bRes = FALSE;
|
|
}
|
|
|
|
// NB. If succeeded, then keep g_hCustomRomF8 handle open - so that any next restart can load it again
|
|
|
|
if (!bRes)
|
|
{
|
|
GetFrame().FrameMessageBox( "Failed to read custom F8 rom", "AppleWin Error", MB_OK );
|
|
CloseHandle(g_hCustomRomF8);
|
|
g_hCustomRomF8 = INVALID_HANDLE_VALUE;
|
|
// Failed, so use default rom...
|
|
}
|
|
}
|
|
|
|
if (GetPropertySheet().GetTheFreezesF8Rom() && IS_APPLE2)
|
|
{
|
|
BYTE* pData = frame.GetResource(IDR_FREEZES_F8_ROM, "ROM", 0x800);
|
|
if (pData)
|
|
{
|
|
memcpy(memrom+Apple2RomSize-F8RomSize, pData, F8RomSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MemInitializeCustomROM(void)
|
|
{
|
|
if (g_hCustomRom == INVALID_HANDLE_VALUE)
|
|
return;
|
|
|
|
SetFilePointer(g_hCustomRom, 0, NULL, FILE_BEGIN);
|
|
DWORD uNumBytesRead;
|
|
BOOL bRes = TRUE;
|
|
|
|
if (GetFileSize(g_hCustomRom, NULL) == Apple2eRomSize)
|
|
{
|
|
std::vector<BYTE> oldRomC0(pCxRomInternal, pCxRomInternal+CxRomSize); // range ctor: [first,last)
|
|
bRes = ReadFile(g_hCustomRom, pCxRomInternal, CxRomSize, &uNumBytesRead, NULL);
|
|
if (uNumBytesRead != CxRomSize)
|
|
{
|
|
memcpy(pCxRomInternal, &oldRomC0[0], CxRomSize); // ROM at $C000...$CFFF
|
|
bRes = FALSE;
|
|
}
|
|
}
|
|
|
|
if (bRes)
|
|
{
|
|
std::vector<BYTE> oldRom(memrom, memrom+Apple2RomSize); // range ctor: [first,last)
|
|
bRes = ReadFile(g_hCustomRom, memrom, Apple2RomSize, &uNumBytesRead, NULL);
|
|
if (uNumBytesRead != Apple2RomSize)
|
|
{
|
|
memcpy(memrom, &oldRom[0], Apple2RomSize); // ROM at $D000...$FFFF
|
|
bRes = FALSE;
|
|
}
|
|
}
|
|
|
|
// NB. If succeeded, then keep g_hCustomRom handle open - so that any next restart can load it again
|
|
|
|
if (!bRes)
|
|
{
|
|
GetFrame().FrameMessageBox( "Failed to read custom rom", "AppleWin Error", MB_OK );
|
|
CloseHandle(g_hCustomRom);
|
|
g_hCustomRom = INVALID_HANDLE_VALUE;
|
|
// Failed, so use default rom...
|
|
}
|
|
}
|
|
|
|
// Called by:
|
|
// . MemInitialize()
|
|
// . MemInitializeFromSnapshot()
|
|
//
|
|
// Since called by LoadState(), then this must not init any cards
|
|
// - it should only init the card I/O hooks
|
|
void MemInitializeIO(void)
|
|
{
|
|
InitIoHandlers();
|
|
|
|
GetCardMgr().InitializeIO(pCxRomPeripheral);
|
|
}
|
|
|
|
// Called by:
|
|
// . Snapshot_LoadState_v2()
|
|
void MemInitializeFromSnapshot(void)
|
|
{
|
|
MemInitializeROM();
|
|
MemInitializeCustomROM();
|
|
MemInitializeCustomF8ROM();
|
|
MemInitializeIO();
|
|
|
|
//
|
|
// Card and Expansion ROM
|
|
//
|
|
|
|
// Remove all the cards' ROMs at $Csnn if internal ROM is enabled
|
|
// Or just $C3nn if SLOT3 ROM is disabled
|
|
if (IsAppleIIeOrAbove(GetApple2Type()))
|
|
{
|
|
if (SW_INTCXROM)
|
|
IoHandlerCardsOut();
|
|
else if (!SW_SLOTC3ROM)
|
|
IoHandlerSlot3CardOut();
|
|
}
|
|
|
|
// Potentially init a card's expansion ROM
|
|
const UINT uSlot = g_uPeripheralRomSlot;
|
|
|
|
if (g_SlotInfo[uSlot].expansionRom != NULL)
|
|
{
|
|
_ASSERT(g_eExpansionRomType == eExpRomPeripheral);
|
|
|
|
memcpy(pCxRomPeripheral + 0x800, g_SlotInfo[uSlot].expansionRom, FIRMWARE_EXPANSION_SIZE);
|
|
// NB. Copied to /mem/ by UpdatePaging(true)
|
|
}
|
|
|
|
GetCardMgr().GetLanguageCardMgr().SetMemModeFromSnapshot();
|
|
|
|
// Finally setup the paging tables
|
|
MemUpdatePaging(true);
|
|
|
|
//
|
|
// VidHD
|
|
//
|
|
|
|
memVidHD = NULL;
|
|
|
|
if (GetCardMgr().GetVidHDCard())
|
|
{
|
|
if (IsApple2PlusOrClone(GetApple2Type()) || IsIIeWithoutAuxMem())
|
|
{
|
|
VidHDCard* vidHD = GetCardMgr().GetVidHDCard();
|
|
memVidHD = vidHD->IsWriteAux() ? memaux : NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline uint32_t getRandomTime()
|
|
{
|
|
return rand() ^ timeGetTime(); // We can't use g_nCumulativeCycles as it will be zero on a fresh execution.
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// Called by:
|
|
// . MemInitialize() eg. on AppleWin start & restart (eg. h/w config changes)
|
|
// . ResetMachineState() eg. Power-cycle ('Apple-Go' button)
|
|
// . Snapshot_LoadState_v2()
|
|
void MemReset()
|
|
{
|
|
// INITIALIZE THE PAGING TABLES
|
|
memset(memshadow, 0, 256*sizeof(LPBYTE));
|
|
memset(memwrite , 0, 256*sizeof(LPBYTE));
|
|
|
|
// INITIALIZE THE RAM IMAGES
|
|
memset(memaux , 0, 0x10000);
|
|
memset(memmain, 0, 0x10000);
|
|
|
|
// Init the I/O ROM vars
|
|
IO_SELECT = 0;
|
|
INTC8ROM = false;
|
|
g_eExpansionRomType = eExpRomNull;
|
|
g_uPeripheralRomSlot = 0;
|
|
|
|
memset(memdirty, 0, 0x100);
|
|
|
|
memVidHD = NULL;
|
|
|
|
g_uActiveBank = 0; // Power-cycle sets RamWorks III to 64KiB bank-0 (GH#1414)
|
|
|
|
//
|
|
|
|
int iByte;
|
|
|
|
// Memory is pseudo-initialized across various models of Apple ][ //e //c
|
|
// We chose a random one for nostalgia's sake
|
|
// To inspect:
|
|
// F2. Ctrl-F2. CALL-151, C050 C053 C057
|
|
// OR
|
|
// F2, Ctrl-F2, F7, HGR
|
|
uint32_t randTime = getRandomTime();
|
|
MemoryInitPattern_e eMemoryInitPattern = static_cast<MemoryInitPattern_e>(g_nMemoryClearType);
|
|
|
|
if (g_nMemoryClearType < 0) // random
|
|
{
|
|
eMemoryInitPattern = static_cast<MemoryInitPattern_e>( randTime % NUM_MIP );
|
|
|
|
// Don't use unless manually specified as a
|
|
// few badly written programs will not work correctly
|
|
// due to buffer overflows or not initializig memory before using.
|
|
if( eMemoryInitPattern == MIP_PAGE_ADDRESS_LOW )
|
|
eMemoryInitPattern = MIP_FF_FF_00_00;
|
|
}
|
|
|
|
switch( eMemoryInitPattern )
|
|
{
|
|
case MIP_FF_FF_00_00:
|
|
for( iByte = 0x0000; iByte < 0xC000; iByte += 4 ) // NB. ODD 16-bit words are zero'd above...
|
|
{
|
|
memmain[ iByte+0 ] = 0xFF;
|
|
memmain[ iByte+1 ] = 0xFF;
|
|
}
|
|
|
|
// Exceptions: xx28 xx29 xx68 xx69 Apple //e
|
|
for( iByte = 0x0000; iByte < 0xC000; iByte += 512 )
|
|
{
|
|
randTime = getRandomTime();
|
|
memmain[ iByte + 0x28 ] = (randTime >> 0) & 0xFF;
|
|
memmain[ iByte + 0x29 ] = (randTime >> 8) & 0xFF;
|
|
randTime = getRandomTime();
|
|
memmain[ iByte + 0x68 ] = (randTime >> 0) & 0xFF;
|
|
memmain[ iByte + 0x69 ] = (randTime >> 8) & 0xFF;
|
|
}
|
|
break;
|
|
|
|
case MIP_FF_00_FULL_PAGE:
|
|
// https://github.com/AppleWin/AppleWin/issues/225
|
|
// AppleWin 1.25 RC2 fails to boot Castle Wolfenstein #225
|
|
// This causes Castle Wolfenstein to not boot properly 100% with an error:
|
|
// ?OVERFLOW ERROR IN 10
|
|
// http://mirrors.apple2.org.za/ftp.apple.asimov.net/images/games/action/wolfenstein/castle_wolfenstein-fixed.dsk
|
|
for( iByte = 0x0000; iByte < 0xC000; iByte += 512 )
|
|
{
|
|
memset( &memmain[ iByte ], 0xFF, 256 );
|
|
|
|
// Exceptions: xx28: 00 xx68:00 Apple //e Platinum NTSC
|
|
memmain[ iByte + 0x28 ] = 0x00;
|
|
memmain[ iByte + 0x68 ] = 0x00;
|
|
}
|
|
break;
|
|
|
|
case MIP_00_FF_HALF_PAGE:
|
|
for( iByte = 0x0080; iByte < 0xC000; iByte += 256 ) // NB. start = 0x80, delta = 0x100 !
|
|
memset( &memmain[ iByte ], 0xFF, 128 );
|
|
break;
|
|
|
|
case MIP_FF_00_HALF_PAGE:
|
|
for( iByte = 0x0000; iByte < 0xC000; iByte += 256 )
|
|
memset( &memmain[ iByte ], 0xFF, 128 );
|
|
break;
|
|
|
|
case MIP_RANDOM:
|
|
unsigned char random[ 256 ];
|
|
for( iByte = 0x0000; iByte < 0xC000; iByte += 256 )
|
|
{
|
|
for( int i = 0; i < 256; i++ )
|
|
{
|
|
randTime = getRandomTime();
|
|
random[ (i+0) & 0xFF ] = (randTime >> 0) & 0xFF;
|
|
random[ (i+1) & 0xFF ] = (randTime >> 11) & 0xFF;
|
|
}
|
|
|
|
memcpy( &memmain[ iByte ], random, 256 );
|
|
}
|
|
break;
|
|
|
|
case MIP_PAGE_ADDRESS_LOW:
|
|
for( iByte = 0x0000; iByte < 0xC000; iByte++ )
|
|
memmain[ iByte ] = iByte & 0xFF;
|
|
break;
|
|
|
|
case MIP_PAGE_ADDRESS_HIGH:
|
|
for( iByte = 0x0000; iByte < 0xC000; iByte += 256 )
|
|
memset( &memmain[ iByte ], (iByte >> 8), 256 );
|
|
break;
|
|
|
|
default: // MIP_ZERO -- nothing to do
|
|
break;
|
|
}
|
|
|
|
// https://github.com/AppleWin/AppleWin/issues/206
|
|
// Work-around for a cold-booting bug in "Pooyan" which expects RNDL and RNDH to be non-zero.
|
|
randTime = getRandomTime();
|
|
memmain[ 0x4E ] = 0x20 | (randTime >> 0) & 0xFF;
|
|
memmain[ 0x4F ] = 0x20 | (randTime >> 8) & 0xFF;
|
|
|
|
// https://github.com/AppleWin/AppleWin/issues/222
|
|
// MIP_PAGE_ADDRESS_LOW breaks a few badly written programs!
|
|
// "Beautiful Boot by Mini Appler" reads past $61FF into $6200
|
|
// - "BeachParty-PoacherWars-DaytonDinger-BombsAway.dsk"
|
|
// - "Dung Beetles, Ms. PacMan, Pooyan, Star Cruiser, Star Thief, Invas. Force.dsk"
|
|
memmain[ 0x620B ] = 0x0;
|
|
|
|
// https://github.com/AppleWin/AppleWin/issues/222
|
|
// MIP_PAGE_ADDRESS_LOW
|
|
// "Copy II+ v5.0.dsk"
|
|
// There is a strange memory checker from $1B03 .. $1C25
|
|
// Stuck in loop at $1BC2: JSR $F88E INSDS2 before crashing to $0: 00 BRK
|
|
memmain[ 0xBFFD ] = 0;
|
|
memmain[ 0xBFFE ] = 0;
|
|
memmain[ 0xBFFF ] = 0;
|
|
|
|
// SET UP THE MEMORY IMAGE
|
|
mem = memimage;
|
|
|
|
// INITIALIZE PAGING, FILLING IN THE 64K MEMORY IMAGE
|
|
ResetPaging(true /*Initialize*/); // init g_memmode
|
|
MemAnnunciatorReset();
|
|
|
|
// INITIALIZE & RESET THE CPU
|
|
// . Do this after ROM has been copied back to mem[], so that PC is correctly init'ed from 6502's reset vector
|
|
CpuInitialize();
|
|
//Sets Caps Lock = false (Pravets 8A/C only)
|
|
|
|
z80_reset(); // NB. Also called above in CpuInitialize()
|
|
|
|
if (g_NoSlotClock)
|
|
g_NoSlotClock->Reset(); // NB. Power-cycle, but not RESET signal
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
static BYTE ReadFloatingBus(const ULONG uExecutedCycles, const bool fullSpeed, const bool auxEmpty = false)
|
|
{
|
|
BYTE* pMain = MemGetMainPtr(0x0000);
|
|
|
|
if (auxEmpty && (SW_AUXREAD || (SW_80STORE && SW_PAGE2)))
|
|
{
|
|
// Special case: Aux slot empty and in 80-col mode: video generator reading floating bus. (GH#1341)
|
|
// Can't rely on using "mem" (ie. the CPU read cache), since "80STORE && PAGE2" will have switched in the non-existent memory from "memaux"!
|
|
// NB. Only care about $400-7FF (ie. TEXT page 1)
|
|
pMain = memmain;
|
|
}
|
|
|
|
return pMain[NTSC_VideoGetScannerAddress(uExecutedCycles, fullSpeed)]; // OK: This does the 2-cycle adjust for ANSI STORY (End Credits)
|
|
}
|
|
|
|
BYTE MemReadFloatingBus(const ULONG uExecutedCycles)
|
|
{
|
|
return ReadFloatingBus(uExecutedCycles, g_bFullSpeed);
|
|
}
|
|
|
|
BYTE MemReadFloatingBus(const BYTE highbit, const ULONG uExecutedCycles)
|
|
{
|
|
BYTE r = ReadFloatingBus(uExecutedCycles, g_bFullSpeed);
|
|
return (r & ~0x80) | (highbit ? 0x80 : 0);
|
|
}
|
|
|
|
BYTE MemReadFloatingBusFromNTSC(void)
|
|
{
|
|
// fullspeed=false: to avoid NTSC_VideoGetScannerAddress() calling NTSC_VideoClockResync()
|
|
// NB. g_bFullSpeed only true when doing NTSC_VideoRedrawWholeScreen()
|
|
return ReadFloatingBus(0, false, true);
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
//#define DEBUG_FLIP_TIMINGS
|
|
|
|
#if defined(_DEBUG) && defined(DEBUG_FLIP_TIMINGS)
|
|
static void DebugFlip(WORD address, ULONG nExecutedCycles)
|
|
{
|
|
static unsigned __int64 uLastFlipCycle = 0;
|
|
static unsigned int uLastPage = -1;
|
|
|
|
if (address != 0x54 && address != 0x55)
|
|
return;
|
|
|
|
const unsigned int uNewPage = address & 1;
|
|
if (uLastPage == uNewPage)
|
|
return;
|
|
uLastPage = uNewPage;
|
|
|
|
CpuCalcCycles(nExecutedCycles); // Update g_nCumulativeCycles
|
|
|
|
const unsigned int uCyclesBetweenFlips = (unsigned int) (uLastFlipCycle ? g_nCumulativeCycles - uLastFlipCycle : 0);
|
|
uLastFlipCycle = g_nCumulativeCycles;
|
|
|
|
if (!uCyclesBetweenFlips)
|
|
return; // 1st time in func
|
|
|
|
const double fFreq = CLK_6502 / (double)uCyclesBetweenFlips;
|
|
|
|
LogOutput("Cycles between flips = %d (%f Hz)\n", uCyclesBetweenFlips, fFreq);
|
|
}
|
|
#endif
|
|
|
|
BYTE __stdcall MemSetPaging(WORD programcounter, WORD address, BYTE write, BYTE value, ULONG nExecutedCycles)
|
|
{
|
|
address &= 0xFF;
|
|
uint32_t lastmemmode = g_memmode;
|
|
#if defined(_DEBUG) && defined(DEBUG_FLIP_TIMINGS)
|
|
DebugFlip(address, nExecutedCycles);
|
|
#endif
|
|
|
|
// DETERMINE THE NEW MEMORY PAGING MODE.
|
|
if (IsAppleIIeOrAbove(GetApple2Type()))
|
|
{
|
|
switch (address)
|
|
{
|
|
case 0x00: SetMemMode(g_memmode & ~MF_80STORE); break;
|
|
case 0x01: SetMemMode(g_memmode | MF_80STORE); break;
|
|
case 0x02: SetMemMode(g_memmode & ~MF_AUXREAD); break;
|
|
case 0x03: SetMemMode(g_memmode | MF_AUXREAD); break;
|
|
case 0x04: SetMemMode(g_memmode & ~MF_AUXWRITE); break;
|
|
case 0x05: SetMemMode(g_memmode | MF_AUXWRITE); break;
|
|
case 0x06: SetMemMode(g_memmode & ~MF_INTCXROM); break;
|
|
case 0x07: SetMemMode(g_memmode | MF_INTCXROM); break;
|
|
case 0x08: SetMemMode(g_memmode & ~MF_ALTZP); break;
|
|
case 0x09: SetMemMode(g_memmode | MF_ALTZP); break;
|
|
case 0x0A: SetMemMode(g_memmode & ~MF_SLOTC3ROM); break;
|
|
case 0x0B: SetMemMode(g_memmode | MF_SLOTC3ROM); break;
|
|
case 0x54: SetMemMode(g_memmode & ~MF_PAGE2); break;
|
|
case 0x55: SetMemMode(g_memmode | MF_PAGE2); break;
|
|
case 0x56: SetMemMode(g_memmode & ~MF_HIRES); break;
|
|
case 0x57: SetMemMode(g_memmode | MF_HIRES); break;
|
|
#ifdef RAMWORKS
|
|
case 0x71: // extended memory aux page number
|
|
case 0x73: // Ramworks III set aux page number
|
|
if ((value < g_uMaxExBanks) && RWpages[value])
|
|
{
|
|
g_uActiveBank = value;
|
|
memaux = RWpages[g_uActiveBank];
|
|
UpdatePaging(false /*Initialize*/);
|
|
}
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (GetCardMgr().GetVidHDCard() && GetCardMgr().QueryAux() == CT_80Col)
|
|
{
|
|
// NB. if aux slot is empty, then writes already occur to memaux
|
|
memVidHD = MemIsWriteAux(g_memmode) ? memaux : NULL;
|
|
}
|
|
}
|
|
else // Apple ][,][+,][J-Plus or clone ][,][+
|
|
{
|
|
if (GetCardMgr().GetVidHDCard())
|
|
{
|
|
VidHDCard* vidHD = GetCardMgr().GetVidHDCard();
|
|
vidHD->VideoIOWrite(programcounter, address, write, value, nExecutedCycles);
|
|
memVidHD = vidHD->IsWriteAux() ? memaux : NULL;
|
|
}
|
|
}
|
|
|
|
if (IsCopamBase64A(GetApple2Type()))
|
|
{
|
|
switch (address)
|
|
{
|
|
case 0x58: SetMemMode(g_memmode & ~MF_ALTROM0); break;
|
|
case 0x59: SetMemMode(g_memmode | MF_ALTROM0); break;
|
|
case 0x5A: SetMemMode(g_memmode & ~MF_ALTROM1); break;
|
|
case 0x5B: SetMemMode(g_memmode | MF_ALTROM1); break;
|
|
}
|
|
}
|
|
|
|
if (MemOptimizeForModeChanging(programcounter, address))
|
|
return write ? 0 : MemReadFloatingBus(nExecutedCycles);
|
|
|
|
// IF THE MEMORY PAGING MODE HAS CHANGED, UPDATE OUR MEMORY IMAGES AND
|
|
// WRITE TABLES.
|
|
if ((lastmemmode != g_memmode) || modechanging)
|
|
{
|
|
// NB. Must check MF_SLOTC3ROM too, as IoHandlerCardsIn() depends on both MF_INTCXROM|MF_SLOTC3ROM
|
|
if ((lastmemmode & (MF_INTCXROM|MF_SLOTC3ROM)) != (g_memmode & (MF_INTCXROM|MF_SLOTC3ROM)))
|
|
{
|
|
if (!SW_INTCXROM)
|
|
{
|
|
if (!INTC8ROM) // GH#423
|
|
{
|
|
// Disable Internal ROM
|
|
// . Similar to $CFFF access
|
|
// . None of the peripheral cards can be driving the bus - so use the null ROM
|
|
memset(pCxRomPeripheral+0x800, 0, FIRMWARE_EXPANSION_SIZE);
|
|
if (GetIsMemCacheValid())
|
|
memset(mem+FIRMWARE_EXPANSION_BEGIN, 0, FIRMWARE_EXPANSION_SIZE);
|
|
g_eExpansionRomType = eExpRomNull;
|
|
g_uPeripheralRomSlot = 0;
|
|
}
|
|
IoHandlerCardsIn();
|
|
}
|
|
else
|
|
{
|
|
// Enable Internal ROM
|
|
memcpy(pCxRomPeripheral+0x800, pCxRomInternal+0x800, FIRMWARE_EXPANSION_SIZE);
|
|
if (GetIsMemCacheValid())
|
|
memcpy(mem+FIRMWARE_EXPANSION_BEGIN, pCxRomInternal+0x800, FIRMWARE_EXPANSION_SIZE);
|
|
g_eExpansionRomType = eExpRomInternal;
|
|
g_uPeripheralRomSlot = 0;
|
|
IoHandlerCardsOut();
|
|
}
|
|
}
|
|
|
|
UpdatePaging(false /*Initialize*/);
|
|
}
|
|
|
|
// Replicate 80STORE, PAGE2 and HIRES to video sub-system
|
|
if ((address <= 1) || ((address >= 0x54) && (address <= 0x57)))
|
|
return GetVideo().VideoSetMode(programcounter,address,write,value,nExecutedCycles);
|
|
|
|
return write ? 0 : MemReadFloatingBus(nExecutedCycles);
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// NB. Not particularly accurate (but good enough for now)
|
|
// . 80STORE && PAGE2 just means that writes occur to aux $400-7FF (and $2000-$3FFF if HIRES=1), not the entire aux 64K
|
|
|
|
bool MemIsWriteAux(uint32_t memMode)
|
|
{
|
|
return (memMode & MF_AUXWRITE) || // Write to aux: $200-$BFFF
|
|
((memMode & MF_80STORE) && (memMode & MF_PAGE2)); // Write to aux: $400-$7FF and $2000-$3FFF
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
bool IsIIeWithoutAuxMem(void)
|
|
{
|
|
return IsAppleIIe(GetApple2Type()) &&
|
|
(GetCardMgr().QueryAux() == CT_Empty || GetCardMgr().QueryAux() == CT_80Col);
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
static uint32_t ReadUINT24FromMemory(uint16_t addr)
|
|
{
|
|
return ReadByteFromMemory(addr) |
|
|
(ReadByteFromMemory(addr + 1) << 8) |
|
|
(ReadByteFromMemory(addr + 2) << 16);
|
|
}
|
|
|
|
bool MemOptimizeForModeChanging(WORD programcounter, WORD address)
|
|
{
|
|
if (IsAppleIIeOrAbove(GetApple2Type()))
|
|
{
|
|
// IF THE EMULATED PROGRAM HAS JUST UPDATED THE MEMORY WRITE MODE AND IS
|
|
// ABOUT TO UPDATE THE MEMORY READ MODE, HOLD OFF ON ANY PROCESSING UNTIL
|
|
// IT DOES SO.
|
|
//
|
|
// NB. A 6502 interrupt occurring between these memory write & read updates could lead to incorrect behaviour.
|
|
// - although any data-race is probably a bug in the 6502 code too.
|
|
if ((address >= 4) && (address <= 5) && // Now: RAMWRTOFF or RAMWRTON
|
|
((ReadUINT24FromMemory(programcounter) & 0x00FFFEFF) == 0x00C0028D)) // Next: STA $C002(RAMRDOFF) or STA $C003(RAMRDON)
|
|
{
|
|
modechanging = true;
|
|
return true;
|
|
}
|
|
|
|
// TODO: support Saturn in any slot.
|
|
// NB. GH#602 asks for any examples of this happening:
|
|
if ((address >= 0x80) && (address <= 0x8F) && (programcounter < 0xC000) && // Now: LC
|
|
(((ReadUINT24FromMemory(programcounter) & 0x00FFFEFF) == 0x00C0048D) || // Next: STA $C004(RAMWRTOFF) or STA $C005(RAMWRTON)
|
|
((ReadUINT24FromMemory(programcounter) & 0x00FFFEFF) == 0x00C0028D))) // or STA $C002(RAMRDOFF) or STA $C003(RAMRDON)
|
|
{
|
|
modechanging = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
void MemAnnunciatorReset(void)
|
|
{
|
|
for (UINT i=0; i<kNumAnnunciators; i++)
|
|
g_Annunciator[i] = 0;
|
|
|
|
if (IsCopamBase64A(GetApple2Type()))
|
|
{
|
|
SetMemMode(g_memmode & ~(MF_ALTROM0|MF_ALTROM1));
|
|
UpdatePaging(false /*Initialize*/);
|
|
}
|
|
}
|
|
|
|
bool MemGetAnnunciator(UINT annunciator)
|
|
{
|
|
return g_Annunciator[annunciator];
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
bool MemHasNoSlotClock(void)
|
|
{
|
|
return g_NoSlotClock != NULL;
|
|
}
|
|
|
|
void MemInsertNoSlotClock(void)
|
|
{
|
|
if (!MemHasNoSlotClock())
|
|
g_NoSlotClock = new CNoSlotClock;
|
|
g_NoSlotClock->Reset();
|
|
}
|
|
|
|
void MemRemoveNoSlotClock(void)
|
|
{
|
|
delete g_NoSlotClock;
|
|
g_NoSlotClock = NULL;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
// NB. Don't need to save 'modechanging', as this is just an optimisation to save calling UpdatePaging() twice.
|
|
// . If we were to save the state when 'modechanging' is set, then on restoring the state, the 6502 code will immediately update the read memory mode.
|
|
// . This will work correctly.
|
|
|
|
#define SS_YAML_KEY_MEMORYMODE "Memory Mode"
|
|
#define SS_YAML_KEY_LASTRAMWRITE "Last RAM Write"
|
|
#define SS_YAML_KEY_IOSELECT "IO_SELECT"
|
|
#define SS_YAML_KEY_IOSELECT_INT "IO_SELECT_InternalROM" // INTC8ROM
|
|
#define SS_YAML_KEY_EXPANSIONROMTYPE "Expansion ROM Type"
|
|
#define SS_YAML_KEY_PERIPHERALROMSLOT "Peripheral ROM Slot"
|
|
#define SS_YAML_KEY_ANNUNCIATOR "Annunciator"
|
|
#define SS_YAML_KEY_LASTSLOTTOSETMAINMEMLC "Last Slot to Set Main Mem LC"
|
|
#define SS_YAML_KEY_MMULCMODE "MMU LC Mode"
|
|
|
|
//
|
|
|
|
// Unit version history:
|
|
// 2: Added version field to card's state
|
|
static const UINT kUNIT_AUXSLOT_VER = 2;
|
|
|
|
// Unit version history:
|
|
// 2: Added: RGB card state
|
|
// 3: Extended: RGB card state ('80COL changed')
|
|
// 4: Support aux empty or aux 1KiB card
|
|
static const UINT kUNIT_CARD_VER = 4;
|
|
|
|
#define SS_YAML_KEY_NUMAUXBANKS "Num Aux Banks"
|
|
#define SS_YAML_KEY_ACTIVEAUXBANK "Active Aux Bank"
|
|
|
|
const std::string& MemGetSnapshotCardName80Col()
|
|
{
|
|
static const std::string name("80 Column");
|
|
return name;
|
|
}
|
|
|
|
const std::string& MemGetSnapshotCardNameExtended80Col()
|
|
{
|
|
static const std::string name("Extended 80 Column");
|
|
return name;
|
|
}
|
|
|
|
const std::string& MemGetSnapshotCardNameRamWorksIIIOld()
|
|
{
|
|
static const std::string name("RamWorksIII");
|
|
return name;
|
|
}
|
|
|
|
const std::string& MemGetSnapshotCardNameRamWorksIII()
|
|
{
|
|
static const std::string name("RamWorks III");
|
|
return name;
|
|
}
|
|
|
|
static const std::string& MemGetSnapshotStructName(void)
|
|
{
|
|
static const std::string name("Memory");
|
|
return name;
|
|
}
|
|
|
|
const std::string& MemGetSnapshotUnitAuxSlotName(void)
|
|
{
|
|
static const std::string name("Auxiliary Slot");
|
|
return name;
|
|
}
|
|
|
|
static const std::string& MemGetSnapshotMainMemStructName(void)
|
|
{
|
|
static const std::string name("Main Memory");
|
|
return name;
|
|
}
|
|
|
|
static const std::string& MemGetSnapshotAuxMemStructName(void)
|
|
{
|
|
static const std::string name("Auxiliary Memory Bank");
|
|
return name;
|
|
}
|
|
|
|
static void MemSaveSnapshotMemory(YamlSaveHelper& yamlSaveHelper, bool bIsMainMem, UINT bank=0, UINT size=64*1024)
|
|
{
|
|
LPBYTE pMemBase = MemGetBankPtr(bank);
|
|
|
|
if (bIsMainMem)
|
|
{
|
|
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", MemGetSnapshotMainMemStructName().c_str());
|
|
yamlSaveHelper.SaveMemory(pMemBase, size);
|
|
}
|
|
else
|
|
{
|
|
YamlSaveHelper::Label state(yamlSaveHelper, "%s%02X:\n", MemGetSnapshotAuxMemStructName().c_str(), bank-1);
|
|
yamlSaveHelper.SaveMemory(pMemBase, size);
|
|
}
|
|
}
|
|
|
|
void MemSaveSnapshot(YamlSaveHelper& yamlSaveHelper)
|
|
{
|
|
// Scope so that "Memory" & "Main Memory" are at same indent level
|
|
{
|
|
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", MemGetSnapshotStructName().c_str());
|
|
// LC bits
|
|
// . For II,II+: set later by slot-0 LC or Saturn
|
|
// . For //e,//c: set in SS_YAML_KEY_MMULCMODE
|
|
yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_MEMORYMODE, g_memmode & ~MF_LANGCARD_MASK); // Clear LC bits
|
|
if (!IsApple2PlusOrClone(GetApple2Type())) // NB. Thesed are set later for II,II+ by slot-0 LC or Saturn
|
|
{
|
|
yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_MMULCMODE, g_memmode & MF_LANGCARD_MASK);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_LASTRAMWRITE, GetLastRamWrite() ? 1 : 0);
|
|
}
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_IOSELECT, IO_SELECT);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_IOSELECT_INT, INTC8ROM ? 1 : 0);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_EXPANSIONROMTYPE, (UINT) g_eExpansionRomType);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_PERIPHERALROMSLOT, g_uPeripheralRomSlot);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_LASTSLOTTOSETMAINMEMLC, GetCardMgr().GetLanguageCardMgr().GetLastSlotToSetMainMemLC());
|
|
|
|
for (UINT i=0; i<kNumAnnunciators; i++)
|
|
{
|
|
std::string annunciator = SS_YAML_KEY_ANNUNCIATOR + std::string(1,'0'+i);
|
|
yamlSaveHelper.SaveBool(annunciator.c_str(), g_Annunciator[i]);
|
|
}
|
|
}
|
|
|
|
if (IsApple2PlusOrClone(GetApple2Type()))
|
|
MemSaveSnapshotMemory(yamlSaveHelper, true, 0, 48*1024); // NB. Language Card/Saturn provides the remaining 16K (or multiple) bank(s)
|
|
else
|
|
MemSaveSnapshotMemory(yamlSaveHelper, true);
|
|
}
|
|
|
|
bool MemLoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT unitVersion)
|
|
{
|
|
if (!yamlLoadHelper.GetSubMap(MemGetSnapshotStructName()))
|
|
return false;
|
|
|
|
// Create default LC type for AppleII machine (do prior to loading saved LC state)
|
|
ResetDefaultMachineMemTypes();
|
|
if (unitVersion == 1)
|
|
g_MemTypeAppleII = CT_LanguageCard; // version=1: original Apple II always has a LC
|
|
else
|
|
g_MemTypeAppleIIPlus = CT_Empty; // version=2+: Apple II/II+ initially start with slot-0 empty
|
|
SetExpansionMemTypeDefault();
|
|
CreateLanguageCard(); // Create default LC now for: (a) //e which has no slot-0 LC (so this is final)
|
|
// (b) II/II+ which get re-created later if slot-0 has a card
|
|
|
|
//
|
|
|
|
IO_SELECT = (BYTE) yamlLoadHelper.LoadUint(SS_YAML_KEY_IOSELECT);
|
|
INTC8ROM = yamlLoadHelper.LoadUint(SS_YAML_KEY_IOSELECT_INT) ? true : false;
|
|
g_eExpansionRomType = (eExpansionRomType) yamlLoadHelper.LoadUint(SS_YAML_KEY_EXPANSIONROMTYPE);
|
|
g_uPeripheralRomSlot = yamlLoadHelper.LoadUint(SS_YAML_KEY_PERIPHERALROMSLOT);
|
|
|
|
if (unitVersion == 1)
|
|
{
|
|
UINT uMemMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_MEMORYMODE) ^ MF_INTCXROM; // Convert from SLOTCXROM to INTCXROM
|
|
SetMemMode(uMemMode);
|
|
|
|
if (GetCardMgr().GetLanguageCardMgr().GetLanguageCard())
|
|
GetCardMgr().GetLanguageCardMgr().GetLanguageCard()->SetLCMemMode(uMemMode & MF_LANGCARD_MASK);
|
|
|
|
SetLastRamWrite(yamlLoadHelper.LoadUint(SS_YAML_KEY_LASTRAMWRITE) ? TRUE : FALSE);
|
|
}
|
|
else
|
|
{
|
|
UINT uMemMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_MEMORYMODE);
|
|
if (IsApple2PlusOrClone(GetApple2Type()) || unitVersion >= 9)
|
|
uMemMode &= ~MF_LANGCARD_MASK; // For II,II+: clear LC bits - set later by slot-0 LC or Saturn (or some other slot-n Saturn)
|
|
// For //e,//c: (>=v9) clear LC bits - set later after reading all cards and we know which card contributes these bits
|
|
// For //e (<9): don't clear, as only old versions only supported the IIe LC from the MMU
|
|
SetMemMode(uMemMode);
|
|
|
|
if (!IsApple2PlusOrClone(GetApple2Type())) // NB. These are set later for II,II+ by slot-0 LC or Saturn
|
|
{
|
|
if (unitVersion < 9)
|
|
{
|
|
GetCardMgr().GetLanguageCardMgr().GetLanguageCard()->SetLCMemMode(uMemMode & MF_LANGCARD_MASK);
|
|
}
|
|
else
|
|
{
|
|
UINT LCMemMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_MMULCMODE);
|
|
GetCardMgr().GetLanguageCardMgr().GetLanguageCard()->SetLCMemMode(LCMemMode);
|
|
}
|
|
SetLastRamWrite(yamlLoadHelper.LoadUint(SS_YAML_KEY_LASTRAMWRITE) ? TRUE : FALSE);
|
|
}
|
|
}
|
|
|
|
if (unitVersion >= 3)
|
|
{
|
|
for (UINT i=0; i<kNumAnnunciators; i++)
|
|
{
|
|
std::string annunciator = SS_YAML_KEY_ANNUNCIATOR + std::string(1,'0'+i);
|
|
g_Annunciator[i] = yamlLoadHelper.LoadBool(annunciator.c_str());
|
|
}
|
|
}
|
|
|
|
GetCardMgr().GetLanguageCardMgr().SetLastSlotToSetMainMemLCFromSnapshot(SLOT0);
|
|
if (unitVersion >= 9)
|
|
{
|
|
GetCardMgr().GetLanguageCardMgr().SetLastSlotToSetMainMemLCFromSnapshot(yamlLoadHelper.LoadUint(SS_YAML_KEY_LASTSLOTTOSETMAINMEMLC));
|
|
}
|
|
|
|
yamlLoadHelper.PopMap();
|
|
|
|
//
|
|
|
|
if (!yamlLoadHelper.GetSubMap( MemGetSnapshotMainMemStructName() ))
|
|
throw std::runtime_error("Card: Expected key: " + MemGetSnapshotMainMemStructName());
|
|
|
|
memset(memmain+0xC000, 0, LanguageCardSlot0::kMemBankSize); // Clear it, as high 16K may not be in the save-state's "Main Memory" (eg. the case of II+ Saturn replacing //e LC)
|
|
|
|
yamlLoadHelper.LoadMemory(memmain, _6502_MEM_LEN);
|
|
if (unitVersion == 1 && IsApple2PlusOrClone(GetApple2Type()))
|
|
{
|
|
// v1 for II/II+ doesn't have a dedicated slot-0 LC, instead the 16K is stored as the top 16K of memmain
|
|
memcpy(g_pMemMainLanguageCard, memmain+0xC000, LanguageCardSlot0::kMemBankSize);
|
|
memset(memmain+0xC000, 0, LanguageCardSlot0::kMemBankSize);
|
|
}
|
|
memset(memdirty, 0, 0x100);
|
|
|
|
yamlLoadHelper.PopMap();
|
|
|
|
// NB. MemInitializeFromSnapshot()->MemUpdatePaging() called at end of Snapshot_LoadState_v2()
|
|
// . At this point, the cards haven't been loaded (no aux mem & any card's expansion ROM is unknown) - so pointless calling MemUpdatePaging() at this stage (GH#1267)
|
|
|
|
return true;
|
|
}
|
|
|
|
void MemSaveSnapshotAux(YamlSaveHelper& yamlSaveHelper)
|
|
{
|
|
if (IS_APPLE2)
|
|
{
|
|
return; // No Aux slot for AppleII
|
|
}
|
|
|
|
if (IS_APPLE2C())
|
|
{
|
|
_ASSERT(g_uMaxExBanks == 1);
|
|
}
|
|
|
|
yamlSaveHelper.UnitHdr(MemGetSnapshotUnitAuxSlotName(), kUNIT_AUXSLOT_VER);
|
|
|
|
// Unit state
|
|
{
|
|
YamlSaveHelper::Label unitState(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
|
|
|
|
const SS_CARDTYPE cardType = GetCardMgr().QueryAux();
|
|
std::string card = cardType == CT_Empty ? Card::GetCardNameEmpty() :
|
|
cardType == CT_80Col ? MemGetSnapshotCardName80Col() :
|
|
cardType == CT_Extended80Col ? MemGetSnapshotCardNameExtended80Col() :
|
|
cardType == CT_RamWorksIII ? MemGetSnapshotCardNameRamWorksIII() :
|
|
"";
|
|
_ASSERT(!card.empty());
|
|
|
|
yamlSaveHelper.SaveString(SS_YAML_KEY_CARD, card.c_str());
|
|
yamlSaveHelper.Save("%s: %d\n", SS_YAML_KEY_VERSION, kUNIT_CARD_VER);
|
|
|
|
// Card state
|
|
if (cardType == CT_80Col)
|
|
{
|
|
YamlSaveHelper::Label cardState(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
|
|
|
|
// 1KiB memory
|
|
{
|
|
const UINT bank = 1;
|
|
LPBYTE pMemBase = MemGetBankPtr(bank);
|
|
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", MemGetSnapshotAuxMemStructName().c_str());
|
|
yamlSaveHelper.SaveMemory(pMemBase + TEXT_PAGE1_BEGIN, TEXT_PAGE1_SIZE);
|
|
}
|
|
}
|
|
else if (cardType == CT_Extended80Col || cardType == CT_RamWorksIII)
|
|
{
|
|
YamlSaveHelper::Label cardState(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
|
|
|
|
yamlSaveHelper.Save("%s: 0x%03X # [0,1..100] 0=no aux mem, 1=128K system, etc\n", SS_YAML_KEY_NUMAUXBANKS, g_uMaxExBanks);
|
|
yamlSaveHelper.Save("%s: 0x%02X # [ 0..FF] 0=memaux\n", SS_YAML_KEY_ACTIVEAUXBANK, g_uActiveBank);
|
|
|
|
for(UINT bank = 1; bank <= g_uMaxExBanks; bank++)
|
|
{
|
|
MemSaveSnapshotMemory(yamlSaveHelper, false, bank);
|
|
}
|
|
|
|
RGB_SaveSnapshot(yamlSaveHelper);
|
|
}
|
|
}
|
|
}
|
|
|
|
static SS_CARDTYPE MemLoadSnapshotAuxCommon(YamlLoadHelper& yamlLoadHelper, const std::string& card)
|
|
{
|
|
g_uMaxExBanks = 1; // Must be at least 1 (for aux mem) - regardless of Apple2 type!
|
|
g_uActiveBank = 0;
|
|
_ASSERT(MemGetBankPtr(1, false)); // Ensure there is always aux mem (eg. for CT_80Col or CT_VidHD)
|
|
|
|
SS_CARDTYPE cardType;
|
|
if (card == Card::GetCardNameEmpty())
|
|
cardType = CT_Empty;
|
|
else if (card == MemGetSnapshotCardName80Col())
|
|
cardType = CT_80Col;
|
|
else if (card == MemGetSnapshotCardNameExtended80Col())
|
|
cardType = CT_Extended80Col;
|
|
else if (card == MemGetSnapshotCardNameRamWorksIII() || card == MemGetSnapshotCardNameRamWorksIIIOld())
|
|
cardType = CT_RamWorksIII;
|
|
else
|
|
throw std::runtime_error(SS_YAML_KEY_UNIT ": AuxSlot: Unknown card: " + card);
|
|
|
|
// "State"
|
|
UINT numAuxBanks = 0, activeAuxBank = 0;
|
|
if (cardType == CT_Extended80Col || cardType == CT_RamWorksIII)
|
|
{
|
|
numAuxBanks = yamlLoadHelper.LoadUint(SS_YAML_KEY_NUMAUXBANKS);
|
|
activeAuxBank = yamlLoadHelper.LoadUint(SS_YAML_KEY_ACTIVEAUXBANK);
|
|
}
|
|
|
|
if (cardType == CT_Empty)
|
|
{
|
|
// nothing to do here
|
|
}
|
|
else if (cardType == CT_80Col)
|
|
{
|
|
const UINT bank1 = 1;
|
|
LPBYTE pBank = MemGetBankPtr(bank1, false);
|
|
_ASSERT(pBank);
|
|
|
|
std::string auxMemName = MemGetSnapshotAuxMemStructName();
|
|
if (!yamlLoadHelper.GetSubMap(auxMemName))
|
|
throw std::runtime_error("Memory: Missing map name: " + auxMemName);
|
|
|
|
yamlLoadHelper.LoadMemory(pBank + TEXT_PAGE1_BEGIN, TEXT_PAGE1_SIZE);
|
|
|
|
yamlLoadHelper.PopMap();
|
|
}
|
|
else
|
|
{
|
|
if (cardType == CT_Extended80Col)
|
|
{
|
|
if (numAuxBanks != 1 || activeAuxBank != 0)
|
|
throw std::runtime_error(SS_YAML_KEY_UNIT ": AuxSlot: Bad aux slot card state");
|
|
}
|
|
else // cardType == CT_RamWorksIII
|
|
{
|
|
if (numAuxBanks < 2 || numAuxBanks > kMaxExMemoryBanks || (activeAuxBank + 1) > numAuxBanks)
|
|
throw std::runtime_error(SS_YAML_KEY_UNIT ": AuxSlot: Bad aux slot card state");
|
|
}
|
|
|
|
g_uMaxExBanks = numAuxBanks;
|
|
g_uActiveBank = activeAuxBank;
|
|
|
|
//
|
|
|
|
for (UINT bank = 1; bank <= g_uMaxExBanks; bank++)
|
|
{
|
|
LPBYTE pBank = MemGetBankPtr(bank, false);
|
|
if (!pBank)
|
|
pBank = RWpages[bank - 1] = ALIGNED_ALLOC(_6502_MEM_LEN);
|
|
|
|
// "Auxiliary Memory Bankxx"
|
|
std::string auxMemName = MemGetSnapshotAuxMemStructName() + ByteToHexStr(bank - 1);
|
|
|
|
if (!yamlLoadHelper.GetSubMap(auxMemName))
|
|
throw std::runtime_error("Memory: Missing map name: " + auxMemName);
|
|
|
|
yamlLoadHelper.LoadMemory(pBank, _6502_MEM_LEN);
|
|
|
|
yamlLoadHelper.PopMap();
|
|
}
|
|
}
|
|
|
|
GetCardMgr().InsertAux(cardType);
|
|
|
|
memaux = RWpages[g_uActiveBank];
|
|
// NB. MemUpdatePaging(true) called at end of Snapshot_LoadState_v2()
|
|
|
|
return cardType;
|
|
}
|
|
|
|
static void MemLoadSnapshotAuxVer1(YamlLoadHelper& yamlLoadHelper)
|
|
{
|
|
std::string card = yamlLoadHelper.LoadString(SS_YAML_KEY_CARD);
|
|
MemLoadSnapshotAuxCommon(yamlLoadHelper, card);
|
|
}
|
|
|
|
static void MemLoadSnapshotAuxVer2(YamlLoadHelper& yamlLoadHelper)
|
|
{
|
|
std::string card = yamlLoadHelper.LoadString(SS_YAML_KEY_CARD);
|
|
UINT cardVersion = yamlLoadHelper.LoadUint(SS_YAML_KEY_VERSION);
|
|
|
|
if (card != Card::GetCardNameEmpty())
|
|
{
|
|
if (!yamlLoadHelper.GetSubMap(std::string(SS_YAML_KEY_STATE)))
|
|
throw std::runtime_error(SS_YAML_KEY_UNIT ": Expected sub-map name: " SS_YAML_KEY_STATE);
|
|
}
|
|
|
|
SS_CARDTYPE cardType = MemLoadSnapshotAuxCommon(yamlLoadHelper, card);
|
|
|
|
if (card == MemGetSnapshotCardNameExtended80Col() || card == MemGetSnapshotCardNameRamWorksIII())
|
|
RGB_LoadSnapshot(yamlLoadHelper, cardVersion);
|
|
}
|
|
|
|
bool MemLoadSnapshotAux(YamlLoadHelper& yamlLoadHelper, UINT unitVersion)
|
|
{
|
|
if (unitVersion < 1 || unitVersion > kUNIT_AUXSLOT_VER)
|
|
throw std::runtime_error(SS_YAML_KEY_UNIT ": AuxSlot: Version mismatch");
|
|
|
|
if (unitVersion == 1)
|
|
MemLoadSnapshotAuxVer1(yamlLoadHelper);
|
|
else
|
|
MemLoadSnapshotAuxVer2(yamlLoadHelper);
|
|
|
|
return true;
|
|
}
|
|
|
|
void NoSlotClockSaveSnapshot(YamlSaveHelper& yamlSaveHelper)
|
|
{
|
|
if (g_NoSlotClock)
|
|
g_NoSlotClock->SaveSnapshot(yamlSaveHelper);
|
|
}
|
|
|
|
void NoSlotClockLoadSnapshot(YamlLoadHelper& yamlLoadHelper)
|
|
{
|
|
if (!g_NoSlotClock)
|
|
g_NoSlotClock = new CNoSlotClock;
|
|
|
|
g_NoSlotClock->LoadSnapshot(yamlLoadHelper);
|
|
}
|