Merge branch 'master' into feature/resizable-ui

This commit is contained in:
Stephen Anthony
2026-07-23 17:00:39 -02:30
93 changed files with 565 additions and 248 deletions
+3 -6
View File
@@ -24,7 +24,7 @@ include:
# Linux ARM 64-bit
- project: 'libretro-infrastructure/ci-templates'
file: '/linux-arm64.yml'
file: '/linux-aarch64.yml'
# MacOS 64-bit
- project: 'libretro-infrastructure/ci-templates'
@@ -90,14 +90,11 @@ libretro-build-linux-x64:
CXXFLAGS: -Wno-deprecated-declarations # Deprecation warnings aren't helpful on the libretro Gitlab
# Linux ARM 64-bit
libretro-build-linux-arm64:
libretro-build-linux-aarch64:
extends:
- .libretro-linux-arm64-make-default
- .libretro-linux-aarch64-make-default
- .core-defs
image: $CI_SERVER_HOST:5050/libretro-infrastructure/libretro-build-arm64-ubuntu:backports
variables:
CC: /usr/bin/gcc-12
CXX: /usr/bin/g++-12
CXXFLAGS: -Wno-deprecated-declarations # Deprecation warnings aren't helpful on the libretro Gitlab
# MacOS 64-bit
-1
View File
@@ -13,7 +13,6 @@ Uses a custom shell configure script + GNU Make (not CMake or autotools).
```bash
./configure # First-time setup; generates config.mak
make -j$(nproc) # Build main stella executable (output: out/stella)
make test # Build and run unit tests (Google Test, output: out.test/stella-test)
make clean # Remove object files
make distclean # Remove object files + config.mak
make DEBUG=1 # Debug build with symbols and _GLIBCXX_DEBUG
+1 -1
View File
@@ -81,7 +81,7 @@ void JPGLibrary::loadImage(string_view filename, FBSurface& surface,
const auto width = static_cast<uInt32>(njGetWidth());
const auto height = static_cast<uInt32>(njGetHeight());
const bool isColor = njIsColor() != 0;
const auto bytesPerPixel = isColor ? 3uz : 1uz;
const auto bytesPerPixel = isColor ? 3UZ : 1UZ;
// njGetImage() points into nanojpeg's internal buffer — no extra copy needed
const ByteSpan pixels{ njGetImage(),
+2 -2
View File
@@ -79,8 +79,8 @@ void Logger::logMessage(string_view message, Level level)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Logger::setLogParameters(int logLevel, bool logToConsole)
{
if (logLevel >= std::to_underlying(Level::MIN) &&
logLevel <= std::to_underlying(Level::MAX))
if (std::cmp_greater_equal(logLevel, std::to_underlying(Level::MIN)) &&
std::cmp_less_equal(logLevel, std::to_underlying(Level::MAX)))
{
myLogLevel = logLevel;
myLogToConsole = logToConsole;
+1 -1
View File
@@ -894,7 +894,7 @@ void PhysicalJoystickHandler::handleAxisEvent(int stick, int axis, int value)
{
const bool isLeft = (j->type == LEFT_STELLADAPTOR ||
j->type == LEFT_2600DAPTOR);
const auto port = isLeft ? 0uz : 1uz;
const auto port = isLeft ? 0UZ : 1UZ;
const auto& ctrl = isLeft ? myOSystem.console().leftController()
: myOSystem.console().rightController();
if(myOSystem.hasConsole() && ctrl.type() == Controller::Type::Driving)
+1 -1
View File
@@ -174,7 +174,7 @@ class PhysicalJoystickHandler
friend std::ostream& operator<<(std::ostream& os, const PhysicalJoystickHandler& jh);
static constexpr JoyDir convertAxisValue(int value) {
return value == std::to_underlying(JoyDir::NONE)
return std::cmp_equal(value, std::to_underlying(JoyDir::NONE))
? JoyDir::NONE
: value > 0 ? JoyDir::POS : JoyDir::NEG;
}
+3 -3
View File
@@ -162,7 +162,7 @@ void PNGLibrary::loadImage(string_view filename, FBSurface& surface,
// And read directly into the surface buffer
auto* row = reinterpret_cast<png_bytep>(base);
for(auto y = 0uz; y < height; ++y, row += rowStride)
for(auto y = 0UZ; y < height; ++y, row += rowStride)
png_read_row(png_ptr, reinterpret_cast<png_bytep>(row), nullptr);
// We're finished reading
@@ -258,7 +258,7 @@ void PNGLibrary::saveImage(string_view filename, const FBSurface& surface,
+ srcRect.y() * rowStride
+ srcRect.x() * sizeof(uInt32);
for(auto y = 0uz; y < height; ++y, row += rowStride)
for(auto y = 0UZ; y < height; ++y, row += rowStride)
png_write_row(png_ptr, reinterpret_cast<png_bytep>(row));
// We're finished writing
@@ -464,7 +464,7 @@ void PNGLibrary::writeMetaData(png_structp png_ptr, png_infop info_ptr,
return;
std::vector<png_text> text_ptr(numMetaData);
for(auto i = 0uz; i < numMetaData; ++i)
for(auto i = 0UZ; i < numMetaData; ++i)
{
text_ptr[i].key = const_cast<char*>(metaData[i].first.c_str());
text_ptr[i].text = const_cast<char*>(metaData[i].second.toCString());
+1 -1
View File
@@ -370,7 +370,7 @@ PaletteArray PaletteHandler::adjustedPalette(const PaletteArray& palette) const
adjust[i] = powf(i * toFloat, gamma) * contrast + brightness;
// Transform original palette into destination palette
for(auto i = 0uz; i < destPalette.size(); i += 2)
for(auto i = 0UZ; i < destPalette.size(); i += 2)
{
const uInt32 pixel = palette[i];
int r = (pixel >> 16) & 0xff;
+1 -1
View File
@@ -359,7 +359,7 @@ string RewindManager::getUnitString(Int64 cycles)
const uInt64 u_cycles = std::abs(cycles);
auto i = 0uz;
auto i = 0UZ;
for(i = 0; i < UNIT_NAMES.size() - 1; ++i)
{
// use the lower unit up to twice the nextCycles unit, except for an exact match of the nextCycles unit
+1 -1
View File
@@ -39,7 +39,7 @@ class StateManager
// MovieRecord,
// MoviePlayback
};
static constexpr string_view STATE_HEADER = "07000003state";
static constexpr string_view STATE_HEADER = "07000004state";
/**
Create a new statemananger class.
+3 -3
View File
@@ -68,7 +68,7 @@ class StringParser
void parseLines()
{
auto start = 0uz;
auto start = 0UZ;
const size_t n = myBuffer.size();
while(start < n)
@@ -84,7 +84,7 @@ class StringParser
void parseWrapped(size_t maxlen)
{
auto start = 0uz;
auto start = 0UZ;
const size_t n = myBuffer.size();
while(start < n)
@@ -100,7 +100,7 @@ class StringParser
void wrapLine(string_view line, size_t maxlen)
{
auto beg = 0uz;
auto beg = 0UZ;
while(beg < line.size())
{
+2 -2
View File
@@ -218,7 +218,7 @@ void ZipHandler::ZipFile::initialize()
myHeaders.clear();
myHeaders.reserve(myEcd.cdTotalEntries);
auto pos = 0uz;
auto pos = 0UZ;
while(pos < myCd.size())
{
// Ensure the fixed-size part of the entry header lies within the central
@@ -350,7 +350,7 @@ void ZipHandler::ZipFile::ensureIndex() const
myHeaderIndex.reserve(myHeaders.size());
for(auto i = 0uz; i < myHeaders.size(); ++i)
for(auto i = 0UZ; i < myHeaders.size(); ++i)
myHeaderIndex.emplace(myHeaders[i].filename, i);
myIndexBuilt = true;
+1 -1
View File
@@ -138,7 +138,7 @@ void LanczosResampler::fillFragment(float* fragment, uInt32 length)
const bool stereoOut = myFormatTo.stereo;
const size_t outputSamples = stereoOut ? (length >> 1) : length;
for (auto i = 0uz; i < outputSamples; ++i) {
for (auto i = 0UZ; i < outputSamples; ++i) {
const float* kernel = myPrecomputedKernels.get() +
static_cast<size_t>(myCurrentKernelIndex) * myKernelSize;
if (++myCurrentKernelIndex == myPrecomputedKernelCount)
+1 -1
View File
@@ -53,7 +53,7 @@ void SimpleResampler::fillFragment(float* fragment, uInt32 length)
const size_t outputSamples = stereoOut ? (length >> 1) : length;
// For the following math, remember that myTimeIndex = time * myFormatFrom.sampleRate * myFormatTo.sampleRate
for (auto i = 0uz; i < outputSamples; ++i) {
for (auto i = 0UZ; i < outputSamples; ++i) {
if (stereoIn) {
const float sampleL = static_cast<float>(
myCurrentFragment[2*static_cast<size_t>(myFragmentIndex)]) * SAMPLE_SCALE;
+17 -17
View File
@@ -238,12 +238,12 @@ namespace BSPF
constexpr bool isLowerAscii(char c) { return c >= 'a' && c <= 'z'; }
// Convert string to given case
inline const string& toUpperCase(string& s)
constexpr string& toUpperCase(string& s)
{
std::ranges::transform(s, s.begin(), [](char c){ return toUpperAscii(c); });
return s;
}
inline const string& toLowerCase(string& s)
constexpr string& toLowerCase(string& s)
{
std::ranges::transform(s, s.begin(), [](char c){ return toLowerAscii(c); });
return s;
@@ -251,7 +251,7 @@ namespace BSPF
// Convert string to integer, using default value on any error
template<int BASE = 10>
inline int stoi(string_view s, int defaultValue = 0)
constexpr int stoi(string_view s, int defaultValue = 0)
{
// Skip leading spaces safely
const auto pos = s.find_first_not_of(' ');
@@ -270,7 +270,7 @@ namespace BSPF
{
// Only compare up to the length of the shorter string
const auto maxsize = std::min(s1.size(), s2.size());
for(auto i = 0uz; i < maxsize; ++i)
for(auto i = 0UZ; i < maxsize; ++i)
{
const char c1 = toUpperAscii(s1[i]);
const char c2 = toUpperAscii(s2[i]);
@@ -336,8 +336,8 @@ namespace BSPF
return false;
// Remaining characters must appear in order
auto pos = 1uz;
for(auto j = 1uz; j < s2.size(); ++j)
auto pos = 1UZ;
for(auto j = 1UZ; j < s2.size(); ++j)
{
const char target = toUpperAscii(s2[j]);
while(pos < s1.size() && toUpperAscii(s1[pos]) != target)
@@ -358,15 +358,15 @@ namespace BSPF
if(s1.empty() || s2.empty()) return false;
// Skip leading '_' for both strings if both start with it
const auto ofs = (s1[0] == '_' && s2[0] == '_') ? 1uz : 0uz;
const auto ofs = (s1[0] == '_' && s2[0] == '_') ? 1UZ : 0UZ;
// First character must match (case insensitive)
if(toUpperAscii(s1[ofs]) != toUpperAscii(s2[ofs]))
return false;
auto pos = 1uz + ofs; // current search position in s1 (absolute)
auto pos = 1UZ + ofs; // current search position in s1 (absolute)
for(auto j = 1uz + ofs; j < s2.size(); ++j)
for(auto j = 1UZ + ofs; j < s2.size(); ++j)
{
const char c2 = s2[j];
@@ -422,10 +422,10 @@ namespace BSPF
pattern = effectivePat;
}
auto si = 0uz; // current position in str
auto pi = 0uz; // current position in pattern
auto si = 0UZ; // current position in str
auto pi = 0UZ; // current position in pattern
auto starPi = string_view::npos; // position of last '*' in pattern
auto starSi = 0uz; // position in str when last '*' was matched
auto starSi = 0UZ; // position in str when last '*' was matched
while(si < str.length())
{
@@ -464,7 +464,7 @@ namespace BSPF
constexpr void replaceAll(string& str, string_view from, string_view to)
{
if(from.empty()) return;
auto start_pos = 0uz;
auto start_pos = 0UZ;
while((start_pos = str.find(from, start_pos)) != string_view::npos)
{
str.replace(start_pos, from.length(), to);
@@ -490,7 +490,7 @@ namespace BSPF
// name from a (potentially shared/imported) properties entry is concatenated
// into a save/snapshot path. Both '/' and '\' are replaced regardless of
// platform so the result can't escape its intended directory.
inline string sanitizeFilename(string_view name)
constexpr string sanitizeFilename(string_view name)
{
string result{name};
std::ranges::replace(result, '/', '_');
@@ -532,10 +532,10 @@ namespace BSPF
if(image.size() < sigsize)
return false;
auto count = 0uz;
for(auto i = 0uz; i <= image.size() - sigsize; ++i)
auto count = 0UZ;
for(auto i = 0UZ; i <= image.size() - sigsize; ++i)
{
auto j = 0uz;
auto j = 0UZ;
for(j = 0; j < sigsize; ++j)
{
if(image[i + j] != signature[j])
+1 -1
View File
@@ -47,7 +47,7 @@ void AtariNTSC::generateKernels()
constexpr float PALETTE_SCALE = rgb_unit / 255.F;
const uInt8* ptr = myRGBPalette.data();
for(auto entry = 0uz; entry < myRGBPalette.size() / 3; ++entry)
for(auto entry = 0UZ; entry < myRGBPalette.size() / 3; ++entry)
{
const float r = (*ptr++) * PALETTE_SCALE + rgb_offset,
g = (*ptr++) * PALETTE_SCALE + rgb_offset,
+4 -4
View File
@@ -25,7 +25,7 @@ void BreakpointMap::add(const Breakpoint& breakpoint, uInt32 flags)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void BreakpointMap::add(uInt16 addr, uInt8 bank, uInt32 flags)
void BreakpointMap::add(uInt16 addr, uInt16 bank, uInt32 flags)
{
add(Breakpoint(addr, bank), flags);
}
@@ -42,7 +42,7 @@ void BreakpointMap::erase(const Breakpoint& breakpoint)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void BreakpointMap::erase(uInt16 addr, uInt8 bank)
void BreakpointMap::erase(uInt16 addr, uInt16 bank)
{
erase(Breakpoint(addr, bank));
}
@@ -55,7 +55,7 @@ uInt32 BreakpointMap::get(const Breakpoint& breakpoint) const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 BreakpointMap::get(uInt16 addr, uInt8 bank) const
uInt32 BreakpointMap::get(uInt16 addr, uInt16 bank) const
{
return get(Breakpoint(addr, bank));
}
@@ -67,7 +67,7 @@ bool BreakpointMap::check(const Breakpoint& breakpoint) const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool BreakpointMap::check(uInt16 addr, uInt8 bank) const
bool BreakpointMap::check(uInt16 addr, uInt16 bank) const
{
return check(Breakpoint(addr, bank));
}
+8 -8
View File
@@ -31,15 +31,15 @@ class BreakpointMap
{
public:
// breakpoint flags
static constexpr uInt32 ONE_SHOT = 1 << 0; // used for 'trace' command
static constexpr uInt8 ANY_BANK = 255; // breakpoint valid in any bank
static constexpr uInt32 ONE_SHOT = 1 << 0; // used for 'trace' command
static constexpr uInt16 ANY_BANK = 0xffff; // breakpoint valid in any bank
struct Breakpoint
{
uInt16 addr{0};
uInt8 bank{0};
uInt16 bank{0};
constexpr Breakpoint(uInt16 c_addr, uInt8 c_bank) : addr{c_addr}, bank{c_bank} { }
constexpr Breakpoint(uInt16 c_addr, uInt16 c_bank) : addr{c_addr}, bank{c_bank} { }
bool operator==(const Breakpoint& other) const
{
@@ -62,19 +62,19 @@ class BreakpointMap
/** Add new breakpoint */
void add(const Breakpoint& breakpoint, uInt32 flags = 0);
void add(uInt16 addr, uInt8 bank, uInt32 flags = 0);
void add(uInt16 addr, uInt16 bank, uInt32 flags = 0);
/** Erase breakpoint */
void erase(const Breakpoint& breakpoint);
void erase(uInt16 addr, uInt8 bank);
void erase(uInt16 addr, uInt16 bank);
/** Get info for breakpoint */
uInt32 get(const Breakpoint& breakpoint) const;
uInt32 get(uInt16 addr, uInt8 bank) const;
uInt32 get(uInt16 addr, uInt16 bank) const;
/** Check if a breakpoint exists */
bool check(const Breakpoint& breakpoint) const;
bool check(uInt16 addr, uInt8 bank) const;
bool check(uInt16 addr, uInt16 bank) const;
/** Returns a sorted list of breakpoints */
BreakpointList getBreakpoints() const;
+1 -1
View File
@@ -1161,7 +1161,7 @@ string CartDebug::clearConfig(int bank)
endbank = startbank + 1;
}
auto count = 0uz;
auto count = 0UZ;
for(uInt32 b = startbank; b < endbank; ++b)
{
count += myBankInfo[b].directiveList.size();
+7 -7
View File
@@ -358,7 +358,7 @@ string Debugger::setRAM(const IntArray& args)
const size_t count = args.size(), written = count - 1;
int address = args[0];
for(auto i = 1uz; i < count; ++i)
for(auto i = 1UZ; i < count; ++i)
mySystem.pokeOob(address++, args[i]);
return std::format("changed {} {}", written,
@@ -443,7 +443,7 @@ int Debugger::trace()
const int targetPC = myCpuDebug->pc() + 3; // return address
// set temporary breakpoint at target PC (if not existing already)
const Int8 bank = myCartDebug->getBank(targetPC);
const uInt16 bank = myCartDebug->getBank(targetPC);
if(!checkBreakPoint(targetPC, bank))
{
// add temporary breakpoint and remove later
@@ -463,7 +463,7 @@ int Debugger::trace()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Debugger::setBreakPoint(uInt16 addr, uInt8 bank, uInt32 flags) const
bool Debugger::setBreakPoint(uInt16 addr, uInt16 bank, uInt32 flags) const
{
if(checkBreakPoint(addr, bank))
return false;
@@ -473,7 +473,7 @@ bool Debugger::setBreakPoint(uInt16 addr, uInt8 bank, uInt32 flags) const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Debugger::clearBreakPoint(uInt16 addr, uInt8 bank) const
bool Debugger::clearBreakPoint(uInt16 addr, uInt16 bank) const
{
if(!checkBreakPoint(addr, bank))
return false;
@@ -483,13 +483,13 @@ bool Debugger::clearBreakPoint(uInt16 addr, uInt8 bank) const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Debugger::checkBreakPoint(uInt16 addr, uInt8 bank) const
bool Debugger::checkBreakPoint(uInt16 addr, uInt16 bank) const
{
return breakPoints().check(addr, bank);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Debugger::toggleBreakPoint(uInt16 addr, uInt8 bank) const
bool Debugger::toggleBreakPoint(uInt16 addr, uInt16 bank) const
{
if(checkBreakPoint(addr, bank))
clearBreakPoint(addr, bank);
@@ -1060,7 +1060,7 @@ Debugger::FunctionDefMap Debugger::getFunctionDefMap() const
string Debugger::builtinHelp()
{
std::ostringstream buf;
auto c_maxlen = 0uz, i_maxlen = 0uz;
auto c_maxlen = 0UZ, i_maxlen = 0UZ;
// Get column widths for aligned output (functions)
for(const auto& func: ourBuiltinFunctions)
+5 -5
View File
@@ -50,6 +50,7 @@ class TiaWindow;
#include "DebuggerDialog.hxx"
#include "FrameBufferConstants.hxx"
#include "Cart.hxx"
#include "BreakpointMap.hxx"
#include "bspf.hxx"
/**
@@ -215,7 +216,7 @@ class Debugger : public DialogContainer
Returns true if successfully set
*/
bool setBreakPoint(uInt16 addr, uInt8 bank = ANY_BANK,
bool setBreakPoint(uInt16 addr, uInt16 bank = BreakpointMap::ANY_BANK,
uInt32 flags = 0) const;
/**
@@ -223,21 +224,21 @@ class Debugger : public DialogContainer
Returns true if successfully cleared
*/
bool clearBreakPoint(uInt16 addr, uInt8 bank) const;
bool clearBreakPoint(uInt16 addr, uInt16 bank) const;
/**
Toggles a breakpoint
Returns new state of breakpoint
*/
bool toggleBreakPoint(uInt16 addr, uInt8 bank) const;
bool toggleBreakPoint(uInt16 addr, uInt16 bank) const;
/**
Checks for a breakpoint.
Returns true if existing, else false
*/
bool checkBreakPoint(uInt16 addr, uInt8 bank) const;
bool checkBreakPoint(uInt16 addr, uInt16 bank) const;
/**
Run the debugger command and return the result.
@@ -448,7 +449,6 @@ class Debugger : public DialogContainer
static std::array<BuiltinFunction, 18> ourBuiltinFunctions;
static std::array<PseudoRegister, 18> ourPseudoRegisters;
static constexpr Int8 ANY_BANK = -1;
bool myFirstLog{true};
private:
+11 -7
View File
@@ -990,14 +990,18 @@ void DebuggerParser::executeBreak()
{
const uInt32 romBankCount = debugger.cartDebug().romBankCount();
const uInt16 addr = (argCount == 0) ? debugger.cpuDebug().pc() : args[0];
uInt8 bank = 0;
// "all banks" is only ever requested explicitly (as bank 'ff'); an
// auto-detected current bank of 255 is a real bank, never a wildcard
bool allBanks = false;
uInt16 bank = 0;
if(argCount < 2)
bank = debugger.cartDebug().getBank(addr);
else
{
bank = args[1];
if(bank >= romBankCount && bank != 0xff)
allBanks = (bank == 0xff);
if(bank >= romBankCount && !allBanks)
{
commandResult << red("invalid bank");
return;
@@ -1005,7 +1009,7 @@ void DebuggerParser::executeBreak()
}
// Helper to format a single breakpoint result line
const auto formatBreak = [&](int b)
const auto formatBreak = [&](uInt16 b)
{
const bool set = debugger.toggleBreakPoint(addr, b);
std::format_to(std::ostreambuf_iterator(commandResult),
@@ -1016,7 +1020,7 @@ void DebuggerParser::executeBreak()
" in bank #{}", b);
};
if(bank != 0xff)
if(!allBanks)
{
formatBreak(bank);
}
@@ -1548,7 +1552,7 @@ void DebuggerParser::executeHelp()
if(argCount == 0) // normal help, show all commands
{
static const size_t clen = []() {
auto len = 0uz;
auto len = 0UZ;
for(const auto& c: commands)
len = std::max(len, c.cmdString.length());
return len;
@@ -1742,7 +1746,7 @@ void DebuggerParser::executeListBreaks()
if(count % 6)
buf += ", ";
buf += debugger.cartDebug().getLabel(bp.addr, true, 4);
if(bp.bank != 255)
if(bp.bank != BreakpointMap::ANY_BANK)
std::format_to(std::back_inserter(buf), " #{}", static_cast<int>(bp.bank));
else
buf += " *";
@@ -2044,7 +2048,7 @@ void DebuggerParser::executeRunTo()
progress.setRange(0, static_cast<int>(max_iterations), 5);
progress.open();
auto count = 0uz;
auto count = 0UZ;
bool done = false;
do {
debugger.step(false);
+4
View File
@@ -105,6 +105,10 @@ bool TimerMap::load(Serializer& in)
{
clear();
const uInt32 count = in.getInt();
// Sanity cap: timers are hand-created in the debugger, so a huge count is
// a corrupt save file. Reject rather than attempt a giant allocation
if(count > 0x10000)
return false;
myList.resize(count);
for(auto& t: myList)
t.load(in);
+4 -4
View File
@@ -83,9 +83,9 @@ void BoosterWidget::loadConfig()
myPins[kJFire]->setState(!getPin(ourPinNo[kJFire]));
myPins[kJBooster]->setState(
getPin(Controller::AnalogPin::Five) == AnalogReadout::connectToVcc());
myPins[kJTrigger]->setState(
getPin(Controller::AnalogPin::Nine) == AnalogReadout::connectToVcc());
myPins[kJTrigger]->setState(
getPin(Controller::AnalogPin::Five) == AnalogReadout::connectToVcc());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -104,12 +104,12 @@ void BoosterWidget::handleCommand(
setPin(ourPinNo[id], !myPins[id]->getState());
break;
case kJBooster:
setPin(Controller::AnalogPin::Five,
setPin(Controller::AnalogPin::Nine,
myPins[id]->getState() ? AnalogReadout::connectToVcc() :
AnalogReadout::disconnect());
break;
case kJTrigger:
setPin(Controller::AnalogPin::Nine,
setPin(Controller::AnalogPin::Five,
myPins[id]->getState() ? AnalogReadout::connectToVcc() :
AnalogReadout::disconnect());
break;
+2 -2
View File
@@ -81,7 +81,7 @@ void GenesisWidget::loadConfig()
myPins[kJBbtn]->setState(!getPin(ourPinNo[kJBbtn]));
myPins[kJCbtn]->setState(
getPin(Controller::AnalogPin::Five) == AnalogReadout::connectToGround());
getPin(Controller::AnalogPin::Nine) == AnalogReadout::connectToGround());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -100,7 +100,7 @@ void GenesisWidget::handleCommand(
setPin(ourPinNo[id], !myPins[id]->getState());
break;
case kJCbtn:
setPin(Controller::AnalogPin::Five,
setPin(Controller::AnalogPin::Nine,
myPins[id]->getState() ? AnalogReadout::connectToGround() :
AnalogReadout::connectToVcc());
break;
+4 -4
View File
@@ -109,9 +109,9 @@ void PaddleWidget::layoutContent(GUI::BoxLayout& col)
void PaddleWidget::loadConfig()
{
myP0Resistance->setValue(static_cast<Int32>(AnalogReadout::MAX_POT_RESISTANCE -
getPin(Controller::AnalogPin::Nine).resistance));
myP1Resistance->setValue(static_cast<Int32>(AnalogReadout::MAX_POT_RESISTANCE -
getPin(Controller::AnalogPin::Five).resistance));
myP1Resistance->setValue(static_cast<Int32>(AnalogReadout::MAX_POT_RESISTANCE -
getPin(Controller::AnalogPin::Nine).resistance));
myP0Fire->setState(!getPin(Controller::DigitalPin::Four));
myP1Fire->setState(!getPin(Controller::DigitalPin::Three));
}
@@ -123,11 +123,11 @@ void PaddleWidget::handleCommand(
switch(cmd)
{
case kP0Changed:
setPin(Controller::AnalogPin::Nine,
setPin(Controller::AnalogPin::Five,
AnalogReadout::connectToVcc(AnalogReadout::MAX_POT_RESISTANCE - myP0Resistance->getValue()));
break;
case kP1Changed:
setPin(Controller::AnalogPin::Five,
setPin(Controller::AnalogPin::Nine,
AnalogReadout::connectToVcc(AnalogReadout::MAX_POT_RESISTANCE - myP1Resistance->getValue()));
break;
case kP0Fire:
+34 -8
View File
@@ -16,6 +16,7 @@
//============================================================================
#include <cctype>
#include <limits>
#include "Base.hxx"
#include "Debugger.hxx"
@@ -94,7 +95,8 @@ private:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// const_to_int converts a string to a number in the current or overridden base.
// Returns -1 on error; negative numbers are the parser's responsibility.
// Returns -1 on error (including overflow); negative numbers are the parser's
// responsibility.
int Lexer::const_to_int(string_view s)
{
Common::Base::Fmt format = Common::Base::format();
@@ -108,29 +110,42 @@ int Lexer::const_to_int(string_view s)
}
}
int ret = 0;
// A bare base prefix with no digits following it (e.g. "$" on its own)
// is not a valid numeral.
if (s.empty())
return -1;
// Accumulate in Int64 and bail as soon as the value can no longer fit in
// the int NUMBER token; this both avoids signed-overflow UB on long/
// adversarial numerals and bounds the work done on them.
static constexpr Int64 kMax = std::numeric_limits<int>::max();
Int64 ret = 0;
switch (format) {
case Common::Base::Fmt::_2:
for (const char c : s) {
if (c != '0' && c != '1') return -1;
ret = ret * 2 + (c - '0');
if (ret > kMax) return -1;
}
return ret;
return static_cast<int>(ret);
case Common::Base::Fmt::_10:
for (const char c : s) {
if (!isdigit(c)) return -1;
ret = ret * 10 + (c - '0');
if (ret > kMax) return -1;
}
return ret;
return static_cast<int>(ret);
case Common::Base::Fmt::_16:
for (const char c : s) {
if (!isxdigit(c)) return -1;
const int dig = (c - '0');
ret = ret * 16 + (dig > 9 ? tolower(c) - 'a' + 10 : dig);
if (ret > kMax) return -1;
}
return ret;
return static_cast<int>(ret);
default:
return 0;
@@ -200,7 +215,7 @@ parser::symbol_type Lexer::yylex()
{
char o{0}, p{0};
while (*myC != '\0') {
while (myC != myEnd) {
switch (myState) {
case State::SPACE:
if (isspace(*myC))
@@ -216,7 +231,7 @@ parser::symbol_type Lexer::yylex()
case State::IDENTIFIER: {
myIdbuf.clear();
myIdbuf += *myC++;
while (is_identifier(*myC))
while (myC != myEnd && is_identifier(*myC))
myIdbuf += *myC++;
myState = State::DEFAULT;
@@ -245,7 +260,7 @@ parser::symbol_type Lexer::yylex()
case State::OPERATOR:
o = *myC++;
if (!*myC)
if (myC == myEnd)
return make_char_tok(o);
if (isspace(*myC)) {
myState = State::SPACE;
@@ -314,8 +329,19 @@ namespace {
} // namespace
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Expression trees are evaluated and destroyed recursively (one C++ stack
// frame per nesting level), so an absurdly long, deeply-nested expression
// (chained operators, nested parens/derefs) could exhaust the stack. Capping
// the input length bounds the tree depth well below any risk of that.
static constexpr size_t kMaxExpressionLength = 1024;
unique_ptr<Expression> parse(string_view in)
{
if (in.size() > kMaxExpressionLength) {
s_errorMsg() = "Expression too long";
return nullptr;
}
unique_ptr<Expression> result;
string parseError;
+41
View File
@@ -16,6 +16,7 @@
//============================================================================
#include "MediaFactory.hxx"
#include "Serializer.hxx"
#include "System.hxx"
#include "AtariVox.hxx"
@@ -145,3 +146,43 @@ void AtariVox::reset()
myLastDataWriteCycle = 0;
SaveKey::reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AtariVox::save(Serializer& out) const
{
if(!SaveKey::save(out)) return false;
try
{
out.putByte(myShiftCount);
out.putShort(myShiftRegister);
out.putLong(myLastDataWriteCycle);
out.putBool(myReadyStateSoftFlow);
out.putBool(myCTSFlip);
}
catch(...)
{
cerr << "ERROR: AtariVox::save\n";
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AtariVox::load(Serializer& in)
{
if(!SaveKey::load(in)) return false;
try
{
myShiftCount = in.getByte();
myShiftRegister = in.getShort();
myLastDataWriteCycle = in.getLong();
myReadyStateSoftFlow = in.getBool();
myCTSFlip = in.getBool();
}
catch(...)
{
cerr << "ERROR: AtariVox::load\n";
return false;
}
return true;
}
+8
View File
@@ -95,6 +95,14 @@ class AtariVox : public SaveKey
return Controller::about(swappedPorts) + myAboutString;
}
/**
Save/load the controller state. Extends the base (which handles the
EEPROM) with the SpeakJet serial shift state, so a state saved mid-byte
doesn't drop the partially-shifted speech byte.
*/
bool save(Serializer& out) const override;
bool load(Serializer& in) override;
private:
void clockDataIn(bool value);
+3 -3
View File
@@ -32,8 +32,8 @@ BoosterGrip::BoosterGrip(Jack jack, const Event& event, const System& system)
myTriggerEvent = Event::RightJoystickFire9;
}
setPin(AnalogPin::Five, AnalogReadout::disconnect());
setPin(AnalogPin::Nine, AnalogReadout::disconnect());
setPin(AnalogPin::Five, AnalogReadout::disconnect());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -49,10 +49,10 @@ void BoosterGrip::updateButtons()
const bool boosterPressed = myEvent.get(myBoosterEvent) != 0 ||
mousePressed(Event::MouseButtonRightValue);
const bool triggerPressed = myEvent.get(myTriggerEvent) != 0;
setPin(AnalogPin::Five, boosterPressed
setPin(AnalogPin::Nine, boosterPressed
? AnalogReadout::connectToVcc()
: AnalogReadout::disconnect());
setPin(AnalogPin::Nine, triggerPressed
setPin(AnalogPin::Five, triggerPressed
? AnalogReadout::connectToVcc()
: AnalogReadout::disconnect());
}
+2 -2
View File
@@ -30,7 +30,7 @@ Cartridge2K::Cartridge2K(ByteSpan image, string_view md5,
const size_t size = std::min(image.size(), bsSize);
// Set image size to closest power-of-two for the given size
auto newSize = 1uz; myBankShift = 0;
auto newSize = 1UZ; myBankShift = 0;
while(newSize < size)
{
newSize <<= 1;
@@ -45,7 +45,7 @@ Cartridge2K::Cartridge2K(ByteSpan image, string_view md5,
myImage.assign(System::PAGE_SIZE, 0);
// newSize is rounded up to a power of two, so it may exceed the actual
// image size; clamp the source read to avoid reading past the image
for(auto i = 0uz; i < System::PAGE_SIZE; i += newSize)
for(auto i = 0UZ; i < System::PAGE_SIZE; i += newSize)
std::copy_n(image.data(), std::min(newSize, size), myImage.data() + i);
myBankShift = System::PAGE_SHIFT;
}
+13
View File
@@ -408,6 +408,19 @@ bool Cartridge4A50::load(Serializer& in)
myIsRomMiddle = in.getBool();
myIsRomHigh = in.getBool();
// Reject a corrupt save state whose slice offsets, combined with the
// ROM/RAM flags, would index myImage/myRAM out of bounds in peek()/poke().
// Each expression is the worst-case index (max address mask) of the
// matching access there
const auto inBounds = [](size_t index, size_t size) { return index < size; };
if(!((myIsRomLow ? inBounds(0x7ffU + mySliceLow, myImage.size())
: inBounds(0x7ffU + mySliceLow, myRAM.size())) &&
(myIsRomMiddle ? inBounds(0x7ffU + mySliceMiddle + 0x10000U, myImage.size())
: inBounds(0x7ffU + mySliceMiddle, myRAM.size())) &&
(myIsRomHigh ? inBounds(0x0ffU + mySliceHigh + 0x10000U, myImage.size())
: inBounds(0x0ffU + mySliceHigh, myRAM.size()))))
return false;
// Last address and data values
myLastData = in.getByte();
myLastAddress = in.getShort();
+15 -5
View File
@@ -225,12 +225,12 @@ void CartridgeAR::finalizeLoad(uInt32 block)
myHeader[3] = static_cast<uInt8>(NUM_PAGES);
// Page-map: page j in the block lives at bank (j/8), page (j%8) in bank
for(auto j = 0uz; j < NUM_PAGES; ++j)
for(auto j = 0UZ; j < NUM_PAGES; ++j)
myHeader[16 + j] = static_cast<uInt8>(((j % 8) << 2) | (j / 8));
// Per-page checksums: must satisfy checksum(data) + map + ck == 0x55
const size_t base = static_cast<size_t>(block) * LOAD_SIZE;
for(auto j = 0uz; j < NUM_PAGES; ++j)
for(auto j = 0UZ; j < NUM_PAGES; ++j)
{
const ByteSpan src = ByteSpan{myLoadImages}.subspan(base + j * 256, 256);
myHeader[64 + j] = static_cast<uInt8>(
@@ -432,7 +432,7 @@ void CartridgeAR::loadIntoRAM(uInt8 load)
// past myHeader (page/checksum tables) or myLoadImages
bool invalidPageChecksumSeen = false;
const size_t numPages = std::min<size_t>(myHeader[3], RAM_SIZE / 256);
for(auto j = 0uz; j < numPages; ++j)
for(auto j = 0UZ; j < numPages; ++j)
{
const size_t bank = myHeader[16 + j] & 0b00011;
const size_t page = (myHeader[16 + j] & 0b11100) >> 2;
@@ -570,6 +570,11 @@ bool CartridgeAR::load(Serializer& in)
// Indicates the offset within the image for the corresponding bank
in.getIntArray(myImageOffset);
// Reject a corrupt save state whose bank offsets would index myImage
// (8K) out of bounds in peek()/poke(); a valid offset is at most RAM_SIZE
if(myImageOffset[0] > RAM_SIZE || myImageOffset[1] > RAM_SIZE)
return false;
// The 6K of RAM and 2K of ROM contained in the Supercharger
in.getByteArray(myImage);
@@ -579,6 +584,11 @@ bool CartridgeAR::load(Serializer& in)
// Indicates how many 8448 loads there are
myNumberOfLoadImages = in.getByte();
// Reject a corrupt count that would read past myLoadImages, whose size is
// fixed at construction to (actual load count * LOAD_SIZE)
if(static_cast<size_t>(myNumberOfLoadImages) * LOAD_SIZE > myLoadImages.size())
return false;
// All of the 8448 byte loads associated with the game
// Note that the size of this array is myNumberOfLoadImages * 8448
in.getByteArray(ByteMSpan{myLoadImages}.first(myNumberOfLoadImages * LOAD_SIZE));
@@ -659,7 +669,7 @@ CartridgeAR::loadPCM(const FSNode& file)
if(file.read(magic, 4) < 4) return {};
float* buf = nullptr;
auto frameCount = 0uz;
auto frameCount = 0UZ;
unsigned int channels = 0, sampleRate = 0;
bool freeAsWAV = false;
@@ -712,7 +722,7 @@ CartridgeAR::loadPCM(const FSNode& file)
if(channels > 1)
{
const float scale = 1.F / static_cast<float>(channels);
for(auto i = 0uz; i < frameCount; ++i)
for(auto i = 0UZ; i < frameCount; ++i)
{
float sum = 0.F;
for(uInt32 ch = 0; ch < channels; ++ch)
+4 -2
View File
@@ -195,7 +195,7 @@ inline void CartridgeBUS::updateMusicModeDataFetchers()
// Let's update counters and flags of the music mode data fetchers
if(wholeClocks > 0)
for(auto x = 0uz; x < myMusicCounters.size(); ++x)
for(auto x = 0UZ; x < myMusicCounters.size(); ++x)
myMusicCounters[x] += myMusicFrequencies[x] * wholeClocks;
}
@@ -785,7 +785,9 @@ bool CartridgeBUS::bank(uInt16 bank, uInt16)
if(hotspotsLocked()) return false;
// Remember what bank we're in
myBankOffset = bank << 12;
// Constrain to a valid bank so a corrupt bank value (e.g. from a
// tampered save state) can never offset myProgramImage[] out of bounds
myBankOffset = (bank % romBankCount()) << 12;
// Setup the page access methods for the current bank
System::PageAccess access(this, System::PageAccessType::READ);
+4 -2
View File
@@ -171,7 +171,7 @@ FORCE_INLINE void CartridgeCDF::updateMusicModeDataFetchers()
// Let's update counters and flags of the music mode data fetchers
if(wholeClocks > 0)
for(auto x = 0uz; x < myMusicCounters.size(); ++x)
for(auto x = 0UZ; x < myMusicCounters.size(); ++x)
myMusicCounters[x] += myMusicFrequencies[x] * wholeClocks;
}
@@ -454,7 +454,9 @@ bool CartridgeCDF::bank(uInt16 bank, uInt16)
if(hotspotsLocked()) return false;
// Remember what bank we're in
myBankOffset = bank << 12;
// Constrain to a valid bank so a corrupt bank value (e.g. from a
// tampered save state) can never offset myProgramImage[] out of bounds
myBankOffset = (bank % romBankCount()) << 12;
// Setup the page access methods for the current bank
System::PageAccess access(this, System::PageAccessType::READ);
+3 -1
View File
@@ -115,7 +115,9 @@ bool CartridgeCM::bank(uInt16 bank, uInt16)
if(hotspotsLocked()) return false;
// Remember what bank we're in
myBankOffset = bank << 12;
// Constrain to a valid bank so a corrupt bank value (e.g. from a
// tampered save state) can never offset myImage[] out of bounds
myBankOffset = (bank % romBankCount()) << 12;
// Although this scheme contains four 4K ROM banks and one 2K RAM bank,
// it's easier to think of things in terms of 2K slices, as follows:
+15 -2
View File
@@ -253,7 +253,9 @@ bool CartridgeCTY::bank(uInt16 bank, uInt16)
if(hotspotsLocked()) return false;
// Remember what bank we're in
myBankOffset = bank << 12;
// Constrain to a valid bank so a corrupt bank value (e.g. from a
// tampered save state) can never offset myImage[] out of bounds
myBankOffset = (bank % romBankCount()) << 12;
// Setup the page access methods for the current bank
System::PageAccess access(this, System::PageAccessType::READ);
@@ -347,7 +349,18 @@ bool CartridgeCTY::load(Serializer& in)
myFractionalClocks = in.getDouble();
in.getIntArray(myMusicCounters);
in.getIntArray(myMusicFrequencies);
myFrequencyImage = ByteSpan{myTuneData}.subspan(in.getLong());
// Validate the tune offset from the save file before indexing myTuneData;
// a corrupt offset would otherwise build an out-of-bounds subspan. A valid
// tune is always 4K and 4K-aligned, so require a full tune to remain
const uInt64 tuneOffset = in.getLong();
if(tuneOffset > myTuneData.size() - 4_KB)
return false;
myFrequencyImage = ByteSpan{myTuneData}.subspan(tuneOffset);
// Guard the tune position so note indexing stays within the current tune
if((static_cast<size_t>(myTunePosition) + 1) * 3 > myFrequencyImage.size())
myTunePosition = 0;
}
catch(...)
{
+1 -1
View File
@@ -28,7 +28,7 @@ CartridgeCV::CartridgeCV(ByteSpan image, string_view md5,
const size_t size = image.size();
if(size <= 2_KB)
{
for(auto i = 0uz; i < 2_KB; i += size)
for(auto i = 0UZ; i < 2_KB; i += size)
// Copy the ROM of <=2K files to the 2nd half of the 4K ROM
// The 1st half is used for RAM
// A non-power-of-2 size won't tile [2K, 4K) evenly, so clamp the
+1 -1
View File
@@ -203,7 +203,7 @@ namespace // anonymous namespace, to keep these functions private
*/
string_view stripCollectionPrefix(string_view stem)
{
auto p = 0uz;
auto p = 0UZ;
while(p < stem.size() && std::isdigit(static_cast<unsigned char>(stem[p])))
++p;
if(p > 0 && p + 2 < stem.size() && stem.substr(p, 3) == " - ")
+4
View File
@@ -402,6 +402,10 @@ bool CartridgeDPC::load(Serializer& in)
// The counter registers for the data fetchers
in.getShortArray(myCounters);
// Counters are 11-bit in hardware and index the 2K display image via
// (2047 - counter); mask so a corrupt save file can't index out of bounds
for(auto& counter: myCounters)
counter &= 0x07ff;
// The flag registers for the data fetchers
in.getByteArray(myFlags);
+15 -2
View File
@@ -174,7 +174,7 @@ FORCE_INLINE void CartridgeDPCPlus::updateMusicModeDataFetchers()
// Let's update counters and flags of the music mode data fetchers
if(wholeClocks > 0)
for(auto x = 0uz; x < myMusicCounters.size(); ++x)
for(auto x = 0UZ; x < myMusicCounters.size(); ++x)
myMusicCounters[x] += myMusicFrequencies[x] * wholeClocks;
}
@@ -633,7 +633,9 @@ bool CartridgeDPCPlus::bank(uInt16 bank, uInt16)
if(hotspotsLocked()) return false;
// Remember what bank we're in
myBankOffset = bank << 12;
// Constrain to a valid bank so a corrupt bank value (e.g. from a
// tampered save state) can never offset myProgramImage[] out of bounds
myBankOffset = (bank % romBankCount()) << 12;
// Setup the page access methods for the current bank
System::PageAccess access(this, System::PageAccessType::READ);
@@ -773,9 +775,17 @@ bool CartridgeDPCPlus::load(Serializer& in)
// The counter registers for the data fetchers
in.getShortArray(myCounters);
// Counters are 12-bit in hardware and index the 4K display image; mask so
// a corrupt save file can't index out of bounds (in peek() or the poke()
// write path, which uses the counter before re-masking it)
for(auto& counter: myCounters)
counter &= 0x0fff;
// The counter registers for the fractional data fetchers
in.getIntArray(myFractionalCounters);
// Fractional counters are 20-bit; (counter >> 8) indexes the display image
for(auto& counter: myFractionalCounters)
counter &= 0x0fffff;
// The fractional registers for the data fetchers
in.getByteArray(myFractionalIncrements);
@@ -795,6 +805,9 @@ bool CartridgeDPCPlus::load(Serializer& in)
// The music waveforms
in.getShortArray(myMusicWaveforms);
// Waveforms are 7-bit; (waveform << 5) indexes the display image
for(auto& waveform: myMusicWaveforms)
waveform &= 0x7f;
// The random number generator register
myRandomNumber = in.getInt();
+1 -1
View File
@@ -45,7 +45,7 @@ void CartridgeDevCard::install(System& system)
System::PageAccess access(this, System::PageAccessType::READWRITE);
for(auto win = 0uz; win < NUM_WINDOWS; ++win)
for(auto win = 0UZ; win < NUM_WINDOWS; ++win)
{
const auto winBase = static_cast<uInt32>(win * WINDOW_SIZE);
for(uInt32 addr = WINDOWS[win]; addr < WINDOWS[win] + WINDOW_SIZE;
+14 -5
View File
@@ -95,21 +95,22 @@ void CartridgeE7::install(System& system)
System::PageAccess access(this, System::PageAccessType::READ);
// The hotspots at $1FE0-$1FE7 live in the page starting here; that page is
// handled separately below since it must dispatch through peek()/poke()
constexpr uInt16 HOTSPOT_PAGE = 0x1FE0 & ~System::PAGE_MASK;
// Set the page accessing methods for the hot spots
for(uInt16 addr = (0x1FE0 & ~System::PAGE_MASK); addr < 0x2000;
addr += System::PAGE_SIZE)
for(uInt16 addr = HOTSPOT_PAGE; addr < 0x2000; addr += System::PAGE_SIZE)
{
access.romAccessBase = &myRomAccessBase[0x1fc0];
access.romPeekCounter = &myRomAccessCounter[0x1fc0];
access.romPokeCounter = &myRomAccessCounter[0x1fc0 + myAccessSize];
mySystem->setPageAccess(addr, access);
}
/*setAccess(0x1FE0 & ~System::PAGE_MASK, System::PAGE_SIZE,
0, nullptr, 0x1fc0, System::PA_NONE, 0x1fc0);*/
// Setup the second segment to always point to the last ROM bank
const auto offset = static_cast<uInt16>(myRAMBank * BANK_SIZE);
setAccess(0x1A00, 0x1FE0U & (~System::PAGE_MASK - 0x1A00),
setAccess(0x1A00, HOTSPOT_PAGE - 0x1A00,
offset, myImage.data(), offset,
System::PageAccessType::READ, static_cast<uInt16>(BANK_SIZE - 1));
myCurrentBank[1] = myRAMBank;
@@ -239,6 +240,10 @@ void CartridgeE7::bankRAM(uInt16 bank)
{
if(hotspotsLocked()) return;
// Constrain to a valid RAM bank (there are 4, as in the 0x1800 hotspots)
// so a corrupt value can't map page access outside myRAM
bank &= 0x03;
// Remember what bank we're in
myCurrentRAM = bank;
const uInt16 offset = bank << 8; // * RAM_BANK_SIZE (256)
@@ -257,6 +262,10 @@ bool CartridgeE7::bank(uInt16 bank, uInt16)
{
if(hotspotsLocked()) return false;
// Constrain to a valid bank so a corrupt value (e.g. from a tampered save
// state) can't map page access outside the ROM image
bank %= romBankCount();
// Remember what bank we're in
myCurrentBank[0] = bank;
+3 -3
View File
@@ -45,7 +45,7 @@ namespace {
{
stream << "\nELF sections:\n\n";
auto i = 0uz;
auto i = 0UZ;
for (const auto& section: elf.getSections()) {
if (section.type != 0x00) stream << i << " " << section << '\n';
i++;
@@ -88,7 +88,7 @@ namespace {
const auto& sections = parser.getSections();
const auto& relocatedSections = linker.getRelocatedSections();
for (auto i = 0uz; i < sections.size(); i++) {
for (auto i = 0UZ; i < sections.size(); i++) {
if (!relocatedSections[i]) continue;
stream
@@ -103,7 +103,7 @@ namespace {
const auto& symbols = parser.getSymbols();
const auto& relocatedSymbols = linker.getRelocatedSymbols();
for (auto i = 0uz; i < symbols.size(); i++) {
for (auto i = 0UZ; i < symbols.size(); i++) {
if (!relocatedSymbols[i]) continue;
stream
+4 -2
View File
@@ -108,7 +108,8 @@ bool CartridgeFE::poke(uInt16 address, uInt8 value)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeFE::save(Serializer& out) const
{
CartridgeEnhanced::save(out);
if(!CartridgeEnhanced::save(out))
return false;
try
{
out.putBool(myLastAccessWasFE);
@@ -125,7 +126,8 @@ bool CartridgeFE::save(Serializer& out) const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeFE::load(Serializer& in)
{
CartridgeEnhanced::load(in);
if(!CartridgeEnhanced::load(in))
return false;
try
{
myLastAccessWasFE = in.getBool();
+4 -2
View File
@@ -104,7 +104,8 @@ bool CartridgeMDM::bank(uInt16 bank, uInt16)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeMDM::save(Serializer& out) const
{
CartridgeEnhanced::save(out);
if(!CartridgeEnhanced::save(out))
return false;
try
{
out.putBool(myBankingDisabled);
@@ -121,7 +122,8 @@ bool CartridgeMDM::save(Serializer& out) const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeMDM::load(Serializer& in)
{
CartridgeEnhanced::load(in);
if(!CartridgeEnhanced::load(in))
return false;
try
{
myBankingDisabled = in.getBool();
+19 -20
View File
@@ -200,14 +200,11 @@ namespace {
out.putByteArray(myBuffer1);
out.putByteArray(myBuffer2);
#if 0 // FIXME - determine whether we need to load/save this
const uInt8* myAudio
const uInt8* myGraph
const uInt8* myGraphOverride
const uInt8* myTimecode
const uInt8* myColor
const uInt8* myColorBK
#endif
// The stream cursors (myAudio/myGraph/myGraphOverride/myTimecode/
// myColor/myColorBK) are pointers into the buffers above and are
// intentionally not serialized: they are re-derived from the buffer
// layout at the next field boundary (swapField), so a mid-field load
// costs at most a partial frame of A/V, which then self-corrects.
}
catch(...)
{
@@ -222,14 +219,8 @@ namespace {
in.getByteArray(myBuffer1);
in.getByteArray(myBuffer2);
#if 0 // FIXME - determine whether we need to load/save this
const uInt8* myAudio
const uInt8* myGraph
const uInt8* myGraphOverride
const uInt8* myTimecode
const uInt8* myColor
const uInt8* myColorBK
#endif
// See save(): the stream cursors are re-derived at the next field
// boundary, so they are intentionally not part of the save format.
}
catch(...)
{
@@ -1566,8 +1557,10 @@ bool MovieCart::save(Serializer& out) const
out.putByte(myButtonsValue);
out.putByte(myVolume);
// FIXME - determine whether we need to load/save this
// const uInt8* myVolumeScale{scales[DEFAULT_LEVEL]};
// myMute is a multi-frame countdown that gates the volume; it doesn't
// self-correct on its own, so it is saved. myVolumeScale is not: it is
// re-derived from myVolume every frame in runStateMachine().
out.putByte(myMute);
out.putByte(myFirstAudioVal);
}
catch(...)
@@ -1604,6 +1597,9 @@ bool MovieCart::load(Serializer& in)
myMode = in.getByte();
myBright = in.getByte();
// myBright indexes shiftBright[] via (v & 0x0f) + myBright; clamp as the
// emulation does so a corrupt save file can't index out of bounds
if(myBright >= MAX_LEVEL) myBright = MAX_LEVEL - 1;
myForceColor = in.getByte();
// expressed in frames
@@ -1620,8 +1616,11 @@ bool MovieCart::load(Serializer& in)
myButtonsValue = in.getByte();
myVolume = in.getByte();
// FIXME - determine whether we need to load/save this
// const uInt8* myVolumeScale{scales[DEFAULT_LEVEL]};
// myVolume indexes scales[]; clamp as the emulation does so a corrupt save
// file can't index out of bounds
if(myVolume >= MAX_LEVEL) myVolume = MAX_LEVEL - 1;
// See save(): myMute is saved; myVolumeScale is re-derived from myVolume
myMute = in.getByte();
myFirstAudioVal = in.getByte();
}
catch(...)
+4 -2
View File
@@ -54,7 +54,8 @@ bool CartridgeTVBoy::bank(uInt16 bank, uInt16)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeTVBoy::save(Serializer& out) const
{
CartridgeEnhanced::save(out);
if(!CartridgeEnhanced::save(out))
return false;
try
{
out.putBool(myBankingDisabled);
@@ -71,7 +72,8 @@ bool CartridgeTVBoy::save(Serializer& out) const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeTVBoy::load(Serializer& in)
{
CartridgeEnhanced::load(in);
if(!CartridgeEnhanced::load(in))
return false;
try
{
myBankingDisabled = in.getBool();
+4 -2
View File
@@ -124,7 +124,8 @@ uInt16 CartridgeWD::getBank(uInt16) const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeWD::save(Serializer& out) const
{
CartridgeEnhanced::save(out);
if(!CartridgeEnhanced::save(out))
return false;
try
{
out.putShort(myCurrentBank);
@@ -143,7 +144,8 @@ bool CartridgeWD::save(Serializer& out) const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeWD::load(Serializer& in)
{
CartridgeEnhanced::load(in);
if(!CartridgeEnhanced::load(in))
return false;
try
{
myCurrentBank = in.getShort();
+10 -10
View File
@@ -49,25 +49,25 @@ void CompuMate::update()
using DP = Controller::DigitalPin;
using AP = Controller::AnalogPin;
lp.setPin(AP::Nine, AnalogReadout::connectToGround());
lp.setPin(AP::Five, AnalogReadout::connectToVcc());
lp.setPin(AP::Five, AnalogReadout::connectToGround());
lp.setPin(AP::Nine, AnalogReadout::connectToVcc());
lp.setPin(DP::Six, true);
rp.setPin(AP::Nine, AnalogReadout::connectToVcc());
rp.setPin(AP::Five, AnalogReadout::connectToGround());
rp.setPin(AP::Five, AnalogReadout::connectToVcc());
rp.setPin(AP::Nine, AnalogReadout::connectToGround());
rp.setPin(DP::Six, true);
rp.setPin(DP::Three, true);
rp.setPin(DP::Four, true);
if(myEvent.get(Event::CompuMateFunc))
lp.setPin(AP::Nine, AnalogReadout::connectToVcc());
lp.setPin(AP::Five, AnalogReadout::connectToVcc());
if(myEvent.get(Event::CompuMateShift))
rp.setPin(AP::Five, AnalogReadout::connectToVcc());
rp.setPin(AP::Nine, AnalogReadout::connectToVcc());
// LUT for CompuMate keys; faster than manual if statements
struct ColMap {
E lp6, rp3, rp6, rp4;
E shiftKey; // key that mimics Shift+lp6 (sets RP.A5 high)
E funcKey; // key that mimics Func+rp4 (sets LP.A9 high)
E shiftKey; // key that mimics Shift+lp6 (sets RP.A9 high)
E funcKey; // key that mimics Func+rp4 (sets LP.A5 high)
};
/**
@@ -122,11 +122,11 @@ void CompuMate::update()
rp.setPin(DP::Four, false);
if(col.shiftKey != E::NoType && myEvent.get(col.shiftKey)) {
rp.setPin(AP::Five, AnalogReadout::connectToVcc());
rp.setPin(AP::Nine, AnalogReadout::connectToVcc());
lp.setPin(DP::Six, false);
}
if(col.funcKey != E::NoType && myEvent.get(col.funcKey)) {
lp.setPin(AP::Nine, AnalogReadout::connectToVcc());
lp.setPin(AP::Five, AnalogReadout::connectToVcc());
rp.setPin(DP::Four, false);
}
if(myBackspaceReleaseCycle && mySystem.cycles() >= myBackspaceReleaseCycle)
+4 -4
View File
@@ -124,11 +124,11 @@ class CompuMate
myHandler{handler}
{
if(jack == Controller::Jack::Left) {
setPin(AnalogPin::Nine, AnalogReadout::connectToGround());
setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
} else {
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc());
setPin(AnalogPin::Five, AnalogReadout::connectToGround());
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc());
} else {
setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
setPin(AnalogPin::Nine, AnalogReadout::connectToGround());
}
}
~CMControl() override = default;
+3 -3
View File
@@ -46,11 +46,11 @@ class System;
pin 2 D5 PIA SWCHA D1 PIA SWCHA
pin 3 D6 PIA SWCHA D2 PIA SWCHA
pin 4 D7 PIA SWCHA D3 PIA SWCHA
pin 5 D7 TIA INPT1 (Dumped) D7 TIA INPT3 (Dumped)
pin 5 D7 TIA INPT0 (Dumped) D7 TIA INPT2 (Dumped)
pin 6 D7 TIA INPT4 (Latched) D7 TIA INPT5 (Latched)
pin 7 +5 +5
pin 8 GND GND
pin 9 D7 TIA INPT0 (Dumped) D7 TIA INPT2 (Dumped)
pin 9 D7 TIA INPT1 (Dumped) D7 TIA INPT3 (Dumped)
Each of the pins connected to the PIA can be configured as an
input or output pin. The "dumped" TIA pins are used to charge
@@ -389,7 +389,7 @@ class Controller : public Serializable
bound.fill(Event::NoType);
bool pressed = false;
auto i = 0uz;
auto i = 0UZ;
for(const Event::Type event: events)
{
bound[i++] = event;
+11 -2
View File
@@ -585,6 +585,15 @@ void CortexM0::MemoryRegion::loadDirtyBits(Serializer& in)
accessWatermarkLow = in.getInt();
accessWatermarkHigh = in.getInt();
// Validate the watermarks read from the save file before using them to build
// a span into backingStore. Without this a corrupt range yields an
// out-of-bounds write, or (via unsigned underflow of high - low + 1) an
// enormous one. The checks are ordered so no subtraction can itself
// underflow: base <= low <= high < base + size
if (accessWatermarkLow < base || accessWatermarkHigh < accessWatermarkLow ||
accessWatermarkHigh - base >= size)
throw std::runtime_error("invalid ARM memory region watermark");
switch (type) {
case MemoryRegionType::directCode:
in.getByteArray(std::span{
@@ -650,14 +659,14 @@ bool CortexM0::load(Serializer& in)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CortexM0::saveDirtyRegions(Serializer& out) const
{
for (auto i = 0uz; i < myNextRegionIndex; i++)
for (auto i = 0UZ; i < myNextRegionIndex; i++)
myRegions[i].saveDirtyBits(out);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CortexM0::loadDirtyRegions(Serializer& in)
{
for (auto i = 0uz; i < myNextRegionIndex; i++)
for (auto i = 0UZ; i < myNextRegionIndex; i++)
myRegions[i].loadDirtyBits(in);
recompileCodeRegions();
+1 -1
View File
@@ -92,7 +92,7 @@ void Driving::updateButtons()
// to this controller, so each can change the button within the input window
// instead of latching a static aggregate.
std::array<Event::Type, MAX_PIN_EVENTS> fire{myFireEvent};
auto n = 1uz;
auto n = 1UZ;
if(myControlID > -1)
{
// The single fire button is triggered by both mouse buttons
+1 -1
View File
@@ -403,7 +403,7 @@ int FBSurface::drawString(const GUI::Font& font, string_view s,
int x0 = x, x1 = 0;
for(auto i = 0uz; i < str.size(); ++i)
for(auto i = 0UZ; i < str.size(); ++i)
{
w = font.getCharWidth(str[i]);
if(x + w > rightX)
+2 -2
View File
@@ -899,7 +899,7 @@ void FrameBuffer::setUIPalette()
const uInt32 bShift = std::countr_zero(bMask());
const uInt32 aMask_ = aMask();
for(auto i = 0uz; i < ui_palette.size(); ++i)
for(auto i = 0UZ; i < ui_palette.size(); ++i)
{
const uInt32 rgb = ui_palette[i];
myFullPalette[kColor + i] = aMask_
@@ -924,7 +924,7 @@ void FrameBuffer::setDisasmPalette()
const uInt32 bShift = std::countr_zero(bMask());
const uInt32 aMask_ = aMask();
for(auto i = 0uz; i < dp.size(); ++i)
for(auto i = 0UZ; i < dp.size(); ++i)
{
const uInt32 rgb = dp[i];
myFullPalette[kUINColors + i] = aMask_
+2 -2
View File
@@ -23,8 +23,8 @@ Genesis::Genesis(Jack jack, const Event& event, const System& system)
myButtonCEvent{(myJack == Jack::Left) ? Event::LeftJoystickFire5
: Event::RightJoystickFire5}
{
setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc());
setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -39,7 +39,7 @@ void Genesis::updateButtons()
// the input window).
const bool buttonCPressed = myEvent.get(myButtonCEvent) != 0 ||
mousePressed(Event::MouseButtonRightValue);
setPin(AnalogPin::Five, buttonCPressed
setPin(AnalogPin::Nine, buttonCPressed
? AnalogReadout::connectToGround()
: AnalogReadout::connectToVcc());
}
+1 -1
View File
@@ -24,7 +24,7 @@
The standard Sega Genesis controller works with the 2600 console for
joystick directions and some of the buttons. Button 'B' corresponds to
the normal fire button (joy0fire), while button 'C' is read through
INPT1 (analog pin 5).
INPT1 (analog pin 9).
@author Stephen Anthony
*/
+3 -3
View File
@@ -32,8 +32,8 @@ Joy2BPlus::Joy2BPlus(Jack jack, const Event& event, const System& system)
myButton3Event = Event::RightJoystickFire9;
}
setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc());
setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -48,10 +48,10 @@ void Joy2BPlus::updateButtons()
const bool buttonCPressed = myEvent.get(myButtonCEvent) != 0 ||
mousePressed(Event::MouseButtonRightValue);
const bool button3Pressed = myEvent.get(myButton3Event) != 0;
setPin(AnalogPin::Five, buttonCPressed
setPin(AnalogPin::Nine, buttonCPressed
? AnalogReadout::connectToGround()
: AnalogReadout::connectToVcc());
setPin(AnalogPin::Nine, button3Pressed
setPin(AnalogPin::Five, button3Pressed
? AnalogReadout::connectToGround()
: AnalogReadout::connectToVcc());
}
+2 -2
View File
@@ -23,8 +23,8 @@
/**
The Joy 2B+ controller works with the 2600 console for joystick directions
and some of the buttons. Button 'B' corresponds to the normal fire button
(joy0fire), while button 'C' is read through INPT1 (analog pin 5) and
button '3' through INPT0 (analog pin 9).
(joy0fire), while button 'C' is read through INPT1 (analog pin 9) and
button '3' through INPT0 (analog pin 5).
@author Thomas Jentzsch
*/
+1 -1
View File
@@ -77,7 +77,7 @@ void Joystick::updateFire(bool bothButtonsFire)
// controller, the mouse button(s) that also trigger it, so each can change
// the button within the input window instead of latching a static aggregate.
std::array<Event::Type, MAX_PIN_EVENTS> fire{myFireEvent};
auto n = 1uz;
auto n = 1UZ;
if(myControlID > -1)
{
fire[n++] = Event::MouseButtonLeftValue;
+2 -2
View File
@@ -112,6 +112,6 @@ void Keyboard::write(DigitalPin pin, bool value)
const ColumnState stateCol2 = processColumn(col2);
setPin(DigitalPin::Six, stateCol2 != ColumnState::gnd);
setPin(AnalogPin::Five, columnStateToAnalogSignal(stateCol1));
setPin(AnalogPin::Nine, columnStateToAnalogSignal(stateCol0));
setPin(AnalogPin::Nine, columnStateToAnalogSignal(stateCol1));
setPin(AnalogPin::Five, columnStateToAnalogSignal(stateCol0));
}
+11
View File
@@ -223,7 +223,18 @@ bool KidVid::load(Serializer& in)
myBlockIdx = in.getInt();
myBlock = in.getInt();
// Reject a corrupt save state whose indices would read past the read-only
// KidVid data tables: myTape drives tapeIndex()/ourBlocks[], mySongPointer
// indexes ourSongPositions[], and (myIdx >> 3) indexes ourData[]
if(myTape > 4 ||
mySongPointer >= ourSongPositions.size() ||
(myIdx >> 3) >= ourData.size())
return false;
myContinueSong = myFilesFound && mySongPlaying;
// The song-continue path in update() indexes ourSongPositions[mySongPointer - 1]
if(mySongPointer == 0)
myContinueSong = false;
return Controller::load(in);
}
+9 -1
View File
@@ -268,7 +268,7 @@ inline void M6502::_execute(uInt64 cycles, DispatchResult& result)
if(myBreakPoints.isInitialized())
{
const uInt8 bank = mySystem->cart().getBank(PC);
const uInt16 bank = mySystem->cart().getBank(PC);
if(myBreakPoints.check(PC, bank))
{
@@ -461,12 +461,17 @@ bool M6502::save(Serializer& out) const
out.putShort(myLastAddress);
out.putShort(myLastPeekAddress);
out.putShort(myLastPokeAddress);
// Base (non-mirrored) peek/poke addresses shown in the debugger
out.putShort(myLastPeekBaseAddress);
out.putShort(myLastPokeBaseAddress);
out.putShort(myDataAddressForPoke);
out.putInt(myLastSrcAddressS);
out.putInt(myLastSrcAddressA);
out.putInt(myLastSrcAddressX);
out.putInt(myLastSrcAddressY);
out.putByte(myFlags);
// Cycles of the last instruction, read via the debugger (_iCycles)
out.putByte(icycles);
out.putBool(myHaltRequested);
out.putLong(myLastBreakCycle);
@@ -512,12 +517,15 @@ bool M6502::load(Serializer& in)
myLastAddress = in.getShort();
myLastPeekAddress = in.getShort();
myLastPokeAddress = in.getShort();
myLastPeekBaseAddress = in.getShort();
myLastPokeBaseAddress = in.getShort();
myDataAddressForPoke = in.getShort();
myLastSrcAddressS = in.getInt();
myLastSrcAddressA = in.getInt();
myLastSrcAddressX = in.getInt();
myLastSrcAddressY = in.getInt();
myFlags = in.getByte();
icycles = in.getByte();
myHaltRequested = in.getBool();
myLastBreakCycle = in.getLong();
+9
View File
@@ -410,6 +410,8 @@ bool M6532::save(Serializer& out) const
out.putLong(mySetTimerCycle);
#ifdef DEBUGGER_SUPPORT
out.putInt(myTimReadCycles);
out.putBool(myTimWrappedOnRead);
out.putBool(myTimWrappedOnWrite);
#endif
out.putByte(myDDRA);
@@ -443,12 +445,19 @@ bool M6532::load(Serializer& in)
myTimer = in.getInt();
mySubTimer = in.getInt();
myDivider = in.getInt();
// myDivider must be one of the four legal timer intervals; anything else
// (e.g. 0) corrupts myDividerShift (shift-by-255) and the (myDivider - 1)
// bit masks used on the hot path
if(myDivider != 1 && myDivider != 8 && myDivider != 64 && myDivider != 1024)
return false;
myDividerShift = static_cast<uInt8>(std::bit_width(myDivider) - 1);
myWrappedThisCycle = in.getBool();
myLastCycle = in.getLong();
mySetTimerCycle = in.getLong();
#ifdef DEBUGGER_SUPPORT
myTimReadCycles = in.getInt();
myTimWrappedOnRead = in.getBool();
myTimWrappedOnWrite = in.getBool();
#endif
myDDRA = in.getByte();
+63
View File
@@ -22,6 +22,7 @@
#include "Control.hxx"
#include "FSNode.hxx"
#include "Serializer.hxx"
#include "System.hxx"
#include "bspf.hxx"
@@ -119,6 +120,68 @@ class MicroChip24LC
*/
bool isPageUsed(uInt32 page) const;
/**
Save the current I2C engine state to the given Serializer. The EEPROM
data array is persisted separately (to its .dat file), so only the
transient transaction state is serialized here. This matters when a
state is saved mid-transaction: without it the in-flight byte/page write
is lost, and the ROM's remaining clocks are parsed as a new command.
*/
bool save(Serializer& out) const {
try {
out.putBool(mySDA);
out.putBool(mySCL);
out.putBool(myTimerActive);
out.putLong(myCyclesWhenTimerSet);
out.putLong(myCyclesWhenSDASet);
out.putLong(myCyclesWhenSCLSet);
out.putByte(static_cast<uInt8>(jpee_state));
out.putBool(jpee_mdat);
out.putBool(jpee_sdat);
out.putBool(jpee_mclk);
out.putBool(jpee_ad_known);
out.putInt(jpee_pptr);
out.putInt(jpee_nb);
out.putInt(jpee_address);
out.putByteArray(jpee_packet);
}
catch(...) {
cerr << "ERROR: MicroChip24LC::save\n";
return false;
}
return true;
}
bool load(Serializer& in) {
try {
mySDA = in.getBool();
mySCL = in.getBool();
myTimerActive = in.getBool();
myCyclesWhenTimerSet = in.getLong();
myCyclesWhenSDASet = in.getLong();
myCyclesWhenSCLSet = in.getLong();
jpee_state = static_cast<JPEEState>(in.getByte());
jpee_mdat = in.getBool();
jpee_sdat = in.getBool();
jpee_mclk = in.getBool();
jpee_ad_known = in.getBool();
jpee_pptr = in.getInt();
// jpee_pptr bounds the write-commit read loop in jpee_data_stop(),
// so reject a corrupt value that would read past jpee_packet
if(jpee_pptr > jpee_packet.size()) return false;
jpee_nb = in.getInt();
jpee_address = in.getInt();
in.getByteArray(jpee_packet);
}
catch(...) {
cerr << "ERROR: MicroChip24LC::load\n";
return false;
}
return true;
}
private:
/**
Process simultaneous SDA and SCL pin updates.
+5 -5
View File
@@ -132,12 +132,12 @@ bool OSystem::initialize(const Settings::Options& options)
STELLA_VERSION,
myFeatures,
myBuildInfo,
AsciiFold::toAscii(myBaseDir.getShortPath()),
AsciiFold::toAscii(myStateDir.getShortPath()),
AsciiFold::toAscii(myNVRamDir.getShortPath()),
AsciiFold::toAscii(baseDir().getShortPath()),
AsciiFold::toAscii(stateDir().getShortPath()),
AsciiFold::toAscii(nvramDir().getShortPath()),
AsciiFold::toAscii(describePersistence()),
AsciiFold::toAscii(myCheatFile.getShortPath()),
AsciiFold::toAscii(myPaletteFile.getShortPath())));
AsciiFold::toAscii(cheatFile().getShortPath()),
AsciiFold::toAscii(paletteFile().getShortPath())));
// NOTE: The framebuffer MUST be created before any other object!!!
// Get relevant information about the video hardware
+7 -7
View File
@@ -27,8 +27,8 @@ Paddles::Paddles(Jack jack, const Event& event, const System& system,
{
// We must start with a physical valid resistance (e.g. 0);
// see commit 38b452e1a047a0dca38c5bcce7c271d40f76736e for more information
setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc());
setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
// The following logic reflects that mapping paddles to different
// devices can be extremely complex
@@ -166,7 +166,7 @@ void Paddles::updateA()
// events driving the fire button (the fire event plus any mouse buttons
// mapped to this paddle) so it can be bound for replay within the input window.
std::array<Event::Type, MAX_PIN_EVENTS> fire{myAFireEvent};
auto n = 1uz;
auto n = 1UZ;
// Paddle movement is a very difficult thing to accurately emulate,
// since it originally came from an analog device that had very
@@ -184,7 +184,7 @@ void Paddles::updateA()
updateMouseA(fire, n);
updateDigitalAxesA();
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc(
setPin(AnalogPin::Five, AnalogReadout::connectToVcc(
AnalogReadout::MAX_POT_RESISTANCE * myPosition[0]));
}
@@ -259,7 +259,7 @@ bool Paddles::updateAnalogAxesA()
if(std::abs(myLastAxisX - sa_xaxis) > 10)
{
conditionAxisInput(myLastAxisX, sa_xaxis);
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc(
setPin(AnalogPin::Five, AnalogReadout::connectToVcc(
AnalogReadout::MAX_POT_RESISTANCE * BSPF::clamp(
(ANALOG_MAX_VALUE - (sa_xaxis * SENSITIVITY + XCENTER)) / float{ANALOG_RANGE},
0.F, 1.F)));
@@ -339,14 +339,14 @@ void Paddles::updateB()
// events driving the fire button so it can be bound for replay within the
// input window.
std::array<Event::Type, MAX_PIN_EVENTS> fire{myBFireEvent};
auto n = 1uz;
auto n = 1UZ;
if(!updateAnalogAxesB())
{
updateMouseB(fire, n);
updateDigitalAxesB();
setPin(AnalogPin::Five, AnalogReadout::connectToVcc(
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc(
AnalogReadout::MAX_POT_RESISTANCE * myPosition[1]));
}
@@ -363,7 +363,7 @@ bool Paddles::updateAnalogAxesB()
if(std::abs(myLastAxisY - sa_yaxis) > 10)
{
conditionAxisInput(myLastAxisY, sa_yaxis);
setPin(AnalogPin::Five, AnalogReadout::connectToVcc(
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc(
AnalogReadout::MAX_POT_RESISTANCE * BSPF::clamp(
(ANALOG_MAX_VALUE - (sa_yaxis * SENSITIVITY + YCENTER)) / float{ANALOG_RANGE},
0.F, 1.F)));
+2 -2
View File
@@ -380,8 +380,8 @@ bool PlusROM::isValidHost(string_view host)
return false;
// Each dot-separated label must be 1..63 characters (RFC 1035)
auto start = 0uz;
for(auto i = 0uz; i <= host.size(); ++i)
auto start = 0UZ;
for(auto i = 0UZ; i <= host.size(); ++i)
{
if(i == host.size() || host[i] == '.')
{
+1 -1
View File
@@ -100,7 +100,7 @@ void PointingDevice::update()
// trigger it. Binding the pin (rather than a static setPin) lets the button
// change within the input window like the other controllers
std::array<Event::Type, MAX_PIN_EVENTS> fire{Event::LeftJoystickFire};
auto n = 1uz;
auto n = 1UZ;
if(myMouseEnabled)
{
fire[n++] = Event::MouseButtonLeftValue;
+4 -4
View File
@@ -40,7 +40,7 @@ bool Properties::save(KeyValueRepository& repo) const
KVRMap props;
auto* atomic = repo.atomic();
for(auto i = 0uz; i < NUM_PROPS; ++i)
for(auto i = 0UZ; i < NUM_PROPS; ++i)
{
if(myProperties[i] == defaultValue(i))
{
@@ -119,7 +119,7 @@ void Properties::reset(PropType key)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Properties::setDefaults()
{
for(auto i = 0uz; i < NUM_PROPS; ++i)
for(auto i = 0UZ; i < NUM_PROPS; ++i)
myProperties[i] = defaultValue(i);
}
@@ -160,7 +160,7 @@ PropType Properties::getPropType(string_view name)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Properties::print() const
{
for(auto i = 0uz; i < NUM_PROPS; ++i)
for(auto i = 0UZ; i < NUM_PROPS; ++i)
{
if(i > 0) cout << '|';
cout << myProperties[i];
@@ -171,7 +171,7 @@ void Properties::print() const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Properties::printHeader()
{
for(auto i = 0uz; i < NUM_PROPS; ++i)
for(auto i = 0UZ; i < NUM_PROPS; ++i)
{
if(i > 0) cout << '|';
cout << ourPropertyNames[i];
+2 -2
View File
@@ -89,7 +89,7 @@ bool PropertiesSet::getMD5(string_view md5, Properties& properties,
BSPF::compareIgnoreCase((*it)[std::to_underlying(PropType::Cart_MD5)], md5) == 0)
{
const auto& entry = *it;
for(auto p = 0uz; p < Properties::NUM_PROPS; ++p)
for(auto p = 0UZ; p < Properties::NUM_PROPS; ++p)
if(entry[p][0] != 0)
properties.set(static_cast<PropType>(p), entry[p]);
@@ -180,7 +180,7 @@ void PropertiesSet::print() const
for(const auto& DefProp: DefProps)
{
properties.setDefaults();
for(auto p = 0uz; p < Properties::NUM_PROPS; ++p)
for(auto p = 0UZ; p < Properties::NUM_PROPS; ++p)
if(DefProp[p][0] != 0)
properties.set(static_cast<PropType>(p), DefProp[p]);
+2 -2
View File
@@ -73,8 +73,8 @@ QuadTari::QuadTari(Jack jack, const OSystem& osystem, const System& system,
mySecondController = addController(secondType, true);
// QuadTari auto detection setting
setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
setPin(AnalogPin::Nine, AnalogReadout::connectToGround());
setPin(AnalogPin::Nine, AnalogReadout::connectToVcc());
setPin(AnalogPin::Five, AnalogReadout::connectToGround());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+12
View File
@@ -112,3 +112,15 @@ bool SaveKey::isPageUsed(uInt32 page) const
{
return myEEPROM->isPageUsed(page);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool SaveKey::save(Serializer& out) const
{
return Controller::save(out) && myEEPROM->save(out);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool SaveKey::load(Serializer& in)
{
return Controller::load(in) && myEEPROM->load(in);
}
+8
View File
@@ -111,6 +111,14 @@ class SaveKey : public Controller
/** Returns true if the page is used by the current ROM */
bool isPageUsed(uInt32 page) const;
/**
Save/load the controller state, including the EEPROM's in-flight I2C
transaction state. The EEPROM data array is persisted separately, to
its own file.
*/
bool save(Serializer& out) const override;
bool load(Serializer& in) override;
private:
// The EEPROM used in the SaveKey
unique_ptr<MT24LC256> myEEPROM;
+1 -1
View File
@@ -292,7 +292,7 @@ class Serializer
// Buffer small writes to a vector, and flush to disk occasionally
void writeBuffered(const uInt8* data, size_t dataSize) {
auto offset = 0uz;
auto offset = 0UZ;
while(dataSize > 0) {
const size_t space = MAX_BUF_SIZE - bufferPos;
const size_t toWrite = std::min(space, dataSize);
+2 -2
View File
@@ -119,8 +119,8 @@ const FBSurface& TIASurface::baseSurface(Common::Rect& rect) const
uInt32 *buf_ptr{nullptr}, pitch{0};
myBaseTiaSurface->basePtr(buf_ptr, pitch);
for(auto y = 0uz; y < height; ++y)
for(auto x = 0uz; x < width; ++x)
for(auto y = 0UZ; y < height; ++y)
for(auto x = 0UZ; x < width; ++x)
*buf_ptr++ = myPalette[*(myTIA->frameBuffer() + y * tiaw + x / 2)];
return *myBaseTiaSurface;
+2 -2
View File
@@ -73,7 +73,7 @@ bool BusTransactionQueue::save(Serializer& out) const
out.putShort(myNextInjectAddress);
out.putLong(myTimestamp);
for (auto i = 0uz; i < myQueueSize; i++)
for (auto i = 0UZ; i < myQueueSize; i++)
myQueue[(myQueueNext + i) % myQueueCapacity].serialize(out);
}
catch (...) {
@@ -96,7 +96,7 @@ bool BusTransactionQueue::load(Serializer& in)
if (myQueueSize > myQueueCapacity) return false;
for (auto i = 0uz; i < myQueueSize; i++)
for (auto i = 0UZ; i < myQueueSize; i++)
myQueue[i].deserialize(in);
}
catch(...) {
+7 -7
View File
@@ -163,7 +163,7 @@ const vector<uInt32>& ElfLinker::getPreinitArray() const
ElfLinker::RelocatedSymbol ElfLinker::findRelocatedSymbol(string_view name) const
{
const auto& symbols = myElf.getSymbols();
for (auto i = 0uz; i < symbols.size(); i++) {
for (auto i = 0UZ; i < symbols.size(); i++) {
if (symbols[i].name != name) continue;
if (!myRelocatedSymbols[i])
@@ -232,7 +232,7 @@ void ElfLinker::relocateSections()
myRelocatedSections.resize(sections.size(), std::nullopt);
// relocate everything that is not .bss
for (auto i = 0uz; i < sections.size(); i++) {
for (auto i = 0UZ; i < sections.size(); i++) {
const auto& section = sections[i];
const auto segmentType = determineSegmentType(section);
@@ -251,7 +251,7 @@ void ElfLinker::relocateSections()
}
// relocate all .bss sections
for (auto i = 0uz; i < sections.size(); i++) {
for (auto i = 0UZ; i < sections.size(); i++) {
const auto& section = sections[i];
if (section.type == ElfFile::SHT_NOBITS) {
@@ -290,7 +290,7 @@ void ElfLinker::copySections()
const auto& sections = myElf.getSections();
// copy segment data
for (auto i = 0uz; i < sections.size(); i++) {
for (auto i = 0UZ; i < sections.size(); i++) {
const auto& relocatedSection = myRelocatedSections[i];
if (!relocatedSection) continue;
@@ -366,7 +366,7 @@ void ElfLinker::relocateSymbols(const vector<ExternalSymbol>& externalSymbols)
const auto& symbols = myElf.getSymbols();
myRelocatedSymbols.resize(symbols.size(), std::nullopt);
for (auto i = 0uz; i < symbols.size(); i++) {
for (auto i = 0UZ; i < symbols.size(); i++) {
const auto& symbol = symbols[i];
if (symbol.section == ElfFile::SHN_ABS) {
@@ -401,7 +401,7 @@ void ElfLinker::applyRelocationsToSections()
const auto& sections = myElf.getSections();
// apply relocations
for (auto iSection = 0uz; iSection < sections.size(); iSection++) {
for (auto iSection = 0UZ; iSection < sections.size(); iSection++) {
const auto& relocations = myElf.getRelocations(iSection);
if (!relocations) continue;
if (!myRelocatedSections[iSection]) continue;
@@ -421,7 +421,7 @@ void ElfLinker::copyInitArrays(vector<uInt32>& initArray, const std::unordered_m
for (const auto& [iSection, offset]: relocatedInitArrays) {
const auto& section = sections[iSection];
for (auto i = 0uz; i < section.size; i += 4)
for (auto i = 0UZ; i < section.size; i += 4)
initArray[(offset + i) >> 2] = read32(elfData + section.offset + i);
}
}
+2 -2
View File
@@ -293,9 +293,9 @@ const ElfParser::Section* ElfParser::getSymtab() const
const ElfParser::Section* ElfParser::getStrtab() const
{
const Section* strtab = nullptr;
auto count = 0uz;
auto count = 0UZ;
for (auto i = 0uz; i < mySections.size(); i++) {
for (auto i = 0UZ; i < mySections.size(); i++) {
if (mySections[i].type != SHT_STRTAB || i == myHeader.shstrIndex) continue;
strtab = &mySections[i];
+2 -2
View File
@@ -121,7 +121,7 @@ bool VcsLib::save(Serializer& out) const
out.putBool(myIsWaitingForRead);
out.putShort(myWaitingForReadAddress);
out.putShort(myCurrentAddress);
out.putBool(myCurrentValue);
out.putByte(myCurrentValue);
if (!myRand.save(out)) return false;
}
@@ -145,7 +145,7 @@ bool VcsLib::load(Serializer& in)
myIsWaitingForRead = in.getBool();
myWaitingForReadAddress = in.getShort();
myCurrentAddress = in.getShort();
myCurrentValue = in.getBool();
myCurrentValue = in.getByte();
if (!myRand.load(in)) return false;
}
+5 -1
View File
@@ -177,11 +177,15 @@ bool Audio::loadSamples(Serializer& in)
try
{
const uInt64 sampleSize = in.getLong();
// The raw sample bytes can't exceed the size of the stream they came from;
// reject a corrupt size before attempting the allocation
if(sampleSize > in.size())
return false;
ByteArray samples(sampleSize);
in.getByteArray(samples);
// Feed all loaded samples into the audio queue
for(auto i = 0uz; i < sampleSize; ++i)
for(auto i = 0UZ; i < sampleSize; ++i)
{
const uInt8 sample = samples[i];
const uInt8 sample0 = sample & 0x0f;
+1
View File
@@ -180,6 +180,7 @@ bool DelayQueue<length, capacity>::load(Serializer& in)
}
myIndex = in.getByte();
if (myIndex >= length) throw std::runtime_error("invalid delay queue index");
}
catch(...)
{
+3 -1
View File
@@ -354,7 +354,9 @@ bool Missile::load(Serializer& in)
myRenderCounter = in.getByte();
myCopy = in.getByte();
myDecodesOffset = in.getByte();
// Mask as in setNusiz(); the decode table has only 8 entries, so an
// out-of-range offset from a corrupt save file must not index past it
myDecodesOffset = in.getByte() & 0x07;
myDecodes = DrawCounterDecodes::get().missileDecodes()[myDecodesOffset];
myColor = in.getByte();
+3 -1
View File
@@ -529,7 +529,9 @@ bool Player::load(Serializer& in)
mySampleCounter = in.getByte();
myDividerChangeCounter = in.getByte();
myDecodesOffset = in.getByte();
// Mask as in setNusiz(); the decode table has only 8 entries, so an
// out-of-range offset from a corrupt save file must not index past it
myDecodesOffset = in.getByte() & 0x07;
myDecodes = DrawCounterDecodes::get().playerDecodes()[myDecodesOffset];
myPatternOld = in.getByte();
+34 -13
View File
@@ -324,6 +324,11 @@ bool TIA::save(Serializer& out) const
for (const AnalogReadout& analogReadout : myAnalogReadouts)
if(!analogReadout.save(out)) return false;
// Cache of the last analog connection pushed to each readout; preserved so
// the change-detection in updateAnalogReadout() stays consistent on reload
for(const auto& conn: myLastAnalogConnections)
if(!conn.save(out)) return false;
if(!myInput0.save(out)) return false;
if(!myInput1.save(out)) return false;
@@ -367,6 +372,12 @@ bool TIA::save(Serializer& out) const
out.putByte(myPFColorDelay);
out.putByte(myBKColorDelay);
out.putByte(myPlSwapDelay);
// Dump-ports state; dumpPortsCycles() (used by e.g. QuadTari to pick a
// sub-controller) is measured relative to myDumpPortsCycles, so both must
// be preserved across save/load
out.putBool(myArePortsDumped);
out.putLong(myDumpPortsCycles);
}
catch(...)
{
@@ -397,6 +408,9 @@ bool TIA::load(Serializer& in)
for (AnalogReadout& analogReadout : myAnalogReadouts)
if(!analogReadout.load(in)) return false;
for(auto& conn: myLastAnalogConnections)
if(!conn.load(in)) return false;
if(!myInput0.load(in)) return false;
if(!myInput1.load(in)) return false;
@@ -441,14 +455,21 @@ bool TIA::load(Serializer& in)
myBKColorDelay = in.getByte();
myPlSwapDelay = in.getByte();
myArePortsDumped = in.getBool();
myDumpPortsCycles = in.getLong();
// Re-apply dev settings
applyDeveloperSettings();
// myCurrentRowPtr is derived state not stored in the save file; recompute
// it from the restored y so renderPixel() writes to the correct scanline,
// including when the debugger saves and loads state mid-scanline
// including when the debugger saves and loads state mid-scanline. Clamp y
// to the buffer height so a corrupt save file can't push the row pointer
// (and thus renderPixel's writes) past the end of myBackBuffer
const uInt32 y = std::min(myFrameManager->getY(),
TIAConstants::frameBufferHeight - 1);
myCurrentRowPtr = myBackBuffer.data() +
static_cast<size_t>(myFrameManager->getY()) * TIAConstants::H_PIXEL;
static_cast<size_t>(y) * TIAConstants::H_PIXEL;
}
catch(...)
{
@@ -469,11 +490,11 @@ void TIA::bindToControllers()
switch (pin) {
using enum Controller::AnalogPin;
case Five:
updateAnalogReadout(1);
updateAnalogReadout(0);
break;
case Nine:
updateAnalogReadout(0);
updateAnalogReadout(1);
break;
default:
@@ -489,11 +510,11 @@ void TIA::bindToControllers()
switch (pin) {
using enum Controller::AnalogPin;
case Five:
updateAnalogReadout(3);
updateAnalogReadout(2);
break;
case Nine:
updateAnalogReadout(2);
updateAnalogReadout(3);
break;
default:
@@ -1316,7 +1337,7 @@ bool TIA::enableFixedColors(bool enable)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool TIA::setFixedColorPalette(string_view colors)
{
for(auto i = 0uz; i < std::min(6uz, colors.size()); ++i)
for(auto i = 0UZ; i < std::min(6UZ, colors.size()); ++i)
{
switch(colors[i])
{
@@ -2279,19 +2300,19 @@ void TIA::updateAnalogReadout(uInt8 idx)
AnalogReadout::Connection connection{};
switch (idx) {
case 0:
connection = myConsole.leftController().read(Controller::AnalogPin::Nine);
break;
case 1:
connection = myConsole.leftController().read(Controller::AnalogPin::Five);
break;
case 1:
connection = myConsole.leftController().read(Controller::AnalogPin::Nine);
break;
case 2:
connection = myConsole.rightController().read(Controller::AnalogPin::Nine);
connection = myConsole.rightController().read(Controller::AnalogPin::Five);
break;
case 3:
connection = myConsole.rightController().read(Controller::AnalogPin::Five);
connection = myConsole.rightController().read(Controller::AnalogPin::Nine);
break;
default:
@@ -240,6 +240,14 @@ bool FrameManager::onSave(Serializer& out) const
out.putBool(myVsyncPending);
out.putInt(myVsyncPendingLines);
// Cycle stamps used to measure VSYNC/VBLANK duration for jitter emulation.
// A state may legally be saved mid-VSYNC (myVsyncPending set), so these must
// be preserved or the next frame's jitter is computed from stale values
out.putLong(myVsyncStart);
out.putLong(myVsyncEnd);
out.putLong(myVblankStart);
out.putLong(myVblankCycles);
return true;
}
@@ -261,6 +269,11 @@ bool FrameManager::onLoad(Serializer& in)
myVsyncPending = in.getBool();
myVsyncPendingLines = in.getInt();
myVsyncStart = in.getLong();
myVsyncEnd = in.getLong();
myVblankStart = in.getLong();
myVblankCycles = in.getLong();
recalculateMetrics();
return true;
}
+2 -2
View File
@@ -117,8 +117,8 @@ void ComboDialog::loadConfig()
{
StringList events = instance().eventHandler().getComboListForEvent(myComboEvent);
const auto size = std::min(events.size(), 8uz);
for(auto i = 0uz; i < size; ++i)
const auto size = std::min(events.size(), 8UZ);
for(auto i = 0UZ; i < size; ++i)
myEvents[i]->setSelected("", events[i]);
// Fill any remaining items to 'None'
+1 -1
View File
@@ -1351,7 +1351,7 @@ void LauncherDialog::openContextMenu(int x, int y)
// Format items for menu, aligning all shortcuts to the right
VariantList varItems;
auto maxLen = 0uz;
auto maxLen = 0UZ;
for(auto& item: items)
maxLen = std::max(maxLen, item.label.length());
+1 -1
View File
@@ -245,7 +245,7 @@ void NavigationWidget::PathWidget::setPath(string_view path)
cutFirst = true;
// Update/add widgets for path parts display
auto idx = 0uz;
auto idx = 0UZ;
for(auto it = nodes.rbegin(); it != nodes.rend(); ++it, ++idx)
{
string name = it->getName();
+1 -1
View File
@@ -235,7 +235,7 @@ class SoundLIBRETRO : public Sound
const uInt32 frameSize = myChannels * (myBitsPerSample / 8);
const double step = static_cast<double>(mySampleRate) / outputRate;
for(auto i = 0uz; i < numSamples && myPos < myEnd; ++i)
for(auto i = 0UZ; i < numSamples && myPos < myEnd; ++i)
{
const Int16 wavL = sample(myPos);
const Int16 wavR = (myChannels > 1) ? sample(myPos + myBitsPerSample / 8) : wavL;
+1 -1
View File
@@ -54,7 +54,7 @@ void post_message(const char* msg, retro_log_level level, unsigned duration_ms =
{
const retro_message_ext ext = {
msg, duration_ms,
level == RETRO_LOG_ERROR ? 3u : 1u,
level == RETRO_LOG_ERROR ? 3U : 1U,
level, RETRO_MESSAGE_TARGET_ALL, RETRO_MESSAGE_TYPE_NOTIFICATION, -1
};
environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, (void*)&ext);
+1 -1
View File
@@ -34,7 +34,7 @@ FSNodePOSIX::FSNodePOSIX(string_view path, bool verify)
if(_path[0] == '~')
{
// Skip the '/' after '~' if present to avoid double slash
const auto replaceLen = (_path.size() > 1 && _path[1] == '/') ? 2uz : 1uz;
const auto replaceLen = (_path.size() > 1 && _path[1] == '/') ? 2UZ : 1UZ;
_path.replace(0, replaceLen, XDGPaths::instance().home());
}
else if(_path[0] == '.')