2018-10-26 18:23:30 +00:00
|
|
|
/*
|
|
|
|
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-2018, Tom Charlesworth, Michael Pohoreski, Nick Westgate
|
|
|
|
|
|
|
|
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: Language Card and Saturn 128K emulation
|
|
|
|
*
|
|
|
|
* Author: various
|
2024-03-22 21:36:50 +00:00
|
|
|
*
|
|
|
|
* Note: Here's what isn't fully emulated:
|
|
|
|
* From UTAII 5-42 (Application Note: Multiple RAM Card Configurations)
|
|
|
|
* . For II/II, INHIBIT' (disable motherboard ROM for $D000-$FFFF) and Apple's 16K RAM card isn't correct:
|
|
|
|
* . "If the expansion RAM is not enabled on a RAM card, the ROM on the card will respond to $F800-$FFFF addressing - period."
|
|
|
|
* . In UTAIIe 5-24, Sather describes this as "a particularly nettlesome associated fact"!
|
|
|
|
* . NB. "When INHIBIT' is low on the Apple IIe, all motherboard ROM is disabled, including high RAM."
|
|
|
|
* . Note: I assume a Saturn card "will release the $F800-$FFFF range when RAM on the card is disabled", since there's no F8 ROM on the Saturn.
|
|
|
|
* . Summary: for a II/II+ with an *Apple* 16K RAM card in slot 0, when (High) RAM is disabled, then:
|
|
|
|
* . ROM on the slot 0 card will respond, along with any Saturn card(s) in other slots which pull INHIBIT' low.
|
|
|
|
* . *** AppleWin emulates a slot 0 LC as if the Sather h/w mod had been applied.
|
|
|
|
* . [UTAII 5-42] "Enable two RAM cards for writing simultaneously..."
|
|
|
|
* "both RAM cards will accept the data from a single store instruction to the $D000-$FFFF range"
|
|
|
|
* *** AppleWin only stores to the last accessed RAM card.
|
|
|
|
* . Presumably enabling two RAM cards for reading RAM will both respond and the result is the OR-sum?
|
|
|
|
* *** AppleWin only loads from the last accessed RAM card.
|
|
|
|
* . The 16K RAM card has a socket for an F8 ROM, whereas the Saturn card doesn't.
|
|
|
|
* Also see UTAII 6-6, where Firmware card and 16K RAM card are described.
|
|
|
|
* . Sather refers to the Apple 16K RAM card, which is just the Apple Language Card.
|
|
|
|
*
|
2018-10-26 18:23:30 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "StdAfx.h"
|
|
|
|
|
2020-11-11 21:15:27 +00:00
|
|
|
#include "LanguageCard.h"
|
2024-03-22 21:36:50 +00:00
|
|
|
#include "CardManager.h"
|
2020-11-26 21:50:06 +00:00
|
|
|
#include "Core.h"
|
2019-10-08 21:12:35 +00:00
|
|
|
#include "CPU.h" // GH#700
|
2018-10-26 18:23:30 +00:00
|
|
|
#include "Log.h"
|
|
|
|
#include "Memory.h"
|
|
|
|
#include "YamlHelper.h"
|
|
|
|
|
|
|
|
|
|
|
|
const UINT LanguageCardUnit::kMemModeInitialState = MF_BANK2 | MF_WRITERAM; // !INTCXROM
|
|
|
|
|
2021-12-11 15:26:09 +00:00
|
|
|
LanguageCardUnit * LanguageCardUnit::create(UINT slot)
|
|
|
|
{
|
|
|
|
return new LanguageCardUnit(CT_LanguageCardIIe, slot);
|
|
|
|
}
|
|
|
|
|
|
|
|
LanguageCardUnit::LanguageCardUnit(SS_CARDTYPE type, UINT slot) :
|
|
|
|
Card(type, slot),
|
2024-03-22 21:36:50 +00:00
|
|
|
m_uLastRamWrite(0),
|
|
|
|
m_memMode(kMemModeInitialState),
|
|
|
|
m_pMemory(NULL)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
2024-03-22 21:36:50 +00:00
|
|
|
if (type != CT_Saturn128K && m_slot != LanguageCardUnit::kSlot0)
|
2022-01-30 21:25:40 +00:00
|
|
|
ThrowErrorInvalidSlot();
|
2021-12-11 15:26:09 +00:00
|
|
|
|
2024-03-22 21:36:50 +00:00
|
|
|
if (m_slot == SLOT0)
|
|
|
|
SetMemMainLanguageCard(NULL, SLOT0, true);
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
2018-11-04 15:07:46 +00:00
|
|
|
LanguageCardUnit::~LanguageCardUnit(void)
|
|
|
|
{
|
2024-03-22 21:36:50 +00:00
|
|
|
// Nothing to do for SetMemMainLanguageCard():
|
|
|
|
// . if //e, then no ptr to clean up (since just using memmain)
|
|
|
|
// . else: subclass will do ptr clean up
|
|
|
|
}
|
|
|
|
|
|
|
|
void LanguageCardUnit::Reset(const bool powerCycle)
|
|
|
|
{
|
|
|
|
// For power on: card's ctor will have set card's local memmode to LanguageCardUnit::kMemModeInitialState.
|
|
|
|
// For reset: II/II+ unaffected, so only for //e or above.
|
|
|
|
if (IsAppleIIeOrAbove(GetApple2Type()))
|
|
|
|
SetLCMemMode(LanguageCardUnit::kMemModeInitialState);
|
2018-11-04 15:07:46 +00:00
|
|
|
}
|
|
|
|
|
2021-11-11 21:45:55 +00:00
|
|
|
void LanguageCardUnit::InitializeIO(LPBYTE pCxRomPeripheral)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
2021-12-11 15:26:09 +00:00
|
|
|
RegisterIoHandler(m_slot, &LanguageCardUnit::IO, &LanguageCardUnit::IO, NULL, NULL, this, NULL);
|
2018-11-09 20:51:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
BYTE __stdcall LanguageCardUnit::IO(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULONG nExecutedCycles)
|
|
|
|
{
|
2021-12-11 15:26:09 +00:00
|
|
|
UINT uSlot = ((uAddr & 0xff) >> 4) - 8;
|
|
|
|
LanguageCardUnit* pLC = (LanguageCardUnit*) MemGetSlotParameters(uSlot);
|
2024-03-22 21:36:50 +00:00
|
|
|
_ASSERT(uSlot == SLOT0);
|
2018-11-09 20:51:51 +00:00
|
|
|
|
2024-03-22 21:36:50 +00:00
|
|
|
UINT memmode = pLC->GetLCMemMode();
|
|
|
|
UINT lastmemmode = memmode;
|
2018-10-26 18:23:30 +00:00
|
|
|
memmode &= ~(MF_BANK2 | MF_HIGHRAM);
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
if (!(uAddr & 8))
|
2018-10-26 18:23:30 +00:00
|
|
|
memmode |= MF_BANK2;
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
if (((uAddr & 2) >> 1) == (uAddr & 1))
|
2018-10-26 18:23:30 +00:00
|
|
|
memmode |= MF_HIGHRAM;
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
if (uAddr & 1) // GH#392
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
2018-11-09 20:51:51 +00:00
|
|
|
if (!bWrite && pLC->GetLastRamWrite())
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
memmode |= MF_WRITERAM; // UTAIIe:5-23
|
|
|
|
}
|
2019-10-08 21:12:35 +00:00
|
|
|
else if (bWrite && pLC->GetLastRamWrite() && pLC->IsOpcodeRMWabs(uAddr)) // GH#700
|
|
|
|
{
|
|
|
|
memmode |= MF_WRITERAM;
|
|
|
|
}
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memmode &= ~MF_WRITERAM; // UTAIIe:5-23
|
|
|
|
}
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
pLC->SetLastRamWrite( ((uAddr & 1) && !bWrite) ); // UTAIIe:5-23
|
2024-03-22 21:36:50 +00:00
|
|
|
pLC->SetLCMemMode(memmode);
|
|
|
|
|
|
|
|
const bool bCardChanged = GetCardMgr().GetLanguageCardMgr().GetLastSlotToSetMainMemLC() != SLOT0;
|
|
|
|
if (bCardChanged)
|
|
|
|
{
|
|
|
|
if (pLC->QueryType() == CT_LanguageCardIIe)
|
|
|
|
SetMemMainLanguageCard(NULL, SLOT0, true);
|
|
|
|
else // CT_LanguageCard
|
|
|
|
SetMemMainLanguageCard(pLC->m_pMemory, SLOT0);
|
|
|
|
}
|
2018-11-09 20:51:51 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
|
2018-11-10 10:30:19 +00:00
|
|
|
if (MemOptimizeForModeChanging(PC, uAddr))
|
|
|
|
return bWrite ? 0 : MemReadFloatingBus(nExecutedCycles);
|
2018-11-09 20:51:51 +00:00
|
|
|
|
|
|
|
// IF THE MEMORY PAGING MODE HAS CHANGED, UPDATE OUR MEMORY IMAGES AND
|
|
|
|
// WRITE TABLES.
|
2024-03-22 21:36:50 +00:00
|
|
|
if ((lastmemmode != memmode) || bCardChanged)
|
2018-11-09 20:51:51 +00:00
|
|
|
{
|
2024-03-22 21:36:50 +00:00
|
|
|
// NB. Always SetMemMode() - locally may be same, but card may've changed
|
|
|
|
SetMemMode((GetMemMode() & ~MF_LANGCARD_MASK) | (memmode & MF_LANGCARD_MASK));
|
2018-11-09 20:51:51 +00:00
|
|
|
MemUpdatePaging(0); // Initialize=0
|
|
|
|
}
|
2018-10-26 18:23:30 +00:00
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
return bWrite ? 0 : MemReadFloatingBus(nExecutedCycles);
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
2019-10-08 21:12:35 +00:00
|
|
|
bool LanguageCardUnit::IsOpcodeRMWabs(WORD addr)
|
|
|
|
{
|
|
|
|
BYTE param1 = mem[(regs.pc - 2) & 0xffff];
|
|
|
|
BYTE param2 = mem[(regs.pc - 1) & 0xffff];
|
|
|
|
if (param1 != (addr & 0xff) || param2 != 0xC0)
|
|
|
|
return false;
|
|
|
|
|
2019-10-12 21:34:35 +00:00
|
|
|
// GH#404, GH#700: INC $C083,X/C08B,X (RMW) to write enable the LC (any 6502/65C02/816)
|
2019-10-08 21:12:35 +00:00
|
|
|
BYTE opcode = mem[(regs.pc - 3) & 0xffff];
|
2019-10-12 21:34:35 +00:00
|
|
|
if (opcode == 0xFE && regs.x == 0) // INC abs,x
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// GH#700: INC $C083/C08B (RMW) to write enable the LC (65C02 only)
|
|
|
|
if (GetMainCpu() != CPU_65C02)
|
|
|
|
return false;
|
|
|
|
|
2019-10-08 21:12:35 +00:00
|
|
|
if (opcode == 0xEE || // INC abs
|
|
|
|
opcode == 0xCE || // DEC abs
|
|
|
|
opcode == 0x6E || // ROR abs
|
|
|
|
opcode == 0x4E || // LSR abs
|
|
|
|
opcode == 0x2E || // ROL abs
|
|
|
|
opcode == 0x0E) // ASL abs
|
|
|
|
return true;
|
|
|
|
|
2019-10-12 17:17:11 +00:00
|
|
|
if (opcode == 0x1C || // TRB abs
|
|
|
|
opcode == 0x0C) // TSB abs
|
2019-10-08 21:12:35 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-03-22 21:36:50 +00:00
|
|
|
void LanguageCardUnit::SetGlobalLCMemMode(void)
|
|
|
|
{
|
|
|
|
SetMemMode((GetMemMode() & ~MF_LANGCARD_MASK) | (GetLCMemMode() & MF_LANGCARD_MASK));
|
|
|
|
}
|
|
|
|
|
2018-10-26 18:23:30 +00:00
|
|
|
//-------------------------------------
|
|
|
|
|
2021-12-11 15:26:09 +00:00
|
|
|
LanguageCardSlot0 * LanguageCardSlot0::create(UINT slot)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
2021-12-11 15:26:09 +00:00
|
|
|
return new LanguageCardSlot0(CT_LanguageCard, slot);
|
|
|
|
}
|
2021-11-25 20:23:21 +00:00
|
|
|
|
2021-12-11 15:26:09 +00:00
|
|
|
LanguageCardSlot0::LanguageCardSlot0(SS_CARDTYPE type, UINT slot)
|
|
|
|
: LanguageCardUnit(type, slot)
|
|
|
|
{
|
2020-12-10 21:08:15 +00:00
|
|
|
m_pMemory = new BYTE[kMemBankSize];
|
2024-03-22 21:36:50 +00:00
|
|
|
if (m_slot == SLOT0)
|
|
|
|
SetMemMainLanguageCard(m_pMemory, SLOT0);
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LanguageCardSlot0::~LanguageCardSlot0(void)
|
|
|
|
{
|
2020-12-10 21:08:15 +00:00
|
|
|
delete [] m_pMemory;
|
2018-10-26 18:23:30 +00:00
|
|
|
m_pMemory = NULL;
|
2024-03-22 21:36:50 +00:00
|
|
|
if (m_slot == SLOT0)
|
|
|
|
SetMemMainLanguageCard(NULL, SLOT0);
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
static const UINT kUNIT_LANGUAGECARD_VER = 1;
|
|
|
|
|
|
|
|
#define SS_YAML_VALUE_CARD_LANGUAGECARD "Language Card"
|
|
|
|
|
|
|
|
#define SS_YAML_KEY_MEMORYMODE "Memory Mode"
|
|
|
|
#define SS_YAML_KEY_LASTRAMWRITE "Last RAM Write"
|
|
|
|
|
2022-02-28 20:52:18 +00:00
|
|
|
const std::string& LanguageCardSlot0::GetSnapshotMemStructName(void)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
static const std::string name("Memory Bank");
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2022-02-28 20:52:18 +00:00
|
|
|
const std::string& LanguageCardSlot0::GetSnapshotCardName(void)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
static const std::string name(SS_YAML_VALUE_CARD_LANGUAGECARD);
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LanguageCardSlot0::SaveLCState(YamlSaveHelper& yamlSaveHelper)
|
|
|
|
{
|
2024-03-22 21:36:50 +00:00
|
|
|
yamlSaveHelper.SaveHexUint32(SS_YAML_KEY_MEMORYMODE, GetLCMemMode() & MF_LANGCARD_MASK);
|
2018-10-26 18:23:30 +00:00
|
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_LASTRAMWRITE, GetLastRamWrite() ? 1 : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LanguageCardSlot0::LoadLCState(YamlLoadHelper& yamlLoadHelper)
|
|
|
|
{
|
2024-03-22 21:36:50 +00:00
|
|
|
UINT memMode = yamlLoadHelper.LoadUint(SS_YAML_KEY_MEMORYMODE) & MF_LANGCARD_MASK;
|
2018-10-26 18:23:30 +00:00
|
|
|
BOOL lastRamWrite = yamlLoadHelper.LoadUint(SS_YAML_KEY_LASTRAMWRITE) ? TRUE : FALSE;
|
2024-03-22 21:36:50 +00:00
|
|
|
SetLCMemMode(memMode);
|
2018-10-26 18:23:30 +00:00
|
|
|
SetLastRamWrite(lastRamWrite);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LanguageCardSlot0::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
|
|
|
|
{
|
|
|
|
if (!IsApple2PlusOrClone(GetApple2Type()))
|
|
|
|
{
|
|
|
|
_ASSERT(0);
|
|
|
|
LogFileOutput("Warning: Save-state attempted to save %s for //e or above\n", GetSnapshotCardName().c_str());
|
|
|
|
return; // No Language Card support for //e and above
|
|
|
|
}
|
|
|
|
|
2021-11-25 20:23:21 +00:00
|
|
|
YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_LANGUAGECARD_VER);
|
2018-10-26 18:23:30 +00:00
|
|
|
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
|
|
|
|
|
|
|
|
SaveLCState(yamlSaveHelper);
|
|
|
|
|
|
|
|
{
|
|
|
|
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", GetSnapshotMemStructName().c_str());
|
|
|
|
yamlSaveHelper.SaveMemory(m_pMemory, kMemBankSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-25 20:23:21 +00:00
|
|
|
bool LanguageCardSlot0::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
if (version != kUNIT_LANGUAGECARD_VER)
|
2022-01-30 21:25:40 +00:00
|
|
|
ThrowErrorInvalidVersion(version);
|
2018-10-26 18:23:30 +00:00
|
|
|
|
|
|
|
// "State"
|
|
|
|
LoadLCState(yamlLoadHelper);
|
|
|
|
|
|
|
|
if (!m_pMemory)
|
|
|
|
{
|
2020-12-10 21:08:15 +00:00
|
|
|
m_pMemory = new BYTE[kMemBankSize];
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!yamlLoadHelper.GetSubMap(GetSnapshotMemStructName()))
|
2021-12-18 16:37:28 +00:00
|
|
|
throw std::runtime_error("Memory: Missing map name: " + GetSnapshotMemStructName());
|
2018-10-26 18:23:30 +00:00
|
|
|
|
|
|
|
yamlLoadHelper.LoadMemory(m_pMemory, kMemBankSize);
|
|
|
|
|
|
|
|
yamlLoadHelper.PopMap();
|
|
|
|
|
|
|
|
// NB. MemUpdatePaging(TRUE) called at end of Snapshot_LoadState_v2()
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
2021-12-11 15:26:09 +00:00
|
|
|
UINT Saturn128K::g_uSaturnBanksFromCmdLine = 0;
|
|
|
|
|
|
|
|
Saturn128K::Saturn128K(UINT slot, UINT banks)
|
|
|
|
: LanguageCardSlot0(CT_Saturn128K, slot)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
m_uSaturnTotalBanks = (banks == 0) ? kMaxSaturnBanks : banks;
|
|
|
|
m_uSaturnActiveBank = 0;
|
|
|
|
|
|
|
|
for (UINT i=0; i<kMaxSaturnBanks; i++)
|
|
|
|
m_aSaturnBanks[i] = NULL;
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
m_aSaturnBanks[0] = m_pMemory; // Reuse memory allocated in base ctor
|
|
|
|
|
|
|
|
for (UINT i = 1; i < m_uSaturnTotalBanks; i++)
|
2020-12-10 21:08:15 +00:00
|
|
|
m_aSaturnBanks[i] = new BYTE[kMemBankSize]; // Saturn banks are 16K, max 8 banks/card
|
2018-10-26 18:23:30 +00:00
|
|
|
|
2024-03-22 21:36:50 +00:00
|
|
|
if (slot == SLOT0)
|
|
|
|
::SetMemMainLanguageCard(m_aSaturnBanks[m_uSaturnActiveBank], SLOT0);
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Saturn128K::~Saturn128K(void)
|
|
|
|
{
|
2018-11-09 20:51:51 +00:00
|
|
|
m_aSaturnBanks[0] = NULL; // just zero this - deallocated in base ctor
|
|
|
|
|
|
|
|
for (UINT i = 1; i < m_uSaturnTotalBanks; i++)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
if (m_aSaturnBanks[i])
|
|
|
|
{
|
2020-12-10 21:08:15 +00:00
|
|
|
delete [] m_aSaturnBanks[i];
|
2018-10-26 18:23:30 +00:00
|
|
|
m_aSaturnBanks[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
2024-03-22 21:36:50 +00:00
|
|
|
|
|
|
|
// NB. want the Saturn128K object that set the ptr via ::SetMemMainLanguageCard() to now set it to NULL (may be from SLOT0 or another slot)
|
|
|
|
// In reality, dtor only called when whole VM is being destroyed, so won't have have use-after-frees.
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
UINT Saturn128K::GetActiveBank(void)
|
|
|
|
{
|
|
|
|
return m_uSaturnActiveBank;
|
|
|
|
}
|
|
|
|
|
2021-11-11 21:45:55 +00:00
|
|
|
void Saturn128K::InitializeIO(LPBYTE pCxRomPeripheral)
|
2018-11-09 20:51:51 +00:00
|
|
|
{
|
2021-12-11 15:26:09 +00:00
|
|
|
RegisterIoHandler(m_slot, &Saturn128K::IO, &Saturn128K::IO, NULL, NULL, this, NULL);
|
2018-11-09 20:51:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
BYTE __stdcall Saturn128K::IO(WORD PC, WORD uAddr, BYTE bWrite, BYTE uValue, ULONG nExecutedCycles)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
Bin Addr.
|
|
|
|
$C0N0 4K Bank A, RAM read, Write protect
|
|
|
|
$C0N1 4K Bank A, ROM read, Write enabled
|
|
|
|
$C0N2 4K Bank A, ROM read, Write protect
|
|
|
|
$C0N3 4K Bank A, RAM read, Write enabled
|
|
|
|
0100 $C0N4 select 16K Bank 1
|
|
|
|
0101 $C0N5 select 16K Bank 2
|
|
|
|
0110 $C0N6 select 16K Bank 3
|
|
|
|
0111 $C0N7 select 16K Bank 4
|
|
|
|
$C0N8 4K Bank B, RAM read, Write protect
|
|
|
|
$C0N9 4K Bank B, ROM read, Write enabled
|
|
|
|
$C0NA 4K Bank B, ROM read, Write protect
|
|
|
|
$C0NB 4K Bank B, RAM read, Write enabled
|
|
|
|
1100 $C0NC select 16K Bank 5
|
|
|
|
1101 $C0ND select 16K Bank 6
|
|
|
|
1110 $C0NE select 16K Bank 7
|
|
|
|
1111 $C0NF select 16K Bank 8
|
|
|
|
*/
|
2021-12-11 15:26:09 +00:00
|
|
|
UINT uSlot = ((uAddr & 0xff) >> 4) - 8;
|
|
|
|
Saturn128K* pLC = (Saturn128K*) MemGetSlotParameters(uSlot);
|
2018-11-09 20:51:51 +00:00
|
|
|
|
|
|
|
_ASSERT(pLC->m_uSaturnTotalBanks);
|
|
|
|
if (!pLC->m_uSaturnTotalBanks)
|
|
|
|
return bWrite ? 0 : MemReadFloatingBus(nExecutedCycles);
|
2018-10-26 18:23:30 +00:00
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
bool bBankChanged = false;
|
2024-03-22 21:36:50 +00:00
|
|
|
UINT memmode = pLC->GetLCMemMode();
|
|
|
|
UINT lastmemmode = memmode;
|
2018-11-09 20:51:51 +00:00
|
|
|
|
|
|
|
if (uAddr & (1<<2))
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
2018-11-09 20:51:51 +00:00
|
|
|
pLC->m_uSaturnActiveBank = 0 // Saturn 128K Language Card Bank 0 .. 7
|
|
|
|
| (uAddr >> 1) & 4
|
|
|
|
| (uAddr >> 0) & 3;
|
2018-10-26 18:23:30 +00:00
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
if (pLC->m_uSaturnActiveBank >= pLC->m_uSaturnTotalBanks)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
// EG. Run RAMTEST128K tests on a Saturn 64K card
|
|
|
|
// TODO: Saturn::UpdatePaging() should deal with this case:
|
|
|
|
// . Technically read floating-bus, write to nothing
|
|
|
|
// . But the mem cache doesn't support floating-bus reads from non-I/O space
|
2018-11-09 20:51:51 +00:00
|
|
|
pLC->m_uSaturnActiveBank = pLC->m_uSaturnTotalBanks-1; // FIXME: just prevent crash for now!
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
2024-03-22 21:36:50 +00:00
|
|
|
::SetMemMainLanguageCard(pLC->m_aSaturnBanks[pLC->m_uSaturnActiveBank], uSlot);
|
2018-11-09 20:51:51 +00:00
|
|
|
bBankChanged = true;
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
memmode &= ~(MF_BANK2 | MF_HIGHRAM);
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
if (!(uAddr & 8))
|
2018-10-26 18:23:30 +00:00
|
|
|
memmode |= MF_BANK2;
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
if (((uAddr & 2) >> 1) == (uAddr & 1))
|
2018-10-26 18:23:30 +00:00
|
|
|
memmode |= MF_HIGHRAM;
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
if (uAddr & 1 && pLC->GetLastRamWrite())// Saturn differs from Apple's 16K LC: any access (LC is read-only)
|
2018-10-26 18:23:30 +00:00
|
|
|
memmode |= MF_WRITERAM;
|
|
|
|
else
|
|
|
|
memmode &= ~MF_WRITERAM;
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
pLC->SetLastRamWrite(uAddr & 1); // Saturn differs from Apple's 16K LC: any access (LC is read-only)
|
2024-03-22 21:36:50 +00:00
|
|
|
pLC->SetLCMemMode(memmode);
|
|
|
|
|
|
|
|
bBankChanged = GetCardMgr().GetLanguageCardMgr().GetLastSlotToSetMainMemLC() != uSlot;
|
|
|
|
if (bBankChanged)
|
|
|
|
{
|
|
|
|
::SetMemMainLanguageCard(pLC->m_aSaturnBanks[pLC->m_uSaturnActiveBank], uSlot);
|
|
|
|
}
|
2018-11-09 20:51:51 +00:00
|
|
|
}
|
|
|
|
|
2024-03-22 21:36:50 +00:00
|
|
|
// NB. Saturn can be put in any slot but MemOptimizeForModeChanging() currently only supports LC in slot 0.
|
|
|
|
// . This optimization (check if next opcode is STA $C002-5) isn't essential, so skip it for now.
|
2018-11-09 20:51:51 +00:00
|
|
|
|
|
|
|
// IF THE MEMORY PAGING MODE HAS CHANGED, UPDATE OUR MEMORY IMAGES AND
|
|
|
|
// WRITE TABLES.
|
|
|
|
if ((lastmemmode != memmode) || bBankChanged)
|
|
|
|
{
|
2024-03-22 21:36:50 +00:00
|
|
|
// NB. Always SetMemMode() - locally may be same, but card or bank may've changed
|
|
|
|
SetMemMode((GetMemMode() & ~MF_LANGCARD_MASK) | (memmode & MF_LANGCARD_MASK));
|
2018-11-09 20:51:51 +00:00
|
|
|
MemUpdatePaging(0); // Initialize=0
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
2018-11-09 20:51:51 +00:00
|
|
|
return bWrite ? 0 : MemReadFloatingBus(nExecutedCycles);
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
static const UINT kUNIT_SATURN_VER = 1;
|
|
|
|
|
|
|
|
#define SS_YAML_VALUE_CARD_SATURN128 "Saturn 128"
|
|
|
|
|
|
|
|
#define SS_YAML_KEY_NUM_SATURN_BANKS "Num Saturn Banks"
|
|
|
|
#define SS_YAML_KEY_ACTIVE_SATURN_BANK "Active Saturn Bank"
|
|
|
|
|
2022-02-28 20:52:18 +00:00
|
|
|
const std::string& Saturn128K::GetSnapshotMemStructName(void)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
static const std::string name("Memory Bank");
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2022-02-28 20:52:18 +00:00
|
|
|
const std::string& Saturn128K::GetSnapshotCardName(void)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
static const std::string name(SS_YAML_VALUE_CARD_SATURN128);
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Saturn128K::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
|
|
|
|
{
|
2021-11-25 20:23:21 +00:00
|
|
|
YamlSaveHelper::Slot slot(yamlSaveHelper, GetSnapshotCardName(), m_slot, kUNIT_SATURN_VER);
|
2018-10-26 18:23:30 +00:00
|
|
|
YamlSaveHelper::Label state(yamlSaveHelper, "%s:\n", SS_YAML_KEY_STATE);
|
|
|
|
|
|
|
|
SaveLCState(yamlSaveHelper);
|
|
|
|
|
|
|
|
yamlSaveHelper.Save("%s: 0x%02X # [1..8] 4=64K, 8=128K card\n", SS_YAML_KEY_NUM_SATURN_BANKS, m_uSaturnTotalBanks);
|
|
|
|
yamlSaveHelper.Save("%s: 0x%02X # [0..7]\n", SS_YAML_KEY_ACTIVE_SATURN_BANK, m_uSaturnActiveBank);
|
|
|
|
|
|
|
|
for(UINT uBank = 0; uBank < m_uSaturnTotalBanks; uBank++)
|
|
|
|
{
|
|
|
|
LPBYTE pMemBase = m_aSaturnBanks[uBank];
|
|
|
|
YamlSaveHelper::Label state(yamlSaveHelper, "%s%02X:\n", GetSnapshotMemStructName().c_str(), uBank);
|
|
|
|
yamlSaveHelper.SaveMemory(pMemBase, kMemBankSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-25 20:23:21 +00:00
|
|
|
bool Saturn128K::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
|
|
|
if (version != kUNIT_SATURN_VER)
|
2022-01-30 21:25:40 +00:00
|
|
|
ThrowErrorInvalidVersion(version);
|
2018-10-26 18:23:30 +00:00
|
|
|
|
|
|
|
// "State"
|
|
|
|
LoadLCState(yamlLoadHelper);
|
|
|
|
|
|
|
|
UINT numBanks = yamlLoadHelper.LoadUint(SS_YAML_KEY_NUM_SATURN_BANKS);
|
|
|
|
UINT activeBank = yamlLoadHelper.LoadUint(SS_YAML_KEY_ACTIVE_SATURN_BANK);
|
|
|
|
|
|
|
|
if (numBanks < 1 || numBanks > kMaxSaturnBanks || activeBank >= numBanks)
|
2021-12-18 16:37:28 +00:00
|
|
|
throw std::runtime_error(SS_YAML_KEY_UNIT ": Bad Saturn card state");
|
2018-10-26 18:23:30 +00:00
|
|
|
|
|
|
|
m_uSaturnTotalBanks = numBanks;
|
|
|
|
m_uSaturnActiveBank = activeBank;
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
for(UINT uBank = 0; uBank < m_uSaturnTotalBanks; uBank++)
|
|
|
|
{
|
2020-12-10 21:08:15 +00:00
|
|
|
if (!m_aSaturnBanks[uBank])
|
2018-10-26 18:23:30 +00:00
|
|
|
{
|
2020-12-10 21:08:15 +00:00
|
|
|
m_aSaturnBanks[uBank] = new BYTE[kMemBankSize];
|
2018-10-26 18:23:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// "Memory Bankxx"
|
2022-03-22 19:19:50 +00:00
|
|
|
std::string memName = GetSnapshotMemStructName() + ByteToHexStr(uBank);
|
2018-10-26 18:23:30 +00:00
|
|
|
|
|
|
|
if (!yamlLoadHelper.GetSubMap(memName))
|
2021-12-18 16:37:28 +00:00
|
|
|
throw std::runtime_error("Memory: Missing map name: " + memName);
|
2018-10-26 18:23:30 +00:00
|
|
|
|
2020-12-10 21:08:15 +00:00
|
|
|
yamlLoadHelper.LoadMemory(m_aSaturnBanks[uBank], kMemBankSize);
|
2018-10-26 18:23:30 +00:00
|
|
|
|
|
|
|
yamlLoadHelper.PopMap();
|
|
|
|
}
|
|
|
|
|
2024-03-22 21:36:50 +00:00
|
|
|
// NB. MemInitializeFromSnapshot() called at end of Snapshot_LoadState_v2():
|
|
|
|
// . SetMemMainLanguageCard() for the slot/card that last set the 16KB LC bank
|
|
|
|
// . MemUpdatePaging(TRUE)
|
2018-10-26 18:23:30 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2021-12-11 15:26:09 +00:00
|
|
|
|
2024-03-22 21:36:50 +00:00
|
|
|
void Saturn128K::SetMemMainLanguageCard(void)
|
|
|
|
{
|
|
|
|
::SetMemMainLanguageCard(m_aSaturnBanks[m_uSaturnActiveBank], m_slot);
|
|
|
|
}
|
|
|
|
|
2021-12-11 15:26:09 +00:00
|
|
|
void Saturn128K::SetSaturnMemorySize(UINT banks)
|
|
|
|
{
|
|
|
|
g_uSaturnBanksFromCmdLine = banks;
|
|
|
|
}
|
|
|
|
|
|
|
|
UINT Saturn128K::GetSaturnMemorySize()
|
|
|
|
{
|
|
|
|
return g_uSaturnBanksFromCmdLine;
|
|
|
|
}
|
2024-03-22 21:36:50 +00:00
|
|
|
|
|
|
|
//-------------------------------------
|
|
|
|
|
|
|
|
/*
|
|
|
|
* LangauageCardManager:
|
|
|
|
* . manage reset for all cards (eg. II/II+'s LC is unaffected, whereas //e's LC is)
|
|
|
|
* . manage lastSlotToSetMainMemLC
|
|
|
|
* . TODO: assist with debugger's display of "sNN" for active 16K bank
|
|
|
|
*/
|
|
|
|
|
|
|
|
void LanguageCardManager::Reset(const bool powerCycle /*=false*/)
|
|
|
|
{
|
|
|
|
if (IsApple2PlusOrClone(GetApple2Type()) && !powerCycle) // For reset : II/II+ unaffected
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (GetLanguageCard())
|
|
|
|
GetLanguageCard()->SetLastRamWrite(0);
|
|
|
|
|
|
|
|
if (IsApple2PlusOrClone(GetApple2Type()) && GetCardMgr().QuerySlot(SLOT0) == CT_Empty)
|
|
|
|
SetMemMode(0);
|
|
|
|
else
|
|
|
|
SetMemMode(LanguageCardUnit::kMemModeInitialState);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LanguageCardManager::SetMemModeFromSnapshot(void)
|
|
|
|
{
|
|
|
|
// If multiple "Language Cards" (eg. LC+Saturn or 2xSaturn) then setup via the last card that selected the 16KB LC bank.
|
|
|
|
// NB. Skip if not Saturn card (ie. a LC), since LC's are only in slot0 and in the ctor it has called SetMainMemLanguageCard()
|
|
|
|
if (GetCardMgr().QuerySlot(m_lastSlotToSetMainMemLCFromSnapshot) == CT_Saturn128K)
|
|
|
|
{
|
|
|
|
Saturn128K& saturn = dynamic_cast<Saturn128K&>(GetCardMgr().GetRef(m_lastSlotToSetMainMemLCFromSnapshot));
|
|
|
|
saturn.SetMemMainLanguageCard();
|
|
|
|
}
|
|
|
|
|
2024-03-22 23:16:37 +00:00
|
|
|
if (GetCardMgr().QuerySlot(m_lastSlotToSetMainMemLCFromSnapshot) != CT_Empty)
|
|
|
|
dynamic_cast<LanguageCardUnit&>(GetCardMgr().GetRef(m_lastSlotToSetMainMemLCFromSnapshot)).SetGlobalLCMemMode();
|
2024-03-22 21:36:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool LanguageCardManager::SetLanguageCard(SS_CARDTYPE type)
|
|
|
|
{
|
|
|
|
if (type == CT_Empty)
|
|
|
|
{
|
|
|
|
m_pLanguageCard = NULL;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
_ASSERT(GetLanguageCard() == NULL);
|
|
|
|
if (GetLanguageCard())
|
|
|
|
return false; // Only support one language card
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case CT_LanguageCard:
|
|
|
|
m_pLanguageCard = LanguageCardSlot0::create(SLOT0);
|
|
|
|
break;
|
|
|
|
case CT_LanguageCardIIe:
|
|
|
|
m_pLanguageCard = LanguageCardUnit::create(SLOT0);
|
|
|
|
break;
|
|
|
|
case CT_Saturn128K:
|
|
|
|
m_pLanguageCard = new Saturn128K(SLOT0, Saturn128K::GetSaturnMemorySize());
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
_ASSERT(0);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|