mirror of
https://github.com/grumpycoders/pcsx-redux.git
synced 2026-10-12 02:15:13 +00:00
Merge remote-tracking branch 'upstream/main' into rewind-defer
This commit is contained in:
@@ -32,6 +32,8 @@ jobs:
|
||||
make -C src/mips/openbios clean
|
||||
make -C src/mips/tests -j 2 PCSX_TESTS=true BUILD=Release
|
||||
cp ./openbios.bin src/mips/openbios/
|
||||
make -C src/mips/monitor/hosts/retail -j 2 BUILD=LTO
|
||||
DBUS_SESSION_BUS_ADDRESS="disabled:" ASAN_OPTIONS=detect_leaks=0 xvfb-run ./pcsx-redux -stdout -lua_stdout -testmode -no-gui-logs -exec "dofile 'src/mips/tests/cdrom/create-test-iso.lua'"
|
||||
- name: Test
|
||||
env:
|
||||
# No dbus-launch in the image, so libdbus autolaunches a session bus, fails to
|
||||
|
||||
@@ -86,6 +86,14 @@ jobs:
|
||||
cp version.json AppDir/usr/share/pcsx-redux/resources
|
||||
linuxdeploy -v 3 --appdir=AppDir -e AppDir/usr/bin/pcsx-redux -d AppDir/usr/share/applications/pcsx-redux.desktop -i AppDir/usr/share/icons/hicolor/256x256/apps/pcsx-redux.png -o appimage
|
||||
mv PCSX-Redux-${{ matrix.arch }}.AppImage PCSX-Redux-HEAD-${{ matrix.arch }}.AppImage
|
||||
- run: |
|
||||
cp src/mips/openbios/openbios.bin .
|
||||
make -C src/mips/openbios clean
|
||||
make -C src/mips/tests -j 2 PCSX_TESTS=true
|
||||
cp ./openbios.bin src/mips/openbios/
|
||||
- run: |
|
||||
make -C src/mips/monitor/hosts/retail -j 2
|
||||
xvfb-run catchsegv ./pcsx-redux -stdout -lua_stdout -testmode -no-gui-logs -exec "dofile 'src/mips/tests/cdrom/create-test-iso.lua'"
|
||||
- name: Test
|
||||
run: |
|
||||
export GTEST_OUTPUT=xml:${TEST_RESULTS}/
|
||||
|
||||
@@ -38,9 +38,13 @@ jobs:
|
||||
make -j 2 all pcsx-redux-tests
|
||||
make -C src/mips/openbios -j 2 BUILD=Release
|
||||
cp src/mips/openbios/openbios.bin .
|
||||
- run: |
|
||||
make -C src/mips/openbios clean
|
||||
make -C src/mips/tests -j 2 PCSX_TESTS=true BUILD=Release
|
||||
cp ./openbios.bin src/mips/openbios/
|
||||
- run: |
|
||||
make -C src/mips/monitor/hosts/retail -j 2 BUILD=LTO
|
||||
xvfb-run catchsegv ./pcsx-redux -stdout -lua_stdout -testmode -no-gui-logs -exec "dofile 'src/mips/tests/cdrom/create-test-iso.lua'"
|
||||
- name: Test
|
||||
run: |
|
||||
xvfb-run catchsegv ./pcsx-redux-tests
|
||||
|
||||
@@ -30,6 +30,8 @@ jobs:
|
||||
make -C src/mips/openbios clean
|
||||
make -C src/mips/tests -j 2 PCSX_TESTS=true BUILD=Release
|
||||
cp ./openbios.bin src/mips/openbios/
|
||||
make -C src/mips/monitor/hosts/retail -j 2 BUILD=LTO
|
||||
xvfb-run ./pcsx-redux -stdout -lua_stdout -testmode -no-gui-logs -exec "dofile 'src/mips/tests/cdrom/create-test-iso.lua'"
|
||||
- name: Test
|
||||
run: |
|
||||
xvfb-run ./pcsx-redux-tests
|
||||
|
||||
+7
-1
@@ -119,9 +119,15 @@ sstate.proto
|
||||
# Sentry Native files
|
||||
.sentry-native
|
||||
|
||||
# NOPS leftovers
|
||||
# nops' configuration file
|
||||
comport.txt
|
||||
|
||||
# Cue sheets.
|
||||
*.cue
|
||||
|
||||
# Various editors' backup files
|
||||
*.bak
|
||||
|
||||
# NVIDIA Nsight
|
||||
GPUCache
|
||||
|
||||
|
||||
Vendored
+2
-1
@@ -9,7 +9,8 @@
|
||||
"compilerPath": "/usr/bin/clang-9",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17",
|
||||
"intelliSenseMode": "clang-x64"
|
||||
"intelliSenseMode": "clang-x64",
|
||||
"configurationProvider": "ms-vscode.makefile-tools"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
|
||||
+408
-404
File diff suppressed because it is too large
Load Diff
+413
-405
File diff suppressed because it is too large
Load Diff
+533
-501
File diff suppressed because it is too large
Load Diff
+408
-404
File diff suppressed because it is too large
Load Diff
+414
-408
File diff suppressed because it is too large
Load Diff
+413
-405
File diff suppressed because it is too large
Load Diff
+414
-408
File diff suppressed because it is too large
Load Diff
+414
-408
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -126,8 +126,8 @@ let
|
||||
({
|
||||
owner = "pcsx-redux";
|
||||
repo = "nugget";
|
||||
rev = "e9f70ebc993d2a4323368ca88d5eabfe1233e0a0";
|
||||
hash = "sha256-qwzQXhj/OlnekJPawrWidV5xGGjbB46OcIgR0RidZio=";
|
||||
rev = "7ffc2b14b3ec4b912e7e79f6e31101ff3f24cc30";
|
||||
hash = "sha256-cg+zmfd7SjZ9RmpJCLEO4xCCgdJLWCA1f0Ug2pKWSh8=";
|
||||
dest = "src/mips";
|
||||
})
|
||||
({
|
||||
|
||||
+16
-1
@@ -30,7 +30,7 @@ bool PCSX::CDRIso::parsemds(const char *isofileString) {
|
||||
|
||||
// copy name of the iso and change extension from .mdf to .mds
|
||||
mdsname = isofile;
|
||||
isofile.replace_extension("mds");
|
||||
mdsname.replace_extension("mds");
|
||||
|
||||
IO<File> fi(new UvFile(mdsname));
|
||||
if (fi->failed()) return false;
|
||||
@@ -49,6 +49,21 @@ bool PCSX::CDRIso::parsemds(const char *isofileString) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we were given the .mds itself, the sectors are in the .mdf
|
||||
if (isofile == mdsname) {
|
||||
std::filesystem::path mdfname = isofile;
|
||||
mdfname.replace_extension("mdf");
|
||||
IO<File> mdf(new UvFile(mdfname));
|
||||
if (mdf->failed()) return false;
|
||||
if (g_emulator->settings.get<Emulator::SettingFullCaching>()) {
|
||||
mdf.asA<UvFile>()->startCaching();
|
||||
}
|
||||
m_cdHandle = mdf;
|
||||
}
|
||||
// readCDDA reads tracks through m_ti[1].handle, and CDRIso::open only fills
|
||||
// it in for multi-track images, so set it here for single-track ones too.
|
||||
m_ti[1].handle = m_cdHandle;
|
||||
|
||||
// get offset to session block
|
||||
fi->rSeek(0x50, SEEK_SET);
|
||||
offset = fi->read<uint32_t>();
|
||||
|
||||
+51
-1
@@ -331,7 +331,7 @@ bool PCSX::CDRIso::open(IO<File> isoFile) {
|
||||
m_ti[1].start = IEC60908b::MSF(0, 2, 0);
|
||||
m_ti[1].pregap = IEC60908b::MSF(0, 0, 0);
|
||||
m_ti[1].handle = m_cdHandle;
|
||||
m_ti[1].length = IEC60908b::MSF(m_ti[1].handle->size() / 2352);
|
||||
m_ti[1].length = IEC60908b::MSF(m_ti[1].handle->size() / (m_isMode1ISO ? 2048 : 2352));
|
||||
}
|
||||
|
||||
if (m_ppf.load(m_isoPath)) {
|
||||
@@ -422,6 +422,56 @@ PCSX::IEC60908b::MSF PCSX::CDRIso::getPregap(uint8_t track) {
|
||||
return IEC60908b::MSF(0, 2, 0);
|
||||
}
|
||||
|
||||
bool PCSX::CDRIso::getLocP(const PCSX::IEC60908b::MSF msf, uint8_t locP[8]) {
|
||||
// TODO: actually check subchannels from iso.
|
||||
auto length = getTD(0) + IEC60908b::MSF{0, 2, 0};
|
||||
if (msf >= length) return false;
|
||||
unsigned track;
|
||||
for (track = m_numtracks; track != 1; track--) {
|
||||
if (m_ti[track].start <= msf) break;
|
||||
}
|
||||
|
||||
bool inPregap = false;
|
||||
IEC60908b::MSF nextPregapStart;
|
||||
if (msf < m_ti[track].start) {
|
||||
// Before track 1 starts: its pregap, index 00, counting down.
|
||||
inPregap = true;
|
||||
} else if (track != m_numtracks) {
|
||||
if (msf >= (m_ti[track + 1].start - m_ti[track + 1].pregap)) {
|
||||
track++;
|
||||
inPregap = true;
|
||||
}
|
||||
}
|
||||
locP[0] = IEC60908b::itob(track);
|
||||
locP[1] = inPregap ? 0 : 1;
|
||||
IEC60908b::MSF relative;
|
||||
if (inPregap) {
|
||||
relative = m_ti[track].start - msf;
|
||||
} else {
|
||||
relative = msf - m_ti[track].start;
|
||||
}
|
||||
relative.toBCD(locP + 2);
|
||||
msf.toBCD(locP + 5);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned PCSX::CDRIso::getTrack(const PCSX::IEC60908b::MSF msf) {
|
||||
auto length = getTD(0) + IEC60908b::MSF{0, 2, 0};
|
||||
if (msf >= length) return 0;
|
||||
unsigned track;
|
||||
for (track = m_numtracks; track != 1; track--) {
|
||||
if (m_ti[track].start <= msf) break;
|
||||
}
|
||||
IEC60908b::MSF nextPregapStart;
|
||||
if (track != m_numtracks) {
|
||||
if (msf >= (m_ti[track + 1].start - m_ti[track + 1].pregap)) {
|
||||
track++;
|
||||
}
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
// Decode 'raw' subchannel data from being packed bitwise.
|
||||
// Essentially is a bitwise matrix transposition.
|
||||
void PCSX::CDRIso::decodeRawSubData() {
|
||||
|
||||
+8
-5
@@ -48,10 +48,16 @@ class CDRIso {
|
||||
enum class TrackType { CLOSED = 0, DATA = 1, CDDA = 2 };
|
||||
TrackType getTrackType(unsigned track) { return m_ti[track].type; }
|
||||
const std::filesystem::path& getIsoPath() { return m_isoPath; }
|
||||
uint8_t getTN() { return std::max(m_numtracks, 1); }
|
||||
uint8_t getTN() { return std::max(m_numtracks, 1U); }
|
||||
IEC60908b::MSF getTD(uint8_t track);
|
||||
IEC60908b::MSF getLength(uint8_t track);
|
||||
IEC60908b::MSF getPregap(uint8_t track);
|
||||
bool getLocP(const IEC60908b::MSF msf, uint8_t locP[8]);
|
||||
unsigned getTrack(const IEC60908b::MSF msf);
|
||||
|
||||
bool failed();
|
||||
|
||||
/* Probably have to change those */
|
||||
bool readTrack(const IEC60908b::MSF time);
|
||||
unsigned readSectors(uint32_t lba, void* buffer, unsigned count);
|
||||
uint8_t* getBuffer();
|
||||
@@ -59,8 +65,6 @@ class CDRIso {
|
||||
bool readCDDA(const IEC60908b::MSF msf, unsigned char* buffer);
|
||||
PPF* getPPF() { return &m_ppf; }
|
||||
|
||||
bool failed();
|
||||
|
||||
unsigned m_cdrIsoMultidiskCount;
|
||||
unsigned m_cdrIsoMultidiskSelect;
|
||||
|
||||
@@ -132,7 +136,6 @@ class CDRIso {
|
||||
ECMFILELUT* m_ecm_savetable = nullptr;
|
||||
|
||||
static inline const size_t ECM_SECTOR_SIZE[4] = {1, 2352, 2336, 2336};
|
||||
static inline const uint8_t ZEROADDRESS[4] = {0, 0, 0, 0};
|
||||
|
||||
struct trackinfo {
|
||||
TrackType type = TrackType::CLOSED;
|
||||
@@ -146,7 +149,7 @@ class CDRIso {
|
||||
|
||||
static constexpr unsigned MAXTRACKS = 100; /* How many tracks can a CD hold? */
|
||||
|
||||
int m_numtracks = 0;
|
||||
unsigned m_numtracks = 0;
|
||||
struct trackinfo m_ti[MAXTRACKS];
|
||||
|
||||
// redump.org SBI files
|
||||
|
||||
@@ -291,10 +291,47 @@ void DynaRecCPU::recCOP1or3(uint32_t code) {
|
||||
}
|
||||
|
||||
// BCzF / BCzT. None of the coprocessors drives the condition input, so BCzF
|
||||
// always branches and BCzT never does. Bit 1 of rt is ignored. Like the rest
|
||||
// of the dynarec, this does not check SR.CUz.
|
||||
// always branches and BCzT never does. Bit 1 of rt is ignored. With SR.CUz
|
||||
// clear, the branch raises a coprocessor unusable exception, COP0 included,
|
||||
// and leaves the block without running the delay slot.
|
||||
void DynaRecCPU::recBCz(uint32_t code) {
|
||||
const unsigned z = (code >> 26) & 3;
|
||||
const auto target = _Imm_ * 4 + m_pc;
|
||||
Label usable;
|
||||
|
||||
flushRegs();
|
||||
gen.Ldr(w4, MemOperand(contextPointer, COP0_OFFSET(12)));
|
||||
gen.Tbnz(w4, 28 + z, &usable);
|
||||
|
||||
// The load started by the previous instruction still lands
|
||||
if (m_firstInstruction && m_fullLoadDelayEmulation) {
|
||||
Label noDelayedLoad;
|
||||
const auto isActiveOffset = (uintptr_t)&m_runtimeLoadDelay.active - (uintptr_t)this;
|
||||
gen.Ldrb(w4, MemOperand(contextPointer, isActiveOffset));
|
||||
gen.Cbz(w4, &noDelayedLoad);
|
||||
call(m_loadDelayHandler);
|
||||
gen.L(noDelayedLoad);
|
||||
}
|
||||
const auto& delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad ^ 1];
|
||||
if (delayedLoad.active && delayedLoad.index != 0) {
|
||||
const auto delayedValueOffset = (uintptr_t)&delayedLoad.value - (uintptr_t)this;
|
||||
gen.Ldr(w4, MemOperand(contextPointer, delayedValueOffset));
|
||||
gen.Str(w4, MemOperand(contextPointer, GPR_OFFSET(delayedLoad.index)));
|
||||
}
|
||||
|
||||
loadThisPointer(arg1.X());
|
||||
gen.Mov(arg2, (static_cast<uint32_t>(Exception::CoprocessorUnusable) << 2) | (z << 28));
|
||||
gen.Mov(arg3, (int32_t)m_inDelaySlot);
|
||||
gen.Mov(w3, m_pc - 4);
|
||||
gen.Str(w3, MemOperand(contextPointer, PC_OFFSET));
|
||||
call(exceptionWrapper);
|
||||
gen.Ldr(x0, MemOperand(contextPointer, CYCLE_OFFSET));
|
||||
gen.Add(x0, x0,
|
||||
(m_instructionCount + m_romInstructionCount * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS);
|
||||
gen.Str(x0, MemOperand(contextPointer, CYCLE_OFFSET));
|
||||
jmp((void*)m_returnFromBlock);
|
||||
|
||||
gen.L(usable);
|
||||
m_nextIsDelaySlot = true;
|
||||
|
||||
if ((_Rt_ & 1) || target == m_pc + 4) {
|
||||
@@ -834,7 +871,8 @@ void DynaRecCPU::recMTC0(uint32_t code) {
|
||||
else {
|
||||
allocateReg(_Rt_);
|
||||
if (_Rd_ == 13) {
|
||||
gen.And(m_gprs[_Rt_].allocatedReg, m_gprs[_Rt_].allocatedReg, ~0xFC00);
|
||||
gen.And(w0, m_gprs[_Rt_].allocatedReg, ~0xFC00);
|
||||
gen.Str(w0, MemOperand(contextPointer, COP0_OFFSET(_Rd_)));
|
||||
} else if (_Rd_ != 6 && _Rd_ != 14 && _Rd_ != 15) { // Don't write to JUMPDEST, EPC or PRID
|
||||
gen.Str(m_gprs[_Rt_].allocatedReg,
|
||||
MemOperand(contextPointer, COP0_OFFSET(_Rd_))); // Write rt to the cop0 reg
|
||||
|
||||
@@ -367,7 +367,8 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
|
||||
m_fullLoadDelayEmulation = fullLoadDelayEmulation;
|
||||
|
||||
const auto startingPC = m_pc;
|
||||
int count = 0; // How many instructions have we compiled?
|
||||
unsigned count = 0; // How many instructions have we compiled?
|
||||
unsigned extra = 0; // How many instructions from rom?
|
||||
|
||||
#if defined(__APPLE__)
|
||||
gen.setRW(); // Mark code cache as readable/writeable before emitting code
|
||||
@@ -412,6 +413,9 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
|
||||
return true;
|
||||
};
|
||||
|
||||
// Target of the branch whose delay slot is compiled next, when it is known at compile time.
|
||||
std::optional<uint32_t> slotBranchTarget;
|
||||
|
||||
// Compile the instruction at m_pc. Returns false if it could not be fetched.
|
||||
auto compileInstruction = [&]() {
|
||||
m_inDelaySlot = m_nextIsDelaySlot;
|
||||
@@ -425,6 +429,10 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
|
||||
uint32_t code = m_regs.code = *p; // Actually read the instruction
|
||||
m_pc += 4; // Increment recompiler PC
|
||||
count++; // Increment instruction count
|
||||
if ((m_pc & 0xffc00000) == 0xbfc00000) extra++;
|
||||
m_instructionCount = count;
|
||||
m_romInstructionCount = extra;
|
||||
slotBranchTarget = staticBranchTarget(code, m_pc);
|
||||
|
||||
const auto func = m_recBSC[code >> 26]; // Look up the opcode in our decoding LUT
|
||||
(*this.*func)(code); // Jump into the handler to recompile it
|
||||
@@ -470,7 +478,85 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
|
||||
processDelayedLoad();
|
||||
m_firstInstruction = false;
|
||||
|
||||
// Ends the block: writes back the registers and the PC, adds the cycles, returns to the dispatcher.
|
||||
const auto endBlock = [&]() {
|
||||
flushRegs();
|
||||
if (!m_pcWrittenBack) {
|
||||
gen.Mov(w0, m_pc);
|
||||
gen.Str(w0, MemOperand(contextPointer, PC_OFFSET));
|
||||
}
|
||||
gen.Ldr(x0, MemOperand(contextPointer, CYCLE_OFFSET));
|
||||
gen.Add(x0, x0, (count + extra * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS);
|
||||
gen.Str(x0, MemOperand(contextPointer, CYCLE_OFFSET));
|
||||
jmp((void*)m_returnFromBlock);
|
||||
};
|
||||
|
||||
while (shouldContinue()) {
|
||||
// A branch in the delay slot of a branch that may be taken. When the first branch is taken, the
|
||||
// second one is relative to the first one's target, and its own delay slot is the instruction at
|
||||
// that target. Compile that path separately, then fall through to the not-taken path, which is
|
||||
// an ordinary branch with its delay slot after it.
|
||||
auto* memory = PCSX::g_emulator->m_mem.get();
|
||||
uint32_t* slotPtr =
|
||||
m_nextIsDelaySlot && m_pcWrittenBack && slotBranchTarget ? memory->getPointer<uint32_t>(m_pc) : nullptr;
|
||||
uint32_t* targetPtr = slotPtr ? memory->getPointer<uint32_t>(*slotBranchTarget) : nullptr;
|
||||
if (slotPtr && targetPtr && isBranch(*slotPtr)) {
|
||||
const uint32_t firstTarget = *slotBranchTarget;
|
||||
const uint32_t slotPC = m_pc;
|
||||
const auto savedLoads0 = m_delayedLoadInfo[0];
|
||||
const auto savedLoads1 = m_delayedLoadInfo[1];
|
||||
const auto savedCurrentLoad = m_currentDelayedLoad;
|
||||
const auto savedCount = count;
|
||||
const auto savedExtra = extra;
|
||||
Label notTaken;
|
||||
|
||||
flushRegs();
|
||||
gen.Ldr(w0, MemOperand(contextPointer, PC_OFFSET));
|
||||
gen.Mov(w1, firstTarget);
|
||||
gen.Cmp(w0, w1);
|
||||
gen.B(¬Taken, ne);
|
||||
|
||||
// Taken: the second branch, based on the first branch's target.
|
||||
m_inDelaySlot = true;
|
||||
m_nextIsDelaySlot = false;
|
||||
m_pcWrittenBack = false;
|
||||
const uint32_t code = m_regs.code = *slotPtr;
|
||||
m_pc = firstTarget;
|
||||
count++;
|
||||
if (((slotPC + 4) & 0xffc00000) == 0xbfc00000) extra++;
|
||||
m_instructionCount = count;
|
||||
m_romInstructionCount = extra;
|
||||
(*this.*m_recBSC[code >> 26])(code);
|
||||
fixDelaySlotBranchLink(code, slotPC + 8);
|
||||
if (!m_pcWrittenBack) {
|
||||
gen.Mov(w0, firstTarget + 4);
|
||||
gen.Str(w0, MemOperand(contextPointer, PC_OFFSET));
|
||||
m_pcWrittenBack = true;
|
||||
}
|
||||
processDelayedLoad();
|
||||
// Its delay slot is the instruction at the first branch's target.
|
||||
m_nextIsDelaySlot = true;
|
||||
if (!compileInstruction()) {
|
||||
return m_invalidBlock;
|
||||
}
|
||||
processDelayedLoad();
|
||||
m_pcWrittenBack = true;
|
||||
endBlock();
|
||||
|
||||
// Not taken: back to the state before the taken path and compile it the ordinary way.
|
||||
gen.L(notTaken);
|
||||
m_delayedLoadInfo[0] = savedLoads0;
|
||||
m_delayedLoadInfo[1] = savedLoads1;
|
||||
m_currentDelayedLoad = savedCurrentLoad;
|
||||
count = savedCount;
|
||||
extra = savedExtra;
|
||||
m_pc = slotPC;
|
||||
m_inDelaySlot = false;
|
||||
m_nextIsDelaySlot = true;
|
||||
m_pcWrittenBack = true;
|
||||
m_stopCompiling = true;
|
||||
m_linkedPC = std::nullopt;
|
||||
}
|
||||
if (!compileInstruction()) {
|
||||
return m_invalidBlock;
|
||||
}
|
||||
@@ -489,7 +575,7 @@ DynarecCallback DynaRecCPU::recompile(DynarecCallback* callback, uint32_t pc, bo
|
||||
}
|
||||
|
||||
gen.Ldr(x0, MemOperand(contextPointer, CYCLE_OFFSET)); // Fetch cycle count from memory
|
||||
gen.Add(x0, x0, count * PCSX::Emulator::BIAS); // Add block cycles
|
||||
gen.Add(x0, x0, (count + extra * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS); // Add block cycles
|
||||
gen.Str(x0, MemOperand(contextPointer, CYCLE_OFFSET)); // Store cycles back to memory
|
||||
|
||||
// Link block else return to dispatcher
|
||||
|
||||
@@ -131,6 +131,8 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
|
||||
DynarecCallback m_needFullLoadDelays;
|
||||
|
||||
uint32_t m_pc;
|
||||
unsigned m_instructionCount; // Instructions compiled so far in the current block
|
||||
unsigned m_romInstructionCount; // How many of those were fetched from ROM
|
||||
Emitter gen;
|
||||
|
||||
bool m_stopCompiling; // Should we stop compiling code?
|
||||
@@ -221,6 +223,37 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
|
||||
// Check if we're executing from valid memory
|
||||
inline bool isPcValid(uint32_t addr) { return m_recompilerLUT[addr >> 16] != m_dummyBlocks; }
|
||||
|
||||
// True for every branch and jump: REGIMM, J, JAL, BEQ, BNE, BLEZ, BGTZ, JR and JALR.
|
||||
static bool isBranch(uint32_t code) {
|
||||
const uint32_t op = code >> 26;
|
||||
if (op == 0) return (code & 0x3e) == 0x08; // JR, JALR
|
||||
return op >= 1 && op <= 7;
|
||||
}
|
||||
|
||||
// Target of the branch or jump in `code` if it is taken, when it is known at compile time.
|
||||
// `pc` is the address after the branch, which is the base of a relative branch.
|
||||
std::optional<uint32_t> staticBranchTarget(uint32_t code, uint32_t pc) {
|
||||
const uint32_t op = code >> 26;
|
||||
if (op == 1 || (op >= 4 && op <= 7)) return (uint32_t)((int16_t)code * 4) + pc;
|
||||
if (op == 2 || op == 3) return (pc & 0xf0000000) | ((code & 0x03ffffff) << 2);
|
||||
if (op == 0 && (code & 0x3e) == 0x08) {
|
||||
const unsigned rs = (code >> 21) & 0x1f;
|
||||
if (m_gprs[rs].isConst()) return m_gprs[rs].val & ~3;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// A linking branch compiled with a moved base still links to its own address + 8.
|
||||
void fixDelaySlotBranchLink(uint32_t code, uint32_t link) {
|
||||
const uint32_t op = code >> 26;
|
||||
if (op == 3 || (op == 1 && ((code >> 17) & 0xf) == 8)) {
|
||||
markConst(31, link);
|
||||
} else if (op == 0 && (code & 0x3f) == 0x09) {
|
||||
const unsigned rd = (code >> 11) & 0x1f;
|
||||
if (rd) markConst(rd, link);
|
||||
}
|
||||
}
|
||||
|
||||
DynarecCallback* getBlockPointer(uint32_t pc);
|
||||
DynarecCallback recompile(DynarecCallback* callback, uint32_t pc, bool align = true,
|
||||
bool fullLoadDelayEmulation = false);
|
||||
|
||||
@@ -792,7 +792,7 @@ void DynaRecCPU::recompileLoadWithDelay(uint32_t code, LoadDelayDependencyType t
|
||||
gen.mov(Xbyak::util::byte[contextPointer + isActiveOffset], 1);
|
||||
gen.mov(dword[contextPointer + indexOffset], _Rt_);
|
||||
} else {
|
||||
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
|
||||
auto& delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
|
||||
const auto delayedLoadValueOffset = (uintptr_t)&delayedLoad.value - (uintptr_t)this;
|
||||
delayedLoad.index = _Rt_;
|
||||
gen.mov(dword[contextPointer + delayedLoadValueOffset], eax);
|
||||
@@ -1430,10 +1430,47 @@ void DynaRecCPU::recCOP1or3(uint32_t code) {
|
||||
}
|
||||
|
||||
// BCzF / BCzT. None of the coprocessors drives the condition input, so BCzF
|
||||
// always branches and BCzT never does. Bit 1 of rt is ignored. Like the rest
|
||||
// of the dynarec, this does not check SR.CUz.
|
||||
// always branches and BCzT never does. Bit 1 of rt is ignored. With SR.CUz
|
||||
// clear, the branch raises a coprocessor unusable exception, COP0 included,
|
||||
// and leaves the block without running the delay slot.
|
||||
void DynaRecCPU::recBCz(uint32_t code) {
|
||||
const unsigned z = (code >> 26) & 3;
|
||||
const auto target = _Imm_ * 4 + m_pc;
|
||||
Label usable;
|
||||
|
||||
flushRegs();
|
||||
gen.test(dword[contextPointer + COP0_OFFSET(12)], 0x10000000 << z);
|
||||
gen.jnz(usable, Xbyak::CodeGenerator::T_NEAR);
|
||||
|
||||
// The load started by the previous instruction still lands
|
||||
if (m_firstInstruction && m_fullLoadDelayEmulation) {
|
||||
Label noDelayedLoad;
|
||||
const auto isActiveOffset = (uintptr_t)&m_runtimeLoadDelay.active - (uintptr_t)this;
|
||||
gen.cmp(Xbyak::util::byte[contextPointer + isActiveOffset], 0);
|
||||
gen.je(noDelayedLoad);
|
||||
gen.call((void*)m_loadDelayHandler);
|
||||
gen.L(noDelayedLoad);
|
||||
}
|
||||
const auto& delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad ^ 1];
|
||||
if (delayedLoad.active && delayedLoad.index != 0) {
|
||||
const auto delayedValueOffset = (uintptr_t)&delayedLoad.value - (uintptr_t)this;
|
||||
gen.mov(eax, dword[contextPointer + delayedValueOffset]);
|
||||
gen.mov(dword[contextPointer + GPR_OFFSET(delayedLoad.index)], eax);
|
||||
}
|
||||
|
||||
loadThisPointer(arg1.cvt64());
|
||||
gen.moveImm(arg2, (static_cast<uint32_t>(Exception::CoprocessorUnusable) << 2) | (z << 28));
|
||||
gen.moveImm(arg3, (int32_t)m_inDelaySlot);
|
||||
gen.mov(dword[contextPointer + PC_OFFSET], m_pc - 4);
|
||||
call(exceptionWrapper);
|
||||
if constexpr (ENABLE_PROFILER) {
|
||||
endProfiling();
|
||||
}
|
||||
gen.add(qword[contextPointer + CYCLE_OFFSET],
|
||||
(m_instructionCount + m_romInstructionCount * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS);
|
||||
gen.jmp((void*)m_returnFromBlock);
|
||||
|
||||
gen.L(usable);
|
||||
m_nextIsDelaySlot = true;
|
||||
|
||||
if ((_Rt_ & 1) || target == m_pc + 4) {
|
||||
@@ -1468,7 +1505,9 @@ void DynaRecCPU::recMTC0(uint32_t code) {
|
||||
else {
|
||||
allocateReg(_Rt_);
|
||||
if (_Rd_ == 13) {
|
||||
gen.and_(m_gprs[_Rt_].allocatedReg, ~0xFC00);
|
||||
gen.mov(eax, m_gprs[_Rt_].allocatedReg);
|
||||
gen.and_(eax, ~0xFC00);
|
||||
gen.mov(dword[contextPointer + COP0_OFFSET(_Rd_)], eax);
|
||||
} else if (_Rd_ != 6 && _Rd_ != 14 && _Rd_ != 15) { // Don't write to JUMPDEST, EPC or PRID
|
||||
gen.mov(dword[contextPointer + COP0_OFFSET(_Rd_)], m_gprs[_Rt_].allocatedReg); // Write rt to the cop0 reg
|
||||
}
|
||||
|
||||
@@ -334,6 +334,7 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
|
||||
|
||||
const auto startingPC = m_pc;
|
||||
unsigned count = 0; // How many instructions have we compiled?
|
||||
unsigned extra = 0; // How many instructions in rom?
|
||||
DynarecCallback* callback = getBlockPointer(m_pc); // Pointer to where we'll store the addr of the emitted code
|
||||
|
||||
if (align) {
|
||||
@@ -395,7 +396,10 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
|
||||
}
|
||||
};
|
||||
|
||||
const auto compileInstruction = [this, &count, &memory]() -> bool {
|
||||
// Target of the branch whose delay slot is compiled next, when it is known at compile time.
|
||||
std::optional<uint32_t> slotBranchTarget;
|
||||
|
||||
const auto compileInstruction = [this, &count, &memory, &extra, &slotBranchTarget]() -> bool {
|
||||
m_inDelaySlot = m_nextIsDelaySlot;
|
||||
m_nextIsDelaySlot = false;
|
||||
|
||||
@@ -405,7 +409,10 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
|
||||
uint32_t code = m_regs.code = *ptr;
|
||||
m_pc += 4; // Increment recompiler PC
|
||||
count++; // Increment instruction count
|
||||
if ((m_pc & 0xffc00000) == 0xbfc00000) extra++;
|
||||
m_instructionCount = count;
|
||||
m_romInstructionCount = extra;
|
||||
slotBranchTarget = staticBranchTarget(code, m_pc);
|
||||
|
||||
const auto func = m_recBSC[code >> 26]; // Look up the opcode in our decoding LUT
|
||||
(*this.*func)(code); // Jump into the handler to recompile it
|
||||
@@ -434,7 +441,82 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
|
||||
processDelayedLoad();
|
||||
m_firstInstruction = false;
|
||||
|
||||
// Ends the block: writes back the registers and the PC, adds the cycles, returns to the dispatcher.
|
||||
const auto endBlock = [this, &count, &extra]() {
|
||||
flushRegs();
|
||||
if (!m_pcWrittenBack) {
|
||||
gen.mov(dword[contextPointer + PC_OFFSET], m_pc);
|
||||
}
|
||||
if constexpr (ENABLE_PROFILER) {
|
||||
endProfiling();
|
||||
}
|
||||
gen.add(qword[contextPointer + CYCLE_OFFSET],
|
||||
(count + extra * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS);
|
||||
gen.jmp((void*)m_returnFromBlock);
|
||||
};
|
||||
|
||||
while (shouldContinue()) {
|
||||
// A branch in the delay slot of a branch that may be taken. When the first branch is taken, the
|
||||
// second one is relative to the first one's target, and its own delay slot is the instruction at
|
||||
// that target. Compile that path separately, then fall through to the not-taken path, which is
|
||||
// an ordinary branch with its delay slot after it.
|
||||
uint32_t* slotPtr =
|
||||
m_nextIsDelaySlot && m_pcWrittenBack && slotBranchTarget ? memory->getPointer<uint32_t>(m_pc) : nullptr;
|
||||
uint32_t* targetPtr = slotPtr ? memory->getPointer<uint32_t>(*slotBranchTarget) : nullptr;
|
||||
if (slotPtr && targetPtr && isBranch(*slotPtr)) {
|
||||
const uint32_t firstTarget = *slotBranchTarget;
|
||||
const uint32_t slotPC = m_pc;
|
||||
const auto savedLoads0 = m_delayedLoadInfo[0];
|
||||
const auto savedLoads1 = m_delayedLoadInfo[1];
|
||||
const auto savedCurrentLoad = m_currentDelayedLoad;
|
||||
const auto savedCount = count;
|
||||
const auto savedExtra = extra;
|
||||
Label notTaken;
|
||||
|
||||
flushRegs();
|
||||
gen.cmp(dword[contextPointer + PC_OFFSET], firstTarget);
|
||||
gen.jne(notTaken, Xbyak::CodeGenerator::T_NEAR);
|
||||
|
||||
// Taken: the second branch, based on the first branch's target.
|
||||
m_inDelaySlot = true;
|
||||
m_nextIsDelaySlot = false;
|
||||
m_pcWrittenBack = false;
|
||||
const uint32_t code = m_regs.code = *slotPtr;
|
||||
m_pc = firstTarget;
|
||||
count++;
|
||||
if (((slotPC + 4) & 0xffc00000) == 0xbfc00000) extra++;
|
||||
m_instructionCount = count;
|
||||
m_romInstructionCount = extra;
|
||||
(*this.*m_recBSC[code >> 26])(code);
|
||||
fixDelaySlotBranchLink(code, slotPC + 8);
|
||||
if (!m_pcWrittenBack) {
|
||||
gen.mov(dword[contextPointer + PC_OFFSET], firstTarget + 4);
|
||||
m_pcWrittenBack = true;
|
||||
}
|
||||
processDelayedLoad();
|
||||
// Its delay slot is the instruction at the first branch's target.
|
||||
m_nextIsDelaySlot = true;
|
||||
if (!compileInstruction()) {
|
||||
return m_invalidBlock;
|
||||
}
|
||||
processDelayedLoad();
|
||||
m_pcWrittenBack = true;
|
||||
endBlock();
|
||||
|
||||
// Not taken: back to the state before the taken path and compile it the ordinary way.
|
||||
gen.L(notTaken);
|
||||
m_delayedLoadInfo[0] = savedLoads0;
|
||||
m_delayedLoadInfo[1] = savedLoads1;
|
||||
m_currentDelayedLoad = savedCurrentLoad;
|
||||
count = savedCount;
|
||||
extra = savedExtra;
|
||||
m_pc = slotPC;
|
||||
m_inDelaySlot = false;
|
||||
m_nextIsDelaySlot = true;
|
||||
m_pcWrittenBack = true;
|
||||
m_stopCompiling = true;
|
||||
m_linkedPC = std::nullopt;
|
||||
}
|
||||
if (!compileInstruction()) {
|
||||
return m_invalidBlock;
|
||||
}
|
||||
@@ -454,7 +536,8 @@ DynarecCallback DynaRecCPU::recompile(uint32_t pc, bool fullLoadDelayEmulation,
|
||||
endProfiling();
|
||||
}
|
||||
|
||||
gen.add(qword[contextPointer + CYCLE_OFFSET], count * PCSX::Emulator::BIAS); // Add block cycles;
|
||||
gen.add(qword[contextPointer + CYCLE_OFFSET],
|
||||
(count + extra * PCSX::Emulator::ROM_EXTRA_BIAS) * PCSX::Emulator::BIAS); // Add block cycles;
|
||||
if (m_linkedPC && ENABLE_BLOCK_LINKING && m_linkedPC.value() != startingPC) {
|
||||
handleLinking();
|
||||
} else {
|
||||
|
||||
@@ -133,8 +133,9 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
|
||||
DynarecCallback m_needFullLoadDelays;
|
||||
|
||||
Emitter gen;
|
||||
uint32_t m_pc; // Recompiler PC
|
||||
unsigned m_instructionCount; // Instructions compiled so far in the current block
|
||||
uint32_t m_pc; // Recompiler PC
|
||||
unsigned m_instructionCount; // Instructions compiled so far in the current block
|
||||
unsigned m_romInstructionCount; // How many of those were fetched from ROM
|
||||
|
||||
bool m_stopCompiling; // Should we stop compiling code?
|
||||
bool m_pcWrittenBack; // Has the PC been written back already by a jump?
|
||||
@@ -397,6 +398,37 @@ class DynaRecCPU final : public PCSX::R3000Acpu {
|
||||
// Check if we're executing from valid memory
|
||||
inline bool isPcValid(uint32_t addr) { return m_recompilerLUT[addr >> 16] != m_dummyBlocks; }
|
||||
|
||||
// True for every branch and jump: REGIMM, J, JAL, BEQ, BNE, BLEZ, BGTZ, JR and JALR.
|
||||
static bool isBranch(uint32_t code) {
|
||||
const uint32_t op = code >> 26;
|
||||
if (op == 0) return (code & 0x3e) == 0x08; // JR, JALR
|
||||
return op >= 1 && op <= 7;
|
||||
}
|
||||
|
||||
// Target of the branch or jump in `code` if it is taken, when it is known at compile time.
|
||||
// `pc` is the address after the branch, which is the base of a relative branch.
|
||||
std::optional<uint32_t> staticBranchTarget(uint32_t code, uint32_t pc) {
|
||||
const uint32_t op = code >> 26;
|
||||
if (op == 1 || (op >= 4 && op <= 7)) return (uint32_t)((int16_t)code * 4) + pc;
|
||||
if (op == 2 || op == 3) return (pc & 0xf0000000) | ((code & 0x03ffffff) << 2);
|
||||
if (op == 0 && (code & 0x3e) == 0x08) {
|
||||
const unsigned rs = (code >> 21) & 0x1f;
|
||||
if (m_gprs[rs].isConst()) return m_gprs[rs].val & ~3;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// A linking branch compiled with a moved base still links to its own address + 8.
|
||||
void fixDelaySlotBranchLink(uint32_t code, uint32_t link) {
|
||||
const uint32_t op = code >> 26;
|
||||
if (op == 3 || (op == 1 && ((code >> 17) & 0xf) == 8)) {
|
||||
markConst(31, link);
|
||||
} else if (op == 0 && (code & 0x3f) == 0x09) {
|
||||
const unsigned rd = (code >> 11) & 0x1f;
|
||||
if (rd) markConst(rd, link);
|
||||
}
|
||||
}
|
||||
|
||||
DynarecCallback* getBlockPointer(uint32_t pc);
|
||||
DynarecCallback recompile(uint32_t pc, bool fullLoadDelayEmulation, bool align = true);
|
||||
void error();
|
||||
|
||||
+1582
-1537
File diff suppressed because it is too large
Load Diff
+151
-89
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "cdrom/cdriso.h"
|
||||
#include "core/decode_xa.h"
|
||||
@@ -38,21 +39,30 @@ namespace Widgets {
|
||||
class IsoBrowser;
|
||||
}
|
||||
|
||||
struct CdrStat {
|
||||
uint32_t Type;
|
||||
uint32_t Status;
|
||||
IEC60908b::MSF Time;
|
||||
};
|
||||
|
||||
class CDRom {
|
||||
public:
|
||||
using MSF = PCSX::IEC60908b::MSF;
|
||||
using MSF = IEC60908b::MSF;
|
||||
CDRom() : m_iso(new CDRIso(new FailedFile)) {}
|
||||
virtual ~CDRom() {}
|
||||
static CDRom* factory();
|
||||
bool isLidOpened() { return m_lidOpenTime < 0 || m_lidOpenTime > (int64_t)time(nullptr); }
|
||||
void setLidOpenTime(int64_t time) { m_lidOpenTime = time; }
|
||||
void check();
|
||||
bool isLidOpen();
|
||||
void closeLid() {
|
||||
m_lidOpen = false;
|
||||
m_lidCloseScheduled = false;
|
||||
}
|
||||
void openLid() {
|
||||
m_lidOpen = true;
|
||||
m_wasLidOpened = true;
|
||||
m_lidCloseScheduled = false;
|
||||
}
|
||||
void scheduleCloseLid() {
|
||||
m_lidOpen = true;
|
||||
m_wasLidOpened = true;
|
||||
m_lidCloseScheduled = true;
|
||||
using namespace std::chrono_literals;
|
||||
m_lidCloseAtCycles = g_emulator->m_cpu->m_regs.getFutureCycle(1s);
|
||||
}
|
||||
void parseIso();
|
||||
|
||||
std::shared_ptr<CDRIso> getIso() { return m_iso; }
|
||||
void clearIso() {
|
||||
@@ -68,24 +78,19 @@ class CDRom {
|
||||
const std::string& getCDRomLabel() { return m_cdromLabel; }
|
||||
|
||||
virtual void reset() = 0;
|
||||
virtual void attenuate(int16_t* buf, int samples, int stereo) = 0;
|
||||
|
||||
virtual void interrupt() = 0;
|
||||
virtual void readInterrupt() = 0;
|
||||
virtual void decodedBufferInterrupt() = 0;
|
||||
virtual void lidSeekInterrupt() = 0;
|
||||
virtual void playInterrupt() = 0;
|
||||
virtual void dmaInterrupt() = 0;
|
||||
virtual void lidInterrupt() = 0;
|
||||
virtual uint8_t read0(void) = 0;
|
||||
virtual uint8_t read1(void) = 0;
|
||||
virtual uint8_t read2(void) = 0;
|
||||
virtual uint8_t read3(void) = 0;
|
||||
virtual void fifoScheduledCallback() = 0;
|
||||
virtual void commandsScheduledCallback() = 0;
|
||||
virtual void readScheduledCallback() = 0;
|
||||
virtual void scheduledDmaCallback() = 0;
|
||||
virtual uint8_t read0() = 0;
|
||||
virtual uint8_t read1() = 0;
|
||||
virtual uint8_t read2() = 0;
|
||||
virtual uint8_t read3() = 0;
|
||||
virtual void write0(uint8_t rt) = 0;
|
||||
virtual void write1(uint8_t rt) = 0;
|
||||
virtual void write2(uint8_t rt) = 0;
|
||||
virtual void write3(uint8_t rt) = 0;
|
||||
virtual void load() = 0;
|
||||
|
||||
virtual void dma(uint32_t madr, uint32_t bcr, uint32_t chcr) = 0;
|
||||
|
||||
@@ -93,74 +98,131 @@ class CDRom {
|
||||
|
||||
protected:
|
||||
std::shared_ptr<CDRIso> m_iso;
|
||||
// savestate stuff starts here
|
||||
uint8_t m_reg1Mode;
|
||||
uint8_t m_reg2;
|
||||
uint8_t m_cmdProcess;
|
||||
uint8_t m_ctrl;
|
||||
uint8_t m_stat;
|
||||
|
||||
uint8_t m_statP;
|
||||
|
||||
uint8_t m_transfer[PCSX::IEC60908b::FRAMESIZE_RAW];
|
||||
unsigned int m_transferIndex;
|
||||
|
||||
MSF m_prev;
|
||||
uint8_t m_param[8];
|
||||
uint8_t m_result[16];
|
||||
|
||||
uint8_t m_paramC;
|
||||
uint8_t m_resultC;
|
||||
uint8_t m_resultP;
|
||||
uint8_t m_resultReady;
|
||||
uint8_t m_cmd;
|
||||
uint8_t m_read;
|
||||
uint8_t m_setlocPending;
|
||||
bool m_locationChanged;
|
||||
uint32_t m_reading;
|
||||
|
||||
MSF m_setSectorPlay;
|
||||
MSF m_setSectorEnd;
|
||||
MSF m_setSector;
|
||||
uint8_t m_track;
|
||||
bool m_play, m_muted;
|
||||
int m_curTrack;
|
||||
int m_mode, m_file, m_channel;
|
||||
bool m_suceeded;
|
||||
int m_firstSector;
|
||||
|
||||
public:
|
||||
// this belongs in the SPU, not here.
|
||||
xa_decode_t m_xa;
|
||||
|
||||
protected:
|
||||
int64_t m_lidOpenTime = 0;
|
||||
uint16_t m_irq;
|
||||
uint8_t m_irqRepeated;
|
||||
uint32_t m_eCycle;
|
||||
|
||||
uint8_t m_seeked;
|
||||
uint8_t m_readRescheduled;
|
||||
|
||||
uint8_t m_driveState;
|
||||
uint8_t m_fastForward;
|
||||
uint8_t m_fastBackward;
|
||||
|
||||
uint8_t m_attenuatorLeftToLeft, m_attenuatorLeftToRight;
|
||||
uint8_t m_attenuatorRightToRight, m_attenuatorRightToLeft;
|
||||
uint8_t m_attenuatorLeftToLeftT, m_attenuatorLeftToRightT;
|
||||
uint8_t m_attenuatorRightToRightT, m_attenuatorRightToLeftT;
|
||||
|
||||
struct {
|
||||
uint8_t track;
|
||||
uint8_t index;
|
||||
uint8_t relative[3];
|
||||
uint8_t absolute[3];
|
||||
} m_subq;
|
||||
bool m_trackChanged;
|
||||
// end savestate
|
||||
friend SaveStates::SaveState SaveStates::constructSaveState();
|
||||
|
||||
bool dataFIFOHasData() { return m_dataFIFOIndex != m_dataFIFOSize; }
|
||||
|
||||
bool m_lidOpen = false;
|
||||
bool m_wasLidOpened = false;
|
||||
bool m_lidCloseScheduled = false;
|
||||
uint32_t m_lidCloseAtCycles = 0;
|
||||
|
||||
// to save/init
|
||||
uint64_t m_seed = 9223521712174600777ull;
|
||||
uint8_t m_dataFIFO[2352] = {0};
|
||||
uint32_t m_dataFIFOIndex = 0;
|
||||
uint32_t m_dataFIFOSize = 0;
|
||||
uint32_t m_dataFIFOPending = 0;
|
||||
uint8_t m_registerAddress = 0;
|
||||
bool m_motorOn = false;
|
||||
bool m_speedChanged = false;
|
||||
bool m_invalidLocL = false;
|
||||
bool m_dataRequested = false;
|
||||
bool m_subheaderFilter = false;
|
||||
bool m_realtime = false;
|
||||
enum class ReadingState : uint8_t {
|
||||
None,
|
||||
Seeking,
|
||||
Reading,
|
||||
} m_readingState = ReadingState::None;
|
||||
bool m_startPlaying = false;
|
||||
enum class ReadingType : uint8_t {
|
||||
None,
|
||||
Normal,
|
||||
Streaming,
|
||||
Play,
|
||||
} m_readingType = ReadingType::None;
|
||||
bool m_autoPause = false;
|
||||
bool m_report = false;
|
||||
bool m_setLocPending = false;
|
||||
bool m_muted = false;
|
||||
bool m_peakFlag = false;
|
||||
uint8_t m_playTrack = 0;
|
||||
uint32_t m_playStartCycle = 0;
|
||||
enum class Status : uint8_t {
|
||||
Idle,
|
||||
ReadingData,
|
||||
Seeking,
|
||||
PlayingCDDA,
|
||||
} m_status = Status::Idle;
|
||||
enum class Speed : uint8_t { Simple, Double } m_speed;
|
||||
enum class ReadSpan : uint8_t { S2048, S2328, S2340 } m_readSpan;
|
||||
uint8_t m_interruptCauseMask = 0x1f;
|
||||
uint8_t m_atv[4] = {0x80, 0, 0x80, 0};
|
||||
uint8_t m_atvPending[4] = {0x80, 0, 0x80, 0};
|
||||
bool m_adpcmMuted = false;
|
||||
uint8_t m_mode = 0;
|
||||
uint8_t m_filterFile = 0;
|
||||
uint8_t m_filterChannel = 0;
|
||||
bool m_xaEnded = false;
|
||||
bool m_xaFirstSector = true;
|
||||
xa_decode_t m_xa;
|
||||
xa_decode_t m_xaOut;
|
||||
bool m_soundMapEnabled = false;
|
||||
|
||||
enum class Cause : uint8_t {
|
||||
None = 0,
|
||||
DataReady = 1,
|
||||
Complete = 2,
|
||||
Acknowledge = 3,
|
||||
End = 4,
|
||||
Error = 5,
|
||||
};
|
||||
|
||||
MSF m_currentPosition;
|
||||
MSF m_seekPosition;
|
||||
uint8_t m_lastLocP[8] = {0};
|
||||
uint8_t m_lastLocL[8] = {0};
|
||||
|
||||
struct QueueElement {
|
||||
uint8_t payload[16];
|
||||
uint8_t value;
|
||||
bool valueRead = false;
|
||||
bool hasValue = false;
|
||||
bool hitMax = false;
|
||||
uint8_t payloadSize = 0;
|
||||
uint8_t payloadIndex = 0;
|
||||
bool isPayloadAtEnd() const { return payloadIndex == payloadSize; }
|
||||
bool isPayloadEmpty() const { return (payloadSize == 0) || hitMax; }
|
||||
bool isPayloadFull() const { return payloadSize == sizeof(payload); }
|
||||
bool empty() const { return valueEmpty() && isPayloadEmpty(); }
|
||||
bool valueEmpty() const { return !hasValue || valueRead; }
|
||||
void clear() {
|
||||
hasValue = false;
|
||||
valueRead = false;
|
||||
payloadSize = 0;
|
||||
payloadIndex = 0;
|
||||
}
|
||||
void setValue(uint8_t newValue) {
|
||||
value = newValue;
|
||||
hasValue = true;
|
||||
}
|
||||
void setValue(Cause cause) { setValue(static_cast<uint8_t>(cause)); }
|
||||
void pushPayloadData(uint8_t value) {
|
||||
if (payloadSize < sizeof(payload)) payload[payloadSize++] = value;
|
||||
}
|
||||
void pushPayloadData(std::string_view values) {
|
||||
for (auto value : values) {
|
||||
pushPayloadData(value);
|
||||
}
|
||||
}
|
||||
uint8_t getValue() const { return valueRead ? 0 : value; }
|
||||
uint8_t readPayloadByte() {
|
||||
while (payloadIndex >= 16) payloadIndex -= 16;
|
||||
uint8_t r = 0;
|
||||
if (payloadIndex < payloadSize) {
|
||||
r = payload[payloadIndex];
|
||||
}
|
||||
if (++payloadIndex == payloadSize) hitMax = true;
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
QueueElement m_commandFifo;
|
||||
QueueElement m_commandExecuting;
|
||||
QueueElement m_responseFifo[2];
|
||||
bool responseFifoFull() { return !m_responseFifo[0].valueEmpty() && !m_responseFifo[1].empty(); }
|
||||
|
||||
private:
|
||||
friend class Widgets::IsoBrowser;
|
||||
std::string m_cdromId;
|
||||
|
||||
@@ -206,7 +206,7 @@ void PCSX::LuaBindings::open_events(Lua L) {
|
||||
} else if (name == "Memory::SetLuts") {
|
||||
createListener<Events::Memory::SetLuts>(L);
|
||||
} else {
|
||||
return L.error("createListener: unknown event name");
|
||||
return L.error("createEventListener: unknown event name '%s'", name.c_str());
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
|
||||
@@ -767,7 +767,7 @@ void PCSX::GdbClient::processMonitorCommand(const std::string& cmd) {
|
||||
auto pathCmd = cmd.substr(8);
|
||||
auto pathView = StringsHelpers::trim(pathCmd);
|
||||
g_emulator->m_cdrom->setIso(new CDRIso(pathView));
|
||||
g_emulator->m_cdrom->check();
|
||||
g_emulator->m_cdrom->parseIso();
|
||||
}
|
||||
} else if (words[0] == "sharedmem") {
|
||||
if (words.size() != 2) {
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#define GPUSTATUS_READYFORVRAM 0x08000000
|
||||
#define GPUSTATUS_IDLE 0x04000000 // CMD ready
|
||||
#define GPUSTATUS_MODE 0x02000000 // Data request mode
|
||||
#define GPUSTATUS_IRQ1 0x01000000 // GP0(1Fh) / GP1(02h)
|
||||
|
||||
namespace PCSX {
|
||||
|
||||
@@ -377,6 +378,7 @@ uint32_t PCSX::GPU::readStatus() {
|
||||
if ((ret & GPUSTATUS_IDLE) == 0) ret &= ~GPUSTATUS_READYFORVRAM;
|
||||
#endif
|
||||
if (m_readFifo->size() != 0) ret |= GPUSTATUS_READYFORVRAM;
|
||||
if (m_irq1Flag) ret |= GPUSTATUS_IRQ1;
|
||||
// Let's pretend our input fifo is always ready for more data.
|
||||
if ((ret & 0x60000000) == 0x20000000) ret |= 0x02000000;
|
||||
return ret;
|
||||
@@ -671,6 +673,9 @@ void PCSX::GPU::Command::processWrite(Buffer &buf, Logged::Origin origin, uint32
|
||||
m_gpu->m_fastFill.setActive();
|
||||
m_gpu->m_fastFill.processWrite(buf, origin, originValue, length);
|
||||
} break;
|
||||
case 0x1f: { // interrupt request (IRQ1)
|
||||
m_gpu->requestIRQ1();
|
||||
} break;
|
||||
default: {
|
||||
gotUnknown = true;
|
||||
} break;
|
||||
@@ -779,6 +784,10 @@ void PCSX::GPU::FastFill::processWrite(Buffer &buf, Logged::Origin origin, uint3
|
||||
raw.y = y;
|
||||
raw.w = w;
|
||||
raw.h = h;
|
||||
// GP0(02h) parameter masking (psx-spx): Xpos ignores its low 4 bits,
|
||||
// Xsiz rounds up to the next multiple of 16 within 0..400h.
|
||||
x &= 0x3f0;
|
||||
w = ((w & 0x3ff) + 0xf) & ~0xf;
|
||||
clipped = GPU::clip(x, y, w, h);
|
||||
m_state = READ_COLOR;
|
||||
m_gpu->m_defaultProcessor.setActive();
|
||||
|
||||
+5
-2
@@ -98,12 +98,13 @@ class GPU {
|
||||
void dma(uint32_t madr, uint32_t bcr, uint32_t chcr);
|
||||
static void gpuInterrupt();
|
||||
|
||||
// These functions do not touch GPUSTAT. GPU backends should mirror the IRQ status into GPUSTAT
|
||||
// when readStatus is called
|
||||
// These set and clear the IRQ1 flag, which readStatus mirrors into GPUSTAT.24.
|
||||
void requestIRQ1() {
|
||||
m_irq1Flag = true;
|
||||
if (!m_detached) g_emulator->m_mem->setIRQ(2);
|
||||
}
|
||||
void acknowledgeIRQ1() {
|
||||
m_irq1Flag = false;
|
||||
if (!m_detached) g_emulator->m_mem->clearIRQ(2);
|
||||
}
|
||||
|
||||
@@ -166,6 +167,7 @@ class GPU {
|
||||
virtual void setDither(int setting) = 0;
|
||||
void reset() {
|
||||
resetBackend();
|
||||
m_irq1Flag = false;
|
||||
m_dataRet = 0;
|
||||
m_readFifo->reset();
|
||||
m_processor->reset();
|
||||
@@ -251,6 +253,7 @@ class GPU {
|
||||
|
||||
private:
|
||||
uint32_t m_statusControl[256];
|
||||
bool m_irq1Flag = false;
|
||||
uint32_t m_envRaw[7];
|
||||
bool m_detached = false;
|
||||
GPULogger *m_logger = nullptr;
|
||||
|
||||
@@ -28,6 +28,7 @@ enum SectorMode {
|
||||
|
||||
typedef struct { char opaque[?]; } LuaIso;
|
||||
typedef struct { char opaque[?]; } IsoReader;
|
||||
typedef struct { uint8_t m, s, f; } MSF;
|
||||
|
||||
void deleteIso(LuaIso* wrapper);
|
||||
bool isIsoFailed(LuaIso* wrapper);
|
||||
@@ -42,6 +43,8 @@ void deleteIsoReader(IsoReader* isoReader);
|
||||
bool isReaderFailed(IsoReader* reader);
|
||||
LuaFile* readerOpen(IsoReader* reader, const char* path);
|
||||
LuaFile* fileisoOpen(LuaIso* wrapper, uint32_t lba, uint32_t size, enum SectorMode mode);
|
||||
uint32_t getIsoTN(LuaIso* wrapper);
|
||||
MSF getIsoTD(LuaIso* wrapper, uint32_t tn);
|
||||
|
||||
typedef struct { char opaque[?]; } DirEntries;
|
||||
DirEntries* readerListDir(IsoReader* reader, const char* path);
|
||||
@@ -189,6 +192,8 @@ local function createIsoWrapper(wrapper)
|
||||
if mode == nil then mode = 'GUESS' end
|
||||
return Support.File._createFileWrapper(C.fileisoOpen(self._wrapper, lba, size, mode))
|
||||
end,
|
||||
getTD = function(self, tn) return C.getIsoTD(self._wrapper, tn) end,
|
||||
getTN = function(self) return C.getIsoTN(self._wrapper) end,
|
||||
}
|
||||
return iso
|
||||
end
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "lua/luafile.h"
|
||||
#include "lua/luawrapper.h"
|
||||
#include "support/strings-helpers.h"
|
||||
#include "supportpsx/iec-60908b.h"
|
||||
#include "supportpsx/iso9660-builder.h"
|
||||
|
||||
namespace {
|
||||
@@ -39,6 +40,10 @@ struct LuaIso {
|
||||
std::shared_ptr<PCSX::CDRIso> iso;
|
||||
};
|
||||
|
||||
struct MSF {
|
||||
uint8_t m, s, f;
|
||||
};
|
||||
|
||||
void deleteIso(LuaIso* wrapper) { delete wrapper; }
|
||||
bool isIsoFailed(LuaIso* wrapper) { return wrapper->iso->failed(); }
|
||||
void isoClearPPF(LuaIso* wrapper) { wrapper->iso->getPPF()->clear(); }
|
||||
@@ -59,6 +64,11 @@ PCSX::LuaFFI::LuaFile* readerOpen(PCSX::ISO9660Reader* reader, const char* path)
|
||||
PCSX::LuaFFI::LuaFile* fileisoOpen(LuaIso* wrapper, uint32_t lba, uint32_t size, PCSX::IEC60908b::SectorMode mode) {
|
||||
return new PCSX::LuaFFI::LuaFile(new PCSX::CDRIsoFile(wrapper->iso, lba, size, mode));
|
||||
}
|
||||
uint32_t getIsoTN(LuaIso* wrapper) { return wrapper->iso->getTN(); }
|
||||
MSF getIsoTD(LuaIso* wrapper, uint32_t tn) {
|
||||
auto ret = wrapper->iso->getTD(tn);
|
||||
return {ret.m, ret.s, ret.f};
|
||||
}
|
||||
|
||||
struct DirEntries {
|
||||
std::vector<PCSX::ISO9660Reader::FullDirEntry> entries;
|
||||
@@ -433,6 +443,8 @@ static void registerAllSymbols(PCSX::Lua L) {
|
||||
REGISTER(L, getCurrentIso);
|
||||
REGISTER(L, openIso);
|
||||
REGISTER(L, openIsoFromFile);
|
||||
REGISTER(L, getIsoTD);
|
||||
REGISTER(L, getIsoTN);
|
||||
REGISTER(L, createIsoReader);
|
||||
REGISTER(L, deleteIsoReader);
|
||||
REGISTER(L, isReaderFailed);
|
||||
|
||||
+5
-5
@@ -733,10 +733,10 @@ void PCSX::MDEC::dma0(uint32_t adr, uint32_t bcr, uint32_t chcr) {
|
||||
break;
|
||||
}
|
||||
|
||||
mdec0Interrupt();
|
||||
scheduledCallback0();
|
||||
}
|
||||
|
||||
void PCSX::MDEC::mdec0Interrupt() {
|
||||
void PCSX::MDEC::scheduledCallback0() {
|
||||
auto &mem = g_emulator->m_mem;
|
||||
mem->clearDMABusy<0>();
|
||||
mem->dmaInterrupt<0>();
|
||||
@@ -836,7 +836,7 @@ void PCSX::MDEC::dma1(uint32_t adr, uint32_t bcr, uint32_t chcr) {
|
||||
}
|
||||
}
|
||||
|
||||
void PCSX::MDEC::mdec1Interrupt() {
|
||||
void PCSX::MDEC::scheduledCallback1() {
|
||||
/* Author : gschwind
|
||||
*
|
||||
* in that case we have done all decoding stuff
|
||||
@@ -864,11 +864,11 @@ void PCSX::MDEC::mdec1Interrupt() {
|
||||
/* this else if avoid to read outside memory */
|
||||
if (mdec.rl >= mdec.rl_end) {
|
||||
mdec.reg1 &= ~MDEC1_STP;
|
||||
mdec0Interrupt();
|
||||
scheduledCallback0();
|
||||
mdec.reg1 &= ~MDEC1_BUSY;
|
||||
} else if (SWAP_LE16(*(mdec.rl)) == MDEC_END_OF_DATA) {
|
||||
mdec.reg1 &= ~MDEC1_STP;
|
||||
mdec0Interrupt();
|
||||
scheduledCallback0();
|
||||
mdec.reg1 &= ~MDEC1_BUSY;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -37,8 +37,8 @@ class MDEC {
|
||||
uint32_t read1();
|
||||
void dma0(uint32_t madr, uint32_t bcr, uint32_t chcr);
|
||||
void dma1(uint32_t madr, uint32_t bcr, uint32_t chcr);
|
||||
void mdec0Interrupt();
|
||||
void mdec1Interrupt();
|
||||
void scheduledCallback0();
|
||||
void scheduledCallback1();
|
||||
|
||||
static const unsigned DSIZE = 8;
|
||||
static const unsigned DSIZE2 = DSIZE * DSIZE;
|
||||
|
||||
@@ -668,6 +668,9 @@ void PadsImpl::Pad::getButtons() {
|
||||
|
||||
auto getKeyboardButtons = [this]() -> uint16_t {
|
||||
if (!ImGui::GetCurrentContext()) return 0xffff;
|
||||
// Don't feed host keys into the emulated pad while ImGui text fields
|
||||
// own the keyboard (typing in the UI would otherwise hit in-game).
|
||||
if (ImGui::GetIO().WantTextInput) return 0xffff;
|
||||
uint16_t result = 0;
|
||||
for (unsigned i = 0; i < 16; i++) {
|
||||
auto key = SdlScancodeToImGuiKey(m_scancodes[i]);
|
||||
|
||||
+5
-5
@@ -23,23 +23,23 @@
|
||||
#include "core/r3000a.h"
|
||||
|
||||
static inline void scheduleGPUDMAIRQ(uint32_t eCycle) {
|
||||
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_GPUDMA, eCycle);
|
||||
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::GPUDMA, eCycle);
|
||||
}
|
||||
|
||||
static inline void scheduleSPUDMAIRQ(uint32_t eCycle) {
|
||||
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_SPUDMA, eCycle);
|
||||
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::SPUDMA, eCycle);
|
||||
}
|
||||
|
||||
static inline void scheduleMDECOUTDMAIRQ(uint32_t eCycle) {
|
||||
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_MDECOUTDMA, eCycle);
|
||||
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::MDECOUTDMA, eCycle);
|
||||
}
|
||||
|
||||
static inline void scheduleMDECINDMAIRQ(uint32_t eCycle) {
|
||||
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_MDECINDMA, eCycle);
|
||||
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::MDECINDMA, eCycle);
|
||||
}
|
||||
|
||||
static inline void scheduleGPUOTCDMAIRQ(uint32_t eCycle) {
|
||||
PCSX::g_emulator->m_cpu->scheduleInterrupt(PCSX::PSXINT_GPUOTCDMA, eCycle);
|
||||
PCSX::g_emulator->m_cpu->schedule(PCSX::Schedule::GPUOTCDMA, eCycle);
|
||||
}
|
||||
|
||||
void dma4(uint32_t madr, uint32_t bcr, uint32_t chcr);
|
||||
|
||||
+10
-8
@@ -115,6 +115,7 @@ class Emulator {
|
||||
typedef Setting<uint32_t, TYPESTRING("FirstChanceException"), 0x00001cf0> FirstChanceException;
|
||||
typedef Setting<bool, TYPESTRING("SkipISR")> SkipISR;
|
||||
typedef Setting<bool, TYPESTRING("LoggingCDROM"), false> LoggingCDROM;
|
||||
typedef Setting<bool, TYPESTRING("LoggingHWCDROM"), false> LoggingHWCDROM;
|
||||
typedef Setting<bool, TYPESTRING("GdbServer"), false> GdbServer;
|
||||
typedef Setting<bool, TYPESTRING("GdbManifest"), true> GdbManifest;
|
||||
enum class GdbLog {
|
||||
@@ -150,12 +151,12 @@ class Emulator {
|
||||
Raw,
|
||||
};
|
||||
typedef Setting<SIO1Mode, TYPESTRING("SIO1Mode"), SIO1Mode::Protobuf> SIO1ModeSetting;
|
||||
typedef Settings<Debug, Trace, KernelLog, FirstChanceException, SkipISR, LoggingCDROM, GdbServer, GdbManifest,
|
||||
GdbLogSetting, GdbServerPort, GdbServerTrace, WebServer, WebServerPort, KernelCallA0_00_1f,
|
||||
KernelCallA0_20_3f, KernelCallA0_40_5f, KernelCallA0_60_7f, KernelCallA0_80_9f,
|
||||
KernelCallA0_a0_bf, KernelCallB0_00_1f, KernelCallB0_20_3f, KernelCallB0_40_5f,
|
||||
KernelCallC0_00_1f, DemangledSymbols, PCdrv, PCdrvBase, SIO1Server, SIO1ServerPort, SIO1Client,
|
||||
SIO1ClientHost, SIO1ClientPort, SIO1ModeSetting>
|
||||
typedef Settings<Debug, Trace, KernelLog, FirstChanceException, SkipISR, LoggingCDROM, LoggingHWCDROM,
|
||||
GdbServer, GdbManifest, GdbLogSetting, GdbServerPort, GdbServerTrace, WebServer, WebServerPort,
|
||||
KernelCallA0_00_1f, KernelCallA0_20_3f, KernelCallA0_40_5f, KernelCallA0_60_7f,
|
||||
KernelCallA0_80_9f, KernelCallA0_a0_bf, KernelCallB0_00_1f, KernelCallB0_20_3f,
|
||||
KernelCallB0_40_5f, KernelCallC0_00_1f, DemangledSymbols, PCdrv, PCdrvBase, SIO1Server,
|
||||
SIO1ServerPort, SIO1Client, SIO1ClientHost, SIO1ClientPort, SIO1ModeSetting>
|
||||
type;
|
||||
};
|
||||
typedef SettingNested<TYPESTRING("Debug"), DebugSettings::type> SettingDebugSettings;
|
||||
@@ -242,8 +243,9 @@ class Emulator {
|
||||
// Make the timing events trigger faster as we are currently assuming everything
|
||||
// takes one cycle, which is not the case on real hardware.
|
||||
// FIXME: Count the proper cycle and get rid of this
|
||||
uint32_t m_psxClockSpeed = 33868800 /* 33.8688 MHz */;
|
||||
enum { BIAS = 2 };
|
||||
static constexpr uint32_t m_psxClockSpeed = 33868800 /* 33.8688 MHz */;
|
||||
static constexpr uint32_t BIAS = 2;
|
||||
static constexpr uint32_t ROM_EXTRA_BIAS = 10;
|
||||
|
||||
template <unsigned alignment = 1>
|
||||
requires((alignment == 1) || (alignment == 4))
|
||||
|
||||
+56
-50
@@ -79,13 +79,27 @@
|
||||
#define _rLo_ m_regs.GPR.n.lo // The LO register
|
||||
|
||||
#define _JumpTarget_ ((_Target_ * 4) + (_PC_ & 0xf0000000)) // Calculates the target during a jump instruction
|
||||
#define _BranchTarget_ ((int16_t)_Im_ * 4 + _PC_) // Calculates the target during a branch instruction
|
||||
// A relative branch adds its offset to the PC register. In the delay slot of a taken branch, the PC
|
||||
// register already holds the first branch's target, so that is the base.
|
||||
#define _BranchTarget_ ((int16_t)_Im_ * 4 + branchBase())
|
||||
|
||||
class InterpretedCPU final : public PCSX::R3000Acpu {
|
||||
public:
|
||||
InterpretedCPU() : R3000Acpu("Interpreted") {}
|
||||
|
||||
private:
|
||||
// An exception taken in a branch delay slot cancels the pending branch. While the delay slot
|
||||
// executes, that branch sits in the other delayed load slot.
|
||||
// A BREAK that PCdrv handles takes no exception, so the branch stays pending.
|
||||
bool exception(uint32_t code, bool bd, bool cop0 = false) {
|
||||
bool taken = R3000Acpu::exception(code, bd, cop0);
|
||||
if (taken && bd) m_delayedLoadInfo[m_currentDelayedLoad ^ 1].pcActive = false;
|
||||
return taken;
|
||||
}
|
||||
bool exception(Exception e, bool bd, bool cop0 = false) {
|
||||
return exception(static_cast<std::underlying_type<Exception>::type>(e) << 2, bd, cop0);
|
||||
}
|
||||
|
||||
virtual bool Implemented() final { return true; }
|
||||
virtual bool Init() override;
|
||||
virtual void Reset() override;
|
||||
@@ -117,6 +131,7 @@ class InterpretedCPU final : public PCSX::R3000Acpu {
|
||||
template <bool debug, bool trace>
|
||||
void execBlock();
|
||||
void doBranch(uint32_t target, bool fromLink);
|
||||
uint32_t branchBase();
|
||||
|
||||
void MTC0(int reg, uint32_t val);
|
||||
|
||||
@@ -333,6 +348,11 @@ class InterpretedCPU final : public PCSX::R3000Acpu {
|
||||
static const intFunc_t s_pgxpPsxBSCMem[64];
|
||||
};
|
||||
|
||||
inline uint32_t InterpretedCPU::branchBase() {
|
||||
auto &pending = m_delayedLoadInfo[m_currentDelayedLoad ^ 1];
|
||||
return pending.pcActive ? pending.pcValue : m_regs.pc;
|
||||
}
|
||||
|
||||
inline void InterpretedCPU::doBranch(uint32_t target, bool fromLink) {
|
||||
m_nextIsDelaySlot = true;
|
||||
delayedPCLoad(target, fromLink);
|
||||
@@ -376,7 +396,7 @@ void InterpretedCPU::psxADDI(uint32_t code) {
|
||||
void InterpretedCPU::psxADDIU(uint32_t code) {
|
||||
if (!_Rt_) return;
|
||||
maybeCancelDelayedLoad(_Rt_);
|
||||
uint32_t newValue = _u32(_rRs_) + _Imm_;
|
||||
uint32_t newValue = _rRs_ + _Imm_;
|
||||
if (_Rt_ == 29) {
|
||||
if (_Rs_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->offsetSP(_rRt_, _Imm_);
|
||||
@@ -390,7 +410,7 @@ void InterpretedCPU::psxADDIU(uint32_t code) {
|
||||
void InterpretedCPU::psxANDI(uint32_t code) {
|
||||
if (!_Rt_) return;
|
||||
maybeCancelDelayedLoad(_Rt_);
|
||||
uint32_t newValue = _u32(_rRs_) & _ImmU_;
|
||||
uint32_t newValue = _rRs_ & _ImmU_;
|
||||
if (_Rt_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
|
||||
}
|
||||
@@ -399,7 +419,7 @@ void InterpretedCPU::psxANDI(uint32_t code) {
|
||||
void InterpretedCPU::psxORI(uint32_t code) {
|
||||
if (!_Rt_) return;
|
||||
maybeCancelDelayedLoad(_Rt_);
|
||||
uint32_t newValue = _u32(_rRs_) | _ImmU_;
|
||||
uint32_t newValue = _rRs_ | _ImmU_;
|
||||
if (_Rt_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
|
||||
}
|
||||
@@ -408,7 +428,7 @@ void InterpretedCPU::psxORI(uint32_t code) {
|
||||
void InterpretedCPU::psxXORI(uint32_t code) {
|
||||
if (!_Rt_) return;
|
||||
maybeCancelDelayedLoad(_Rt_);
|
||||
uint32_t newValue = _u32(_rRs_) ^ _ImmU_;
|
||||
uint32_t newValue = _rRs_ ^ _ImmU_;
|
||||
if (_Rt_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
|
||||
}
|
||||
@@ -417,7 +437,7 @@ void InterpretedCPU::psxXORI(uint32_t code) {
|
||||
void InterpretedCPU::psxSLTI(uint32_t code) {
|
||||
if (!_Rt_) return;
|
||||
maybeCancelDelayedLoad(_Rt_);
|
||||
uint32_t newValue = _i32(_rRs_) < _Imm_;
|
||||
uint32_t newValue = int32_t(_rRs_) < _Imm_;
|
||||
if (_Rt_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
|
||||
}
|
||||
@@ -426,7 +446,7 @@ void InterpretedCPU::psxSLTI(uint32_t code) {
|
||||
void InterpretedCPU::psxSLTIU(uint32_t code) {
|
||||
if (!_Rt_) return;
|
||||
maybeCancelDelayedLoad(_Rt_);
|
||||
uint32_t newValue = _u32(_rRs_) < ((uint32_t)_Imm_);
|
||||
uint32_t newValue = _rRs_ < ((uint32_t)_Imm_);
|
||||
if (_Rt_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRt_, newValue);
|
||||
}
|
||||
@@ -523,7 +543,7 @@ void InterpretedCPU::psxSUBU(uint32_t code) {
|
||||
void InterpretedCPU::psxAND(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _u32(_rRs_) & _u32(_rRt_);
|
||||
uint32_t newValue = _rRs_ & _rRt_;
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -532,7 +552,7 @@ void InterpretedCPU::psxAND(uint32_t code) {
|
||||
void InterpretedCPU::psxOR(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _u32(_rRs_) | _u32(_rRt_);
|
||||
uint32_t newValue = _rRs_ | _rRt_;
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -541,7 +561,7 @@ void InterpretedCPU::psxOR(uint32_t code) {
|
||||
void InterpretedCPU::psxXOR(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _u32(_rRs_) ^ _u32(_rRt_);
|
||||
uint32_t newValue = _rRs_ ^ _rRt_;
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -550,7 +570,7 @@ void InterpretedCPU::psxXOR(uint32_t code) {
|
||||
void InterpretedCPU::psxNOR(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = ~(_u32(_rRs_) | _u32(_rRt_));
|
||||
uint32_t newValue = ~(_rRs_ | _rRt_);
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -559,7 +579,7 @@ void InterpretedCPU::psxNOR(uint32_t code) {
|
||||
void InterpretedCPU::psxSLT(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _i32(_rRs_) < _i32(_rRt_);
|
||||
uint32_t newValue = int32_t(_rRs_) < int32_t(_rRt_);
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -568,7 +588,7 @@ void InterpretedCPU::psxSLT(uint32_t code) {
|
||||
void InterpretedCPU::psxSLTU(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _u32(_rRs_) < _u32(_rRt_);
|
||||
uint32_t newValue = _rRs_ < _rRt_;
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -629,7 +649,7 @@ void InterpretedCPU::psxMULTU(uint32_t code) {
|
||||
* Format: OP rs, offset *
|
||||
*********************************************************/
|
||||
#define RepZBranchi32(op) \
|
||||
if (_i32(_rRs_) op 0) { \
|
||||
if (int32_t(_rRs_) op 0) { \
|
||||
doBranch(_BranchTarget_, false); \
|
||||
}
|
||||
#define RepZBranchLinki32(op) \
|
||||
@@ -657,7 +677,7 @@ void InterpretedCPU::psxBLTZAL(uint32_t code) { RepZBranchLinki32(<); } // Bra
|
||||
void InterpretedCPU::psxSLL(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _u32(_rRt_) << _Sa_;
|
||||
uint32_t newValue = _rRt_ << _Sa_;
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -666,7 +686,7 @@ void InterpretedCPU::psxSLL(uint32_t code) {
|
||||
void InterpretedCPU::psxSRA(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _i32(_rRt_) >> _Sa_;
|
||||
uint32_t newValue = int32_t(_rRt_) >> _Sa_;
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -675,7 +695,7 @@ void InterpretedCPU::psxSRA(uint32_t code) {
|
||||
void InterpretedCPU::psxSRL(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _u32(_rRt_) >> _Sa_;
|
||||
uint32_t newValue = _rRt_ >> _Sa_;
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -689,7 +709,7 @@ void InterpretedCPU::psxSRL(uint32_t code) {
|
||||
void InterpretedCPU::psxSLLV(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _u32(_rRt_) << (_u32(_rRs_) & 0x1f);
|
||||
uint32_t newValue = _rRt_ << (_rRs_ & 0x1f);
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -698,7 +718,7 @@ void InterpretedCPU::psxSLLV(uint32_t code) {
|
||||
void InterpretedCPU::psxSRAV(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _i32(_rRt_) >> (_u32(_rRs_) & 0x1f);
|
||||
uint32_t newValue = int32_t(_rRt_) >> (_rRs_ & 0x1f);
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -707,7 +727,7 @@ void InterpretedCPU::psxSRAV(uint32_t code) {
|
||||
void InterpretedCPU::psxSRLV(uint32_t code) {
|
||||
if (!_Rd_) return;
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t newValue = _u32(_rRt_) >> (_u32(_rRs_) & 0x1f);
|
||||
uint32_t newValue = _rRt_ >> (_rRs_ & 0x1f);
|
||||
if (_Rd_ == 29) {
|
||||
PCSX::g_emulator->m_callStacks->setSP(_rRd_, newValue);
|
||||
}
|
||||
@@ -773,21 +793,11 @@ void InterpretedCPU::psxMTLO(uint32_t code) {
|
||||
void InterpretedCPU::psxBREAK(uint32_t code) {
|
||||
m_regs.pc -= 4;
|
||||
exception(Exception::Break, m_inDelaySlot);
|
||||
if (m_inDelaySlot) {
|
||||
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
|
||||
if (!delayedLoad.pcActive) abort();
|
||||
delayedLoad.pcActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void InterpretedCPU::psxSYSCALL(uint32_t code) {
|
||||
m_regs.pc -= 4;
|
||||
exception(Exception::Syscall, m_inDelaySlot);
|
||||
if (m_inDelaySlot) {
|
||||
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
|
||||
if (!delayedLoad.pcActive) abort();
|
||||
delayedLoad.pcActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void InterpretedCPU::psxRFE(uint32_t code) {
|
||||
@@ -839,7 +849,7 @@ void InterpretedCPU::psxJR(uint32_t code) {
|
||||
}
|
||||
|
||||
void InterpretedCPU::psxJALR(uint32_t code) {
|
||||
uint32_t temp = _u32(_rRs_);
|
||||
uint32_t temp = _rRs_;
|
||||
if (_Rd_) {
|
||||
maybeCancelDelayedLoad(_Rd_);
|
||||
uint32_t ra = m_regs.pc + 4;
|
||||
@@ -868,12 +878,12 @@ void InterpretedCPU::psxJALR(uint32_t code) {
|
||||
* Format: OP rt, offset(base) *
|
||||
*********************************************************/
|
||||
|
||||
#define _oB_ (_u32(_rRs_) + _Imm_)
|
||||
#define _oB_ (_rRs_ + _Imm_)
|
||||
|
||||
void InterpretedCPU::psxLB(uint32_t code) {
|
||||
// load delay = 1 latency
|
||||
if (_Rt_) {
|
||||
_i32(delayedLoadRef(_Rt_)) = (int8_t)PCSX::g_emulator->m_mem->read8(_oB_);
|
||||
delayedLoadRef<int32_t>(_Rt_) = (int8_t)PCSX::g_emulator->m_mem->read8(_oB_);
|
||||
} else {
|
||||
PCSX::g_emulator->m_mem->read8(_oB_);
|
||||
}
|
||||
@@ -882,7 +892,7 @@ void InterpretedCPU::psxLB(uint32_t code) {
|
||||
void InterpretedCPU::psxLBU(uint32_t code) {
|
||||
// load delay = 1 latency
|
||||
if (_Rt_) {
|
||||
_u32(delayedLoadRef(_Rt_)) = PCSX::g_emulator->m_mem->read8(_oB_);
|
||||
delayedLoadRef(_Rt_) = PCSX::g_emulator->m_mem->read8(_oB_);
|
||||
} else {
|
||||
PCSX::g_emulator->m_mem->read8(_oB_);
|
||||
}
|
||||
@@ -899,7 +909,7 @@ void InterpretedCPU::psxLH(uint32_t code) {
|
||||
}
|
||||
|
||||
if (_Rt_) {
|
||||
_i32(delayedLoadRef(_Rt_)) = (short)PCSX::g_emulator->m_mem->read16(_oB_);
|
||||
delayedLoadRef<int32_t>(_Rt_) = (short)PCSX::g_emulator->m_mem->read16(_oB_);
|
||||
} else {
|
||||
PCSX::g_emulator->m_mem->read16(_oB_);
|
||||
}
|
||||
@@ -916,7 +926,7 @@ void InterpretedCPU::psxLHU(uint32_t code) {
|
||||
}
|
||||
|
||||
if (_Rt_) {
|
||||
_u32(delayedLoadRef(_Rt_)) = PCSX::g_emulator->m_mem->read16(_oB_);
|
||||
delayedLoadRef(_Rt_) = PCSX::g_emulator->m_mem->read16(_oB_);
|
||||
} else {
|
||||
PCSX::g_emulator->m_mem->read16(_oB_);
|
||||
}
|
||||
@@ -944,7 +954,7 @@ void InterpretedCPU::psxLW(uint32_t code) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
_u32(delayedLoadRef(_Rt_)) = val;
|
||||
delayedLoadRef(_Rt_) = val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,7 +965,7 @@ void InterpretedCPU::psxLWL(uint32_t code) {
|
||||
|
||||
// load delay = 1 latency
|
||||
if (!_Rt_) return;
|
||||
_u32(delayedLoadRef(_Rt_, LWL_MASK[shift])) = mem << LWL_SHIFT[shift];
|
||||
delayedLoadRef(_Rt_, LWL_MASK[shift]) = mem << LWL_SHIFT[shift];
|
||||
|
||||
/*
|
||||
Mem = 1234. Reg = abcd
|
||||
@@ -973,7 +983,7 @@ void InterpretedCPU::psxLWR(uint32_t code) {
|
||||
|
||||
// load delay = 1 latency
|
||||
if (!_Rt_) return;
|
||||
_u32(delayedLoadRef(_Rt_, LWR_MASK[shift])) = mem >> LWR_SHIFT[shift];
|
||||
delayedLoadRef(_Rt_, LWR_MASK[shift]) = mem >> LWR_SHIFT[shift];
|
||||
|
||||
/*
|
||||
Mem = 1234. Reg = abcd
|
||||
@@ -1017,7 +1027,7 @@ void InterpretedCPU::psxSWL(uint32_t code) {
|
||||
// the preserved bytes aren't consumed, so msan doesn't check any of them
|
||||
uint32_t mem = PCSX::g_emulator->m_mem->read32Masked(addr, 0);
|
||||
|
||||
PCSX::g_emulator->m_mem->write32Masked(addr, (_u32(_rRt_) >> SWL_SHIFT[shift]) | (mem & SWL_MASK[shift]),
|
||||
PCSX::g_emulator->m_mem->write32Masked(addr, ((_rRt_) >> SWL_SHIFT[shift]) | (mem & SWL_MASK[shift]),
|
||||
PCSX::Memory::leftByteMask(shift));
|
||||
/*
|
||||
Mem = 1234. Reg = abcd
|
||||
@@ -1035,7 +1045,7 @@ void InterpretedCPU::psxSWR(uint32_t code) {
|
||||
// the preserved bytes aren't consumed, so msan doesn't check any of them
|
||||
uint32_t mem = PCSX::g_emulator->m_mem->read32Masked(addr, 0);
|
||||
|
||||
PCSX::g_emulator->m_mem->write32Masked(addr, (_u32(_rRt_) << SWR_SHIFT[shift]) | (mem & SWR_MASK[shift]),
|
||||
PCSX::g_emulator->m_mem->write32Masked(addr, ((_rRt_) << SWR_SHIFT[shift]) | (mem & SWR_MASK[shift]),
|
||||
PCSX::Memory::rightByteMask(shift));
|
||||
|
||||
/*
|
||||
@@ -1054,13 +1064,13 @@ void InterpretedCPU::psxSWR(uint32_t code) {
|
||||
void InterpretedCPU::psxMFC0(uint32_t code) {
|
||||
// load delay = 1 latency
|
||||
if (!_Rt_) return;
|
||||
_i32(delayedLoadRef(_Rt_)) = (int)m_regs.CP0.r[_Rd_];
|
||||
delayedLoadRef<int32_t>(_Rt_) = (int)m_regs.CP0.r[_Rd_];
|
||||
}
|
||||
|
||||
void InterpretedCPU::psxCFC0(uint32_t code) {
|
||||
// load delay = 1 latency
|
||||
if (!_Rt_) return;
|
||||
_i32(delayedLoadRef(_Rt_)) = (int)m_regs.CP0.r[_Rd_];
|
||||
delayedLoadRef<int32_t>(_Rt_) = (int)m_regs.CP0.r[_Rd_];
|
||||
}
|
||||
|
||||
void InterpretedCPU::psxTestSWInts() {
|
||||
@@ -1092,8 +1102,8 @@ inline void InterpretedCPU::MTC0(int reg, uint32_t val) {
|
||||
}
|
||||
}
|
||||
|
||||
void InterpretedCPU::psxMTC0(uint32_t code) { MTC0(_Rd_, _u32(_rRt_)); }
|
||||
void InterpretedCPU::psxCTC0(uint32_t code) { MTC0(_Rd_, _u32(_rRt_)); }
|
||||
void InterpretedCPU::psxMTC0(uint32_t code) { MTC0(_Rd_, _rRt_); }
|
||||
void InterpretedCPU::psxCTC0(uint32_t code) { MTC0(_Rd_, _rRt_); }
|
||||
|
||||
void InterpretedCPU::psxMFC2(uint32_t code) {
|
||||
gteStall();
|
||||
@@ -1131,11 +1141,6 @@ void InterpretedCPU::psxBCz(uint32_t code, unsigned z) {
|
||||
if ((m_regs.CP0.n.Status & (0x10000000 << z)) == 0) {
|
||||
m_regs.pc -= 4;
|
||||
exception((static_cast<uint32_t>(Exception::CoprocessorUnusable) << 2) | (z << 28), m_inDelaySlot);
|
||||
if (m_inDelaySlot) {
|
||||
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
|
||||
if (!delayedLoad.pcActive) abort();
|
||||
delayedLoad.pcActive = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((_Rt_ & 1) == 0) doBranch(_BranchTarget_, false);
|
||||
@@ -1676,6 +1681,7 @@ inline void InterpretedCPU::execBlock() {
|
||||
|
||||
m_regs.pc += 4;
|
||||
m_regs.cycle += PCSX::Emulator::BIAS;
|
||||
if ((m_regs.pc & 0xffc00000) == 0xbfc00000) m_regs.cycle += PCSX::Emulator::BIAS * 10;
|
||||
|
||||
cIntFunc_t func = s_pPsxBSC[code >> 26];
|
||||
(*this.*func)(code);
|
||||
|
||||
+61
-72
@@ -81,7 +81,7 @@ void PCSX::R3000Acpu::psxReset() {
|
||||
|
||||
void PCSX::R3000Acpu::psxShutdown() { Shutdown(); }
|
||||
|
||||
void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
bool PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
auto& emuSettings = g_emulator->settings;
|
||||
auto& debugSettings = emuSettings.get<Emulator::SettingDebugSettings>();
|
||||
unsigned ec = (code >> 2) & 0x1f;
|
||||
@@ -102,14 +102,14 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
regs.v0 = 0;
|
||||
regs.v1 = 0;
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
case 0x102: { // PCcreat
|
||||
if (m_availableFDs.empty()) {
|
||||
regs.v0 = -1;
|
||||
regs.v1 = -1;
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
std::filesystem::path basepath = debugSettings.get<Emulator::DebugSettings::PCdrvBase>();
|
||||
memFile->rSeek(m_regs.GPR.n.a0);
|
||||
@@ -127,14 +127,14 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
regs.v1 = fd;
|
||||
}
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
case 0x103: { // PCopen
|
||||
if (m_availableFDs.empty()) {
|
||||
regs.v0 = -1;
|
||||
regs.v1 = -1;
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
std::filesystem::path basepath = debugSettings.get<Emulator::DebugSettings::PCdrvBase>();
|
||||
memFile->rSeek(m_regs.GPR.n.a0);
|
||||
@@ -158,7 +158,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
regs.v1 = fd;
|
||||
}
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
case 0x104: { // PCclose
|
||||
fd = m_regs.GPR.n.a0;
|
||||
@@ -174,7 +174,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
m_availableFDs.push_back(fd);
|
||||
}
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
case 0x105: { // PCread
|
||||
auto filei = m_pcdrvFiles.find(m_regs.GPR.n.a1);
|
||||
@@ -182,21 +182,21 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
regs.v0 = -1;
|
||||
regs.v1 = -1;
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
IO<File> file = *filei;
|
||||
if (file->failed() || file->eof()) {
|
||||
regs.v0 = -1;
|
||||
regs.v1 = -1;
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
auto slice = file->read(regs.a2);
|
||||
regs.v0 = 0;
|
||||
regs.v1 = slice.size();
|
||||
memFile->writeAt(std::move(slice), regs.a3);
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
case 0x106: { // PCwrite
|
||||
auto filei = m_pcdrvFiles.find(m_regs.GPR.n.a1);
|
||||
@@ -204,7 +204,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
regs.v0 = -1;
|
||||
regs.v1 = -1;
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
IO<File> file = *filei;
|
||||
auto slice = memFile->readAt(regs.a2, regs.a3);
|
||||
@@ -214,7 +214,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
regs.v0 = 0;
|
||||
}
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
case 0x107: { // PClseek
|
||||
auto filei = m_pcdrvFiles.find(m_regs.GPR.n.a0);
|
||||
@@ -222,7 +222,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
regs.v0 = -1;
|
||||
regs.v1 = -1;
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
IO<File> file = *filei;
|
||||
int wheel;
|
||||
@@ -240,14 +240,14 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
regs.v0 = -1;
|
||||
regs.v1 = -1;
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
ssize_t ret = file->rSeek(regs.a2, wheel);
|
||||
if (ret < 0) {
|
||||
regs.v0 = -1;
|
||||
regs.v1 = ret;
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (file->writable()) {
|
||||
file->wSeek(regs.a2, wheel);
|
||||
@@ -261,7 +261,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
regs.v1 = ret;
|
||||
}
|
||||
m_regs.pc += 4;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@@ -298,6 +298,7 @@ void PCSX::R3000Acpu::exception(uint32_t code, bool bd, bool cop0) {
|
||||
m_regs.CP0.n.Cause = code;
|
||||
// Set the Status
|
||||
m_regs.CP0.n.Status = (m_regs.CP0.n.Status & ~0x3f) | ((m_regs.CP0.n.Status & 0xf) << 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PCSX::R3000Acpu::restorePCdrvFile(const std::filesystem::path& filename, uint16_t fd) {
|
||||
@@ -329,73 +330,49 @@ void PCSX::R3000Acpu::restorePCdrvFile(const std::filesystem::path& filename, ui
|
||||
}
|
||||
|
||||
void PCSX::R3000Acpu::branchTest() {
|
||||
#if 0
|
||||
if( SPU_async )
|
||||
{
|
||||
static int init;
|
||||
int elapsed;
|
||||
|
||||
if( init == 0 ) {
|
||||
// 10 apu cycles
|
||||
// - Final Fantasy Tactics (distorted - dropped sound effects)
|
||||
m_regs.intCycle[PSXINT_SPUASYNC].cycle = g_emulator->m_psxClockSpeed / 44100 * 10;
|
||||
|
||||
init = 1;
|
||||
}
|
||||
|
||||
elapsed = m_regs.cycle - m_regs.intCycle[PSXINT_SPUASYNC].sCycle;
|
||||
if (elapsed >= m_regs.intCycle[PSXINT_SPUASYNC].cycle) {
|
||||
SPU_async( elapsed );
|
||||
|
||||
m_regs.intCycle[PSXINT_SPUASYNC].sCycle = m_regs.cycle;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint64_t cycle = m_regs.cycle;
|
||||
|
||||
if (cycle >= g_emulator->m_counters->m_psxNextCounter) g_emulator->m_counters->update();
|
||||
|
||||
if (m_regs.spuInterrupt.exchange(false)) g_emulator->m_spu->interrupt();
|
||||
|
||||
const uint32_t interrupts = m_regs.interrupt;
|
||||
const uint32_t scheduleMask = m_regs.scheduleMask;
|
||||
|
||||
int32_t lowestDistance = std::numeric_limits<int64_t>::max();
|
||||
uint64_t lowestTarget = cycle;
|
||||
uint64_t* targets = m_regs.intTargets;
|
||||
uint64_t* targets = m_regs.scheduleTargets;
|
||||
|
||||
if ((interrupts != 0) && (m_regs.lowestTarget < cycle)) {
|
||||
#define checkAndUpdate(irq, act) \
|
||||
{ \
|
||||
constexpr uint32_t mask = 1 << irq; \
|
||||
if ((interrupts & mask) != 0) { \
|
||||
uint64_t target = targets[irq]; \
|
||||
int64_t dist = target - cycle; \
|
||||
if (dist > 0) { \
|
||||
if (lowestDistance > dist) { \
|
||||
lowestDistance = dist; \
|
||||
lowestTarget = target; \
|
||||
} \
|
||||
} else { \
|
||||
m_regs.interrupt &= ~mask; \
|
||||
PSXIRQ_LOG("Triggering interrupt %08x\n", magic_enum::enum_integer(irq)); \
|
||||
act(); \
|
||||
} \
|
||||
} \
|
||||
if ((scheduleMask != 0) && (m_regs.lowestTarget < cycle)) {
|
||||
#define checkAndUpdate(irq_, act) \
|
||||
{ \
|
||||
constexpr unsigned irq = static_cast<unsigned>(irq_); \
|
||||
constexpr uint32_t mask = 1 << irq; \
|
||||
if ((scheduleMask & mask) != 0) { \
|
||||
uint64_t target = targets[irq]; \
|
||||
int64_t dist = target - cycle; \
|
||||
if (dist > 0) { \
|
||||
if (lowestDistance > dist) { \
|
||||
lowestDistance = dist; \
|
||||
lowestTarget = target; \
|
||||
} \
|
||||
} else { \
|
||||
m_regs.scheduleMask &= ~mask; \
|
||||
PSXIRQ_LOG("Calling scheduled callback %08x\n", irq); \
|
||||
act(); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
checkAndUpdate(PSXINT_SIO, g_emulator->m_sio->interrupt);
|
||||
checkAndUpdate(PSXINT_SIO1, g_emulator->m_sio1->interrupt);
|
||||
checkAndUpdate(PSXINT_CDR, g_emulator->m_cdrom->interrupt);
|
||||
checkAndUpdate(PSXINT_CDREAD, g_emulator->m_cdrom->readInterrupt);
|
||||
checkAndUpdate(PSXINT_GPUDMA, GPU::gpuInterrupt);
|
||||
checkAndUpdate(PSXINT_MDECOUTDMA, g_emulator->m_mdec->mdec1Interrupt);
|
||||
checkAndUpdate(PSXINT_SPUDMA, spuInterrupt);
|
||||
checkAndUpdate(PSXINT_MDECINDMA, g_emulator->m_mdec->mdec0Interrupt);
|
||||
checkAndUpdate(PSXINT_GPUOTCDMA, gpuotcInterrupt);
|
||||
checkAndUpdate(PSXINT_CDRDMA, g_emulator->m_cdrom->dmaInterrupt);
|
||||
checkAndUpdate(PSXINT_CDRPLAY, g_emulator->m_cdrom->playInterrupt);
|
||||
checkAndUpdate(PSXINT_CDRDBUF, g_emulator->m_cdrom->decodedBufferInterrupt);
|
||||
checkAndUpdate(PSXINT_CDRLID, g_emulator->m_cdrom->lidSeekInterrupt);
|
||||
checkAndUpdate(Schedule::SIO, g_emulator->m_sio->scheduledCallback);
|
||||
checkAndUpdate(Schedule::SIO1, g_emulator->m_sio1->scheduledCallback);
|
||||
checkAndUpdate(Schedule::CDRFIFO, g_emulator->m_cdrom->fifoScheduledCallback);
|
||||
checkAndUpdate(Schedule::CDRCOMMANDS, g_emulator->m_cdrom->commandsScheduledCallback);
|
||||
checkAndUpdate(Schedule::CDREAD, g_emulator->m_cdrom->readScheduledCallback);
|
||||
checkAndUpdate(Schedule::GPUDMA, GPU::gpuInterrupt);
|
||||
checkAndUpdate(Schedule::MDECOUTDMA, g_emulator->m_mdec->scheduledCallback1);
|
||||
checkAndUpdate(Schedule::SPUDMA, spuInterrupt);
|
||||
checkAndUpdate(Schedule::MDECINDMA, g_emulator->m_mdec->scheduledCallback0);
|
||||
checkAndUpdate(Schedule::GPUOTCDMA, gpuotcInterrupt);
|
||||
checkAndUpdate(Schedule::CDRDMA, g_emulator->m_cdrom->scheduledDmaCallback);
|
||||
m_regs.lowestTarget = lowestTarget;
|
||||
}
|
||||
auto& mem = g_emulator->m_mem;
|
||||
@@ -413,6 +390,18 @@ void PCSX::R3000Acpu::branchTest() {
|
||||
}
|
||||
|
||||
PSXIRQ_LOG("Interrupt: %x %x\n", istat, imask);
|
||||
// A branch in the delay slot of a taken branch leaves its own jump pending here, and the next
|
||||
// instruction is that branch's delay slot. The interrupt is taken on that delay slot: EPC is the
|
||||
// branch with BD set, and the pending jump is dropped, since returning runs the branch again.
|
||||
for (auto& pending : m_delayedLoadInfo) {
|
||||
if (!pending.pcActive) continue;
|
||||
pending.pcActive = false;
|
||||
pending.fromLink = false;
|
||||
m_nextIsDelaySlot = false;
|
||||
m_regs.pc = pending.branchPC + 4;
|
||||
exception(0x400, true);
|
||||
return;
|
||||
}
|
||||
exception(0x400, 0);
|
||||
}
|
||||
}
|
||||
|
||||
+54
-48
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <map>
|
||||
@@ -162,22 +163,18 @@ typedef union {
|
||||
PAIR p[32];
|
||||
} psxCP2Ctrl;
|
||||
|
||||
enum {
|
||||
PSXINT_SIO = 0,
|
||||
PSXINT_SIO1,
|
||||
PSXINT_CDR,
|
||||
PSXINT_CDREAD,
|
||||
PSXINT_GPUDMA,
|
||||
PSXINT_MDECOUTDMA,
|
||||
PSXINT_SPUDMA,
|
||||
PSXINT_GPUBUSY,
|
||||
PSXINT_MDECINDMA,
|
||||
PSXINT_GPUOTCDMA,
|
||||
PSXINT_CDRDMA,
|
||||
PSXINT_SPUASYNC,
|
||||
PSXINT_CDRDBUF,
|
||||
PSXINT_CDRLID,
|
||||
PSXINT_CDRPLAY
|
||||
enum class Schedule : unsigned {
|
||||
SIO = 0,
|
||||
SIO1,
|
||||
CDRFIFO,
|
||||
CDRCOMMANDS,
|
||||
CDREAD,
|
||||
GPUDMA,
|
||||
MDECOUTDMA,
|
||||
SPUDMA,
|
||||
MDECINDMA,
|
||||
GPUOTCDMA,
|
||||
CDRDMA,
|
||||
};
|
||||
|
||||
struct psxRegisters {
|
||||
@@ -189,32 +186,24 @@ struct psxRegisters {
|
||||
uint32_t code; // The current instruction
|
||||
uint64_t cycle;
|
||||
uint64_t previousCycles;
|
||||
uint32_t interrupt;
|
||||
uint32_t scheduleMask;
|
||||
std::atomic<bool> spuInterrupt;
|
||||
uint64_t intTargets[32];
|
||||
uint64_t scheduleTargets[32];
|
||||
uint64_t lowestTarget;
|
||||
uint64_t muldivReady; // cycle at which hi/lo can be read without stalling
|
||||
uint64_t gteReady; // cycle at which the GTE can be accessed without stalling
|
||||
uint8_t iCacheAddr[0x1000];
|
||||
uint8_t iCacheCode[0x1000];
|
||||
uint32_t getFutureCycle(std::chrono::nanoseconds delay) const { return cycle + durationToCycles(delay); }
|
||||
std::chrono::nanoseconds getFutureTime(uint32_t futureCycle) const {
|
||||
return std::chrono::nanoseconds(int64_t(int32_t(futureCycle - cycle)) * 1'000'000'000 /
|
||||
Emulator::m_psxClockSpeed);
|
||||
}
|
||||
static constexpr uint32_t durationToCycles(std::chrono::nanoseconds duration) {
|
||||
return duration.count() * Emulator::m_psxClockSpeed / 1'000'000'000;
|
||||
}
|
||||
};
|
||||
|
||||
// U64 and S64 are used to wrap long integer constants.
|
||||
#define U64(val) val##ULL
|
||||
#define S64(val) val##LL
|
||||
|
||||
#if defined(__BIGENDIAN__)
|
||||
|
||||
#define _i32(x) reinterpret_cast<int32_t *>(&x)[0]
|
||||
#define _u32(x) reinterpret_cast<uint32_t *>(&x)[0]
|
||||
|
||||
#else
|
||||
|
||||
#define _i32(x) reinterpret_cast<int32_t *>(&x)[0]
|
||||
#define _u32(x) reinterpret_cast<uint32_t *>(&x)[0]
|
||||
|
||||
#endif
|
||||
|
||||
// R3000A Instruction Macros
|
||||
#define _PC_ PCSX::g_emulator->m_cpu->m_regs.pc // The next PC to be executed
|
||||
|
||||
@@ -300,21 +289,35 @@ class R3000Acpu {
|
||||
CoprocessorUnusable = 11,
|
||||
ArithmeticOverflow = 12,
|
||||
};
|
||||
void exception(Exception e, bool bd, bool cop0 = false) {
|
||||
exception(static_cast<std::underlying_type<Exception>::type>(e) << 2, bd, cop0);
|
||||
bool exception(Exception e, bool bd, bool cop0 = false) {
|
||||
return exception(static_cast<std::underlying_type<Exception>::type>(e) << 2, bd, cop0);
|
||||
}
|
||||
void exception(uint32_t code, bool bd, bool cop0 = false);
|
||||
bool exception(uint32_t code, bool bd, bool cop0 = false);
|
||||
void branchTest();
|
||||
|
||||
void psxSetPGXPMode(uint32_t pgxpMode);
|
||||
|
||||
void scheduleInterrupt(unsigned interrupt, uint32_t eCycle) {
|
||||
PSXIRQ_LOG("Scheduling interrupt %08x at %08x\n", interrupt, eCycle);
|
||||
void schedule(Schedule s_, uint32_t eCycle) {
|
||||
unsigned s = static_cast<unsigned>(s_);
|
||||
PSXIRQ_LOG("Scheduling callback %08x at %08x\n", s, eCycle);
|
||||
const uint64_t cycle = m_regs.cycle;
|
||||
uint64_t target = cycle + uint64_t(eCycle * m_interruptScales[interrupt]);
|
||||
m_regs.interrupt |= (1 << interrupt);
|
||||
m_regs.intTargets[interrupt] = target;
|
||||
if (target < m_regs.lowestTarget) m_regs.lowestTarget = target;
|
||||
uint64_t target = cycle + uint64_t(double(eCycle) * m_scheduleScales[s]);
|
||||
m_regs.scheduleMask |= (1 << s);
|
||||
m_regs.scheduleTargets[s] = target;
|
||||
int64_t lowest = m_regs.lowestTarget - cycle;
|
||||
int64_t maybeNewLowest = target - cycle;
|
||||
if (maybeNewLowest < lowest) m_regs.lowestTarget = target;
|
||||
}
|
||||
|
||||
void unschedule(Schedule s_) {
|
||||
unsigned s = static_cast<unsigned>(s_);
|
||||
PSXIRQ_LOG("Unscheduling callback %08x\n", s);
|
||||
m_regs.scheduleMask &= ~(1 << s);
|
||||
}
|
||||
|
||||
bool isScheduled(Schedule s_) {
|
||||
unsigned s = static_cast<unsigned>(s_);
|
||||
return (m_regs.scheduleMask & (1 << s)) != 0;
|
||||
}
|
||||
|
||||
// GTE command execution time, indexed by the command's function field.
|
||||
@@ -364,13 +367,13 @@ class R3000Acpu {
|
||||
void muldivCancel() { m_regs.muldivReady = 0; }
|
||||
|
||||
psxRegisters m_regs;
|
||||
float m_interruptScales[15] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
float m_scheduleScales[static_cast<unsigned>(Schedule::CDRDMA) + 1] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
bool m_shellStarted = false;
|
||||
|
||||
virtual void Reset() {
|
||||
invalidateCache();
|
||||
m_regs.interrupt = 0;
|
||||
m_regs.scheduleMask = 0;
|
||||
}
|
||||
bool m_inISR = false;
|
||||
bool m_nextIsDelaySlot = false;
|
||||
@@ -380,18 +383,20 @@ class R3000Acpu {
|
||||
uint32_t value = 0;
|
||||
uint32_t mask = 0;
|
||||
uint32_t pcValue = 0;
|
||||
uint32_t branchPC = 0;
|
||||
bool active = false;
|
||||
bool pcActive = false;
|
||||
bool fromLink = false;
|
||||
} m_delayedLoadInfo[2];
|
||||
unsigned m_currentDelayedLoad = 0;
|
||||
uint32_t &delayedLoadRef(unsigned reg, uint32_t mask = 0) {
|
||||
template <typename T = uint32_t>
|
||||
T &delayedLoadRef(unsigned reg, uint32_t mask = 0) {
|
||||
if (reg >= 32) abort();
|
||||
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
|
||||
delayedLoad.active = true;
|
||||
delayedLoad.index = reg;
|
||||
delayedLoad.mask = mask;
|
||||
return delayedLoad.value;
|
||||
return reinterpret_cast<T &>(delayedLoad.value);
|
||||
}
|
||||
void delayedLoad(unsigned reg, uint32_t value, uint32_t mask = 0) {
|
||||
auto &ref = delayedLoadRef(reg, mask);
|
||||
@@ -401,6 +406,7 @@ class R3000Acpu {
|
||||
auto &delayedLoad = m_delayedLoadInfo[m_currentDelayedLoad];
|
||||
delayedLoad.pcActive = true;
|
||||
delayedLoad.pcValue = value;
|
||||
delayedLoad.branchPC = m_regs.pc - 4;
|
||||
delayedLoad.fromLink = fromLink;
|
||||
}
|
||||
void flushCurrentDelayedLoad() {
|
||||
|
||||
+2
-2
@@ -308,7 +308,7 @@ void PCSX::SIO::writeCtrl16(uint16_t value) {
|
||||
m_bufferIndex = 0;
|
||||
m_regs.status = StatusFlags::TX_DATACLEAR | StatusFlags::TX_FINISHED;
|
||||
g_emulator->m_mem->writeHardwareRegister<0x1044>(m_regs.status);
|
||||
PCSX::g_emulator->m_cpu->m_regs.interrupt &= ~(1 << PCSX::PSXINT_SIO);
|
||||
g_emulator->m_cpu->unschedule(Schedule::SIO);
|
||||
m_currentDevice = DeviceType::None;
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ uint16_t PCSX::SIO::readStatus16() {
|
||||
return hard;
|
||||
}
|
||||
|
||||
void PCSX::SIO::interrupt() {
|
||||
void PCSX::SIO::scheduledCallback() {
|
||||
SIO0_LOG("Sio Interrupt (CP0.Status = %x)\n", PCSX::g_emulator->m_cpu->m_regs.CP0.n.Status);
|
||||
m_regs.status |= StatusFlags::IRQ;
|
||||
g_emulator->m_mem->writeHardwareRegister<0x1044>(m_regs.status);
|
||||
|
||||
+2
-2
@@ -93,7 +93,7 @@ class SIO {
|
||||
|
||||
void acknowledge();
|
||||
void init();
|
||||
void interrupt();
|
||||
void scheduledCallback();
|
||||
void reset();
|
||||
|
||||
bool copyMcdFile(McdBlock block);
|
||||
@@ -234,7 +234,7 @@ class SIO {
|
||||
bool isReceiveIRQReady();
|
||||
bool isTransmitReady();
|
||||
static inline void scheduleInterrupt(uint64_t eCycle) {
|
||||
g_emulator->m_cpu->scheduleInterrupt(PSXINT_SIO, eCycle);
|
||||
g_emulator->m_cpu->schedule(Schedule::SIO, eCycle);
|
||||
#if 0
|
||||
// Breaks Twisted Metal 2 intro
|
||||
m_statusReg &= ~RX_FIFONOTEMPTY;
|
||||
|
||||
+2
-2
@@ -127,7 +127,7 @@ void PCSX::SIO1::sio1StateMachine() {
|
||||
}
|
||||
}
|
||||
|
||||
void PCSX::SIO1::interrupt() {
|
||||
void PCSX::SIO1::scheduledCallback() {
|
||||
SIO1_LOG("SIO1 Interrupt (CP0.Status = %x)\n", PCSX::g_emulator->m_cpu->m_regs.CP0.n.Status);
|
||||
g_emulator->m_mem->setIRQ(IRQ8_SIO);
|
||||
m_regs.status |= SR_IRQ;
|
||||
@@ -282,7 +282,7 @@ void PCSX::SIO1::writeCtrl16(uint16_t v) {
|
||||
m_sio1fifo.asA<Fifo>()->reset();
|
||||
}
|
||||
|
||||
PCSX::g_emulator->m_cpu->m_regs.interrupt &= ~(1 << PCSX::PSXINT_SIO1);
|
||||
g_emulator->m_cpu->unschedule(Schedule::SIO1);
|
||||
}
|
||||
|
||||
if (!(m_regs.control & CR_RXEN)) {
|
||||
|
||||
+3
-3
@@ -70,7 +70,7 @@ class SIO1 {
|
||||
enum class SIO1Mode { Raw, Protobuf };
|
||||
SIO1Mode m_sio1Mode = SIO1Mode::Protobuf;
|
||||
SIO1Mode getSIO1Mode() { return m_sio1Mode; }
|
||||
void interrupt();
|
||||
void scheduledCallback();
|
||||
|
||||
void poll() {
|
||||
if (fifoError()) return;
|
||||
@@ -93,7 +93,7 @@ class SIO1 {
|
||||
m_decodeState = READ_SIZE;
|
||||
messageSize = 0;
|
||||
initialMessage = true;
|
||||
g_emulator->m_cpu->m_regs.interrupt &= ~(1 << PCSX::PSXINT_SIO1);
|
||||
g_emulator->m_cpu->unschedule(Schedule::SIO1);
|
||||
}
|
||||
|
||||
void stopSIO1Connection() {
|
||||
@@ -247,7 +247,7 @@ class SIO1 {
|
||||
|
||||
enum { READ_SIZE, READ_MESSAGE } m_decodeState = READ_SIZE;
|
||||
|
||||
inline void scheduleInterrupt(uint32_t eCycle) { g_emulator->m_cpu->scheduleInterrupt(PSXINT_SIO1, eCycle); }
|
||||
inline void scheduleInterrupt(uint32_t eCycle) { g_emulator->m_cpu->schedule(Schedule::SIO1, eCycle); }
|
||||
|
||||
void updateStat();
|
||||
void transmitData();
|
||||
|
||||
@@ -44,6 +44,8 @@ class SPUInterface {
|
||||
virtual void writeRegister(uint32_t, uint16_t) = 0;
|
||||
virtual void playCDDAchannel(int16_t *, int) = 0;
|
||||
virtual void playADPCMchannel(xa_decode_t *) = 0;
|
||||
// CD audio volume matrix (ATV0..ATV3) for XA, applied after the interpolator.
|
||||
virtual void setXAVolume(const uint8_t atv[4]) = 0;
|
||||
virtual void writeDMAMem(uint16_t *, int) = 0;
|
||||
virtual void readDMAMem(uint16_t *, int) = 0;
|
||||
virtual void lockSPURAM() = 0;
|
||||
|
||||
+155
-120
@@ -34,71 +34,71 @@
|
||||
#include "spu/interface.h"
|
||||
|
||||
PCSX::SaveStates::SaveState PCSX::SaveStates::constructSaveState() {
|
||||
// clang-format off
|
||||
return SaveState {
|
||||
SaveStateInfo {
|
||||
VersionString {},
|
||||
Version {},
|
||||
auto* cd = g_emulator->m_cdrom.get();
|
||||
return SaveState{
|
||||
SaveStateInfo{
|
||||
VersionString{},
|
||||
Version{},
|
||||
},
|
||||
Thumbnail {},
|
||||
Memory {
|
||||
RAM { g_emulator->m_mem->m_wram },
|
||||
ROM { g_emulator->m_mem->m_bios },
|
||||
EXP1 { g_emulator->m_mem->m_exp1 },
|
||||
HardwareMemory { g_emulator->m_mem->m_hard },
|
||||
SRAM { g_emulator->m_mem->m_sram },
|
||||
Thumbnail{},
|
||||
Memory{
|
||||
RAM{g_emulator->m_mem->m_wram},
|
||||
ROM{g_emulator->m_mem->m_bios},
|
||||
EXP1{g_emulator->m_mem->m_exp1},
|
||||
HardwareMemory{g_emulator->m_mem->m_hard},
|
||||
SRAM{g_emulator->m_mem->m_sram},
|
||||
},
|
||||
Registers {
|
||||
GPR { g_emulator->m_cpu->m_regs.GPR.r },
|
||||
CP0 { g_emulator->m_cpu->m_regs.CP0.r },
|
||||
CP2D { g_emulator->m_cpu->m_regs.CP2D.r },
|
||||
CP2C { g_emulator->m_cpu->m_regs.CP2C.r },
|
||||
PC { g_emulator->m_cpu->m_regs.pc },
|
||||
Code { g_emulator->m_cpu->m_regs.code },
|
||||
Cycle { g_emulator->m_cpu->m_regs.cycle },
|
||||
Interrupt { g_emulator->m_cpu->m_regs.interrupt },
|
||||
ICacheAddr { g_emulator->m_cpu->m_regs.iCacheAddr },
|
||||
ICacheCode { g_emulator->m_cpu->m_regs.iCacheCode },
|
||||
NextIsDelaySlot { g_emulator->m_cpu->m_nextIsDelaySlot },
|
||||
DelaySlotInfo1 {
|
||||
DelaySlotIndex { g_emulator->m_cpu->m_delayedLoadInfo[0].index },
|
||||
DelaySlotValue { g_emulator->m_cpu->m_delayedLoadInfo[0].value },
|
||||
DelaySlotMask { g_emulator->m_cpu->m_delayedLoadInfo[0].mask },
|
||||
DelaySlotPcValue { g_emulator->m_cpu->m_delayedLoadInfo[0].pcValue },
|
||||
DelaySlotActive { g_emulator->m_cpu->m_delayedLoadInfo[0].active },
|
||||
DelaySlotPcActive { g_emulator->m_cpu->m_delayedLoadInfo[0].pcActive },
|
||||
DelaySlotFromLink { g_emulator->m_cpu->m_delayedLoadInfo[0].fromLink }
|
||||
Registers{
|
||||
GPR{g_emulator->m_cpu->m_regs.GPR.r},
|
||||
CP0{g_emulator->m_cpu->m_regs.CP0.r},
|
||||
CP2D{g_emulator->m_cpu->m_regs.CP2D.r},
|
||||
CP2C{g_emulator->m_cpu->m_regs.CP2C.r},
|
||||
PC{g_emulator->m_cpu->m_regs.pc},
|
||||
Code{g_emulator->m_cpu->m_regs.code},
|
||||
Cycle{g_emulator->m_cpu->m_regs.cycle},
|
||||
ScheduleMask{g_emulator->m_cpu->m_regs.scheduleMask},
|
||||
ICacheAddr{g_emulator->m_cpu->m_regs.iCacheAddr},
|
||||
ICacheCode{g_emulator->m_cpu->m_regs.iCacheCode},
|
||||
NextIsDelaySlot{g_emulator->m_cpu->m_nextIsDelaySlot},
|
||||
DelaySlotInfo1{
|
||||
DelaySlotIndex{g_emulator->m_cpu->m_delayedLoadInfo[0].index},
|
||||
DelaySlotValue{g_emulator->m_cpu->m_delayedLoadInfo[0].value},
|
||||
DelaySlotMask{g_emulator->m_cpu->m_delayedLoadInfo[0].mask},
|
||||
DelaySlotPcValue{g_emulator->m_cpu->m_delayedLoadInfo[0].pcValue},
|
||||
DelaySlotActive{g_emulator->m_cpu->m_delayedLoadInfo[0].active},
|
||||
DelaySlotPcActive{g_emulator->m_cpu->m_delayedLoadInfo[0].pcActive},
|
||||
DelaySlotFromLink{g_emulator->m_cpu->m_delayedLoadInfo[0].fromLink},
|
||||
},
|
||||
DelaySlotInfo2 {
|
||||
DelaySlotIndex { g_emulator->m_cpu->m_delayedLoadInfo[1].index },
|
||||
DelaySlotValue { g_emulator->m_cpu->m_delayedLoadInfo[1].value },
|
||||
DelaySlotMask { g_emulator->m_cpu->m_delayedLoadInfo[1].mask },
|
||||
DelaySlotPcValue { g_emulator->m_cpu->m_delayedLoadInfo[1].pcValue },
|
||||
DelaySlotActive { g_emulator->m_cpu->m_delayedLoadInfo[1].active },
|
||||
DelaySlotPcActive { g_emulator->m_cpu->m_delayedLoadInfo[1].pcActive },
|
||||
DelaySlotFromLink { g_emulator->m_cpu->m_delayedLoadInfo[1].fromLink }
|
||||
DelaySlotInfo2{
|
||||
DelaySlotIndex{g_emulator->m_cpu->m_delayedLoadInfo[1].index},
|
||||
DelaySlotValue{g_emulator->m_cpu->m_delayedLoadInfo[1].value},
|
||||
DelaySlotMask{g_emulator->m_cpu->m_delayedLoadInfo[1].mask},
|
||||
DelaySlotPcValue{g_emulator->m_cpu->m_delayedLoadInfo[1].pcValue},
|
||||
DelaySlotActive{g_emulator->m_cpu->m_delayedLoadInfo[1].active},
|
||||
DelaySlotPcActive{g_emulator->m_cpu->m_delayedLoadInfo[1].pcActive},
|
||||
DelaySlotFromLink{g_emulator->m_cpu->m_delayedLoadInfo[1].fromLink},
|
||||
},
|
||||
CurrentDelayedLoad { g_emulator->m_cpu->m_currentDelayedLoad },
|
||||
IntTargetsField { g_emulator->m_cpu->m_regs.intTargets },
|
||||
InISR { g_emulator->m_cpu->m_inISR },
|
||||
MulDivReady { g_emulator->m_cpu->m_regs.muldivReady },
|
||||
GteReady { g_emulator->m_cpu->m_regs.gteReady },
|
||||
CurrentDelayedLoad{g_emulator->m_cpu->m_currentDelayedLoad},
|
||||
ScheduleTargetsField{g_emulator->m_cpu->m_regs.scheduleTargets},
|
||||
InISR{g_emulator->m_cpu->m_inISR},
|
||||
MulDivReady{g_emulator->m_cpu->m_regs.muldivReady},
|
||||
GteReady{g_emulator->m_cpu->m_regs.gteReady},
|
||||
},
|
||||
GPU {},
|
||||
SPU {},
|
||||
SIO {
|
||||
SIOBuffer { g_emulator->m_sio->m_buffer },
|
||||
SIOStatusReg { g_emulator->m_sio->m_regs.status },
|
||||
SIOModeReg { g_emulator->m_sio->m_regs.mode },
|
||||
SIOCtrlReg { g_emulator->m_sio->m_regs.control },
|
||||
SIOBaudReg { g_emulator->m_sio->m_regs.baud },
|
||||
SIOBufferMaxIndex { g_emulator->m_sio->m_maxBufferIndex },
|
||||
SIOBufferIndex { g_emulator->m_sio->m_bufferIndex },
|
||||
SIOPadState { g_emulator->m_sio->m_padState },
|
||||
SIOCurrentDevice { g_emulator->m_sio->m_currentDevice },
|
||||
SIOMCD1TempBuffer { g_emulator->m_sio->m_memoryCard[0].m_tempBuffer },
|
||||
SIOMCD1DirectoryFlag { g_emulator->m_sio->m_memoryCard[0].m_directoryFlag },
|
||||
SIOMCD1ChecksumIn{ g_emulator->m_sio->m_memoryCard[0].m_checksumIn},
|
||||
GPU{},
|
||||
SPU{},
|
||||
SIO{
|
||||
SIOBuffer{g_emulator->m_sio->m_buffer},
|
||||
SIOStatusReg{g_emulator->m_sio->m_regs.status},
|
||||
SIOModeReg{g_emulator->m_sio->m_regs.mode},
|
||||
SIOCtrlReg{g_emulator->m_sio->m_regs.control},
|
||||
SIOBaudReg{g_emulator->m_sio->m_regs.baud},
|
||||
SIOBufferMaxIndex{g_emulator->m_sio->m_maxBufferIndex},
|
||||
SIOBufferIndex{g_emulator->m_sio->m_bufferIndex},
|
||||
SIOPadState{g_emulator->m_sio->m_padState},
|
||||
SIOCurrentDevice{g_emulator->m_sio->m_currentDevice},
|
||||
SIOMCD1TempBuffer{g_emulator->m_sio->m_memoryCard[0].m_tempBuffer},
|
||||
SIOMCD1DirectoryFlag{g_emulator->m_sio->m_memoryCard[0].m_directoryFlag},
|
||||
SIOMCD1ChecksumIn{g_emulator->m_sio->m_memoryCard[0].m_checksumIn},
|
||||
SIOMCD1ChecksumOut{g_emulator->m_sio->m_memoryCard[0].m_checksumOut},
|
||||
SIOMCD1CommandTicks{g_emulator->m_sio->m_memoryCard[0].m_commandTicks},
|
||||
SIOMCD1CurrentCommand{g_emulator->m_sio->m_memoryCard[0].m_currentCommand},
|
||||
@@ -113,68 +113,97 @@ PCSX::SaveStates::SaveState PCSX::SaveStates::constructSaveState() {
|
||||
SIOMCD2Sector{g_emulator->m_sio->m_memoryCard[1].m_sector},
|
||||
SIOMCD2DataOffset{g_emulator->m_sio->m_memoryCard[1].m_dataOffset},
|
||||
},
|
||||
CDRom {
|
||||
CDReg1Mode { g_emulator->m_cdrom->m_reg1Mode },
|
||||
CDReg2 { g_emulator->m_cdrom->m_reg2 },
|
||||
CDCmdProcess { g_emulator->m_cdrom->m_cmdProcess },
|
||||
CDCtrl { g_emulator->m_cdrom->m_ctrl },
|
||||
CDStat { g_emulator->m_cdrom->m_stat },
|
||||
CDStatP { g_emulator->m_cdrom->m_statP },
|
||||
CDTransfer { reinterpret_cast<uint8_t*>(g_emulator->m_cdrom->m_transfer) },
|
||||
CDTransferIndex { g_emulator->m_cdrom->m_transferIndex },
|
||||
CDPrev { g_emulator->m_cdrom->m_prev.data },
|
||||
CDParam { g_emulator->m_cdrom->m_param },
|
||||
CDResult { g_emulator->m_cdrom->m_result },
|
||||
CDParamC { g_emulator->m_cdrom->m_paramC },
|
||||
CDResultC { g_emulator->m_cdrom->m_resultC },
|
||||
CDResultP { g_emulator->m_cdrom->m_resultP },
|
||||
CDResultReady { g_emulator->m_cdrom->m_resultReady },
|
||||
CDCmd { g_emulator->m_cdrom->m_cmd },
|
||||
CDRead { g_emulator->m_cdrom->m_read },
|
||||
CDSetLocPending { g_emulator->m_cdrom->m_setlocPending },
|
||||
CDReading { g_emulator->m_cdrom->m_reading },
|
||||
CDSetSectorPlay { g_emulator->m_cdrom->m_setSectorPlay.data },
|
||||
CDSetSectorEnd { g_emulator->m_cdrom->m_setSectorEnd.data },
|
||||
CDSetSector { g_emulator->m_cdrom->m_setSector.data },
|
||||
CDTrack { g_emulator->m_cdrom->m_track },
|
||||
CDPlay { g_emulator->m_cdrom->m_play },
|
||||
CDMuted { g_emulator->m_cdrom->m_muted },
|
||||
CDCurTrack { g_emulator->m_cdrom->m_curTrack },
|
||||
CDMode { g_emulator->m_cdrom->m_mode },
|
||||
CDFile { g_emulator->m_cdrom->m_file },
|
||||
CDChannel { g_emulator->m_cdrom->m_channel },
|
||||
CDSuceeded { g_emulator->m_cdrom->m_suceeded },
|
||||
CDFirstSector { g_emulator->m_cdrom->m_firstSector },
|
||||
CDIRQ { g_emulator->m_cdrom->m_irq },
|
||||
CDIrqRepeated { g_emulator->m_cdrom->m_irqRepeated },
|
||||
CDECycle { g_emulator->m_cdrom->m_eCycle },
|
||||
CDSeeked { g_emulator->m_cdrom->m_seeked },
|
||||
CDReadRescheduled { g_emulator->m_cdrom->m_readRescheduled },
|
||||
CDDriveState { g_emulator->m_cdrom->m_driveState },
|
||||
CDFastForward { g_emulator->m_cdrom->m_fastForward },
|
||||
CDFastBackward { g_emulator->m_cdrom->m_fastBackward },
|
||||
CDAttenuatorLeftToLeft { g_emulator->m_cdrom->m_attenuatorLeftToLeft },
|
||||
CDAttenuatorLeftToRight { g_emulator->m_cdrom->m_attenuatorLeftToRight },
|
||||
CDAttenuatorRightToRight { g_emulator->m_cdrom->m_attenuatorRightToRight },
|
||||
CDAttenuatorRightToLeft { g_emulator->m_cdrom->m_attenuatorRightToLeft },
|
||||
CDAttenuatorLeftToLeftT { g_emulator->m_cdrom->m_attenuatorLeftToLeftT },
|
||||
CDAttenuatorLeftToRightT { g_emulator->m_cdrom->m_attenuatorLeftToRightT },
|
||||
CDAttenuatorRightToRightT { g_emulator->m_cdrom->m_attenuatorRightToRightT },
|
||||
CDAttenuatorRightToLeftT { g_emulator->m_cdrom->m_attenuatorRightToLeftT },
|
||||
CDSubQTrack { g_emulator->m_cdrom->m_subq.track },
|
||||
CDSubQIndex { g_emulator->m_cdrom->m_subq.index },
|
||||
CDSubQRelative { g_emulator->m_cdrom->m_subq.relative },
|
||||
CDSubQAbsolute { g_emulator->m_cdrom->m_subq.absolute },
|
||||
CDTrackChanged { g_emulator->m_cdrom->m_trackChanged },
|
||||
CDLocationChanged { g_emulator->m_cdrom->m_locationChanged },
|
||||
Hardware{},
|
||||
Counters{},
|
||||
MDEC{},
|
||||
PCdrvFilesField{},
|
||||
CallStacks{},
|
||||
CDRom{
|
||||
CDDataFIFO{cd->m_dataFIFO},
|
||||
CDDataFIFOIndex{cd->m_dataFIFOIndex},
|
||||
CDDataFIFOSize{cd->m_dataFIFOSize},
|
||||
CDDataFIFOPending{cd->m_dataFIFOPending},
|
||||
CDRegisterAddress{cd->m_registerAddress},
|
||||
CDMotorOn{cd->m_motorOn},
|
||||
CDSpeedChanged{cd->m_speedChanged},
|
||||
CDInvalidLocL{cd->m_invalidLocL},
|
||||
CDDataRequested{cd->m_dataRequested},
|
||||
CDSubheaderFilter{cd->m_subheaderFilter},
|
||||
CDRealtime{cd->m_realtime},
|
||||
CDReadingState{reinterpret_cast<uint8_t&>(cd->m_readingState)},
|
||||
CDStartPlaying{cd->m_startPlaying},
|
||||
CDReadingType{reinterpret_cast<uint8_t&>(cd->m_readingType)},
|
||||
CDAutoPause{cd->m_autoPause},
|
||||
CDReport{cd->m_report},
|
||||
CDSetLocPending{cd->m_setLocPending},
|
||||
CDMuted{cd->m_muted},
|
||||
CDPeakFlag{cd->m_peakFlag},
|
||||
CDPlayTrack{cd->m_playTrack},
|
||||
CDPlayStartCycle{cd->m_playStartCycle},
|
||||
CDStatus{reinterpret_cast<uint8_t&>(cd->m_status)},
|
||||
CDSpeed{reinterpret_cast<uint8_t&>(cd->m_speed)},
|
||||
CDReadSpan{reinterpret_cast<uint8_t&>(cd->m_readSpan)},
|
||||
CDInterruptCauseMask{cd->m_interruptCauseMask},
|
||||
CDATV{cd->m_atv},
|
||||
CDATVPending{cd->m_atvPending},
|
||||
CDADPCMMuted{cd->m_adpcmMuted},
|
||||
CDMode{cd->m_mode},
|
||||
CDFilterFile{cd->m_filterFile},
|
||||
CDFilterChannel{cd->m_filterChannel},
|
||||
CDXAEnded{cd->m_xaEnded},
|
||||
CDXAFirstSector{cd->m_xaFirstSector},
|
||||
CDSoundMapEnabled{cd->m_soundMapEnabled},
|
||||
CDCurrentPosition{CDMSF{CDMSFMinute{cd->m_currentPosition.m}, CDMSFSecond{cd->m_currentPosition.s},
|
||||
CDMSFFrame{cd->m_currentPosition.f}}},
|
||||
CDSeekPosition{CDMSF{CDMSFMinute{cd->m_seekPosition.m}, CDMSFSecond{cd->m_seekPosition.s},
|
||||
CDMSFFrame{cd->m_seekPosition.f}}},
|
||||
CDLastLocP{cd->m_lastLocP},
|
||||
CDLastLocL{cd->m_lastLocL},
|
||||
CDSeed{cd->m_seed},
|
||||
CDLidOpen{cd->m_lidOpen},
|
||||
CDWasLidOpened{cd->m_wasLidOpened},
|
||||
CDLidCloseScheduled{cd->m_lidCloseScheduled},
|
||||
CDLidCloseAtCycles{cd->m_lidCloseAtCycles},
|
||||
CDCommandFifo{CDQueueElement{
|
||||
CDQueuePayload{cd->m_commandFifo.payload},
|
||||
CDQueueValue{cd->m_commandFifo.value},
|
||||
CDQueueValueRead{cd->m_commandFifo.valueRead},
|
||||
CDQueueHasValue{cd->m_commandFifo.hasValue},
|
||||
CDQueueHitMax{cd->m_commandFifo.hitMax},
|
||||
CDQueuePayloadSize{cd->m_commandFifo.payloadSize},
|
||||
CDQueuePayloadIndex{cd->m_commandFifo.payloadIndex},
|
||||
}},
|
||||
CDCommandExecuting{CDQueueElement{
|
||||
CDQueuePayload{cd->m_commandExecuting.payload},
|
||||
CDQueueValue{cd->m_commandExecuting.value},
|
||||
CDQueueValueRead{cd->m_commandExecuting.valueRead},
|
||||
CDQueueHasValue{cd->m_commandExecuting.hasValue},
|
||||
CDQueueHitMax{cd->m_commandExecuting.hitMax},
|
||||
CDQueuePayloadSize{cd->m_commandExecuting.payloadSize},
|
||||
CDQueuePayloadIndex{cd->m_commandExecuting.payloadIndex},
|
||||
}},
|
||||
CDResponseFifo0{CDQueueElement{
|
||||
CDQueuePayload{cd->m_responseFifo[0].payload},
|
||||
CDQueueValue{cd->m_responseFifo[0].value},
|
||||
CDQueueValueRead{cd->m_responseFifo[0].valueRead},
|
||||
CDQueueHasValue{cd->m_responseFifo[0].hasValue},
|
||||
CDQueueHitMax{cd->m_responseFifo[0].hitMax},
|
||||
CDQueuePayloadSize{cd->m_responseFifo[0].payloadSize},
|
||||
CDQueuePayloadIndex{cd->m_responseFifo[0].payloadIndex},
|
||||
}},
|
||||
CDResponseFifo1{CDQueueElement{
|
||||
CDQueuePayload{cd->m_responseFifo[1].payload},
|
||||
CDQueueValue{cd->m_responseFifo[1].value},
|
||||
CDQueueValueRead{cd->m_responseFifo[1].valueRead},
|
||||
CDQueueHasValue{cd->m_responseFifo[1].hasValue},
|
||||
CDQueueHitMax{cd->m_responseFifo[1].hitMax},
|
||||
CDQueuePayloadSize{cd->m_responseFifo[1].payloadSize},
|
||||
CDQueuePayloadIndex{cd->m_responseFifo[1].payloadIndex},
|
||||
}},
|
||||
CDXALeft{CDADPCM{CDADPCMY0{cd->m_xa.left.y0}, CDADPCMY1{cd->m_xa.left.y1}}},
|
||||
CDXARight{CDADPCM{CDADPCMY0{cd->m_xa.right.y0}, CDADPCMY1{cd->m_xa.right.y1}}},
|
||||
},
|
||||
Hardware {},
|
||||
Counters {},
|
||||
MDEC {},
|
||||
PCdrvFilesField {},
|
||||
CallStacks {},
|
||||
};
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
namespace PCSX {
|
||||
@@ -389,13 +418,16 @@ bool PCSX::SaveStates::load(std::string_view data) {
|
||||
g_emulator->m_cpu->m_regs.pc &= ~3;
|
||||
g_emulator->m_gpu->deserialize(&wrapper);
|
||||
g_emulator->m_spu->load(state.get<SPUField>());
|
||||
#if 0
|
||||
g_emulator->m_cdrom->load();
|
||||
#endif
|
||||
|
||||
g_emulator->m_counters->deserialize(&wrapper);
|
||||
g_emulator->m_mdec->deserialize(&wrapper);
|
||||
|
||||
auto& xa = state.get<SPUField>().get<SaveStates::XAField>();
|
||||
|
||||
#if 0
|
||||
g_emulator->m_cdrom->m_xa.freq = xa.get<SaveStates::XAFrequency>().value;
|
||||
g_emulator->m_cdrom->m_xa.nbits = xa.get<SaveStates::XANBits>().value;
|
||||
g_emulator->m_cdrom->m_xa.nsamples = xa.get<SaveStates::XANSamples>().value;
|
||||
@@ -408,6 +440,7 @@ bool PCSX::SaveStates::load(std::string_view data) {
|
||||
g_emulator->m_cdrom->m_xa.right.y1 = right.get<SaveStates::ADPCMDecodeY1>().value;
|
||||
xa.get<SaveStates::XAPCM>().copyTo(reinterpret_cast<uint8_t*>(g_emulator->m_cdrom->m_xa.pcm));
|
||||
g_emulator->m_spu->playADPCMchannel(&g_emulator->m_cdrom->m_xa);
|
||||
#endif
|
||||
|
||||
g_emulator->m_cpu->closeAllPCdrvFiles();
|
||||
for (auto& file : state.get<PCdrvFilesField>().value) {
|
||||
@@ -500,6 +533,8 @@ void PCSX::GPU::deserialize(const SaveStateWrapper* w) {
|
||||
writeStatus(m_statusControl[7]);
|
||||
writeStatus(m_statusControl[5]);
|
||||
writeStatus(m_statusControl[4]);
|
||||
// The replayed GP1(00h)/GP1(02h) above clear the IRQ1 flag; put back the saved one.
|
||||
m_irq1Flag = gpu.get<GPUStatus>().value & 0x01000000;
|
||||
}
|
||||
|
||||
void PCSX::MDEC::deserialize(const SaveStateWrapper* w) {
|
||||
|
||||
+89
-77
@@ -57,7 +57,7 @@ typedef Protobuf::RepeatedFieldRef<Protobuf::UInt32, 32, TYPESTRING("cp2c"), 4>
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("pc"), 5> PC;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("code"), 6> Code;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt64, TYPESTRING("cycle"), 7> Cycle;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("interrupt"), 8> Interrupt;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("schedule_mask"), 8> ScheduleMask;
|
||||
// skip id 9
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<0x1000>, TYPESTRING("icache_addr"), 10> ICacheAddr;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<0x1000>, TYPESTRING("icache_code"), 11> ICacheCode;
|
||||
@@ -76,13 +76,13 @@ typedef Protobuf::Message<TYPESTRING("DelaySlotInfo"), DelaySlotIndex, DelaySlot
|
||||
typedef Protobuf::MessageField<DelaySlotInfo, TYPESTRING("delay_slot_info_1"), 14> DelaySlotInfo1;
|
||||
typedef Protobuf::MessageField<DelaySlotInfo, TYPESTRING("delay_slot_info_2"), 15> DelaySlotInfo2;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("current_delayed_load"), 16> CurrentDelayedLoad;
|
||||
typedef Protobuf::RepeatedFieldRef<Protobuf::UInt64, 32, TYPESTRING("interrupt_targets"), 17> IntTargetsField;
|
||||
typedef Protobuf::RepeatedFieldRef<Protobuf::UInt64, 32, TYPESTRING("schedule_targets"), 17> ScheduleTargetsField;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("in_isr"), 18> InISR;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt64, TYPESTRING("muldiv_ready"), 19> MulDivReady;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt64, TYPESTRING("gte_ready"), 20> GteReady;
|
||||
typedef Protobuf::Message<TYPESTRING("Registers"), GPR, CP0, CP2D, CP2C, PC, Code, Cycle, Interrupt, ICacheAddr,
|
||||
typedef Protobuf::Message<TYPESTRING("Registers"), GPR, CP0, CP2D, CP2C, PC, Code, Cycle, ScheduleMask, ICacheAddr,
|
||||
ICacheCode, NextIsDelaySlot, DelaySlotInfo1, DelaySlotInfo2, CurrentDelayedLoad,
|
||||
IntTargetsField, InISR, MulDivReady, GteReady>
|
||||
ScheduleTargetsField, InISR, MulDivReady, GteReady>
|
||||
Registers;
|
||||
typedef Protobuf::MessageField<Registers, TYPESTRING("registers"), 4> RegistersField;
|
||||
|
||||
@@ -182,76 +182,7 @@ typedef Protobuf::Message<TYPESTRING("SIO"), SIOBuffer, SIOStatusReg, SIOModeReg
|
||||
SIOMCD2CurrentCommand, SIOMCD2Sector, SIOMCD2DataOffset>
|
||||
SIO;
|
||||
typedef Protobuf::MessageField<SIO, TYPESTRING("sio"), 7> SIOField;
|
||||
|
||||
// skip id 1
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("reg1_mode"), 2> CDReg1Mode;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("reg2"), 3> CDReg2;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("cmd_process"), 4> CDCmdProcess;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("ctrl"), 5> CDCtrl;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("stat"), 6> CDStat;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("stat_p"), 7> CDStatP;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<2352>, TYPESTRING("transfer"), 8> CDTransfer;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("transfer_index"), 9> CDTransferIndex;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("prev"), 10> CDPrev;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<8>, TYPESTRING("param"), 11> CDParam;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<16>, TYPESTRING("result"), 12> CDResult;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("param_c"), 13> CDParamC;
|
||||
// skip id 14
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("result_c"), 15> CDResultC;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("result_p"), 16> CDResultP;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("result_ready"), 17> CDResultReady;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("cmd"), 18> CDCmd;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("read"), 19> CDRead;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("set_loc_pending"), 20> CDSetLocPending;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("reading"), 21> CDReading;
|
||||
// skip id 22
|
||||
// skip id 23
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("set_sector_play"), 24> CDSetSectorPlay;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("set_sector_end"), 25> CDSetSectorEnd;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("set_sector"), 26> CDSetSector;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("track"), 27> CDTrack;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("play"), 28> CDPlay;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("muted"), 29> CDMuted;
|
||||
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("cur_track"), 30> CDCurTrack;
|
||||
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("mode"), 31> CDMode;
|
||||
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("file"), 32> CDFile;
|
||||
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("channel"), 33> CDChannel;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("suceeded"), 34> CDSuceeded;
|
||||
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("first_sector"), 35> CDFirstSector;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt16, TYPESTRING("irq"), 36> CDIRQ;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("irq_repeated"), 37> CDIrqRepeated;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("e_cycle"), 38> CDECycle;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("seeked"), 39> CDSeeked;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("read_scheduled"), 40> CDReadRescheduled;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("drive_state"), 41> CDDriveState;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("fast_forward"), 42> CDFastForward;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("fast_backward"), 43> CDFastBackward;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_left_to_left"), 44> CDAttenuatorLeftToLeft;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_left_to_right"), 45> CDAttenuatorLeftToRight;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_right_to_right"), 46> CDAttenuatorRightToRight;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_right_to_left"), 47> CDAttenuatorRightToLeft;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_left_to_left_t"), 48> CDAttenuatorLeftToLeftT;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_left_to_right_t"), 49> CDAttenuatorLeftToRightT;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_right_to_right_t"), 50> CDAttenuatorRightToRightT;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("attenuator_right_to_left_t"), 51> CDAttenuatorRightToLeftT;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("subq_track"), 52> CDSubQTrack;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("subq_index"), 53> CDSubQIndex;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<3>, TYPESTRING("subq_relative"), 54> CDSubQRelative;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<3>, TYPESTRING("subq_absolute"), 55> CDSubQAbsolute;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("track_changed"), 56> CDTrackChanged;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("location_changed"), 57> CDLocationChanged;
|
||||
|
||||
typedef Protobuf::Message<
|
||||
TYPESTRING("CDRom"), CDReg1Mode, CDReg2, CDCmdProcess, CDCtrl, CDStat, CDStatP, CDTransfer, CDTransferIndex, CDPrev,
|
||||
CDParam, CDResult, CDParamC, CDResultC, CDResultP, CDResultReady, CDCmd, CDRead, CDSetLocPending, CDReading,
|
||||
CDSetSectorPlay, CDSetSectorEnd, CDSetSector, CDTrack, CDPlay, CDMuted, CDCurTrack, CDMode, CDFile, CDChannel,
|
||||
CDSuceeded, CDFirstSector, CDIRQ, CDIrqRepeated, CDECycle, CDSeeked, CDReadRescheduled, CDDriveState, CDFastForward,
|
||||
CDFastBackward, CDAttenuatorLeftToLeft, CDAttenuatorLeftToRight, CDAttenuatorRightToRight, CDAttenuatorRightToLeft,
|
||||
CDAttenuatorLeftToLeftT, CDAttenuatorLeftToRightT, CDAttenuatorRightToRightT, CDAttenuatorRightToLeftT, CDSubQTrack,
|
||||
CDSubQIndex, CDSubQRelative, CDSubQAbsolute, CDTrackChanged, CDLocationChanged>
|
||||
CDRom;
|
||||
typedef Protobuf::MessageField<CDRom, TYPESTRING("cdrom"), 8> CDRomField;
|
||||
|
||||
// skip id 8
|
||||
typedef Protobuf::EmptyMessage<TYPESTRING("Hardware")> Hardware;
|
||||
typedef Protobuf::MessageField<Hardware, TYPESTRING("hardware"), 9> HardwareField;
|
||||
|
||||
@@ -313,6 +244,7 @@ typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("sp"), 2> CallSP;
|
||||
typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("fp"), 3> CallFP;
|
||||
typedef Protobuf::Field<Protobuf::Bool, TYPESTRING("shadow"), 4> Shadow;
|
||||
typedef Protobuf::Message<TYPESTRING("Call"), CallRA, CallSP, CallFP, Shadow> Call;
|
||||
|
||||
typedef Protobuf::RepeatedVariableField<Call, TYPESTRING("calls"), 1> Calls;
|
||||
typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("low"), 2> LowSP;
|
||||
typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("high"), 3> HighSP;
|
||||
@@ -321,19 +253,99 @@ typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("fp"), 5> PresumedFP;
|
||||
typedef Protobuf::Field<Protobuf::Bool, TYPESTRING("iscurrent"), 6> CallstackIsCurrent;
|
||||
typedef Protobuf::Message<TYPESTRING("Calls"), Calls, LowSP, HighSP, PresumedRA, PresumedFP, CallstackIsCurrent>
|
||||
CallStack;
|
||||
|
||||
typedef Protobuf::RepeatedVariableField<CallStack, TYPESTRING("CallStacks"), 1> CallStacksMessageField;
|
||||
typedef Protobuf::Field<Protobuf::UInt32, TYPESTRING("currentSP"), 2> CallStacksCurrentSP;
|
||||
typedef Protobuf::Message<TYPESTRING("CallStacks"), CallStacksMessageField, CallStacksCurrentSP> CallStacks;
|
||||
typedef Protobuf::MessageField<CallStacks, TYPESTRING("callstacks"), 14> CallStacksField;
|
||||
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<16>, TYPESTRING("payload"), 1> CDQueuePayload;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("value"), 2> CDQueueValue;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("value_read"), 3> CDQueueValueRead;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("has_value"), 4> CDQueueHasValue;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("hit_max"), 5> CDQueueHitMax;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("payload_size"), 6> CDQueuePayloadSize;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("payload_index"), 7> CDQueuePayloadIndex;
|
||||
typedef Protobuf::Message<TYPESTRING("CDQueueElement"), CDQueuePayload, CDQueueValue, CDQueueValueRead, CDQueueHasValue,
|
||||
CDQueueHitMax, CDQueuePayloadSize, CDQueuePayloadIndex>
|
||||
CDQueueElement;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("m"), 1> CDMSFMinute;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("s"), 2> CDMSFSecond;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("f"), 3> CDMSFFrame;
|
||||
typedef Protobuf::Message<TYPESTRING("CDMSF"), CDMSFMinute, CDMSFSecond, CDMSFFrame> CDMSF;
|
||||
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("y0"), 1> CDADPCMY0;
|
||||
typedef Protobuf::FieldRef<Protobuf::Int32, TYPESTRING("y1"), 2> CDADPCMY1;
|
||||
typedef Protobuf::Message<TYPESTRING("CDADPCM"), CDADPCMY0, CDADPCMY1> CDADPCM;
|
||||
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<2352>, TYPESTRING("dataFIFO"), 1> CDDataFIFO;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("dataFIFOIndex"), 2> CDDataFIFOIndex;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("dataFIFOSize"), 3> CDDataFIFOSize;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("dataFIFOPending"), 4> CDDataFIFOPending;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("registerAddress"), 5> CDRegisterAddress;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("motorOn"), 6> CDMotorOn;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("speedChanged"), 7> CDSpeedChanged;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("invalidLocL"), 8> CDInvalidLocL;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("dataRequested"), 9> CDDataRequested;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("subheaderFilter"), 10> CDSubheaderFilter;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("realtime"), 11> CDRealtime;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("readingState"), 12> CDReadingState;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("startPlaying"), 13> CDStartPlaying;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("readingType"), 14> CDReadingType;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("autoPause"), 15> CDAutoPause;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("report"), 16> CDReport;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("setLocPending"), 17> CDSetLocPending;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("muted"), 18> CDMuted;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("peakFlag"), 19> CDPeakFlag;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("playTrack"), 20> CDPlayTrack;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("playStartCycle"), 21> CDPlayStartCycle;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("status"), 22> CDStatus;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("speed"), 23> CDSpeed;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("readSpan"), 24> CDReadSpan;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("interruptCauseMask"), 25> CDInterruptCauseMask;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("atv"), 26> CDATV;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<4>, TYPESTRING("atvPending"), 27> CDATVPending;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("adpcmMuted"), 28> CDADPCMMuted;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("mode"), 29> CDMode;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("filterFile"), 30> CDFilterFile;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt8, TYPESTRING("filterChannel"), 31> CDFilterChannel;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("xaEnded"), 32> CDXAEnded;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("xaFirstSector"), 33> CDXAFirstSector;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("soundMapEnabled"), 34> CDSoundMapEnabled;
|
||||
typedef Protobuf::MessageField<CDMSF, TYPESTRING("currentPosition"), 35> CDCurrentPosition;
|
||||
typedef Protobuf::MessageField<CDMSF, TYPESTRING("seekPosition"), 36> CDSeekPosition;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<8>, TYPESTRING("lastLocP"), 37> CDLastLocP;
|
||||
typedef Protobuf::FieldPtr<Protobuf::FixedBytes<8>, TYPESTRING("lastLocL"), 38> CDLastLocL;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt64, TYPESTRING("seed"), 39> CDSeed;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("lidOpen"), 40> CDLidOpen;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("wasLidOpened"), 41> CDWasLidOpened;
|
||||
typedef Protobuf::FieldRef<Protobuf::Bool, TYPESTRING("lidCloseScheduled"), 42> CDLidCloseScheduled;
|
||||
typedef Protobuf::FieldRef<Protobuf::UInt32, TYPESTRING("lidCloseAtCycles"), 43> CDLidCloseAtCycles;
|
||||
typedef Protobuf::MessageField<CDQueueElement, TYPESTRING("commandFifo"), 44> CDCommandFifo;
|
||||
typedef Protobuf::MessageField<CDQueueElement, TYPESTRING("commandExecuting"), 45> CDCommandExecuting;
|
||||
typedef Protobuf::MessageField<CDQueueElement, TYPESTRING("responseFifo0"), 46> CDResponseFifo0;
|
||||
typedef Protobuf::MessageField<CDQueueElement, TYPESTRING("responseFifo1"), 47> CDResponseFifo1;
|
||||
typedef Protobuf::MessageField<CDADPCM, TYPESTRING("xaLeft"), 48> CDXALeft;
|
||||
typedef Protobuf::MessageField<CDADPCM, TYPESTRING("xaRight"), 49> CDXARight;
|
||||
typedef Protobuf::Message<
|
||||
TYPESTRING("CDRom"), CDDataFIFO, CDDataFIFOIndex, CDDataFIFOSize, CDDataFIFOPending, CDRegisterAddress, CDMotorOn,
|
||||
CDSpeedChanged, CDInvalidLocL, CDDataRequested, CDSubheaderFilter, CDRealtime, CDReadingState, CDStartPlaying,
|
||||
CDReadingType, CDAutoPause, CDReport, CDSetLocPending, CDMuted, CDPeakFlag, CDPlayTrack, CDPlayStartCycle, CDStatus,
|
||||
CDSpeed, CDReadSpan, CDInterruptCauseMask, CDATV, CDATVPending, CDADPCMMuted, CDMode, CDFilterFile, CDFilterChannel,
|
||||
CDXAEnded, CDXAFirstSector, CDSoundMapEnabled, CDCurrentPosition, CDSeekPosition, CDLastLocP, CDLastLocL, CDSeed,
|
||||
CDLidOpen, CDWasLidOpened, CDLidCloseScheduled, CDLidCloseAtCycles, CDCommandFifo, CDCommandExecuting,
|
||||
CDResponseFifo0, CDResponseFifo1, CDXALeft, CDXARight>
|
||||
CDRom;
|
||||
typedef Protobuf::MessageField<CDRom, TYPESTRING("cdrom"), 15> CDRomField;
|
||||
|
||||
typedef Protobuf::Message<TYPESTRING("SaveState"), SaveStateInfoField, ThumbnailField, MemoryField, RegistersField,
|
||||
GPUField, SPUField, SIOField, CDRomField, HardwareField, CountersField, MDECField,
|
||||
PCdrvFilesField, CallStacksField>
|
||||
GPUField, SPUField, SIOField, HardwareField, CountersField, MDECField, PCdrvFilesField,
|
||||
CallStacksField, CDRomField>
|
||||
SaveState;
|
||||
|
||||
typedef Protobuf::ProtoFile<SaveStateInfo, Thumbnail, Memory, DelaySlotInfo, Registers, GPU, ADPCMDecode, XA,
|
||||
::PCSX::SPU::Chan::Data, ::PCSX::SPU::ADSRInfo, ::PCSX::SPU::ADSRInfoEx, Channel, SPU, SIO,
|
||||
CDRom, Hardware, Rcnt, Counters, MDEC, PCdrvFile, Call, CallStack, CallStacks, SaveState>
|
||||
CDQueueElement, CDMSF, CDADPCM, CDRom, Hardware, Rcnt, Counters, MDEC, PCdrvFile, Call,
|
||||
CallStack, CallStacks, SaveState>
|
||||
ProtoFile;
|
||||
|
||||
SaveState constructSaveState();
|
||||
|
||||
+8
-4
@@ -584,17 +584,21 @@ void PCSX::SoftGPU::impl::write0(BlitVramVram *prim) {
|
||||
imageSX = prim->w;
|
||||
imageSY = prim->h;
|
||||
|
||||
if ((imageX0 == imageX1) && (imageY0 == imageY1)) return;
|
||||
// An in-place copy still rewrites every pixel, so it is only a no-op without set-mask.
|
||||
if ((imageX0 == imageX1) && (imageY0 == imageY1) && !getSetMask16()) return;
|
||||
if (imageSX <= 0) return;
|
||||
if (imageSY <= 0) return;
|
||||
|
||||
// The copy honours the GP0(E6h) mask settings, per halfword. The masked case takes
|
||||
// this per-pixel path, which walks in the same order as the fast paths below.
|
||||
if ((imageY0 + imageSY) > VRAM_HEIGHT || (imageX0 + imageSX) > 1024 || (imageY1 + imageSY) > VRAM_HEIGHT ||
|
||||
(imageX1 + imageSX) > 1024) {
|
||||
(imageX1 + imageSX) > 1024 || getCheckMask() || getSetMask16()) {
|
||||
int i, j;
|
||||
for (j = 0; j < imageSY; j++) {
|
||||
for (i = 0; i < imageSX; i++) {
|
||||
m_vram16[(1024 * ((imageY1 + j) & VRAM_Y_MASK)) + ((imageX1 + i) & VRAM_X_MASK)] =
|
||||
m_vram16[(1024 * ((imageY0 + j) & VRAM_Y_MASK)) + ((imageX0 + i) & VRAM_X_MASK)];
|
||||
uint16_t &dst = m_vram16[(1024 * ((imageY1 + j) & VRAM_Y_MASK)) + ((imageX1 + i) & VRAM_X_MASK)];
|
||||
if (getCheckMask() && (dst & 0x8000)) continue;
|
||||
dst = m_vram16[(1024 * ((imageY0 + j) & VRAM_Y_MASK)) + ((imageX0 + i) & VRAM_X_MASK)] | getSetMask16();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,9 @@ struct SoftRenderer {
|
||||
void drawingAreaEnd(GPU::DrawingAreaEnd *prim);
|
||||
void drawingOffset(GPU::DrawingOffset *prim);
|
||||
void maskBit(GPU::MaskBit *prim);
|
||||
// GP0(E6h) state, for the VRAM transfers that honour it outside the rasterizer.
|
||||
bool getCheckMask() const { return m_checkMask; }
|
||||
uint16_t getSetMask16() const { return m_setMask16; }
|
||||
|
||||
bool checkCoord4(int16_t &x0, int16_t &y0, int16_t &x1, int16_t &y1, int16_t &x2, int16_t &y2, int16_t &x3,
|
||||
int16_t &y3);
|
||||
|
||||
+18
-21
@@ -568,7 +568,7 @@ void PCSX::GUI::init(std::function<void()> applyArguments) {
|
||||
});
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
||||
m_hasCoreProfile = true;
|
||||
@@ -582,12 +582,12 @@ void PCSX::GUI::init(std::function<void()> applyArguments) {
|
||||
}
|
||||
|
||||
// SDL splits window and GL context creation, so the 3.0 fallback has to
|
||||
// cover both: a 3.2-core context can fail to materialize even after the
|
||||
// cover both: a 3.3-core context can fail to materialize even after the
|
||||
// window itself succeeded. On platforms where the pixel format binds at
|
||||
// window creation (Win32 WGL is the strict case) a clean retry needs a
|
||||
// fresh window too, so we destroy and recreate both.
|
||||
if (!m_window || !m_glContext) {
|
||||
g_system->log(LogClass::UI, "SDL failed to create OpenGL 3.2 core context, retrying with any 3.0 profile\n");
|
||||
g_system->log(LogClass::UI, "SDL failed to create OpenGL 3.3 core context, retrying with any 3.0 profile\n");
|
||||
if (m_glContext) {
|
||||
SDL_GL_DestroyContext(m_glContext);
|
||||
m_glContext = nullptr;
|
||||
@@ -1316,12 +1316,12 @@ void PCSX::GUI::endFrame() {
|
||||
if (ImGui::MenuItem(_("Reload Disk Image"), nullptr, nullptr, currentIso && !currentIso->failed())) {
|
||||
PCSX::g_emulator->m_cdrom->clearIso();
|
||||
PCSX::g_emulator->m_cdrom->setIso(new CDRIso(currentIso->getIsoPath()));
|
||||
PCSX::g_emulator->m_cdrom->check();
|
||||
PCSX::g_emulator->m_cdrom->parseIso();
|
||||
g_system->hardReset();
|
||||
}
|
||||
if (ImGui::MenuItem(_("Close Disk Image"))) {
|
||||
PCSX::g_emulator->m_cdrom->setIso(new CDRIso(new FailedFile));
|
||||
PCSX::g_emulator->m_cdrom->check();
|
||||
PCSX::g_emulator->m_cdrom->parseIso();
|
||||
}
|
||||
if (ImGui::MenuItem(_("Load binary"))) {
|
||||
showOpenBinaryDialog = true;
|
||||
@@ -1390,16 +1390,13 @@ void PCSX::GUI::endFrame() {
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(_("Open LID"))) {
|
||||
PCSX::g_emulator->m_cdrom->setLidOpenTime(-1);
|
||||
PCSX::g_emulator->m_cdrom->lidInterrupt();
|
||||
PCSX::g_emulator->m_cdrom->openLid();
|
||||
}
|
||||
if (ImGui::MenuItem(_("Close LID"))) {
|
||||
PCSX::g_emulator->m_cdrom->setLidOpenTime(0);
|
||||
PCSX::g_emulator->m_cdrom->lidInterrupt();
|
||||
PCSX::g_emulator->m_cdrom->closeLid();
|
||||
}
|
||||
if (ImGui::MenuItem(_("Open and close LID"))) {
|
||||
PCSX::g_emulator->m_cdrom->setLidOpenTime((int64_t)time(nullptr) + 2);
|
||||
PCSX::g_emulator->m_cdrom->lidInterrupt();
|
||||
PCSX::g_emulator->m_cdrom->scheduleCloseLid();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(_("Reset settings..."))) {
|
||||
@@ -1649,7 +1646,7 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)"));
|
||||
std::vector<PCSX::u8string> fileToOpen = m_openIsoFileDialog.selected();
|
||||
if (!fileToOpen.empty()) {
|
||||
PCSX::g_emulator->m_cdrom->setIso(new CDRIso(reinterpret_cast<const char*>(fileToOpen[0].c_str())));
|
||||
PCSX::g_emulator->m_cdrom->check();
|
||||
PCSX::g_emulator->m_cdrom->parseIso();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2321,8 +2318,8 @@ which may include additional checks.
|
||||
Also will make the boot time substantially
|
||||
faster by not displaying the logo.)"));
|
||||
auto bios = settings.get<Emulator::SettingBios>().string();
|
||||
ImGui::InputText(_("BIOS file"), const_cast<char*>(reinterpret_cast<const char*>(bios.c_str())), bios.length(),
|
||||
ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputText(_("BIOS file"), const_cast<char*>(reinterpret_cast<const char*>(bios.c_str())),
|
||||
bios.length() + 1, ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::SameLine();
|
||||
selectBiosDialog = ImGui::Button("...");
|
||||
if (ImGui::Checkbox(_("Enable Debugger"), &debugSettings.get<Emulator::DebugSettings::Debug>().value)) {
|
||||
@@ -2508,18 +2505,18 @@ of the emulator to take effect.)");
|
||||
|
||||
void PCSX::GUI::interruptsScaler() {
|
||||
static const char* names[] = {
|
||||
"SIO", "SIO1", "CDR", "CDR Read", "GPU DMA", "MDEC Out DMA", "SPU DMA",
|
||||
"GPU Busy", "MDEC In DMA", "GPU OTC DMA", "CDR DMA", "SPU", "CDR Decoded Buffer", "CDR Lid Seek",
|
||||
"CDR Play",
|
||||
"SIO", "SIO1", "CDR FIFO", "CDR Command", "CDR Reads", "GPU DMA",
|
||||
"MDEC Out DMA", "SPU DMA", "MDEC In DMA", "GPU OTC DMA", "CDR DMA",
|
||||
};
|
||||
if (ImGui::Begin(_("Interrupt Scaler"), &m_showInterruptsScaler)) {
|
||||
static_assert(std::size(names) == std::extent_v<decltype(R3000Acpu::m_scheduleScales)>);
|
||||
if (ImGui::Begin(_("Scheduler Scaler"), &m_showInterruptsScaler)) {
|
||||
if (ImGui::Button(_("Reset all"))) {
|
||||
for (auto& scale : g_emulator->m_cpu->m_interruptScales) {
|
||||
for (auto& scale : g_emulator->m_cpu->m_scheduleScales) {
|
||||
scale = 1.0f;
|
||||
}
|
||||
}
|
||||
unsigned counter = 0;
|
||||
for (auto& scale : g_emulator->m_cpu->m_interruptScales) {
|
||||
for (auto& scale : g_emulator->m_cpu->m_scheduleScales) {
|
||||
ImGui::SliderFloat(names[counter], &scale, 0.0f, 100.0f, "%.3f", ImGuiSliderFlags_AlwaysClamp);
|
||||
counter++;
|
||||
}
|
||||
@@ -2837,7 +2834,7 @@ void PCSX::GUI::magicOpen(const char* pathStr) {
|
||||
|
||||
// Iso loader is last because its detection is the most broken at the moment.
|
||||
g_emulator->m_cdrom->setIso(new CDRIso(path));
|
||||
g_emulator->m_cdrom->check();
|
||||
g_emulator->m_cdrom->parseIso();
|
||||
}
|
||||
|
||||
bool PCSX::GUI::getSaveStateExists(uint32_t slot) {
|
||||
|
||||
@@ -44,6 +44,13 @@ std::string_view PCSX::Shaders::CrtLottes::Output::frag() {
|
||||
// from Timothy Lottes
|
||||
|
||||
precision highp float;
|
||||
#if __VERSION__ == 300
|
||||
// On MacOS, with shader version 410, these are illegal, but on
|
||||
// Windows, with shader version 300 es and old drivers, these
|
||||
// are mandatory.
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
uniform sampler2D Texture;
|
||||
|
||||
uniform vec2 u_srcSize;
|
||||
@@ -149,6 +156,12 @@ std::string_view PCSX::Shaders::CrtLottes::Offscreen::frag() {
|
||||
// from Timothy Lottes
|
||||
|
||||
precision highp float;
|
||||
#if __VERSION__ == 300
|
||||
// On MacOS, with shader version 410, these are illegal, but on
|
||||
// Windows, with shader version 300 es and old drivers, these
|
||||
// are mandatory.
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
uniform sampler2D Texture;
|
||||
in vec2 Frag_UV;
|
||||
|
||||
@@ -61,6 +61,12 @@ void main() {
|
||||
|
||||
static const GLchar *s_defaultPixelShader = GL_SHADER_VERSION R"(
|
||||
precision highp float;
|
||||
#if __VERSION__ == 300
|
||||
// On MacOS, with shader version 410, these are illegal, but on
|
||||
// Windows, with shader version 300 es and old drivers, these
|
||||
// are mandatory.
|
||||
precision highp usampler2D;
|
||||
#endif
|
||||
|
||||
uniform usampler2D u_dataHeatmap;
|
||||
uniform usampler2D u_audioHeatmap;
|
||||
|
||||
@@ -539,7 +539,7 @@ void PCSX::Widgets::IsoBrowser::draw(CDRom* cdrom, const char* title) {
|
||||
showOpenIsoFileDialog = ImGui::MenuItem(_("Open Disk Image"));
|
||||
if (ImGui::MenuItem(_("Close Disk Image"))) {
|
||||
g_emulator->m_cdrom->setIso(new CDRIso(new FailedFile));
|
||||
g_emulator->m_cdrom->check();
|
||||
g_emulator->m_cdrom->parseIso();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
@@ -559,7 +559,7 @@ void PCSX::Widgets::IsoBrowser::draw(CDRom* cdrom, const char* title) {
|
||||
std::vector<PCSX::u8string> fileToOpen = m_openIsoFileDialog.selected();
|
||||
if (!fileToOpen.empty()) {
|
||||
g_emulator->m_cdrom->setIso(new CDRIso(reinterpret_cast<const char*>(fileToOpen[0].c_str())));
|
||||
g_emulator->m_cdrom->check();
|
||||
g_emulator->m_cdrom->parseIso();
|
||||
}
|
||||
}
|
||||
auto iso = cdrom->m_iso.get();
|
||||
|
||||
@@ -108,6 +108,8 @@ bool PCSX::Widgets::Log::draw(GUI* gui, const char* title) {
|
||||
auto& debugSettings = g_emulator->settings.get<Emulator::SettingDebugSettings>();
|
||||
changed |= ImGui::MenuItem(_("Log CD-ROM commands"), nullptr,
|
||||
&debugSettings.get<Emulator::DebugSettings::LoggingCDROM>().value);
|
||||
changed |= ImGui::MenuItem(_("Log CD-ROM hardware access"), nullptr,
|
||||
&debugSettings.get<Emulator::DebugSettings::LoggingHWCDROM>().value);
|
||||
changed |=
|
||||
ImGui::MenuItem(_("CPU trace"), nullptr, &debugSettings.get<Emulator::DebugSettings::Trace>().value);
|
||||
changed |= ImGui::MenuItem(_("Skip ISR during CPU traces"), nullptr,
|
||||
|
||||
@@ -33,7 +33,7 @@ bool PCSX::Widgets::PIOCart::draw(const char* title) {
|
||||
return false;
|
||||
} else {
|
||||
auto pio_rom = settings.get<Emulator::SettingEXP1Filepath>().string();
|
||||
ImGui::InputText("ROM", const_cast<char*>(reinterpret_cast<const char*>(pio_rom.c_str())), pio_rom.length(),
|
||||
ImGui::InputText("ROM", const_cast<char*>(reinterpret_cast<const char*>(pio_rom.c_str())), pio_rom.length() + 1,
|
||||
ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::SameLine();
|
||||
selectEXP1Dialog = ImGui::Button("...");
|
||||
|
||||
@@ -50,6 +50,13 @@ void main() {
|
||||
|
||||
static const GLchar *s_defaultPixelShader = GL_SHADER_VERSION R"(
|
||||
precision highp float;
|
||||
#if __VERSION__ == 300
|
||||
// On MacOS, with shader version 410, these are illegal, but on
|
||||
// Windows, with shader version 300 es and old drivers, these
|
||||
// are mandatory.
|
||||
precision highp sampler2D;
|
||||
precision highp usampler2D;
|
||||
#endif
|
||||
|
||||
uniform sampler2D u_ramTexture;
|
||||
uniform usampler2D u_readHeatmap;
|
||||
|
||||
@@ -377,7 +377,7 @@ void PCSX::Widgets::Registers::draw(PCSX::GUI* gui, PCSX::psxRegisters* register
|
||||
contextMenu("##pc", registers->pc, 16);
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::Text("cycle: %08x", registers->cycle);
|
||||
ImGui::Text("int : %08x", registers->interrupt);
|
||||
ImGui::Text("sched: %08x", registers->scheduleMask);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::PopFont();
|
||||
|
||||
@@ -82,6 +82,12 @@ void main() {
|
||||
static const GLchar *const c_defaultPixelShader = GL_SHADER_VERSION R"(
|
||||
// The Pixel Shader is most likely what the user will want to change.
|
||||
precision highp float;
|
||||
#if __VERSION__ == 300
|
||||
// On MacOS, with shader version 410, these are illegal, but on
|
||||
// Windows, with shader version 300 es and old drivers, these
|
||||
// are mandatory.
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
uniform sampler2D Texture;
|
||||
in vec2 Frag_UV;
|
||||
in vec4 Frag_Color;
|
||||
@@ -137,6 +143,12 @@ end
|
||||
)";
|
||||
|
||||
static const GLchar *const c_24bppConversionFragShader = GL_SHADER_VERSION R"(
|
||||
#if __VERSION__ == 300
|
||||
// On MacOS, with shader version 410, these are illegal, but on
|
||||
// Windows, with shader version 300 es and old drivers, these
|
||||
// are mandatory.
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
uniform sampler2D Texture;
|
||||
in vec2 Frag_UV;
|
||||
in vec4 Frag_Color;
|
||||
|
||||
@@ -52,6 +52,13 @@ void main() {
|
||||
|
||||
static const GLchar *s_defaultPixelShader = GL_SHADER_VERSION R"(
|
||||
precision highp float;
|
||||
#if __VERSION__ == 300
|
||||
// On MacOS, with shader version 410, these are illegal, but on
|
||||
// Windows, with shader version 300 es and old drivers, these
|
||||
// are mandatory.
|
||||
precision highp sampler2D;
|
||||
precision highp usampler2D;
|
||||
#endif
|
||||
|
||||
uniform int u_24shift;
|
||||
uniform bool u_alpha;
|
||||
|
||||
@@ -69,6 +69,8 @@ local bufferMeta = {
|
||||
end
|
||||
elseif index == 'pbSlice' then
|
||||
return Support._internal.createPBSliceFromBuffer(buffer)
|
||||
elseif index == 'cast' then
|
||||
return function(buffer, ctype) return ffi.cast(ctype, buffer.data) end
|
||||
end
|
||||
error('Unknown index `' .. index .. '` for LuaBuffer')
|
||||
end,
|
||||
|
||||
+8
-4
@@ -413,7 +413,7 @@ int pcsxMain(int argc, char **argv) {
|
||||
if (isoToOpen.empty()) isoToOpen = args.get<std::string>("loadiso", "");
|
||||
if (isoToOpen.empty()) isoToOpen = args.get<std::string>("disk", "");
|
||||
if (!isoToOpen.empty()) emulator->m_cdrom->setIso(new PCSX::CDRIso(isoToOpen));
|
||||
emulator->m_cdrom->check();
|
||||
emulator->m_cdrom->parseIso();
|
||||
|
||||
// After settings are loaded, we're fine setting the SPU part of the emulation.
|
||||
emulator->m_spu->init();
|
||||
@@ -557,9 +557,13 @@ runner.init({
|
||||
// This will ensure we don't do certain cleanups that are awaiting other tasks,
|
||||
// which could result in deadlocks on exit in case we encountered a serious problem.
|
||||
// This may cause data loss when writing files, but that's life when encountering
|
||||
// a serious problem in a software.
|
||||
system->setEmergencyExit();
|
||||
uvThread.setEmergencyExit();
|
||||
// a serious problem in a software. An error from the command-line archives or
|
||||
// Lua scripts happens before anything is running, so that one still shuts down
|
||||
// normally; skipping the uv thread's join there aborts the process on exit.
|
||||
if (!system->m_inStartup) {
|
||||
system->setEmergencyExit();
|
||||
uvThread.setEmergencyExit();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
Submodule src/mips updated: e9f70ebc99...7ffc2b14b3
+22
-1
@@ -37,8 +37,15 @@ namespace EnvelopeTables {
|
||||
// steps: a phase counts samples in the fraction accumulator up to
|
||||
// denominator[rate], then steps the level by numerator_increase[rate] (rising)
|
||||
// or numerator_decrease[rate] (falling, negative).
|
||||
// The hardware adds 0x8000 >> (shift - 11) to its step counter each sample and clamps
|
||||
// that increment to at least 1, so every shift from 26 up steps once per 32768 samples
|
||||
// (psx-spx: "a rate of 0x76 behaves like 0x6A").
|
||||
constexpr int kMaxEffectiveShift = 26;
|
||||
struct DenominatorGenerator {
|
||||
static consteval int32_t calculateValue(std::size_t rate) { return (rate < 48) ? 1 : (1 << ((rate >> 2) - 11)); }
|
||||
static consteval int32_t calculateValue(std::size_t rate) {
|
||||
const int shift = static_cast<int>(rate >> 2);
|
||||
return (rate < 48) ? 1 : (1 << (std::min(shift, kMaxEffectiveShift) - 11));
|
||||
}
|
||||
};
|
||||
|
||||
struct NumeratorIncreaseGenerator {
|
||||
@@ -56,6 +63,10 @@ struct NumeratorDecreaseGenerator {
|
||||
constexpr auto denominator = PCSX::generateTable<128, DenominatorGenerator>();
|
||||
constexpr auto numerator_increase = PCSX::generateTable<128, NumeratorIncreaseGenerator>();
|
||||
constexpr auto numerator_decrease = PCSX::generateTable<128, NumeratorDecreaseGenerator>();
|
||||
|
||||
static_assert(denominator.data[0x6a] == 32768);
|
||||
static_assert(denominator.data[0x76] == denominator.data[0x6a]);
|
||||
static_assert(denominator.data[0x7c] == 32768);
|
||||
} // namespace EnvelopeTables
|
||||
|
||||
namespace {
|
||||
@@ -71,6 +82,10 @@ constexpr int32_t kSustainLevelMask = 0xf; // ...compared against the 0..15
|
||||
// (val & 0x7f00) >> 8, sustain is (val & 0x1fc0) >> 6), so the knee's rate-step lands past the
|
||||
// end of the 128-entry tables on the slowest rates. Saturate instead of reading off the end.
|
||||
constexpr int kMaxRateIndex = 127;
|
||||
// A rate field of all ones (step and shift bits together) never steps. That is 0x7f for
|
||||
// attack and sustain and 0x1f for release, tested on the register value before the knee.
|
||||
constexpr int kFrozenRate = 0x7f;
|
||||
constexpr int kFrozenCoarseRate = 0x1f;
|
||||
} // namespace
|
||||
|
||||
// Write the freshly computed envelope state back and return the full 15-bit
|
||||
@@ -93,6 +108,8 @@ int PCSX::SPU::AdsrEnvelope::Attack() {
|
||||
int32_t envelopeVolFraction = m_adsrx.get<exEnvelopeVolF>().value;
|
||||
const bool exponential = m_adsrx.get<exAttackModeExp>().value != 0;
|
||||
|
||||
if (rateIndex == kFrozenRate) return commit(envelopeVol, envelopeVolFraction);
|
||||
|
||||
// Past the knee the exponential attack curve flattens to a slower rate.
|
||||
if (exponential && envelopeVol >= kExponentialKnee) {
|
||||
rateIndex = std::min(rateIndex + kExponentialKneeRateStep, kMaxRateIndex);
|
||||
@@ -139,6 +156,8 @@ int PCSX::SPU::AdsrEnvelope::Sustain() {
|
||||
const bool exponential = m_adsrx.get<exSustainModeExp>().value != 0;
|
||||
const bool increase = m_adsrx.get<exSustainIncrease>().value != 0;
|
||||
|
||||
if (rateIndex == kFrozenRate) return commit(envelopeVol, envelopeVolFraction);
|
||||
|
||||
if (increase) {
|
||||
// Past the knee the exponential rise flattens to a slower rate.
|
||||
if (exponential && envelopeVol >= kExponentialKnee) {
|
||||
@@ -171,6 +190,8 @@ int PCSX::SPU::AdsrEnvelope::Release(bool &channelOn) {
|
||||
int32_t envelopeVolFraction = m_adsrx.get<exEnvelopeVolF>().value;
|
||||
const bool exponential = m_adsrx.get<exReleaseModeExp>().value != 0;
|
||||
|
||||
if (m_adsrx.get<exReleaseRate>().value == kFrozenCoarseRate) return commit(envelopeVol, envelopeVolFraction);
|
||||
|
||||
if (++envelopeVolFraction >= EnvelopeTables::denominator.data[rateIndex]) {
|
||||
envelopeVolFraction = 0;
|
||||
envelopeVol += exponential ? (EnvelopeTables::numerator_decrease.data[rateIndex] * envelopeVol) >>
|
||||
|
||||
+8
-5
@@ -86,6 +86,9 @@ class impl final : public SPUInterface {
|
||||
void writeDMAMem(uint16_t *, int) final;
|
||||
void readDMAMem(uint16_t *, int) final;
|
||||
virtual void playADPCMchannel(xa_decode_t *) final;
|
||||
void setXAVolume(const uint8_t atv[4]) final {
|
||||
for (unsigned i = 0; i < 4; i++) xaAtv[i] = atv[i];
|
||||
}
|
||||
|
||||
void save(SaveStates::SPU &) final;
|
||||
void load(const SaveStates::SPU &) final;
|
||||
@@ -173,8 +176,6 @@ class impl final : public SPUInterface {
|
||||
// Sound buffer sizes.
|
||||
// 400 ms complete sound buffer.
|
||||
static const size_t SOUNDSIZE = 70560;
|
||||
// 137 ms test buffer. If less than this is buffered, a new upload happens.
|
||||
static const size_t TESTSIZE = 24192;
|
||||
|
||||
// Roughly 1 ms of data.
|
||||
static const size_t NSSIZE = 45;
|
||||
@@ -254,6 +255,10 @@ class impl final : public SPUInterface {
|
||||
|
||||
// The temporary capture buffer for CD audio left/right.
|
||||
CaptureBuffer captureBuffer;
|
||||
// Emulated cycle of the last CD audio fed to the capture buffer. While the CD is
|
||||
// feeding, an empty buffer means the emulation is behind the mixer, not silence.
|
||||
// 0 means the CD has not fed anything yet.
|
||||
std::atomic<uint64_t> cdFeedCycle = 0;
|
||||
// The capture buffer index for voice 1 and voice 3.
|
||||
int32_t capBufVoiceIndex = 0;
|
||||
|
||||
@@ -340,9 +345,6 @@ class impl final : public SPUInterface {
|
||||
int iCycle = 0;
|
||||
int16_t *pS;
|
||||
|
||||
// Secure start counter.
|
||||
int secureStart = 0;
|
||||
|
||||
// XA
|
||||
xa_decode_t *xapGlobal = 0;
|
||||
|
||||
@@ -355,6 +357,7 @@ class impl final : public SPUInterface {
|
||||
unsigned xaRingPos = 0;
|
||||
int xaSixStep = 6;
|
||||
int16_t xaLastL = 0, xaLastR = 0;
|
||||
uint8_t xaAtv[4] = {0x80, 0, 0x80, 0};
|
||||
int16_t zigzag(const int16_t *ring, unsigned table);
|
||||
|
||||
SDLAudio m_audioOut = {settings};
|
||||
|
||||
+70
-72
@@ -158,79 +158,77 @@ int PCSX::SPU::ReverbUnit::mixLeft(int ns, uint16_t* spuMem, uint16_t spuCtrl) {
|
||||
// master enable bit is clear. That bit only gates writes into the buffer.
|
||||
// Games that stream audio into the work area depend on this.
|
||||
const bool writeEnabled = (spuCtrl & kReverbMasterEnable) != 0;
|
||||
{
|
||||
firPushIn(*(mixStart + (ns << 1)), *(mixStart + (ns << 1) + 1));
|
||||
altLeftTick = (callCount & 1) != 0;
|
||||
const int in = sat16(altLeftTick ? firDecimate(h44L) : firDecimate(h44R));
|
||||
const int sSame = altLeftTick ? rvb.IIR_SRC_A0 : rvb.IIR_SRC_A1;
|
||||
const int dSame = altLeftTick ? rvb.IIR_DEST_A0 : rvb.IIR_DEST_A1;
|
||||
const int sDiff = altLeftTick ? rvb.IIR_SRC_B0 : rvb.IIR_SRC_B1;
|
||||
const int dDiff = altLeftTick ? rvb.IIR_DEST_B0 : rvb.IIR_DEST_B1;
|
||||
const int inCoef = altLeftTick ? rvb.IN_COEF_L : rvb.IN_COEF_R;
|
||||
const int c1 = altLeftTick ? rvb.ACC_SRC_A0 : rvb.ACC_SRC_A1;
|
||||
const int c2 = altLeftTick ? rvb.ACC_SRC_B0 : rvb.ACC_SRC_B1;
|
||||
const int c3 = altLeftTick ? rvb.ACC_SRC_C0 : rvb.ACC_SRC_C1;
|
||||
const int c4 = altLeftTick ? rvb.ACC_SRC_D0 : rvb.ACC_SRC_D1;
|
||||
const int mA = altLeftTick ? rvb.MIX_DEST_A0 : rvb.MIX_DEST_A1;
|
||||
const int mB = altLeftTick ? rvb.MIX_DEST_B0 : rvb.MIX_DEST_B1;
|
||||
auto at = [&](int off, int extra) { return (int)reinterpret_cast<int16_t*>(spuMem)[wrapOffset(off, extra)]; };
|
||||
const int iirSame =
|
||||
sat16(rdiv15((int64_t)(getBuffer(sSame, spuMem) * rvb.IIR_COEF)) + rdiv15((int64_t)(in * inCoef)));
|
||||
const int iirDiff =
|
||||
sat16(rdiv15((int64_t)(getBuffer(sDiff, spuMem) * rvb.IIR_COEF)) + rdiv15((int64_t)(in * inCoef)));
|
||||
// psx-spx stores the IIR result at [mLSAME] and reads the previous value back
|
||||
// from [mLSAME-2], i.e. the destination cell itself one 16-bit sample earlier -
|
||||
// hence the -1 extraSample on the read and none on the store.
|
||||
if (writeEnabled) {
|
||||
setBuffer<0>(dSame,
|
||||
rdiv15((int64_t)(iirSame * rvb.IIR_ALPHA)) +
|
||||
rdiv15((int64_t)(at(dSame, -1) * (32768L - rvb.IIR_ALPHA))),
|
||||
spuMem);
|
||||
setBuffer<0>(dDiff,
|
||||
rdiv15((int64_t)(iirDiff * rvb.IIR_ALPHA)) +
|
||||
rdiv15((int64_t)(at(dDiff, -1) * (32768L - rvb.IIR_ALPHA))),
|
||||
spuMem);
|
||||
}
|
||||
int out = sat16(rdiv15((int64_t)(getBuffer(c1, spuMem) * rvb.ACC_COEF_A)) +
|
||||
rdiv15((int64_t)(getBuffer(c2, spuMem) * rvb.ACC_COEF_B)) +
|
||||
rdiv15((int64_t)(getBuffer(c3, spuMem) * rvb.ACC_COEF_C)) +
|
||||
rdiv15((int64_t)(getBuffer(c4, spuMem) * rvb.ACC_COEF_D)));
|
||||
// psx-spx "SPU Reverb Formula", APF1 then APF2, three clauses each:
|
||||
// Lout=Lout-vAPF1*[mLAPF1-dAPF1], [mLAPF1]=Lout, Lout=Lout*vAPF1+[mLAPF1-dAPF1]
|
||||
// Lout=Lout-vAPF2*[mLAPF2-dAPF2], [mLAPF2]=Lout, Lout=Lout*vAPF2+[mLAPF2-dAPF2]
|
||||
// LeftOutput = Lout*vLOUT (all products divided by 8000h)
|
||||
const int tapA = getBuffer(mA - rvb.FB_SRC_A, spuMem);
|
||||
out = sat16(out - rdiv15((int64_t)(tapA * rvb.FB_ALPHA)));
|
||||
if (writeEnabled) setBuffer<0>(mA, out, spuMem);
|
||||
out = sat16(rdiv15((int64_t)(out * rvb.FB_ALPHA)) + tapA);
|
||||
const int tapB = getBuffer(mB - rvb.FB_SRC_B, spuMem);
|
||||
out = sat16(out - rdiv15((int64_t)(tapB * rvb.FB_X)));
|
||||
if (writeEnabled) setBuffer<0>(mB, out, spuMem);
|
||||
out = sat16(rdiv15((int64_t)(out * rvb.FB_X)) + tapB);
|
||||
// vLOUT/vROUT are signed 16bit and their products divide by 8000h, not 4000h.
|
||||
// registers.cc assigns these from a uint16_t with no cast, unlike every other
|
||||
// reverb coefficient there, so sign-extend at the point of use.
|
||||
const int vol = altLeftTick ? (int)(int16_t)rvb.VolLeft : (int)(int16_t)rvb.VolRight;
|
||||
const int wet = rdiv15((int64_t)out * vol);
|
||||
if (altLeftTick) {
|
||||
rvb.wetLeft = wet;
|
||||
for (int i = 19; i > 0; i--) h22L[i] = h22L[i - 1];
|
||||
h22L[0] = wet;
|
||||
} else {
|
||||
rvb.wetRight = wet;
|
||||
for (int i = 19; i > 0; i--) h22R[i] = h22R[i - 1];
|
||||
h22R[0] = wet;
|
||||
// Address advances once per COMPLETE 22.05kHz iteration, after both halves.
|
||||
rvb.CurrAddr++;
|
||||
if (rvb.CurrAddr > 0x3ffff) rvb.CurrAddr = rvb.StartAddr;
|
||||
}
|
||||
// Emit the even (interpolating) polyphase on this channel's own compute cycle
|
||||
// and the centre-tap passthrough on the other one, which puts the wet output one
|
||||
// phase earlier than pairing them the obvious way round. h22[9] and not h22[10]:
|
||||
// on the off cycle h22[10] is 21 ticks old, so the parity swap on its own would
|
||||
// jitter the group delay between 19 and 21.
|
||||
return altLeftTick ? firInterp(h22L, false) : h22L[9];
|
||||
firPushIn(*(mixStart + (ns << 1)), *(mixStart + (ns << 1) + 1));
|
||||
altLeftTick = (callCount & 1) != 0;
|
||||
const int in = sat16(altLeftTick ? firDecimate(h44L) : firDecimate(h44R));
|
||||
const int sSame = altLeftTick ? rvb.IIR_SRC_A0 : rvb.IIR_SRC_A1;
|
||||
const int dSame = altLeftTick ? rvb.IIR_DEST_A0 : rvb.IIR_DEST_A1;
|
||||
const int sDiff = altLeftTick ? rvb.IIR_SRC_B0 : rvb.IIR_SRC_B1;
|
||||
const int dDiff = altLeftTick ? rvb.IIR_DEST_B0 : rvb.IIR_DEST_B1;
|
||||
const int inCoef = altLeftTick ? rvb.IN_COEF_L : rvb.IN_COEF_R;
|
||||
const int c1 = altLeftTick ? rvb.ACC_SRC_A0 : rvb.ACC_SRC_A1;
|
||||
const int c2 = altLeftTick ? rvb.ACC_SRC_B0 : rvb.ACC_SRC_B1;
|
||||
const int c3 = altLeftTick ? rvb.ACC_SRC_C0 : rvb.ACC_SRC_C1;
|
||||
const int c4 = altLeftTick ? rvb.ACC_SRC_D0 : rvb.ACC_SRC_D1;
|
||||
const int mA = altLeftTick ? rvb.MIX_DEST_A0 : rvb.MIX_DEST_A1;
|
||||
const int mB = altLeftTick ? rvb.MIX_DEST_B0 : rvb.MIX_DEST_B1;
|
||||
auto at = [&](int off, int extra) { return (int)reinterpret_cast<int16_t*>(spuMem)[wrapOffset(off, extra)]; };
|
||||
const int iirSame =
|
||||
sat16(rdiv15((int64_t)(getBuffer(sSame, spuMem) * rvb.IIR_COEF)) + rdiv15((int64_t)(in * inCoef)));
|
||||
const int iirDiff =
|
||||
sat16(rdiv15((int64_t)(getBuffer(sDiff, spuMem) * rvb.IIR_COEF)) + rdiv15((int64_t)(in * inCoef)));
|
||||
// psx-spx stores the IIR result at [mLSAME] and reads the previous value back
|
||||
// from [mLSAME-2], i.e. the destination cell itself one 16-bit sample earlier -
|
||||
// hence the -1 extraSample on the read and none on the store.
|
||||
if (writeEnabled) {
|
||||
setBuffer<0>(
|
||||
dSame,
|
||||
rdiv15((int64_t)(iirSame * rvb.IIR_ALPHA)) + rdiv15((int64_t)(at(dSame, -1) * (32768L - rvb.IIR_ALPHA))),
|
||||
spuMem);
|
||||
setBuffer<0>(
|
||||
dDiff,
|
||||
rdiv15((int64_t)(iirDiff * rvb.IIR_ALPHA)) + rdiv15((int64_t)(at(dDiff, -1) * (32768L - rvb.IIR_ALPHA))),
|
||||
spuMem);
|
||||
}
|
||||
int out = sat16(rdiv15((int64_t)(getBuffer(c1, spuMem) * rvb.ACC_COEF_A)) +
|
||||
rdiv15((int64_t)(getBuffer(c2, spuMem) * rvb.ACC_COEF_B)) +
|
||||
rdiv15((int64_t)(getBuffer(c3, spuMem) * rvb.ACC_COEF_C)) +
|
||||
rdiv15((int64_t)(getBuffer(c4, spuMem) * rvb.ACC_COEF_D)));
|
||||
// psx-spx "SPU Reverb Formula", APF1 then APF2, three clauses each:
|
||||
// Lout=Lout-vAPF1*[mLAPF1-dAPF1], [mLAPF1]=Lout, Lout=Lout*vAPF1+[mLAPF1-dAPF1]
|
||||
// Lout=Lout-vAPF2*[mLAPF2-dAPF2], [mLAPF2]=Lout, Lout=Lout*vAPF2+[mLAPF2-dAPF2]
|
||||
// LeftOutput = Lout*vLOUT (all products divided by 8000h)
|
||||
const int tapA = getBuffer(mA - rvb.FB_SRC_A, spuMem);
|
||||
out = sat16(out - rdiv15((int64_t)(tapA * rvb.FB_ALPHA)));
|
||||
if (writeEnabled) setBuffer<0>(mA, out, spuMem);
|
||||
out = sat16(rdiv15((int64_t)(out * rvb.FB_ALPHA)) + tapA);
|
||||
const int tapB = getBuffer(mB - rvb.FB_SRC_B, spuMem);
|
||||
out = sat16(out - rdiv15((int64_t)(tapB * rvb.FB_X)));
|
||||
if (writeEnabled) setBuffer<0>(mB, out, spuMem);
|
||||
out = sat16(rdiv15((int64_t)(out * rvb.FB_X)) + tapB);
|
||||
// vLOUT/vROUT are signed 16bit and their products divide by 8000h, not 4000h.
|
||||
// registers.cc assigns these from a uint16_t with no cast, unlike every other
|
||||
// reverb coefficient there, so sign-extend at the point of use.
|
||||
const int vol = altLeftTick ? (int)(int16_t)rvb.VolLeft : (int)(int16_t)rvb.VolRight;
|
||||
const int wet = rdiv15((int64_t)out * vol);
|
||||
if (altLeftTick) {
|
||||
rvb.wetLeft = wet;
|
||||
for (int i = 19; i > 0; i--) h22L[i] = h22L[i - 1];
|
||||
h22L[0] = wet;
|
||||
} else {
|
||||
rvb.wetRight = wet;
|
||||
for (int i = 19; i > 0; i--) h22R[i] = h22R[i - 1];
|
||||
h22R[0] = wet;
|
||||
// Address advances once per COMPLETE 22.05kHz iteration, after both halves.
|
||||
rvb.CurrAddr++;
|
||||
if (rvb.CurrAddr > 0x3ffff) rvb.CurrAddr = rvb.StartAddr;
|
||||
}
|
||||
// Emit the even (interpolating) polyphase on this channel's own compute cycle
|
||||
// and the centre-tap passthrough on the other one, which puts the wet output one
|
||||
// phase earlier than pairing them the obvious way round. h22[9] and not h22[10]:
|
||||
// on the off cycle h22[10] is 21 ticks old, so the parity swap on its own would
|
||||
// jitter the group delay between 19 and 21.
|
||||
return altLeftTick ? firInterp(h22L, false) : h22L[9];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ class SDLAudio {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
size_t getBytesBuffered(unsigned streamId = 0) {
|
||||
size_t getFramesBuffered(unsigned streamId = 0) {
|
||||
switch (streamId) {
|
||||
case 0:
|
||||
return m_voicesStream.buffered();
|
||||
|
||||
+14
-31
@@ -23,6 +23,8 @@
|
||||
#include <cstdlib>
|
||||
#include <thread>
|
||||
|
||||
#include "core/psxemulator.h"
|
||||
#include "core/r3000a.h"
|
||||
#include "spu/adsr.h"
|
||||
#include "spu/externals.h"
|
||||
#include "spu/interface.h"
|
||||
@@ -424,35 +426,8 @@ void PCSX::SPU::impl::MainThread() {
|
||||
// Run until we are shutting down.
|
||||
while (!endThread) {
|
||||
int volumeDivisor = 4 - settings.get<Volume>();
|
||||
//--------------------------------------------------//
|
||||
// At the start of each pass, check whether there is enough free space in the audio output
|
||||
// buffer to fill in new data, or whether there is a new channel to start. If neither, wait
|
||||
// until free space is available or a new channel gets started.
|
||||
|
||||
// Should a new channel start immediately, that is, is at least one bit in 0..MAXCHANNEL
|
||||
// set?
|
||||
if (newChannelMask) {
|
||||
// Set secureStart.
|
||||
secureStart++;
|
||||
if (secureStart > 5)
|
||||
// If it has been set 5 times in a row, meaning a new sample has been started on 5
|
||||
// tries in a row, reset it to give the sound update a chance.
|
||||
secureStart = 0;
|
||||
} else
|
||||
// 0: no new channel should start.
|
||||
secureStart = 0;
|
||||
|
||||
// No new start, no thread end, and still enough data in the sound buffer?
|
||||
while (!secureStart && !endThread && (m_audioOut.getBytesBuffered() > TESTSIZE)) {
|
||||
// Reset secureStart.
|
||||
secureStart = 0;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(5ms);
|
||||
|
||||
// If a new channel kicks in, or the sound buffer runs low, leave the loop.
|
||||
if (newChannelMask) secureStart = 1;
|
||||
}
|
||||
// Pacing comes from the blocking enqueue in feedStreamData at the bottom of the loop: the
|
||||
// output ring only drains as the audio output consumes it.
|
||||
|
||||
{
|
||||
// capBufVoiceIndex is reset from the emulation thread (resetCaptureBuffer).
|
||||
@@ -571,10 +546,18 @@ void PCSX::SPU::impl::MainThread() {
|
||||
void PCSX::SPU::impl::writeCaptureBufferCD(int numbSamples) {
|
||||
std::lock_guard<std::mutex> lock(cbMtx);
|
||||
if (mixIrqAddress) {
|
||||
// CD audio arrives once per sector, 1/75 s apart in emulated time. Within a few
|
||||
// sectors of the last one the CD is still feeding, and an empty buffer only means
|
||||
// the emulation runs slower than the mixer: wait for it instead of writing silence.
|
||||
// The mixer starts before the CPU exists.
|
||||
const auto *cpu = PCSX::g_emulator->m_cpu.get();
|
||||
const uint64_t feeding = PCSX::g_emulator->m_psxClockSpeed * 4 / 75;
|
||||
const uint64_t lastFeed = cdFeedCycle;
|
||||
const bool cdFeeding = cpu && (lastFeed != 0) && (cpu->m_regs.cycle - lastFeed < feeding);
|
||||
for (int n = 0; n < numbSamples; n++) {
|
||||
if (captureBuffer.startIndex == captureBuffer.endIndex) {
|
||||
// If there are no samples left in the temp buffer,
|
||||
// we still HAVE to keep writing to the capture buffer.
|
||||
if (cdFeeding) break;
|
||||
// Nothing is feeding the CD input: the capture records silence.
|
||||
spuMem[captureBuffer.currIndex] = 0;
|
||||
spuMem[captureBuffer.currIndex + 0x200] = 0;
|
||||
} else {
|
||||
|
||||
+37
-2
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "core/psxemulator.h"
|
||||
#include "core/r3000a.h"
|
||||
#include "spu/externals.h"
|
||||
#include "spu/interface.h"
|
||||
|
||||
@@ -76,6 +78,32 @@ void PCSX::SPU::impl::FeedXA(xa_decode_t *xap) {
|
||||
// mixer thread. Taken unconditionally: gating it on an unlocked read of mixIrqAddress
|
||||
// is itself a race, and could pair a skipped lock with a later unlock.
|
||||
std::unique_lock<std::mutex> cbLock(cbMtx);
|
||||
cdFeedCycle = PCSX::g_emulator->m_cpu->m_regs.cycle;
|
||||
|
||||
if (xap->freq == 44100) {
|
||||
// CD-DA is already at the output rate and skips the zigzag interpolator.
|
||||
for (int i = 0; i < xap->nsamples; i++) {
|
||||
int16_t l = xap->pcm[i * 2];
|
||||
int16_t r = xap->pcm[i * 2 + 1];
|
||||
if (mixIrqAddress) {
|
||||
captureBuffer.CDCapLeft[captureBuffer.endIndex] = (uint16_t)l;
|
||||
captureBuffer.CDCapRight[captureBuffer.endIndex] = (uint16_t)r;
|
||||
captureBuffer.endIndex = (captureBuffer.endIndex + 1) % CaptureBuffer::CB_SIZE;
|
||||
if (captureBuffer.endIndex == captureBuffer.startIndex) {
|
||||
g_system->log(LogClass::SPU, "Capture buffer is overflowing. Increase CB_SIZE.\n");
|
||||
}
|
||||
}
|
||||
SDLAudio::Frame f;
|
||||
f.L = l / voldiv;
|
||||
f.R = r / voldiv;
|
||||
*XAFeed++ = f;
|
||||
}
|
||||
cbLock.unlock();
|
||||
if (XAFeed != XABuffer) {
|
||||
m_audioOut.feedStreamData(reinterpret_cast<SDLAudio::Frame *>(XABuffer), (XAFeed - XABuffer), 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < xap->nsamples; i++) {
|
||||
int16_t l, r;
|
||||
@@ -98,8 +126,15 @@ void PCSX::SPU::impl::FeedXA(xa_decode_t *xap) {
|
||||
if (--xaSixStep > 0) continue;
|
||||
xaSixStep = 6;
|
||||
for (unsigned t = 0; t < 7; t++) {
|
||||
int16_t rawSampleL = zigzag(xaRingL, t);
|
||||
int16_t rawSampleR = zigzag(xaRingR, t);
|
||||
int64_t zl = zigzag(xaRingL, t);
|
||||
int64_t zr = zigzag(xaRingR, t);
|
||||
// ATV matrix after the interpolator, then 1.026 before the 16-bit clamp and 0.973
|
||||
// after it. On a SCPH-9002 the level rises linearly with ATV up to the clamp, and the
|
||||
// clamped level sits at 31880, as with CD-DA.
|
||||
int outL = std::clamp(int(((zl * xaAtv[0] + zr * xaAtv[3]) * 33617) >> 22), -0x8000, 0x7fff);
|
||||
int outR = std::clamp(int(((zr * xaAtv[2] + zl * xaAtv[1]) * 33617) >> 22), -0x8000, 0x7fff);
|
||||
int16_t rawSampleL = (outL * 31880) >> 15;
|
||||
int16_t rawSampleR = (outR * 31880) >> 15;
|
||||
if (mixIrqAddress) {
|
||||
captureBuffer.CDCapLeft[captureBuffer.endIndex] = (uint16_t)rawSampleL;
|
||||
captureBuffer.CDCapRight[captureBuffer.endIndex] = (uint16_t)rawSampleR;
|
||||
|
||||
@@ -128,6 +128,49 @@ struct MSF {
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
MSF &operator--() {
|
||||
if (f == 0) {
|
||||
f = 74;
|
||||
if (s == 0) {
|
||||
s = 59;
|
||||
if (m == 0) {
|
||||
m = 99;
|
||||
} else {
|
||||
m--;
|
||||
}
|
||||
} else {
|
||||
s--;
|
||||
}
|
||||
} else {
|
||||
f--;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
MSF operator--(int) {
|
||||
MSF tmp = *this;
|
||||
--(*this);
|
||||
return tmp;
|
||||
}
|
||||
MSF operator+(const MSF &other) const {
|
||||
MSF tmp = *this;
|
||||
tmp += other;
|
||||
return tmp;
|
||||
}
|
||||
MSF operator-(const MSF &other) const {
|
||||
MSF tmp = *this;
|
||||
tmp -= other;
|
||||
return tmp;
|
||||
}
|
||||
MSF &operator+=(const MSF &other) {
|
||||
uint32_t lba = toLBA() + other.toLBA();
|
||||
*this = MSF(lba);
|
||||
return *this;
|
||||
}
|
||||
MSF &operator-=(const MSF &other) {
|
||||
uint32_t lba = toLBA() - other.toLBA();
|
||||
*this = MSF(lba);
|
||||
return *this;
|
||||
}
|
||||
constexpr uint32_t toLBA() const { return (m * 60 + s) * 75 + f; }
|
||||
constexpr void toBCD(uint8_t *dst) const {
|
||||
dst[0] = itob(m);
|
||||
@@ -157,6 +200,53 @@ void computeEDCECC(uint8_t *sector);
|
||||
// Compute the CRC-16 for the SubQ channel.
|
||||
uint16_t subqCRC(const uint8_t *d, int len = 10);
|
||||
|
||||
struct SubHeaders {
|
||||
uint8_t fileNumber;
|
||||
uint8_t channelNumber;
|
||||
uint8_t subMode;
|
||||
uint8_t codingInfo;
|
||||
bool fromBuffer(const uint8_t buffer[]) {
|
||||
fileNumber = buffer[0];
|
||||
channelNumber = buffer[1];
|
||||
subMode = buffer[2];
|
||||
codingInfo = buffer[3];
|
||||
return (buffer[0] == buffer[4]) && (buffer[1] == buffer[5]) && (buffer[2] == buffer[6]) &&
|
||||
(buffer[3] == buffer[7]);
|
||||
}
|
||||
void toBuffer(uint8_t buffer[]) const {
|
||||
buffer[0] = buffer[4] = fileNumber;
|
||||
buffer[1] = buffer[5] = channelNumber;
|
||||
buffer[2] = buffer[6] = subMode;
|
||||
buffer[3] = buffer[7] = codingInfo;
|
||||
}
|
||||
bool isEndOfRecord() const { return subMode & 0x01; }
|
||||
bool isVideo() const { return subMode & 0x02; }
|
||||
bool isAudio() const { return subMode & 0x04; }
|
||||
bool isData() const { return subMode & 0x08; }
|
||||
bool isTrigger() const { return subMode & 0x10; }
|
||||
bool isForm2() const { return subMode & 0x20; }
|
||||
bool isRealTime() const { return subMode & 0x40; }
|
||||
bool isEOF() const { return subMode & 0x80; }
|
||||
|
||||
void setEndOfRecord() { subMode |= 0x01; }
|
||||
void setVideo() { subMode |= 0x02; }
|
||||
void setAudio() { subMode |= 0x04; }
|
||||
void setData() { subMode |= 0x08; }
|
||||
void setTrigger() { subMode |= 0x10; }
|
||||
void setForm2() { subMode |= 0x20; }
|
||||
void setRealTime() { subMode |= 0x40; }
|
||||
void setEOF() { subMode |= 0x80; }
|
||||
|
||||
void clearEndOfRecord() { subMode &= ~0x01; }
|
||||
void clearVideo() { subMode &= ~0x02; }
|
||||
void clearAudio() { subMode &= ~0x04; }
|
||||
void clearData() { subMode &= ~0x08; }
|
||||
void clearTrigger() { subMode &= ~0x10; }
|
||||
void clearForm2() { subMode &= ~0x20; }
|
||||
void clearRealTime() { subMode &= ~0x40; }
|
||||
void clearEOF() { subMode &= ~0x80; }
|
||||
};
|
||||
|
||||
} // namespace IEC60908b
|
||||
} // namespace PCSX
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ PCSX::IEC60908b::MSF PCSX::ISO9660Builder::writeSectorAt(const uint8_t* sectorDa
|
||||
ptr[15] = 2;
|
||||
ptr[16] = ptr[20] = 0;
|
||||
ptr[17] = ptr[21] = 0;
|
||||
ptr[18] = ptr[22] = 8;
|
||||
ptr[18] = ptr[22] = 8 | 0x20; // form 2 flag
|
||||
ptr[19] = ptr[23] = 0;
|
||||
memcpy(ptr + 24, sectorData, 2324);
|
||||
compute_edcecc(ptr);
|
||||
|
||||
@@ -59,6 +59,7 @@ enum MidiMeta : uint8_t {
|
||||
MIDI_META_COPYRIGHT = 0x02,
|
||||
MIDI_META_TRACK_NAME = 0x03,
|
||||
MIDI_META_MARKER = 0x06,
|
||||
MIDI_META_CUE_POINT = 0x07,
|
||||
MIDI_META_END_OF_TRACK = 0x2F,
|
||||
MIDI_META_TEMPO = 0x51,
|
||||
};
|
||||
@@ -302,8 +303,8 @@ inline void MidiFile::parseTrack(PCSX::IO<PCSX::File> track) {
|
||||
uint8_t b2 = track->byte();
|
||||
ev.tempo = (b0 << 16) | (b1 << 8) | b2;
|
||||
events.push_back(ev);
|
||||
} else if (metaType == MIDI_META_MARKER || metaType == MIDI_META_TEXT || metaType == MIDI_META_TRACK_NAME ||
|
||||
metaType == MIDI_META_COPYRIGHT) {
|
||||
} else if (metaType == MIDI_META_MARKER || metaType == MIDI_META_CUE_POINT || metaType == MIDI_META_TEXT ||
|
||||
metaType == MIDI_META_TRACK_NAME || metaType == MIDI_META_COPYRIGHT) {
|
||||
// Text-based meta events
|
||||
MidiEvent ev = {};
|
||||
ev.absoluteTick = absTick;
|
||||
|
||||
@@ -31,3 +31,9 @@ function TestBasic:test_coroutine()
|
||||
local r = coroutine.yield()
|
||||
lu.assertEquals(r, 42)
|
||||
end
|
||||
|
||||
function TestBasic:test_unknown_event_name()
|
||||
local ok, err = pcall(PCSX.Events.createEventListener, 'GPU::VSync', function() end)
|
||||
lu.assertFalse(ok)
|
||||
lu.assertStrContains(err, "'GPU::VSync'")
|
||||
end
|
||||
|
||||
@@ -328,6 +328,96 @@ function TestIsoBuilder:test_m2RawRoundTrip()
|
||||
end
|
||||
end
|
||||
|
||||
-- Payload size per sector for each sector mode, where that payload sits in the raw frame, and the form bit
|
||||
-- the builder has to put in the subheader for the modes where it synthesizes one.
|
||||
local sectorLayouts = {
|
||||
{ mode = 'RAW', size = 2352, offset = 0 },
|
||||
{ mode = 'M2_RAW', size = 2336, offset = 16 },
|
||||
{ mode = 'M2_FORM1', size = 2048, offset = 24, form = 0x00 },
|
||||
{ mode = 'M2_FORM2', size = 2324, offset = 24, form = 0x20 },
|
||||
}
|
||||
|
||||
local function patternByte(i) return (i * 7 + 3) % 251 end
|
||||
|
||||
local function patternData(size)
|
||||
local data = ffi.new('uint8_t[?]', size)
|
||||
for i = 0, size - 1 do data[i] = patternByte(i) end
|
||||
return data
|
||||
end
|
||||
|
||||
-- Counts the bytes of a read back payload that differ from the pattern, reporting the first one.
|
||||
local function assertPattern(file, base, size, what)
|
||||
local bad, first = 0, nil
|
||||
for i = 0, size - 1 do
|
||||
if file:readU8At(base + i) ~= patternByte(i) then
|
||||
bad = bad + 1
|
||||
first = first or i
|
||||
end
|
||||
end
|
||||
lu.assertEquals(bad, 0, string.format('%s: %d of %d payload bytes corrupted, first at byte %s', what, bad,
|
||||
size, tostring(first)))
|
||||
end
|
||||
|
||||
local function assertForm(file, base, form, what)
|
||||
for _, offset in ipairs({ 18, 22 }) do
|
||||
lu.assertEquals(bit.band(file:readU8At(base + offset), 0x20), form,
|
||||
string.format('%s: wrong form bit in subheader byte %d', what, offset))
|
||||
end
|
||||
end
|
||||
|
||||
function TestIsoBuilder:test_writeSectorRoundTrip()
|
||||
-- Every mode writeSectorAt takes must land the caller's payload in the frame untouched.
|
||||
for _, layout in ipairs(sectorLayouts) do
|
||||
local out = Support.File.buffer()
|
||||
local builder = PCSX.isoBuilder(out)
|
||||
for lba = 0, 1 do
|
||||
builder:writeSectorAt(patternData(layout.size), layout.size, lba, layout.mode)
|
||||
end
|
||||
out:rSeek(0)
|
||||
for lba = 0, 1 do
|
||||
local what = string.format('writeSectorAt %s sector %d', layout.mode, lba)
|
||||
assertPattern(out, lba * 2352 + layout.offset, layout.size, what)
|
||||
if layout.form then assertForm(out, lba * 2352, layout.form, what) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TestIsoBuilder:test_closeRoundTrip()
|
||||
-- Same for file content laid out by close(), through both the single threaded writer and the parallel one.
|
||||
for _, layout in ipairs(sectorLayouts) do
|
||||
for _, threads in ipairs({ 1, 4 }) do
|
||||
local what = string.format('close(%d) %s', threads, layout.mode)
|
||||
local size = 2 * layout.size
|
||||
local payload = Support.File.buffer()
|
||||
local data = ffi.new('uint8_t[?]', size)
|
||||
for i = 0, size - 1 do data[i] = patternByte(i % layout.size) end
|
||||
payload:write(ffi.cast('const char*', data), size)
|
||||
payload:rSeek(0)
|
||||
|
||||
local out = Support.File.buffer()
|
||||
local builder = PCSX.isoBuilder(out)
|
||||
builder:writeLicense()
|
||||
builder:setVolumeIdent('ROUNDTRIP')
|
||||
local root = builder:createRoot(1)
|
||||
local file = builder:createFile(root, 'PATTERN.BIN', payload)
|
||||
file:setSectorMode(layout.mode)
|
||||
builder:close(threads)
|
||||
|
||||
out:rSeek(0)
|
||||
local iso = PCSX.openIso(out)
|
||||
local lba = file:getLBA()
|
||||
local rb = iso:open(lba, size, layout.mode)
|
||||
for s = 0, 1 do
|
||||
assertPattern(rb, s * layout.size, layout.size, what .. ' sector ' .. s)
|
||||
end
|
||||
if layout.form then
|
||||
local raw = iso:open(lba, 2 * 2352, 'RAW')
|
||||
for s = 0, 1 do assertForm(raw, s * 2352, layout.form, what .. ' sector ' .. s) end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TestIsoBuilder:test_anchorErrorOnBackwardLBA()
|
||||
-- Anchoring to an LBA that's already passed must raise an error at close time.
|
||||
local out = Support.File.buffer()
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
-- Copyright (C) 2026 PCSX-Redux authors
|
||||
--
|
||||
-- This program 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.
|
||||
--
|
||||
-- This program 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 this program; if not, write to the
|
||||
-- Free Software Foundation, Inc.,
|
||||
-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
local lu = require 'luaunit'
|
||||
|
||||
TestMds = {}
|
||||
|
||||
local content = string.rep('0123456789ABCDEF', 256)
|
||||
|
||||
local function u16(v) return string.char(v % 256, math.floor(v / 256) % 256) end
|
||||
local function u32(v) return u16(v % 65536) .. u16(math.floor(v / 65536)) end
|
||||
|
||||
local function pad(s, n) return s .. string.rep('\0', n - #s) end
|
||||
|
||||
-- Build a one-track ISO, then store it as an .mdf with 96 bytes of
|
||||
-- interleaved subchannel after each 2352-byte frame, plus a minimal .mds.
|
||||
local function writeImage(base)
|
||||
local out = Support.File.buffer()
|
||||
local builder = PCSX.isoBuilder(out)
|
||||
builder:writeLicense()
|
||||
local root = builder:createRoot(1)
|
||||
local data = Support.File.buffer()
|
||||
data:write(content)
|
||||
data:rSeek(0)
|
||||
builder:createFile(root, 'TEST.DAT', data)
|
||||
builder:close()
|
||||
|
||||
local size = out:size()
|
||||
lu.assertEquals(size % 2352, 0)
|
||||
local sectors = size / 2352
|
||||
out:rSeek(0)
|
||||
local mdf = io.open(base .. '.mdf', 'wb')
|
||||
for _ = 1, sectors do
|
||||
mdf:write(tostring(out:read(2352)), string.rep('\0', 96))
|
||||
end
|
||||
mdf:close()
|
||||
|
||||
-- Header: signature, session block at 0x58, one track block at 0x70,
|
||||
-- its extra block (pregap, length) at 0xc0.
|
||||
local mds = pad('MEDIA DESCRIPTOR', 0x50) .. u32(0x58) .. u32(0)
|
||||
mds = mds .. pad(string.rep('\0', 14) .. u16(1) .. u32(0) .. u32(0x70), 0x18)
|
||||
local track = string.char(0xec, 0x08, 0x14, 0x00, 0x01) .. string.rep('\0', 4) .. string.char(0, 2, 0) ..
|
||||
u32(0xc0) .. u16(2448)
|
||||
mds = mds .. pad(pad(track, 0x28) .. u32(0) .. u32(0), 0x50)
|
||||
mds = mds .. u32(0) .. u32(sectors)
|
||||
local f = io.open(base .. '.mds', 'wb')
|
||||
f:write(mds)
|
||||
f:close()
|
||||
end
|
||||
|
||||
local function checkRead(path)
|
||||
local iso = PCSX.openIso(path)
|
||||
lu.assertFalse(iso:failed())
|
||||
local file = iso:createReader():open('TEST.DAT;1')
|
||||
lu.assertFalse(file:failed())
|
||||
lu.assertEquals(file:size(), #content)
|
||||
lu.assertEquals(tostring(file:read(#content)), content)
|
||||
end
|
||||
|
||||
function TestMds:setUp()
|
||||
self.base = os.tmpname()
|
||||
writeImage(self.base)
|
||||
end
|
||||
|
||||
function TestMds:tearDown()
|
||||
os.remove(self.base .. '.mdf')
|
||||
os.remove(self.base .. '.mds')
|
||||
os.remove(self.base)
|
||||
end
|
||||
|
||||
function TestMds:test_openMdf() checkRead(self.base .. '.mdf') end
|
||||
|
||||
function TestMds:test_openMds() checkRead(self.base .. '.mds') end
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Copyright (C) 2026 PCSX-Redux authors
|
||||
--
|
||||
-- This program 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.
|
||||
--
|
||||
-- This program 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 this program; if not, write to the
|
||||
-- Free Software Foundation, Inc.,
|
||||
-- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
error('startup script error')
|
||||
@@ -0,0 +1,72 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2022 PCSX-Redux authors *
|
||||
* *
|
||||
* This program 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. *
|
||||
* *
|
||||
* This program 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 this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "main/main.h"
|
||||
|
||||
TEST(cdrom, Interpreter) {
|
||||
MainInvoker invoker("-run", "-stdout", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
|
||||
"-iso", "test.cue", "-loadexe", "src/mips/tests/cdrom/cdrom.ps-exe");
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
|
||||
TEST(cdrom, Dynarec) {
|
||||
MainInvoker invoker("-run", "-stdout", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-dynarec", "-iso",
|
||||
"test.cue", "-loadexe", "src/mips/tests/cdrom/cdrom.ps-exe");
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
// Boots the test disc through OpenBIOS's shell and kernel loader, with no -loadexe. Passes once
|
||||
// the disc's boot executable is in RAM and the CPU is running inside it, fails after 6000 frames.
|
||||
static const char c_discBoot[] = R"(
|
||||
local f = assert(io.open('src/mips/monitor/hosts/retail/monitor-retail.ps-exe', 'rb'))
|
||||
local exe = f:read('*a')
|
||||
f:close()
|
||||
local function u32(o)
|
||||
local a, b, c, d = exe:byte(o + 1, o + 4)
|
||||
return a + b * 0x100 + c * 0x10000 + d * 0x1000000
|
||||
end
|
||||
local tAddr, tSize = u32(0x18), u32(0x1c)
|
||||
local text = exe:sub(0x801, 0x800 + tSize)
|
||||
local frames = 0
|
||||
DiscBootListener = PCSX.Events.createEventListener('GPU::Vsync', function()
|
||||
frames = frames + 1
|
||||
local pc = PCSX.getRegisters().pc
|
||||
if pc >= tAddr and pc < tAddr + tSize then
|
||||
local ram = ffi.string(PCSX.getMemPtr() + bit.band(tAddr, 0x1fffff), tSize)
|
||||
if ram == text then PCSX.quit(0) end
|
||||
end
|
||||
if frames >= 6000 then PCSX.quit(1) end
|
||||
end)
|
||||
)";
|
||||
|
||||
TEST(cdrom, OpenBIOSBootsDiscInterpreter) {
|
||||
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
|
||||
"-iso", "test.cue", "-exec", c_discBoot);
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
|
||||
TEST(cdrom, OpenBIOSBootsDiscDynarec) {
|
||||
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-dynarec", "-iso",
|
||||
"test.cue", "-exec", c_discBoot);
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
@@ -28,6 +28,13 @@ TEST(COPBranch, Interpreter) {
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
|
||||
TEST(COPBranch, Dynarec) {
|
||||
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-dynarec", "-luacov",
|
||||
"-loadexe", "src/mips/tests/cop-branch/cop-branch.ps-exe");
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
|
||||
TEST(COPBranch, Disassembler) {
|
||||
static constexpr struct {
|
||||
uint32_t code;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2026 PCSX-Redux authors *
|
||||
* *
|
||||
* This program 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. *
|
||||
* *
|
||||
* This program 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 this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "main/main.h"
|
||||
|
||||
// Boots the gpu-command guest suite headless under OpenBIOS. ret == 0 means
|
||||
// every GP0 env command's GPUSTAT reflection matched the soft GPU.
|
||||
TEST(GPUCommand, StatusAfterWrite) {
|
||||
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
|
||||
"-loadexe", "src/mips/tests/gpu-command/gpu-command.ps-exe");
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2026 PCSX-Redux authors *
|
||||
* *
|
||||
* This program 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. *
|
||||
* *
|
||||
* This program 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 this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "main/main.h"
|
||||
|
||||
// Boots the gpu-fbrect guest suite headless under OpenBIOS. The guest
|
||||
// returns cester's failed-test count as its exit code; -testmode maps that
|
||||
// to the process exit code, so ret == 0 means every GP0(0x02) FillVram
|
||||
// assertion matched the soft GPU.
|
||||
TEST(GPUFBRect, FillVram) {
|
||||
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
|
||||
"-loadexe", "src/mips/tests/gpu-fbrect/gpu-fbrect.ps-exe");
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2026 PCSX-Redux authors *
|
||||
* *
|
||||
* This program 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. *
|
||||
* *
|
||||
* This program 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 this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "main/main.h"
|
||||
|
||||
// Boots the gpu-irq guest suite headless under OpenBIOS. ret == 0 means the
|
||||
// GP0(1Fh)/GP1(02h) IRQ flag behaviour in GPUSTAT.24 matched the soft GPU.
|
||||
TEST(GPUIRQ, RequestAcknowledge) {
|
||||
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
|
||||
"-loadexe", "src/mips/tests/gpu-irq/gpu-irq.ps-exe");
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
|
||||
// IRQ1 survives a save state. Code poked into RAM raises it with GP0(1Fh) and keeps copying
|
||||
// GPUSTAT to 0x80100100; a state is saved, GP1(02h) acks the flag, the state is loaded, and
|
||||
// GPUSTAT.24 must be set again. Loading replays GP1 writes that clear the flag before
|
||||
// GPU::deserialize puts it back.
|
||||
static const char c_irq1SaveState[] = R"(
|
||||
local ram = ffi.cast('uint32_t*', PCSX.getMemPtr())
|
||||
local base = 0x100000 / 4
|
||||
local out = 0x100100 / 4
|
||||
local stubs = {
|
||||
-- 0x80100000: GP0(1Fh) raises IRQ1, then GPUSTAT -> 0x80100100 forever
|
||||
0x3c081f80, 0x3c091f00, 0xad091810, 0x3c0b8010,
|
||||
0x8d0a1814, 0x00000000, 0xad6a0100, 0x08040004, 0x00000000,
|
||||
0, 0, 0, 0, 0, 0, 0,
|
||||
-- 0x80100040: GP1(02h) acks IRQ1, then the same loop
|
||||
0x3c081f80, 0x3c090200, 0xad091814, 0x3c0b8010,
|
||||
0x8d0a1814, 0x00000000, 0xad6a0100, 0x08040014, 0x00000000,
|
||||
}
|
||||
local sentinel = 0xdeadbeef
|
||||
local frames = 0
|
||||
local state
|
||||
local function irq1() return bit.band(ram[out], 0x01000000) ~= 0 and ram[out] ~= sentinel end
|
||||
IRQ1SaveStateListener = PCSX.Events.createEventListener('GPU::Vsync', function()
|
||||
frames = frames + 1
|
||||
if frames == 120 then
|
||||
for i, w in ipairs(stubs) do ram[base + i - 1] = w end
|
||||
ram[out] = sentinel
|
||||
PCSX.getRegisters().pc = 0x80100000
|
||||
elseif frames == 122 then
|
||||
if not irq1() then print('IRQ1 never raised') PCSX.quit(2) return end
|
||||
state = PCSX.createSaveState()
|
||||
ram[out] = sentinel
|
||||
PCSX.getRegisters().pc = 0x80100040
|
||||
elseif frames == 124 then
|
||||
if ram[out] == sentinel or irq1() then print('GP1(02h) did not ack IRQ1') PCSX.quit(3) return end
|
||||
PCSX.loadSaveState(state)
|
||||
elseif frames == 125 then
|
||||
ram[out] = sentinel
|
||||
elseif frames == 127 then
|
||||
if irq1() then PCSX.quit(0) else print('IRQ1 lost across the save state') PCSX.quit(1) end
|
||||
end
|
||||
end)
|
||||
)";
|
||||
|
||||
TEST(GPUIRQ, SaveStateKeepsFlag) {
|
||||
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
|
||||
"-exec", c_irq1SaveState);
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2026 PCSX-Redux authors *
|
||||
* *
|
||||
* This program 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. *
|
||||
* *
|
||||
* This program 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 this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "main/main.h"
|
||||
|
||||
// Boots the gpu-polyline guest suite headless under OpenBIOS. The guest
|
||||
// returns cester's failed-test count as its exit code; -testmode maps that
|
||||
// to the process exit code, so ret == 0 means every GP0(0x48) terminator
|
||||
// assertion matched the soft GPU.
|
||||
TEST(GPUPolyline, Terminator) {
|
||||
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
|
||||
"-loadexe", "src/mips/tests/gpu-polyline/gpu-polyline.ps-exe");
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/***************************************************************************
|
||||
* Copyright (C) 2026 PCSX-Redux authors *
|
||||
* *
|
||||
* This program 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. *
|
||||
* *
|
||||
* This program 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 this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "main/main.h"
|
||||
|
||||
// Boots the gpu-vram-move guest suite headless under OpenBIOS. ret == 0 means
|
||||
// every GP0(0x80) VRAM-to-VRAM copy assertion matched the soft GPU.
|
||||
TEST(GPUVRAMMove, Copy) {
|
||||
MainInvoker invoker("-no-ui", "-run", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
|
||||
"-loadexe", "src/mips/tests/gpu-vram-move/gpu-vram-move.ps-exe");
|
||||
int ret = invoker.invoke();
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
|
||||
***************************************************************************/
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "main/main.h"
|
||||
|
||||
@@ -73,3 +75,14 @@ TEST(LuaRewind, Interpreter) { EXPECT_EQ(runLuaIntTest("tests.lua.rewind"), 0);
|
||||
TEST(LuaRewind, Dynarec) { EXPECT_EQ(runLuaDynTest("tests.lua.rewind"), 0); }
|
||||
TEST(LuaIsoBuilder, Interpreter) { EXPECT_EQ(runLuaIntTest("tests.lua.isobuilder"), 0); }
|
||||
TEST(LuaIsoBuilder, Dynarec) { EXPECT_EQ(runLuaDynTest("tests.lua.isobuilder"), 0); }
|
||||
TEST(LuaMds, Interpreter) { EXPECT_EQ(runLuaIntTest("tests.lua.mds"), 0); }
|
||||
|
||||
TEST(LuaStartup, DofileError) {
|
||||
MainInvoker failing("-no-ui", "-cli", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter",
|
||||
"-dofile", "tests/lua/startup-error.lua");
|
||||
EXPECT_THROW(failing.invoke(), std::runtime_error);
|
||||
// The failed startup still has to shut down cleanly, or nothing else can start afterwards.
|
||||
MainInvoker next("-no-ui", "-cli", "-bios", "src/mips/openbios/openbios.bin", "-testmode", "-interpreter", "-exec",
|
||||
"PCSX.quit(0)");
|
||||
EXPECT_EQ(next.invoke(), 0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Every field of the CD-ROM save state has to come back out of a save state
|
||||
* exactly as it went in.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/sstate.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace PCSX::SaveStates;
|
||||
|
||||
struct Queue {
|
||||
uint8_t payload[16] = {};
|
||||
uint8_t value = 0;
|
||||
bool valueRead = false;
|
||||
bool hasValue = false;
|
||||
bool hitMax = false;
|
||||
uint8_t payloadSize = 0;
|
||||
uint8_t payloadIndex = 0;
|
||||
};
|
||||
|
||||
struct State {
|
||||
uint8_t datafifo[2352] = {};
|
||||
uint32_t datafifoindex = 0;
|
||||
uint32_t datafifosize = 0;
|
||||
uint32_t datafifopending = 0;
|
||||
uint8_t registeraddress = 0;
|
||||
bool motoron = false;
|
||||
bool speedchanged = false;
|
||||
bool invalidlocl = false;
|
||||
bool datarequested = false;
|
||||
bool subheaderfilter = false;
|
||||
bool realtime = false;
|
||||
uint8_t readingstate = 0;
|
||||
bool startplaying = false;
|
||||
uint8_t readingtype = 0;
|
||||
bool autopause = false;
|
||||
bool report = false;
|
||||
bool setlocpending = false;
|
||||
bool muted = false;
|
||||
bool peakflag = false;
|
||||
uint8_t playtrack = 0;
|
||||
uint32_t playstartcycle = 0;
|
||||
uint8_t status = 0;
|
||||
uint8_t speed = 0;
|
||||
uint8_t readspan = 0;
|
||||
uint8_t interruptcausemask = 0;
|
||||
uint8_t atv[4] = {};
|
||||
uint8_t atvpending[4] = {};
|
||||
bool adpcmmuted = false;
|
||||
uint8_t mode = 0;
|
||||
uint8_t filterfile = 0;
|
||||
uint8_t filterchannel = 0;
|
||||
bool xaended = false;
|
||||
bool xafirstsector = false;
|
||||
bool soundmapenabled = false;
|
||||
uint8_t currentposition[3] = {};
|
||||
uint8_t seekposition[3] = {};
|
||||
uint8_t lastlocp[8] = {};
|
||||
uint8_t lastlocl[8] = {};
|
||||
uint64_t seed = 0;
|
||||
bool lidopen = false;
|
||||
bool waslidopened = false;
|
||||
bool lidclosescheduled = false;
|
||||
uint32_t lidcloseatcycles = 0;
|
||||
Queue commandfifo;
|
||||
Queue commandexecuting;
|
||||
Queue responsefifo0;
|
||||
Queue responsefifo1;
|
||||
int32_t xaleft[2] = {};
|
||||
int32_t xaright[2] = {};
|
||||
};
|
||||
|
||||
CDRom bind(State& s) {
|
||||
return CDRom{
|
||||
CDDataFIFO{s.datafifo},
|
||||
CDDataFIFOIndex{s.datafifoindex},
|
||||
CDDataFIFOSize{s.datafifosize},
|
||||
CDDataFIFOPending{s.datafifopending},
|
||||
CDRegisterAddress{s.registeraddress},
|
||||
CDMotorOn{s.motoron},
|
||||
CDSpeedChanged{s.speedchanged},
|
||||
CDInvalidLocL{s.invalidlocl},
|
||||
CDDataRequested{s.datarequested},
|
||||
CDSubheaderFilter{s.subheaderfilter},
|
||||
CDRealtime{s.realtime},
|
||||
CDReadingState{s.readingstate},
|
||||
CDStartPlaying{s.startplaying},
|
||||
CDReadingType{s.readingtype},
|
||||
CDAutoPause{s.autopause},
|
||||
CDReport{s.report},
|
||||
CDSetLocPending{s.setlocpending},
|
||||
CDMuted{s.muted},
|
||||
CDPeakFlag{s.peakflag},
|
||||
CDPlayTrack{s.playtrack},
|
||||
CDPlayStartCycle{s.playstartcycle},
|
||||
CDStatus{s.status},
|
||||
CDSpeed{s.speed},
|
||||
CDReadSpan{s.readspan},
|
||||
CDInterruptCauseMask{s.interruptcausemask},
|
||||
CDATV{s.atv},
|
||||
CDATVPending{s.atvpending},
|
||||
CDADPCMMuted{s.adpcmmuted},
|
||||
CDMode{s.mode},
|
||||
CDFilterFile{s.filterfile},
|
||||
CDFilterChannel{s.filterchannel},
|
||||
CDXAEnded{s.xaended},
|
||||
CDXAFirstSector{s.xafirstsector},
|
||||
CDSoundMapEnabled{s.soundmapenabled},
|
||||
CDCurrentPosition{CDMSF{CDMSFMinute{s.currentposition[0]}, CDMSFSecond{s.currentposition[1]}, CDMSFFrame{s.currentposition[2]}}},
|
||||
CDSeekPosition{CDMSF{CDMSFMinute{s.seekposition[0]}, CDMSFSecond{s.seekposition[1]}, CDMSFFrame{s.seekposition[2]}}},
|
||||
CDLastLocP{s.lastlocp},
|
||||
CDLastLocL{s.lastlocl},
|
||||
CDSeed{s.seed},
|
||||
CDLidOpen{s.lidopen},
|
||||
CDWasLidOpened{s.waslidopened},
|
||||
CDLidCloseScheduled{s.lidclosescheduled},
|
||||
CDLidCloseAtCycles{s.lidcloseatcycles},
|
||||
CDCommandFifo{CDQueueElement{CDQueuePayload{s.commandfifo.payload}, CDQueueValue{s.commandfifo.value}, CDQueueValueRead{s.commandfifo.valueRead}, CDQueueHasValue{s.commandfifo.hasValue}, CDQueueHitMax{s.commandfifo.hitMax}, CDQueuePayloadSize{s.commandfifo.payloadSize}, CDQueuePayloadIndex{s.commandfifo.payloadIndex}}},
|
||||
CDCommandExecuting{CDQueueElement{CDQueuePayload{s.commandexecuting.payload}, CDQueueValue{s.commandexecuting.value}, CDQueueValueRead{s.commandexecuting.valueRead}, CDQueueHasValue{s.commandexecuting.hasValue}, CDQueueHitMax{s.commandexecuting.hitMax}, CDQueuePayloadSize{s.commandexecuting.payloadSize}, CDQueuePayloadIndex{s.commandexecuting.payloadIndex}}},
|
||||
CDResponseFifo0{CDQueueElement{CDQueuePayload{s.responsefifo0.payload}, CDQueueValue{s.responsefifo0.value}, CDQueueValueRead{s.responsefifo0.valueRead}, CDQueueHasValue{s.responsefifo0.hasValue}, CDQueueHitMax{s.responsefifo0.hitMax}, CDQueuePayloadSize{s.responsefifo0.payloadSize}, CDQueuePayloadIndex{s.responsefifo0.payloadIndex}}},
|
||||
CDResponseFifo1{CDQueueElement{CDQueuePayload{s.responsefifo1.payload}, CDQueueValue{s.responsefifo1.value}, CDQueueValueRead{s.responsefifo1.valueRead}, CDQueueHasValue{s.responsefifo1.hasValue}, CDQueueHitMax{s.responsefifo1.hitMax}, CDQueuePayloadSize{s.responsefifo1.payloadSize}, CDQueuePayloadIndex{s.responsefifo1.payloadIndex}}},
|
||||
CDXALeft{CDADPCM{CDADPCMY0{s.xaleft[0]}, CDADPCMY1{s.xaleft[1]}}},
|
||||
CDXARight{CDADPCM{CDADPCMY0{s.xaright[0]}, CDADPCMY1{s.xaright[1]}}},
|
||||
};
|
||||
}
|
||||
|
||||
// No field may hold its default, so a field that is lost or swapped shows up.
|
||||
void fill(State& s) {
|
||||
for (unsigned i = 0; i < 2352; i++) s.datafifo[i] = static_cast<uint8_t>(i * 7 + 2);
|
||||
s.datafifoindex = 0x80000000u + 314187;
|
||||
s.datafifosize = 0x80000000u + 418916;
|
||||
s.datafifopending = 0x80000000u + 523645;
|
||||
s.registeraddress = 79;
|
||||
s.motoron = true;
|
||||
s.speedchanged = true;
|
||||
s.invalidlocl = true;
|
||||
s.datarequested = true;
|
||||
s.subheaderfilter = true;
|
||||
s.realtime = true;
|
||||
s.readingstate = 92;
|
||||
s.startplaying = true;
|
||||
s.readingtype = 105;
|
||||
s.autopause = true;
|
||||
s.report = true;
|
||||
s.setlocpending = true;
|
||||
s.muted = true;
|
||||
s.peakflag = true;
|
||||
s.playtrack = 118;
|
||||
s.playstartcycle = 0x80000000u + 1047290;
|
||||
s.status = 144;
|
||||
s.speed = 157;
|
||||
s.readspan = 170;
|
||||
s.interruptcausemask = 183;
|
||||
for (unsigned i = 0; i < 4; i++) s.atv[i] = static_cast<uint8_t>(i * 7 + 15);
|
||||
for (unsigned i = 0; i < 4; i++) s.atvpending[i] = static_cast<uint8_t>(i * 7 + 16);
|
||||
s.adpcmmuted = true;
|
||||
s.mode = 222;
|
||||
s.filterfile = 235;
|
||||
s.filterchannel = 248;
|
||||
s.xaended = true;
|
||||
s.xafirstsector = true;
|
||||
s.soundmapenabled = true;
|
||||
for (unsigned i = 0; i < 3; i++) s.currentposition[i] = static_cast<uint8_t>(i * 7 + 20);
|
||||
for (unsigned i = 0; i < 3; i++) s.seekposition[i] = static_cast<uint8_t>(i * 7 + 21);
|
||||
for (unsigned i = 0; i < 8; i++) s.lastlocp[i] = static_cast<uint8_t>(i * 7 + 22);
|
||||
for (unsigned i = 0; i < 8; i++) s.lastlocl[i] = static_cast<uint8_t>(i * 7 + 23);
|
||||
s.seed = 0x8000000000000000ull + 24000072;
|
||||
s.lidopen = true;
|
||||
s.waslidopened = true;
|
||||
s.lidclosescheduled = true;
|
||||
s.lidcloseatcycles = 0x80000000u + 2618225;
|
||||
for (unsigned i = 0; i < 16; i++) s.commandfifo.payload[i] = static_cast<uint8_t>(i * 7 + 26);
|
||||
s.commandfifo.value = 101;
|
||||
s.commandfifo.valueRead = true;
|
||||
s.commandfifo.hasValue = true;
|
||||
s.commandfifo.hitMax = true;
|
||||
s.commandfifo.payloadSize = 114;
|
||||
s.commandfifo.payloadIndex = 127;
|
||||
for (unsigned i = 0; i < 16; i++) s.commandexecuting.payload[i] = static_cast<uint8_t>(i * 7 + 30);
|
||||
s.commandexecuting.value = 153;
|
||||
s.commandexecuting.valueRead = true;
|
||||
s.commandexecuting.hasValue = true;
|
||||
s.commandexecuting.hitMax = true;
|
||||
s.commandexecuting.payloadSize = 166;
|
||||
s.commandexecuting.payloadIndex = 179;
|
||||
for (unsigned i = 0; i < 16; i++) s.responsefifo0.payload[i] = static_cast<uint8_t>(i * 7 + 34);
|
||||
s.responsefifo0.value = 205;
|
||||
s.responsefifo0.valueRead = true;
|
||||
s.responsefifo0.hasValue = true;
|
||||
s.responsefifo0.hitMax = true;
|
||||
s.responsefifo0.payloadSize = 218;
|
||||
s.responsefifo0.payloadIndex = 231;
|
||||
for (unsigned i = 0; i < 16; i++) s.responsefifo1.payload[i] = static_cast<uint8_t>(i * 7 + 38);
|
||||
s.responsefifo1.value = 6;
|
||||
s.responsefifo1.valueRead = true;
|
||||
s.responsefifo1.hasValue = true;
|
||||
s.responsefifo1.hitMax = true;
|
||||
s.responsefifo1.payloadSize = 19;
|
||||
s.responsefifo1.payloadIndex = 32;
|
||||
s.xaleft[0] = -332598;
|
||||
s.xaleft[1] = -340517;
|
||||
s.xaright[0] = -348436;
|
||||
s.xaright[1] = -356355;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(SaveStateCDRom, EveryFieldRoundTrips) {
|
||||
State in, out;
|
||||
fill(in);
|
||||
PCSX::Protobuf::OutSlice outSlice;
|
||||
bind(in).serialize(&outSlice);
|
||||
std::string data = outSlice.finalize();
|
||||
|
||||
CDRom loaded = bind(out);
|
||||
PCSX::Protobuf::InSlice inSlice(reinterpret_cast<const uint8_t*>(data.data()), data.size());
|
||||
loaded.deserialize(&inSlice, 0);
|
||||
loaded.commit();
|
||||
|
||||
for (unsigned i = 0; i < 2352; i++) EXPECT_EQ(out.datafifo[i], in.datafifo[i]) << "datafifo[" << i << "]";
|
||||
EXPECT_EQ(out.datafifoindex, in.datafifoindex) << "datafifoindex";
|
||||
EXPECT_EQ(out.datafifosize, in.datafifosize) << "datafifosize";
|
||||
EXPECT_EQ(out.datafifopending, in.datafifopending) << "datafifopending";
|
||||
EXPECT_EQ(out.registeraddress, in.registeraddress) << "registeraddress";
|
||||
EXPECT_EQ(out.motoron, in.motoron) << "motoron";
|
||||
EXPECT_EQ(out.speedchanged, in.speedchanged) << "speedchanged";
|
||||
EXPECT_EQ(out.invalidlocl, in.invalidlocl) << "invalidlocl";
|
||||
EXPECT_EQ(out.datarequested, in.datarequested) << "datarequested";
|
||||
EXPECT_EQ(out.subheaderfilter, in.subheaderfilter) << "subheaderfilter";
|
||||
EXPECT_EQ(out.realtime, in.realtime) << "realtime";
|
||||
EXPECT_EQ(out.readingstate, in.readingstate) << "readingstate";
|
||||
EXPECT_EQ(out.startplaying, in.startplaying) << "startplaying";
|
||||
EXPECT_EQ(out.readingtype, in.readingtype) << "readingtype";
|
||||
EXPECT_EQ(out.autopause, in.autopause) << "autopause";
|
||||
EXPECT_EQ(out.report, in.report) << "report";
|
||||
EXPECT_EQ(out.setlocpending, in.setlocpending) << "setlocpending";
|
||||
EXPECT_EQ(out.muted, in.muted) << "muted";
|
||||
EXPECT_EQ(out.peakflag, in.peakflag) << "peakflag";
|
||||
EXPECT_EQ(out.playtrack, in.playtrack) << "playtrack";
|
||||
EXPECT_EQ(out.playstartcycle, in.playstartcycle) << "playstartcycle";
|
||||
EXPECT_EQ(out.status, in.status) << "status";
|
||||
EXPECT_EQ(out.speed, in.speed) << "speed";
|
||||
EXPECT_EQ(out.readspan, in.readspan) << "readspan";
|
||||
EXPECT_EQ(out.interruptcausemask, in.interruptcausemask) << "interruptcausemask";
|
||||
for (unsigned i = 0; i < 4; i++) EXPECT_EQ(out.atv[i], in.atv[i]) << "atv[" << i << "]";
|
||||
for (unsigned i = 0; i < 4; i++) EXPECT_EQ(out.atvpending[i], in.atvpending[i]) << "atvpending[" << i << "]";
|
||||
EXPECT_EQ(out.adpcmmuted, in.adpcmmuted) << "adpcmmuted";
|
||||
EXPECT_EQ(out.mode, in.mode) << "mode";
|
||||
EXPECT_EQ(out.filterfile, in.filterfile) << "filterfile";
|
||||
EXPECT_EQ(out.filterchannel, in.filterchannel) << "filterchannel";
|
||||
EXPECT_EQ(out.xaended, in.xaended) << "xaended";
|
||||
EXPECT_EQ(out.xafirstsector, in.xafirstsector) << "xafirstsector";
|
||||
EXPECT_EQ(out.soundmapenabled, in.soundmapenabled) << "soundmapenabled";
|
||||
for (unsigned i = 0; i < 3; i++) EXPECT_EQ(out.currentposition[i], in.currentposition[i]) << "currentposition[" << i << "]";
|
||||
for (unsigned i = 0; i < 3; i++) EXPECT_EQ(out.seekposition[i], in.seekposition[i]) << "seekposition[" << i << "]";
|
||||
for (unsigned i = 0; i < 8; i++) EXPECT_EQ(out.lastlocp[i], in.lastlocp[i]) << "lastlocp[" << i << "]";
|
||||
for (unsigned i = 0; i < 8; i++) EXPECT_EQ(out.lastlocl[i], in.lastlocl[i]) << "lastlocl[" << i << "]";
|
||||
EXPECT_EQ(out.seed, in.seed) << "seed";
|
||||
EXPECT_EQ(out.lidopen, in.lidopen) << "lidopen";
|
||||
EXPECT_EQ(out.waslidopened, in.waslidopened) << "waslidopened";
|
||||
EXPECT_EQ(out.lidclosescheduled, in.lidclosescheduled) << "lidclosescheduled";
|
||||
EXPECT_EQ(out.lidcloseatcycles, in.lidcloseatcycles) << "lidcloseatcycles";
|
||||
for (unsigned i = 0; i < 16; i++) EXPECT_EQ(out.commandfifo.payload[i], in.commandfifo.payload[i]) << "commandfifo.payload[" << i << "]";
|
||||
EXPECT_EQ(out.commandfifo.value, in.commandfifo.value) << "commandfifo.value";
|
||||
EXPECT_EQ(out.commandfifo.valueRead, in.commandfifo.valueRead) << "commandfifo.valueRead";
|
||||
EXPECT_EQ(out.commandfifo.hasValue, in.commandfifo.hasValue) << "commandfifo.hasValue";
|
||||
EXPECT_EQ(out.commandfifo.hitMax, in.commandfifo.hitMax) << "commandfifo.hitMax";
|
||||
EXPECT_EQ(out.commandfifo.payloadSize, in.commandfifo.payloadSize) << "commandfifo.payloadSize";
|
||||
EXPECT_EQ(out.commandfifo.payloadIndex, in.commandfifo.payloadIndex) << "commandfifo.payloadIndex";
|
||||
for (unsigned i = 0; i < 16; i++) EXPECT_EQ(out.commandexecuting.payload[i], in.commandexecuting.payload[i]) << "commandexecuting.payload[" << i << "]";
|
||||
EXPECT_EQ(out.commandexecuting.value, in.commandexecuting.value) << "commandexecuting.value";
|
||||
EXPECT_EQ(out.commandexecuting.valueRead, in.commandexecuting.valueRead) << "commandexecuting.valueRead";
|
||||
EXPECT_EQ(out.commandexecuting.hasValue, in.commandexecuting.hasValue) << "commandexecuting.hasValue";
|
||||
EXPECT_EQ(out.commandexecuting.hitMax, in.commandexecuting.hitMax) << "commandexecuting.hitMax";
|
||||
EXPECT_EQ(out.commandexecuting.payloadSize, in.commandexecuting.payloadSize) << "commandexecuting.payloadSize";
|
||||
EXPECT_EQ(out.commandexecuting.payloadIndex, in.commandexecuting.payloadIndex) << "commandexecuting.payloadIndex";
|
||||
for (unsigned i = 0; i < 16; i++) EXPECT_EQ(out.responsefifo0.payload[i], in.responsefifo0.payload[i]) << "responsefifo0.payload[" << i << "]";
|
||||
EXPECT_EQ(out.responsefifo0.value, in.responsefifo0.value) << "responsefifo0.value";
|
||||
EXPECT_EQ(out.responsefifo0.valueRead, in.responsefifo0.valueRead) << "responsefifo0.valueRead";
|
||||
EXPECT_EQ(out.responsefifo0.hasValue, in.responsefifo0.hasValue) << "responsefifo0.hasValue";
|
||||
EXPECT_EQ(out.responsefifo0.hitMax, in.responsefifo0.hitMax) << "responsefifo0.hitMax";
|
||||
EXPECT_EQ(out.responsefifo0.payloadSize, in.responsefifo0.payloadSize) << "responsefifo0.payloadSize";
|
||||
EXPECT_EQ(out.responsefifo0.payloadIndex, in.responsefifo0.payloadIndex) << "responsefifo0.payloadIndex";
|
||||
for (unsigned i = 0; i < 16; i++) EXPECT_EQ(out.responsefifo1.payload[i], in.responsefifo1.payload[i]) << "responsefifo1.payload[" << i << "]";
|
||||
EXPECT_EQ(out.responsefifo1.value, in.responsefifo1.value) << "responsefifo1.value";
|
||||
EXPECT_EQ(out.responsefifo1.valueRead, in.responsefifo1.valueRead) << "responsefifo1.valueRead";
|
||||
EXPECT_EQ(out.responsefifo1.hasValue, in.responsefifo1.hasValue) << "responsefifo1.hasValue";
|
||||
EXPECT_EQ(out.responsefifo1.hitMax, in.responsefifo1.hitMax) << "responsefifo1.hitMax";
|
||||
EXPECT_EQ(out.responsefifo1.payloadSize, in.responsefifo1.payloadSize) << "responsefifo1.payloadSize";
|
||||
EXPECT_EQ(out.responsefifo1.payloadIndex, in.responsefifo1.payloadIndex) << "responsefifo1.payloadIndex";
|
||||
EXPECT_EQ(out.xaleft[0], in.xaleft[0]) << "xaleft[0]";
|
||||
EXPECT_EQ(out.xaleft[1], in.xaleft[1]) << "xaleft[1]";
|
||||
EXPECT_EQ(out.xaright[0], in.xaright[0]) << "xaright[0]";
|
||||
EXPECT_EQ(out.xaright[1], in.xaright[1]) << "xaright[1]";
|
||||
}
|
||||
+165
-118
@@ -6,15 +6,18 @@
|
||||
#include "fmt/format.h"
|
||||
#include "imgui_stdlib.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define _PRISizeT "I"
|
||||
#if defined(_MSC_VER) && !defined(snprintf)
|
||||
#define ImSnprintf _snprintf
|
||||
#else
|
||||
#define _PRISizeT "z"
|
||||
#define ImSnprintf snprintf
|
||||
#endif
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#define _PRISizeT "I"
|
||||
#else
|
||||
#define _PRISizeT "z"
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#if defined(_MSC_VER) || defined(_UCRT)
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
|
||||
#endif
|
||||
@@ -34,13 +37,16 @@ MemoryEditor::MemoryEditor(bool& show, size_t base_addr, size_t &goto_addr) : Op
|
||||
OptAddrDigitsCount = 0;
|
||||
OptFooterExtraHeight = 0.0f;
|
||||
HighlightColor = IM_COL32(255, 255, 255, 50);
|
||||
HighlightFn = NULL;
|
||||
HighlightFn = nullptr;
|
||||
MouseHovered = false;
|
||||
MouseHoveredAddr = 0;
|
||||
// State/Internals
|
||||
ContentsWidthChanged = false;
|
||||
DataPreviewAddr = DataEditingAddr = (size_t)-1;
|
||||
DataEditingTakeFocus = false;
|
||||
GotoAddr = (size_t)-1;
|
||||
HighlightMin = HighlightMax = (size_t)-1;
|
||||
PreviewEndianess = 0;
|
||||
PreviewEndianness = 0;
|
||||
PreviewDataType = ImGuiDataType_S32;
|
||||
RestoreOffset = true;
|
||||
}
|
||||
@@ -66,17 +72,17 @@ void MemoryEditor::CalcSizes(Sizes& s, size_t mem_size)
|
||||
s.ByteSpacingWidth = (float)(int)(s.GlyphWidth * 0.5f);
|
||||
s.HexCellWidth = (float)(int)((s.ByteWidth * (float)(int)DataTypeGetSize(PreviewDataType) + s.ByteSpacingWidth)); // "FF " we include trailing space in the width to easily catch clicks everywhere
|
||||
s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
|
||||
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth;
|
||||
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * (float)(int)(Cols / DataTypeGetSize(PreviewDataType)));
|
||||
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd;
|
||||
s.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth;
|
||||
s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * (float)(int)(Cols / DataTypeGetSize(PreviewDataType)));
|
||||
s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX;
|
||||
if (OptShowAscii)
|
||||
{
|
||||
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
|
||||
s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1;
|
||||
if (OptMidColsCount > 0)
|
||||
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
|
||||
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
|
||||
s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
|
||||
s.OffsetAsciiMaxX = s.OffsetAsciiMinX + Cols * s.GlyphWidth;
|
||||
}
|
||||
s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
|
||||
s.WindowWidth = s.OffsetAsciiMaxX + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
|
||||
if (PushMonoFont) ImGui::PopFont();
|
||||
}
|
||||
|
||||
@@ -138,19 +144,22 @@ void MemoryEditor::DrawContents(size_t mem_size)
|
||||
// Draw vertical separator
|
||||
ImVec2 window_pos = ImGui::GetWindowPos();
|
||||
if (OptShowAscii)
|
||||
draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
|
||||
draw_list->AddLine(ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
|
||||
// We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
|
||||
const ImVec2 avail_size = ImGui::GetContentRegionAvail();
|
||||
const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
|
||||
ImGuiListClipper clipper;
|
||||
clipper.Begin(line_total_count, s.LineHeight);
|
||||
|
||||
bool data_next = false;
|
||||
|
||||
if (ReadOnly || !WriteFn || DataEditingAddr >= mem_size)
|
||||
// Without a WriteFn, behave as ReadOnly: bytes can still be selected and previewed, but not written.
|
||||
const bool read_only = ReadOnly || !WriteFn;
|
||||
if (DataEditingAddr >= mem_size)
|
||||
DataEditingAddr = (size_t)-1;
|
||||
if (DataPreviewAddr >= mem_size)
|
||||
DataPreviewAddr = (size_t)-1;
|
||||
@@ -182,30 +191,39 @@ void MemoryEditor::DrawContents(size_t mem_size)
|
||||
return fmt::format(fmt::runtime(OptUpperCaseHex ? "{:02X}" : "{:02x}"), byte);
|
||||
};
|
||||
|
||||
MouseHovered = false;
|
||||
MouseHoveredAddr = 0;
|
||||
|
||||
while (clipper.Step())
|
||||
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
|
||||
{
|
||||
size_t addr = (size_t)(line_i * Cols);
|
||||
size_t addr = (size_t)line_i * Cols;
|
||||
ImGui::Text(format_address, s.AddrDigitsCount, BaseAddr + addr);
|
||||
|
||||
// Draw Hexadecimal
|
||||
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
|
||||
{
|
||||
float byte_pos_x = s.PosHexStart + s.ByteWidth * n + s.ByteSpacingWidth * (n / preview_data_type_size);
|
||||
float byte_pos_x = s.OffsetHexMinX + s.ByteWidth * n + s.ByteSpacingWidth * (n / preview_data_type_size);
|
||||
if (OptMidColsCount > 0)
|
||||
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
|
||||
ImGui::SameLine(byte_pos_x);
|
||||
|
||||
// Draw highlight
|
||||
// Draw highlight or custom background color
|
||||
size_t DataPreviewHighlightBase = DataPreviewAddr & ~(preview_data_type_size - 1);
|
||||
bool is_highlight_from_user_range = (HighlightMin && addr >= HighlightMin && addr < HighlightMax);
|
||||
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(addr));
|
||||
bool is_highlight_from_preview = (addr >= DataPreviewHighlightBase && addr < DataPreviewHighlightBase + preview_data_type_size);
|
||||
|
||||
ImU32 bg_color = 0;
|
||||
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
|
||||
bg_color = HighlightColor;
|
||||
else if (BgColorFn)
|
||||
bg_color = BgColorFn(addr);
|
||||
if (bg_color != 0)
|
||||
{
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
float highlight_width = s.ByteWidth;
|
||||
draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor);
|
||||
float bg_width = s.ByteWidth;
|
||||
draw_list->AddRectFilled(pos, ImVec2(pos.x + bg_width, pos.y + s.LineHeight), bg_color);
|
||||
}
|
||||
|
||||
if (DataEditingAddr == addr)
|
||||
@@ -245,8 +263,10 @@ void MemoryEditor::DrawContents(size_t mem_size)
|
||||
};
|
||||
UserData user_data;
|
||||
user_data.CursorPos = -1;
|
||||
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadByte(addr));
|
||||
ImSnprintf(user_data.CurrentBufOverwrite, 3, format_byte, ReadByte(addr));
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
|
||||
if (read_only)
|
||||
flags |= ImGuiInputTextFlags_ReadOnly;
|
||||
#if IMGUI_VERSION_NUM >= 18104
|
||||
flags |= ImGuiInputTextFlags_AlwaysOverwrite;
|
||||
#else
|
||||
@@ -263,12 +283,15 @@ void MemoryEditor::DrawContents(size_t mem_size)
|
||||
if (data_editing_addr_next != (size_t)-1)
|
||||
data_write = data_next = false;
|
||||
unsigned int data_input_value = 0;
|
||||
if (data_write && sscanf(DataInputBuf.c_str(), "%X", &data_input_value) == 1)
|
||||
if (!read_only && data_write && sscanf(DataInputBuf.c_str(), "%X", &data_input_value) == 1)
|
||||
{
|
||||
if (WriteFn) {
|
||||
WriteFn(addr, (ImU8)data_input_value);
|
||||
Cache.invalidate();
|
||||
}
|
||||
WriteFn(addr, (ImU8)data_input_value);
|
||||
Cache.invalidate();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
MouseHovered = true;
|
||||
MouseHoveredAddr = addr;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
@@ -295,10 +318,15 @@ void MemoryEditor::DrawContents(size_t mem_size)
|
||||
else
|
||||
ImGui::Text(format_byte_space, b);
|
||||
}
|
||||
if (!ReadOnly && WriteFn && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0))
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
DataEditingTakeFocus = true;
|
||||
data_editing_addr_next = addr;
|
||||
MouseHovered = true;
|
||||
MouseHoveredAddr = addr;
|
||||
if (ImGui::IsMouseClicked(0))
|
||||
{
|
||||
DataEditingTakeFocus = true;
|
||||
data_editing_addr_next = addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,17 +334,23 @@ void MemoryEditor::DrawContents(size_t mem_size)
|
||||
if (OptShowAscii)
|
||||
{
|
||||
// Draw ASCII values
|
||||
ImGui::SameLine(s.PosAsciiStart);
|
||||
ImGui::SameLine(s.OffsetAsciiMinX);
|
||||
ImVec2 pos = ImGui::GetCursorScreenPos();
|
||||
addr = line_i * Cols;
|
||||
addr = (size_t)line_i * Cols;
|
||||
|
||||
const float mouse_off_x = ImGui::GetIO().MousePos.x - pos.x;
|
||||
const size_t mouse_addr = (mouse_off_x >= 0.0f && mouse_off_x < s.OffsetAsciiMaxX - s.OffsetAsciiMinX) ? addr + (size_t)(mouse_off_x / s.GlyphWidth) : (size_t)-1;
|
||||
|
||||
ImGui::PushID(line_i);
|
||||
if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight)))
|
||||
if (ImGui::InvisibleButton("ascii", ImVec2(s.OffsetAsciiMaxX - s.OffsetAsciiMinX, s.LineHeight)))
|
||||
{
|
||||
DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth);
|
||||
if (!ReadOnly && WriteFn) {
|
||||
DataEditingAddr = DataPreviewAddr;
|
||||
DataEditingTakeFocus = true;
|
||||
}
|
||||
DataEditingAddr = DataPreviewAddr = mouse_addr;
|
||||
DataEditingTakeFocus = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
MouseHovered = true;
|
||||
MouseHoveredAddr = mouse_addr;
|
||||
}
|
||||
ImGui::PopID();
|
||||
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
|
||||
@@ -326,6 +360,10 @@ void MemoryEditor::DrawContents(size_t mem_size)
|
||||
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
|
||||
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
|
||||
}
|
||||
else if (BgColorFn)
|
||||
{
|
||||
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), BgColorFn(addr));
|
||||
}
|
||||
unsigned char c = ReadByte(addr);
|
||||
char display_c = (c < 32 || c >= 128) ? '.' : c;
|
||||
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
|
||||
@@ -338,7 +376,10 @@ void MemoryEditor::DrawContents(size_t mem_size)
|
||||
ImGui::EndChild();
|
||||
|
||||
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
|
||||
ImVec2 backup_pos = ImGui::GetCursorScreenPos();
|
||||
ImGui::SetCursorPosX(s.WindowWidth);
|
||||
ImGui::Dummy(ImVec2(0.0f, 0.0f));
|
||||
ImGui::SetCursorScreenPos(backup_pos);
|
||||
|
||||
if (data_next && DataEditingAddr + 1 < mem_size)
|
||||
{
|
||||
@@ -368,6 +409,20 @@ void MemoryEditor::DrawContents(size_t mem_size)
|
||||
ImGui::Separator();
|
||||
DrawPreviewLine(s, mem_size);
|
||||
}
|
||||
|
||||
if (GotoAddr != (size_t)-1)
|
||||
{
|
||||
if (GotoAddr < mem_size)
|
||||
{
|
||||
// s.LineHeight is measured with the mono font (see CalcSizes), matching the clipper's line height.
|
||||
ImGui::BeginChild("##scrolling");
|
||||
ImGui::SetScrollY((GotoAddr / Cols) * s.LineHeight - avail_size.y * 0.5f);
|
||||
ImGui::EndChild();
|
||||
DataEditingAddr = DataPreviewAddr = GotoAddr;
|
||||
DataEditingTakeFocus = true;
|
||||
}
|
||||
GotoAddr = (size_t)-1;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryEditor::DrawOptionsLine(const Sizes& s, size_t mem_size)
|
||||
@@ -411,21 +466,6 @@ void MemoryEditor::DrawOptionsLine(const Sizes& s, size_t mem_size)
|
||||
}
|
||||
}
|
||||
|
||||
if (GotoAddr != (size_t)-1) {
|
||||
if (GotoAddr < mem_size) {
|
||||
ImGui::BeginChild("##scrolling");
|
||||
if (PushMonoFont) PushMonoFont();
|
||||
ImGui::SetScrollFromPosY(
|
||||
ImGui::GetCursorStartPos().y +
|
||||
(GotoAddr / Cols) * ImGui::GetTextLineHeight());
|
||||
if (PushMonoFont) ImGui::PopFont();
|
||||
ImGui::EndChild();
|
||||
DataEditingAddr = DataPreviewAddr = GotoAddr;
|
||||
DataEditingTakeFocus = true;
|
||||
}
|
||||
GotoAddr = (size_t)-1;
|
||||
}
|
||||
|
||||
// Clear Input Address and reset to beginning of address space
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear Input")) {
|
||||
@@ -450,16 +490,21 @@ void MemoryEditor::DrawPreviewLine(const Sizes& s, size_t mem_size)
|
||||
ImGui::Text("Preview as:");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
|
||||
|
||||
static const ImGuiDataType supported_data_types[] = { ImGuiDataType_S8, ImGuiDataType_U8, ImGuiDataType_S16, ImGuiDataType_U16, ImGuiDataType_S32, ImGuiDataType_U32, ImGuiDataType_S64, ImGuiDataType_U64, ImGuiDataType_Float, ImGuiDataType_Double, ImGuiDataType_Bool };
|
||||
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
|
||||
{
|
||||
for (int n = 0; n < ImGuiDataType_COUNT; n++)
|
||||
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n))
|
||||
PreviewDataType = (ImGuiDataType)n;
|
||||
for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++)
|
||||
{
|
||||
ImGuiDataType data_type = supported_data_types[n];
|
||||
if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
|
||||
PreviewDataType = data_type;
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
|
||||
ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0");
|
||||
ImGui::Combo("##combo_endianness", &PreviewEndianness, "LE\0BE\0\0");
|
||||
|
||||
char buf[128] = "";
|
||||
float x = s.GlyphWidth * 6.0f;
|
||||
@@ -476,18 +521,19 @@ void MemoryEditor::DrawPreviewLine(const Sizes& s, size_t mem_size)
|
||||
ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
|
||||
}
|
||||
|
||||
// Utilities for Data Preview
|
||||
// Utilities for Data Preview (since we don't access imgui_internal.h)
|
||||
// FIXME: This technically depends on ImGuiDataType order.
|
||||
const char* MemoryEditor::DataTypeGetDesc(ImGuiDataType data_type) const
|
||||
{
|
||||
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double", "Bool" };
|
||||
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
|
||||
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(descs));
|
||||
return descs[data_type];
|
||||
}
|
||||
|
||||
size_t MemoryEditor::DataTypeGetSize(ImGuiDataType data_type) const
|
||||
{
|
||||
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double), 1 };
|
||||
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
|
||||
IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(sizes));
|
||||
return sizes[data_type];
|
||||
}
|
||||
|
||||
@@ -500,18 +546,18 @@ const char* MemoryEditor::DataFormatGetDesc(DataFormat data_format) const
|
||||
|
||||
bool MemoryEditor::IsBigEndian() const
|
||||
{
|
||||
uint16_t x = 1;
|
||||
ImU16 x = 1;
|
||||
char c[2];
|
||||
memcpy(c, &x, 2);
|
||||
return c[0] != 0;
|
||||
}
|
||||
|
||||
void* MemoryEditor::EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
|
||||
void* MemoryEditor::EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
|
||||
{
|
||||
if (is_little_endian)
|
||||
{
|
||||
uint8_t* dst = (uint8_t*)_dst;
|
||||
uint8_t* src = (uint8_t*)_src + s - 1;
|
||||
ImU8* dst = (ImU8*)_dst;
|
||||
ImU8* src = (ImU8*)_src + s - 1;
|
||||
for (int i = 0, n = (int)s; i < n; ++i)
|
||||
memcpy(dst++, src--, 1);
|
||||
return _dst;
|
||||
@@ -522,7 +568,7 @@ void* MemoryEditor::EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int
|
||||
}
|
||||
}
|
||||
|
||||
void* MemoryEditor::EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
|
||||
void* MemoryEditor::EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
|
||||
{
|
||||
if (is_little_endian)
|
||||
{
|
||||
@@ -530,23 +576,23 @@ void* MemoryEditor::EndianessCopyLittleEndian(void* _dst, void* _src, size_t s,
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t* dst = (uint8_t*)_dst;
|
||||
uint8_t* src = (uint8_t*)_src + s - 1;
|
||||
ImU8* dst = (ImU8*)_dst;
|
||||
ImU8* src = (ImU8*)_src + s - 1;
|
||||
for (int i = 0, n = (int)s; i < n; ++i)
|
||||
memcpy(dst++, src--, 1);
|
||||
return _dst;
|
||||
}
|
||||
}
|
||||
|
||||
void* MemoryEditor::EndianessCopy(void* dst, void* src, size_t size) const
|
||||
void* MemoryEditor::EndiannessCopy(void* dst, void* src, size_t size) const
|
||||
{
|
||||
static void* (*fp)(void*, void*, size_t, int) = NULL;
|
||||
if (fp == NULL)
|
||||
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian;
|
||||
return fp(dst, src, size, PreviewEndianess);
|
||||
static void* (*fp)(void*, void*, size_t, int) = nullptr;
|
||||
if (fp == nullptr)
|
||||
fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
|
||||
return fp(dst, src, size, PreviewEndianness);
|
||||
}
|
||||
|
||||
const char* MemoryEditor::FormatBinary(const uint8_t* buf, int width) const
|
||||
const char* MemoryEditor::FormatBinary(const ImU8* buf, int width) const
|
||||
{
|
||||
IM_ASSERT(width <= 64);
|
||||
size_t out_n = 0;
|
||||
@@ -567,7 +613,7 @@ const char* MemoryEditor::FormatBinary(const uint8_t* buf, int width) const
|
||||
void MemoryEditor::DrawPreviewData(size_t addr, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
|
||||
{
|
||||
if (addr >= mem_size) { out_buf[0] = 0; return; }
|
||||
uint8_t buf[8];
|
||||
ImU8 buf[8];
|
||||
size_t elem_size = DataTypeGetSize(data_type);
|
||||
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
|
||||
for (int i = 0, n = (int)size; i < n; ++i)
|
||||
@@ -575,8 +621,8 @@ void MemoryEditor::DrawPreviewData(size_t addr, size_t mem_size, ImGuiDataType d
|
||||
|
||||
if (data_format == DataFormat_Bin)
|
||||
{
|
||||
uint8_t binbuf[8];
|
||||
EndianessCopy(binbuf, buf, size);
|
||||
ImU8 binbuf[8];
|
||||
EndiannessCopy(binbuf, buf, size);
|
||||
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
|
||||
return;
|
||||
}
|
||||
@@ -586,94 +632,95 @@ void MemoryEditor::DrawPreviewData(size_t addr, size_t mem_size, ImGuiDataType d
|
||||
{
|
||||
case ImGuiDataType_S8:
|
||||
{
|
||||
int8_t int8 = 0;
|
||||
EndianessCopy(&int8, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; }
|
||||
ImS8 data = 0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_U8:
|
||||
{
|
||||
uint8_t uint8 = 0;
|
||||
EndianessCopy(&uint8, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; }
|
||||
ImU8 data = 0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_S16:
|
||||
{
|
||||
int16_t int16 = 0;
|
||||
EndianessCopy(&int16, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; }
|
||||
ImS16 data = 0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_U16:
|
||||
{
|
||||
uint16_t uint16 = 0;
|
||||
EndianessCopy(&uint16, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; }
|
||||
ImU16 data = 0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_S32:
|
||||
{
|
||||
int32_t int32 = 0;
|
||||
EndianessCopy(&int32, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; }
|
||||
ImS32 data = 0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_U32:
|
||||
{
|
||||
uint32_t uint32 = 0;
|
||||
EndianessCopy(&uint32, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; }
|
||||
ImU32 data = 0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_S64:
|
||||
{
|
||||
int64_t int64 = 0;
|
||||
EndianessCopy(&int64, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; }
|
||||
ImS64 data = 0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_U64:
|
||||
{
|
||||
uint64_t uint64 = 0;
|
||||
EndianessCopy(&uint64, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; }
|
||||
ImU64 data = 0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_Float:
|
||||
{
|
||||
float float32 = 0.0f;
|
||||
EndianessCopy(&float32, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; }
|
||||
float data = 0.0f;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_Double:
|
||||
{
|
||||
double float64 = 0.0;
|
||||
EndianessCopy(&float64, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; }
|
||||
double data = 0.0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
|
||||
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
|
||||
break;
|
||||
}
|
||||
case ImGuiDataType_Bool:
|
||||
{
|
||||
int8_t int8 = 0;
|
||||
EndianessCopy(&int8, buf, size);
|
||||
if (int8 == 0)
|
||||
ImS8 data = 0;
|
||||
EndiannessCopy(&data, buf, size);
|
||||
if (data == 0)
|
||||
memcpy(out_buf, "false", 6);
|
||||
else
|
||||
memcpy(out_buf, "true", 5);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
case ImGuiDataType_COUNT:
|
||||
break;
|
||||
} // Switch
|
||||
|
||||
+22
-17
@@ -88,6 +88,7 @@ struct MemoryEditor
|
||||
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
|
||||
ImU32 HighlightColor; // // background color of highlighted bytes.
|
||||
bool (*HighlightFn)(size_t off); // = 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
|
||||
std::function<ImU32(size_t off)> BgColorFn = nullptr; // optional handler to return custom background color of individual bytes.
|
||||
std::function<void()> PushMonoFont = nullptr;
|
||||
size_t& OffsetAddr; // referenced from PCSX-Redux Settings
|
||||
|
||||
@@ -134,6 +135,10 @@ struct MemoryEditor
|
||||
|
||||
mutable ReadCache Cache;
|
||||
|
||||
// Public read-only data
|
||||
bool MouseHovered; // set when mouse is hovering a value.
|
||||
size_t MouseHoveredAddr; // the address currently being hovered if MouseHovered is set.
|
||||
|
||||
private:
|
||||
// [Internal State]
|
||||
bool ContentsWidthChanged;
|
||||
@@ -144,7 +149,7 @@ private:
|
||||
std::string AddrInputBuf;
|
||||
size_t GotoAddr;
|
||||
size_t HighlightMin, HighlightMax;
|
||||
int PreviewEndianess;
|
||||
int PreviewEndianness;
|
||||
bool RestoreOffset;
|
||||
const size_t BaseAddr;
|
||||
|
||||
@@ -156,18 +161,18 @@ public:
|
||||
|
||||
struct Sizes
|
||||
{
|
||||
int AddrDigitsCount;
|
||||
float LineHeight;
|
||||
float GlyphWidth;
|
||||
float ByteWidth;
|
||||
float ByteSpacingWidth;
|
||||
float HexCellWidth;
|
||||
float SpacingBetweenMidCols;
|
||||
float PosHexStart;
|
||||
float PosHexEnd;
|
||||
float PosAsciiStart;
|
||||
float PosAsciiEnd;
|
||||
float WindowWidth;
|
||||
int AddrDigitsCount; // Number of digits required to represent maximum address.
|
||||
float LineHeight; // Height of each line (no spacing), measured with the mono font.
|
||||
float GlyphWidth; // Glyph width (assume mono-space).
|
||||
float ByteWidth; // Width of a single byte "FF" ~2.0f * GlyphWidth.
|
||||
float ByteSpacingWidth; // Spacing after each preview-type group of bytes ~0.5f * GlyphWidth.
|
||||
float HexCellWidth; // Width of a hex edit cell (one preview-type group of bytes plus trailing spacing).
|
||||
float SpacingBetweenMidCols; // Spacing between each columns section (OptMidColsCount).
|
||||
float OffsetHexMinX;
|
||||
float OffsetHexMaxX;
|
||||
float OffsetAsciiMinX;
|
||||
float OffsetAsciiMaxX;
|
||||
float WindowWidth; // Ideal window width.
|
||||
|
||||
Sizes() { memset(this, 0, sizeof(*this)); }
|
||||
};
|
||||
@@ -184,10 +189,10 @@ public:
|
||||
size_t DataTypeGetSize(ImGuiDataType data_type) const;
|
||||
const char* DataFormatGetDesc(DataFormat data_format) const;
|
||||
bool IsBigEndian() const;
|
||||
static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian);
|
||||
static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian);
|
||||
void* EndianessCopy(void* dst, void* src, size_t size) const;
|
||||
const char* FormatBinary(const uint8_t* buf, int width) const;
|
||||
static void* EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian);
|
||||
static void* EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian);
|
||||
void* EndiannessCopy(void* dst, void* src, size_t size) const;
|
||||
const char* FormatBinary(const ImU8* buf, int width) const;
|
||||
|
||||
private:
|
||||
// [Internal]
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -55,6 +56,7 @@ using PCSX::MidiConverter::MIDI_CC_VOLUME;
|
||||
using PCSX::MidiConverter::MIDI_DRUM_CHANNEL;
|
||||
using PCSX::MidiConverter::MIDI_META;
|
||||
using PCSX::MidiConverter::MIDI_META_COPYRIGHT;
|
||||
using PCSX::MidiConverter::MIDI_META_CUE_POINT;
|
||||
using PCSX::MidiConverter::MIDI_META_MARKER;
|
||||
using PCSX::MidiConverter::MIDI_META_TRACK_NAME;
|
||||
using PCSX::MidiConverter::MIDI_META_TEMPO;
|
||||
@@ -92,6 +94,7 @@ enum PsmEventType : uint8_t {
|
||||
PSM_TEMPO_CHANGE = 0x0A,
|
||||
PSM_LOOP_POINT = 0x0B,
|
||||
PSM_END = 0x0C,
|
||||
PSM_USER = 0x0D,
|
||||
PSM_LONG_WAIT = 0xFF,
|
||||
};
|
||||
|
||||
@@ -498,6 +501,26 @@ void ConvertContext::generate() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// A cue point whose text is "tag:payload" becomes a USER event for the program playing the song.
|
||||
if (mev.type == MIDI_META && mev.data1 == MIDI_META_CUE_POINT) {
|
||||
std::string_view text = mev.textData;
|
||||
auto colon = text.find(':');
|
||||
unsigned tag = 0;
|
||||
uint32_t payload = 0;
|
||||
if (colon != std::string_view::npos) {
|
||||
auto end = text.data() + colon;
|
||||
auto [p1, e1] = std::from_chars(text.data(), end, tag);
|
||||
auto [p2, e2] = std::from_chars(end + 1, text.data() + text.size(), payload);
|
||||
if (e1 == std::errc() && p1 == end && e2 == std::errc() && p2 == text.data() + text.size() &&
|
||||
tag <= 255) {
|
||||
emitEvent(PSM_USER, tag, payload, mev.absoluteTick);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fmt::print(stderr, "Ignoring cue point \"{}\" at tick {}: expected tag:payload\n", text, mev.absoluteTick);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mev.type == MIDI_META) continue;
|
||||
|
||||
if (mev.type == MIDI_PROGRAM_CHANGE) {
|
||||
|
||||
@@ -125,7 +125,10 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\src\gui\gui.cc" />
|
||||
<ClCompile Include="..\..\src\gui\gui.cc">
|
||||
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">/bigobj /utf-8 /validate-charset %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">/bigobj /utf-8 /validate-charset %(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\gui\luaimguiextra.cc" />
|
||||
<ClCompile Include="..\..\src\gui\luaimplot.cc" />
|
||||
<ClCompile Include="..\..\src\gui\luatvg.cc" />
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\..\tests\pcsxrunner\basic.cc" />
|
||||
<ClCompile Include="..\..\..\tests\pcsxrunner\cdrom.cc" />
|
||||
<ClCompile Include="..\..\..\tests\pcsxrunner\cop0.cc" />
|
||||
<ClCompile Include="..\..\..\tests\pcsxrunner\copbranch.cc" />
|
||||
<ClCompile Include="..\..\..\tests\pcsxrunner\cpu.cc" />
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
<ClCompile Include="..\..\..\tests\pcsxrunner\cpu.cc">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\tests\pcsxrunner\cdrom.cc">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\..\tests\pcsxrunner\cop0.cc">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
||||
Reference in New Issue
Block a user