Skip to content

Commit b8f3f08

Browse files
Add in ISaveRam implementation for C64, using the deltas of disks.
Add in better docs for `DeltaSerializer`. Fix C64 not remembering disk changes when swapping disks (swapping disks essentially just reset the disk previously)
1 parent ece5d25 commit b8f3f08

File tree

9 files changed

+199
-45
lines changed

9 files changed

+199
-45
lines changed

src/BizHawk.Client.Common/config/PathEntryCollection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public void ResolveWithDefaults()
202202

203203
CommonEntriesFor(VSystemID.Raw.Arcade, basePath: Path.Combine(".", "Arcade")),
204204

205-
CommonEntriesFor(VSystemID.Raw.C64, basePath: Path.Combine(".", "C64"), omitSaveRAM: true),
205+
CommonEntriesFor(VSystemID.Raw.C64, basePath: Path.Combine(".", "C64")),
206206

207207
CommonEntriesFor(VSystemID.Raw.ChannelF, basePath: Path.Combine(".", "Channel F"), omitSaveRAM: true),
208208

src/BizHawk.Client.EmuHawk/MainForm.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using BizHawk.Emulation.Cores;
2727
using BizHawk.Emulation.Cores.Arcades.MAME;
2828
using BizHawk.Emulation.Cores.Calculators.TI83;
29+
using BizHawk.Emulation.Cores.Computers.Commodore64;
2930
using BizHawk.Emulation.Cores.Consoles.NEC.PCE;
3031
using BizHawk.Emulation.Cores.Consoles.Nintendo.Ares64;
3132
using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES;
@@ -1921,7 +1922,7 @@ private void LoadSaveRam()
19211922
byte[] sram;
19221923

19231924
// some cores might not know how big the saveram ought to be, so just send it the whole file
1924-
if (Emulator is MGBAHawk || Emulator is NeoGeoPort || (Emulator is NES && (Emulator as NES).BoardName == "FDS"))
1925+
if (Emulator is C64 or MGBAHawk or NeoGeoPort or NES { BoardName: "FDS" })
19251926
{
19261927
sram = File.ReadAllBytes(saveRamPath);
19271928
}

src/BizHawk.Common/DeltaSerializer.cs

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,42 @@ namespace BizHawk.Common
55
{
66
/// <summary>
77
/// Serializes deltas between data, mainly for ROM like structures which are actually writable, and therefore the differences need to be saved
8-
/// Uses simple RLE encoding to keep the size down
8+
/// Uses a simple delta format in order to keep size down
9+
/// DELTA FORMAT DETAILS FOLLOWS
10+
/// The format comprises of an indeterminate amount of blocks. These blocks start with a 4 byte header. This header is read as a native endian 32-bit two's complement signed integer.
11+
/// If the header is positive, then the header indicates the amount of bytes which are identical between the original and current spans.
12+
/// Positive headers are blocks by themselves, so the next header will proceed immediately after a positive header.
13+
/// If the header is negative, then the header indicates the negation of the amount of bytes which differ between the original and current spans.
14+
/// A negative header will have the negated header amount of bytes proceed it, which will be the bitwise XOR between the original and differing bytes.
15+
/// A header of -0x80000000 is considered ill-formed.
16+
/// This format does not stipulate requirements for whether blocks of non-differing bytes necessarily will use a positive header.
17+
/// Thus, an implementation is free to use negative headers only, although without combination of positive headers, this will obviously not have great results wrt final size.
18+
/// More practically, an implementation may want to avoid using positive headers when the block is rather small (e.g. smaller than the header itself, and thus not shrinking the result).
19+
/// Subsequently, it may not mind putting some identical bytes within the negative header's block.
20+
/// XORing the same values result in 0, so doing this will not leave trace of the original data.
921
/// </summary>
1022
public static class DeltaSerializer
1123
{
12-
public static ReadOnlySpan<byte> GetDelta<T>(ReadOnlySpan<T> original, ReadOnlySpan<T> data)
24+
public static ReadOnlySpan<byte> GetDelta<T>(ReadOnlySpan<T> original, ReadOnlySpan<T> current)
1325
where T : unmanaged
1426
{
1527
var orignalAsBytes = MemoryMarshal.AsBytes(original);
16-
var dataAsBytes = MemoryMarshal.AsBytes(data);
28+
var currentAsBytes = MemoryMarshal.AsBytes(current);
1729

18-
if (orignalAsBytes.Length != dataAsBytes.Length)
30+
if (orignalAsBytes.Length != currentAsBytes.Length)
1931
{
20-
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(dataAsBytes.Length)}");
32+
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(currentAsBytes.Length)}");
2133
}
2234

2335
var index = 0;
24-
var end = dataAsBytes.Length;
36+
var end = currentAsBytes.Length;
2537
var ret = new byte[end + 4].AsSpan(); // worst case scenario size (i.e. everything is different)
2638
var retSize = 0;
2739

2840
while (index < end)
2941
{
3042
var blockStart = index;
31-
while (index < end && orignalAsBytes[index] == dataAsBytes[index])
43+
while (index < end && orignalAsBytes[index] == currentAsBytes[index])
3244
{
3345
index++;
3446
}
@@ -40,7 +52,7 @@ public static ReadOnlySpan<byte> GetDelta<T>(ReadOnlySpan<T> original, ReadOnlyS
4052
var different = 0;
4153
while (index < end && same < 8) // in case we hit end of span before, this does nothing and different is 0
4254
{
43-
if (orignalAsBytes[index] != dataAsBytes[index])
55+
if (orignalAsBytes[index] != currentAsBytes[index])
4456
{
4557
different++;
4658
same = 0; // note: same is set to 0 on first iteration
@@ -68,15 +80,15 @@ public static ReadOnlySpan<byte> GetDelta<T>(ReadOnlySpan<T> original, ReadOnlyS
6880
retSize += 4;
6981
for (var i = blockStart; i < index - same; i++)
7082
{
71-
ret[retSize++] = (byte)(orignalAsBytes[i] ^ dataAsBytes[i]);
83+
ret[retSize++] = (byte)(orignalAsBytes[i] ^ currentAsBytes[i]);
7284
}
7385
}
7486

7587
if (same > 0) // same is 4-8, 8 indicates we hit the 8 same bytes threshold, 4-7 indicate hit end of span
7688
{
7789
if (same == 8)
7890
{
79-
while (index < end && orignalAsBytes[index] == dataAsBytes[index])
91+
while (index < end && orignalAsBytes[index] == currentAsBytes[index])
8092
{
8193
index++;
8294
}
@@ -97,19 +109,19 @@ public static ReadOnlySpan<byte> GetDelta<T>(ReadOnlySpan<T> original, ReadOnlyS
97109
return ret.Slice(0, retSize);
98110
}
99111

100-
public static void ApplyDelta<T>(ReadOnlySpan<T> original, Span<T> data, ReadOnlySpan<byte> delta)
112+
public static void ApplyDelta<T>(ReadOnlySpan<T> original, Span<T> current, ReadOnlySpan<byte> delta)
101113
where T : unmanaged
102114
{
103115
var orignalAsBytes = MemoryMarshal.AsBytes(original);
104-
var dataAsBytes = MemoryMarshal.AsBytes(data);
116+
var currentAsBytes = MemoryMarshal.AsBytes(current);
105117

106-
if (orignalAsBytes.Length != dataAsBytes.Length)
118+
if (orignalAsBytes.Length != currentAsBytes.Length)
107119
{
108-
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(dataAsBytes.Length)}");
120+
throw new InvalidOperationException($"{nameof(orignalAsBytes.Length)} must equal {nameof(currentAsBytes.Length)}");
109121
}
110122

111123
var dataPos = 0;
112-
var dataEnd = dataAsBytes.Length;
124+
var dataEnd = currentAsBytes.Length;
113125
var deltaPos = 0;
114126
var deltaEnd = delta.Length;
115127

@@ -126,14 +138,14 @@ public static void ApplyDelta<T>(ReadOnlySpan<T> original, Span<T> data, ReadOnl
126138
{
127139
header = -header;
128140

129-
if (dataEnd - dataPos < header || deltaEnd - deltaPos < header)
141+
if (header == int.MinValue || dataEnd - dataPos < header || deltaEnd - deltaPos < header)
130142
{
131143
throw new InvalidOperationException("Corrupt delta header!");
132144
}
133145

134146
for (var i = 0; i < header; i++)
135147
{
136-
dataAsBytes[dataPos + i] = (byte)(orignalAsBytes[dataPos + i] ^ delta[deltaPos + i]);
148+
currentAsBytes[dataPos + i] = (byte)(orignalAsBytes[dataPos + i] ^ delta[deltaPos + i]);
137149
}
138150

139151
deltaPos += header;
@@ -145,7 +157,7 @@ public static void ApplyDelta<T>(ReadOnlySpan<T> original, Span<T> data, ReadOnl
145157
throw new InvalidOperationException("Corrupt delta header!");
146158
}
147159

148-
orignalAsBytes.Slice(dataPos, header).CopyTo(dataAsBytes.Slice(dataPos, header));
160+
orignalAsBytes.Slice(dataPos, header).CopyTo(currentAsBytes.Slice(dataPos, header));
149161
}
150162

151163
dataPos += header;

src/BizHawk.Common/Serializer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -748,18 +748,18 @@ public void SyncFixedString(string name, ref string val, int length)
748748
}
749749
}
750750

751-
public void SyncDelta<T>(string name, T[] original, T[] data)
751+
public void SyncDelta<T>(string name, T[] original, T[] current)
752752
where T : unmanaged
753753
{
754754
if (IsReader)
755755
{
756756
var delta = Array.Empty<byte>();
757757
Sync(name, ref delta, useNull: false);
758-
DeltaSerializer.ApplyDelta<T>(original, data, delta);
758+
DeltaSerializer.ApplyDelta<T>(original, current, delta);
759759
}
760760
else
761761
{
762-
var delta = DeltaSerializer.GetDelta<T>(original, data).ToArray(); // TODO: don't create array here (need .net update to write span to binary writer)
762+
var delta = DeltaSerializer.GetDelta<T>(original, current).ToArray(); // TODO: don't create array here (need .net update to write span to binary writer)
763763
Sync(name, ref delta, useNull: false);
764764
}
765765
}

src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.IStatable.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ private void SyncState(Serializer ser)
1515
ser.Sync(nameof(CurrentDisk), ref _currentDisk);
1616
if (oldDisk != _currentDisk)
1717
{
18-
InitDisk();
18+
// don't use InitDisk here, no need to load in soon to be overwritten deltas
19+
InitMedia(_roms[_currentDisk]);
1920
}
2021
ser.Sync("PreviousDiskPressed", ref _prevPressed);
2122
ser.Sync("NextDiskPressed", ref _nextPressed);

src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.Motherboard.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public Motherboard(C64 c64, C64.VicType initRegion, C64.BorderType borderType, C
129129
{
130130
case C64.DiskDriveType.Commodore1541:
131131
case C64.DiskDriveType.Commodore1541II:
132-
DiskDrive = new Drive1541(ClockNumerator, ClockDenominator);
132+
DiskDrive = new Drive1541(ClockNumerator, ClockDenominator, () => _c64.CurrentDisk);
133133
Serial.Connect(DiskDrive);
134134
break;
135135
}

src/BizHawk.Emulation.Cores/Computers/Commodore64/C64.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ public C64(CoreLoadParameters<C64Settings, C64SyncSettings> lp)
3030
_cyclesPerFrame = _board.Vic.CyclesPerFrame;
3131
_memoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" });
3232

33+
if (_board.DiskDrive != null)
34+
{
35+
_board.DiskDrive.InitSaveRam(_roms.Count);
36+
ser.Register<ISaveRam>(_board.DiskDrive);
37+
}
38+
3339
InitMedia(_roms[_currentDisk]);
3440
HardReset();
3541

@@ -172,6 +178,7 @@ public string BoardName
172178

173179
private void IncrementDisk()
174180
{
181+
_board.DiskDrive.SaveDeltas();
175182
_currentDisk++;
176183
if (CurrentDisk >= _roms.Count)
177184
{
@@ -183,6 +190,7 @@ private void IncrementDisk()
183190

184191
private void DecrementDisk()
185192
{
193+
_board.DiskDrive.SaveDeltas();
186194
_currentDisk--;
187195
if (_currentDisk < 0)
188196
{
@@ -195,12 +203,14 @@ private void DecrementDisk()
195203
private void InitDisk()
196204
{
197205
InitMedia(_roms[_currentDisk]);
206+
_board.DiskDrive.LoadDeltas();
198207
}
199208

200209
public void SetDisk(int discNum)
201210
{
202211
if (_currentDisk != discNum)
203212
{
213+
_board.DiskDrive.SaveDeltas();
204214
_currentDisk = discNum;
205215
InitDisk();
206216
}

src/BizHawk.Emulation.Cores/Computers/Commodore64/Media/Disk.cs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ public Disk(int trackCapacity)
2626
_tracks = new int[trackCapacity][];
2727
FillMissingTracks();
2828
_originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray();
29-
_usedTracks = new bool[trackCapacity];
3029
Valid = true;
3130
}
3231

@@ -49,7 +48,6 @@ public Disk(IList<byte[]> trackData, IList<int> trackNumbers, IList<int> trackDe
4948
FillMissingTracks();
5049
Valid = true;
5150
_originalMedia = _tracks.Select(t => (int[])t.Clone()).ToArray();
52-
_usedTracks = new bool[trackCapacity];
5351
}
5452

5553
private int[] ConvertToFluxTransitions(int density, byte[] bytes, int fluxBitOffset)
@@ -135,29 +133,41 @@ private void FillMissingTracks()
135133
}
136134
}
137135

138-
public int[] GetDataForTrack(int halftrack)
136+
public void AttachTracker(bool[] usedTracks)
139137
{
140-
_usedTracks[halftrack] = true; // TODO: probably can be smarter about this with the WriteProtected flag
141-
return _tracks[halftrack];
138+
if (_tracks.Length != usedTracks.Length)
139+
{
140+
throw new InvalidOperationException("track and tracker length mismatch! (this should be impossible, please report)");
141+
}
142+
143+
_usedTracks = usedTracks;
142144
}
143145

144-
public void SyncState(Serializer ser)
146+
/// <summary>
147+
/// Generic update of the deltas stored in Drive1541's ISaveRam implementation.
148+
/// deltaUpdateCallback will be called for each track which has been possibly dirtied
149+
/// </summary>
150+
/// <param name="deltaUpdateCallback">callback</param>
151+
public void DeltaUpdate(Action<int, int[], int[]> deltaUpdateCallback)
145152
{
146-
ser.Sync(nameof(WriteProtected), ref WriteProtected);
147-
var oldUsedTracks = _usedTracks; // Sync changes reference if loading state (we don't care in the saving state case)
148-
ser.Sync(nameof(_usedTracks), ref _usedTracks, useNull: false);
149-
150153
for (var i = 0; i < _tracks.Length; i++)
151154
{
152155
if (_usedTracks[i])
153156
{
154-
ser.SyncDelta($"MediaState{i}", _originalMedia[i], _tracks[i]);
155-
}
156-
else if (ser.IsReader && oldUsedTracks[i]) // _tracks[i] might be different, but in the state it wasn't, so just copy _originalMedia[i]
157-
{
158-
_originalMedia[i].AsSpan().CopyTo(_tracks[i]);
157+
deltaUpdateCallback(i, _originalMedia[i], _tracks[i]);
159158
}
160159
}
161160
}
161+
162+
public int[] GetDataForTrack(int halftrack)
163+
{
164+
_usedTracks[halftrack] = true;
165+
return _tracks[halftrack];
166+
}
167+
168+
public void SyncState(Serializer ser)
169+
{
170+
ser.Sync(nameof(WriteProtected), ref WriteProtected);
171+
}
162172
}
163173
}

0 commit comments

Comments
 (0)