Skip to content

Commit 33e820b

Browse files
committed
cpu/system: Improve auto-joypad poll timing, modify state handling
Backport: bsnes-emu/bsnes#351 by Morilli This commit breaks state compatibility with standalone bsnes. Previous to this commit, the raw state data was still compatible, but would require some manipulation on the user's part as the standalone creates compressed states and encapsulates them in .bsz format. As standalone bsnes also broke the state format for this change, it made sense to use this opportunity to version the states differently in bsnes-jg. Effort has been taken to allow loading states created in previous versions of the emu, with the caveat that there may be some unexpected behaviour, though this is unlikely in nearly all cases.
1 parent fe9a2b6 commit 33e820b

File tree

4 files changed

+89
-34
lines changed

4 files changed

+89
-34
lines changed

src/cpu.cpp

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -478,13 +478,21 @@ void CPU::writeCPU(unsigned addr, uint8_t data) {
478478
//bit 0 is shared between JOYSER0 and JOYSER1:
479479
//strobing $4016.d0 affects both controller port latches.
480480
//$4017 bit 0 writes are ignored.
481-
controllerPort1.device->latch(data & 1);
482-
controllerPort2.device->latch(data & 1);
481+
status.cpuLatch = data & 1;
482+
controllerPort1.device->latch(status.autoJoypadLatch | status.cpuLatch);
483+
controllerPort2.device->latch(status.autoJoypadLatch | status.cpuLatch);
483484
return;
484485

485486
case 0x4200: //NMITIMEN
486487
io.autoJoypadPoll = data & 1;
487-
if(!io.autoJoypadPoll) status.autoJoypadCounter = 33; // Disable auto-joypad read
488+
if(status.autoJoypadCounter == 0) {
489+
// allow controller latches during this time
490+
status.autoJoypadLatch = io.autoJoypadPoll;
491+
controllerPort1.device->latch(status.autoJoypadLatch | status.cpuLatch);
492+
controllerPort2.device->latch(status.autoJoypadLatch | status.cpuLatch);
493+
} else if (!io.autoJoypadPoll && status.autoJoypadCounter >= 2) {
494+
status.autoJoypadCounter = 33;
495+
}
488496
nmitimenUpdate(data);
489497
return;
490498

@@ -820,47 +828,56 @@ void CPU::dmaEdge() {
820828

821829
//called every 128 clocks from inside the CPU::stepOnce() function
822830
void CPU::joypadEdge() {
823-
//it is not yet confirmed if polling can be stopped early and/or (re)started later
824-
if(!io.autoJoypadPoll) return;
825-
826-
if(vcounter() == ppu.vdisp() && hcounter() >= 130 && hcounter() <= 256) {
831+
if(vcounter() == ppu.vdisp() && (counter.cpu & 255) == 0 && hcounter() >= 130 && hcounter() <= 384) {
827832
//begin new polling sequence
828833
status.autoJoypadCounter = 0;
829-
}
834+
} else {
835+
//stop after polling has been completed for this frame
836+
if(status.autoJoypadCounter >= 33) return;
830837

831-
//stop after polling has been completed for this frame
832-
if(status.autoJoypadCounter >= 33) return;
838+
status.autoJoypadCounter++;
839+
}
833840

834841
if(status.autoJoypadCounter == 0) {
835842
//latch controller states on the first polling cycle
836-
controllerPort1.device->latch(1);
837-
controllerPort2.device->latch(1);
843+
status.autoJoypadLatch = io.autoJoypadPoll;
844+
controllerPort1.device->latch(status.autoJoypadLatch | status.cpuLatch);
845+
controllerPort2.device->latch(status.autoJoypadLatch | status.cpuLatch);
846+
if(io.autoJoypadPoll) {
847+
//shift registers are cleared to zero at start of auto-joypad polling
848+
io.joy1 = 0;
849+
io.joy2 = 0;
850+
io.joy3 = 0;
851+
io.joy4 = 0;
852+
}
838853
}
839854

840855
if(status.autoJoypadCounter == 1) {
841856
//release latch and begin reading on the second cycle
842-
controllerPort1.device->latch(0);
843-
controllerPort2.device->latch(0);
844-
845-
//shift registers are cleared to zero at start of auto-joypad polling
846-
io.joy1 = 0;
847-
io.joy2 = 0;
848-
io.joy3 = 0;
849-
io.joy4 = 0;
857+
status.autoJoypadLatch = 0;
858+
controllerPort1.device->latch(status.autoJoypadLatch | status.cpuLatch);
859+
controllerPort2.device->latch(status.autoJoypadLatch | status.cpuLatch);
850860
}
851861

852-
if(status.autoJoypadCounter >= 2 && !(status.autoJoypadCounter & 1)) {
853-
//sixteen bits are shifted into joy{1-4}, one bit per 256 clocks
854-
uint8_t port0 = controllerPort1.device->data();
855-
uint8_t port1 = controllerPort2.device->data();
856-
857-
io.joy1 = io.joy1 << 1 | (port0 & 1);
858-
io.joy2 = io.joy2 << 1 | (port1 & 1);
859-
io.joy3 = io.joy3 << 1 | ((port0 & 2) >> 1);
860-
io.joy4 = io.joy4 << 1 | ((port1 & 2) >> 1);
862+
if(status.autoJoypadCounter != 1 && !io.autoJoypadPoll) {
863+
// if auto-joypad polling is disabled at this point skip the rest of the polling
864+
status.autoJoypadCounter = 33;
865+
return;
861866
}
862867

863-
++status.autoJoypadCounter;
868+
if(status.autoJoypadCounter >= 2) {
869+
//sixteen bits are shifted into joy{1-4}, one bit per 256 clocks
870+
//the bits are read on one 128-clock cycle and written on the next
871+
if ((status.autoJoypadCounter & 1) == 0) {
872+
status.autoJoypadPort1 = controllerPort1.device->data();
873+
status.autoJoypadPort2 = controllerPort2.device->data();
874+
} else {
875+
io.joy1 = io.joy1 << 1 | (status.autoJoypadPort1 & 1);
876+
io.joy2 = io.joy2 << 1 | (status.autoJoypadPort2 & 1);
877+
io.joy3 = io.joy3 << 1 | ((status.autoJoypadPort1 & 2) >> 1);
878+
io.joy4 = io.joy4 << 1 | ((status.autoJoypadPort2 & 2) >> 1);
879+
}
880+
}
864881
}
865882

866883
//nmiPoll() and irqPoll() are called once every four clock cycles;
@@ -1005,6 +1022,14 @@ void CPU::serialize(serializer& s) {
10051022

10061023
s.integer(status.autoJoypadCounter);
10071024

1025+
if (s.version) {
1026+
s.integer(status.autoJoypadPort1);
1027+
s.integer(status.autoJoypadPort2);
1028+
1029+
s.boolean(status.cpuLatch);
1030+
s.boolean(status.autoJoypadLatch);
1031+
}
1032+
10081033
s.integer(io.wramAddress);
10091034

10101035
s.boolean(io.hirqEnable);

src/cpu.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
145145
bool hdmaMode = 0; //0 = init, 1 = run
146146

147147
unsigned autoJoypadCounter = 33; //state machine; 4224 / 128 = 33 (inactive)
148+
149+
unsigned autoJoypadPort1 = 0;
150+
unsigned autoJoypadPort2 = 0;
151+
152+
bool cpuLatch = 0;
153+
bool autoJoypadLatch = 0;
148154
} status;
149155

150156
struct IO {

src/serializer.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@
3636

3737
struct serializer;
3838

39+
/* This value is used by bsnes-jg as a state version number, since the state
40+
format broke during the 2.0.2 development cycle. The number is a hex
41+
representation of the bsnes-jg version when the state format last changed,
42+
where each byte represents the patch, minor, and major version from the
43+
least to most significant byte. The previously used "signature" in the state
44+
is used to determine whether the state was created previous to the first
45+
post-fork state format break.
46+
*/
47+
static const unsigned serializerVersion = 0x00020002;
48+
3949
template<typename T>
4050
struct has_serialize {
4151
template<typename C> static char test(decltype(std::declval<C>().serialize(std::declval<serializer&>()))*);
@@ -46,6 +56,8 @@ struct has_serialize {
4656
struct serializer {
4757
enum Mode : unsigned { Load, Save, Size };
4858

59+
unsigned version = serializerVersion;
60+
4961
Mode mode() const;
5062
const uint8_t* data() const;
5163
unsigned size() const;

src/system.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include "dsp.hpp"
5050
#include "emulator.hpp"
5151
#include "expansion/expansion.hpp"
52+
#include "logger.hpp"
5253
#include "random.hpp"
5354
#include "settings.hpp"
5455
#include "sufamiturbo.hpp"
@@ -66,11 +67,13 @@ serializer System::serialize(bool synchronize) {
6667
if(!information.serializeSize[synchronize]) return {}; //should never occur
6768
if(synchronize) runToSave();
6869

69-
unsigned signature = 0x31545342;
70+
unsigned signature = serializerVersion; // previously 0x31545342
7071
unsigned serializeSize = information.serializeSize[synchronize];
7172
char version[16] = {};
7273
char description[512] = {};
7374
bool placeholder = false;
75+
76+
// Note: This is here for legacy compatibility only. This version is forever frozen at 115
7477
std::memcpy(&version, (const char*)SerializerVersion.c_str(), SerializerVersion.size());
7578

7679
serializer s(serializeSize);
@@ -99,10 +102,19 @@ bool System::unserialize(serializer& s) {
99102
s.boolean(synchronize);
100103
s.boolean(placeholder);
101104

102-
if((signature != 0x31545342)
103-
|| (serializeSize != information.serializeSize[synchronize])
104-
|| (std::string{version} != SerializerVersion))
105+
if (signature == 0x31545342) {
106+
/* When the state format changed during the 2.0.2 dev cycle, the size
107+
increased by 10. Do update this whenever the state format changes!!
108+
*/
109+
if ((serializeSize + 10) != information.serializeSize[synchronize]
110+
|| (std::string{version} != SerializerVersion))
111+
return false;
112+
logger.log(Logger::WRN, "Loading deprecated state version, success not guaranteed.\n");
113+
s.version = 0;
114+
}
115+
else if ((signature != serializerVersion) || (serializeSize != information.serializeSize[synchronize])) {
105116
return false;
117+
}
106118

107119
if(synchronize) power(/* reset = */ false);
108120
serializeAll(s, synchronize);

0 commit comments

Comments
 (0)