-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScreenWriteTraceCommand.cs
More file actions
166 lines (157 loc) · 6.16 KB
/
Copy pathScreenWriteTraceCommand.cs
File metadata and controls
166 lines (157 loc) · 6.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
using System.Globalization;
using Subterra.Spectrum;
namespace Subterra.Tools;
/// <summary>
/// Boot the snapshot, run N frames, then trace every write into
/// Spectrum bitmap memory (\$4000-\$57FF) and attribute memory
/// (\$5800-\$5AFF) during one further frame. Reports the top
/// "hot PCs" — the addresses of the game-code instructions that
/// did most of the writing.
/// </summary>
internal static class ScreenWriteTraceCommand
{
public static int Run(string[] args)
{
if (args.Length < 3)
{
Console.Error.WriteLine(
"usage: scrwrite-trace <48k.rom> <file.z80> <frames-before-trace> [-keys=...]");
return 2;
}
var rom = File.ReadAllBytes(args[0]);
var snap = Z80SnapshotReader.Load(args[1]);
int frames = int.Parse(args[2], CultureInfo.InvariantCulture);
string keys = "";
for (int i = 3; i < args.Length; i++)
{
if (args[i].StartsWith("-keys=", StringComparison.Ordinal))
keys = args[i].Substring("-keys=".Length);
}
var sys = new Spectrum48(rom);
sys.LoadSnapshot(snap);
for (int f = 0; f < frames; f++)
{
ApplyKeys(sys, keys, f);
sys.RunFrame();
}
ApplyKeys(sys, keys, frames);
var bitmapHits = new Dictionary<ushort, int>();
var attrHits = new Dictionary<ushort, int>();
var writtenBitmapAddresses = new SortedSet<ushort>();
var writtenAttrAddresses = new SortedSet<ushort>();
var bitmapTrace = new List<(ushort pc, ushort addr, byte oldVal, byte newVal)>();
sys.MemoryWritten += (addr, value) =>
{
ushort pc = sys.Cpu.PC;
if (addr < 0x5800)
{
bitmapHits[pc] = bitmapHits.GetValueOrDefault(pc) + 1;
writtenBitmapAddresses.Add(addr);
// Capture the first ~40 writes in full detail so we can
// hand-trace which sprite went to which screen byte.
if (bitmapTrace.Count < 40)
{
bitmapTrace.Add((pc, addr, 0, value));
}
}
else if (addr < 0x5B00)
{
attrHits[pc] = attrHits.GetValueOrDefault(pc) + 1;
writtenAttrAddresses.Add(addr);
}
};
// Capture A (the sprite byte) at the moment of the XOR-draw
// primitive so we can see the actual sprite-data byte stream.
var spriteBytes = new List<byte>();
sys.Cpu.MaskableInterrupt();
long target = sys.Cpu.Cycles + Spectrum48.TStatesPerFrame;
while (sys.Cpu.Cycles < target)
{
if (sys.Cpu.PC == 0xE1E1) // about to execute XOR (HL)
{
spriteBytes.Add(sys.Cpu.A);
}
sys.Cpu.Step();
}
Console.WriteLine($"After {frames} frames, traced 1 frame:");
Console.WriteLine($" bitmap writes: {bitmapHits.Values.Sum()} into {writtenBitmapAddresses.Count} distinct addresses");
Console.WriteLine($" attr writes: {attrHits.Values.Sum()} into {writtenAttrAddresses.Count} distinct addresses");
Console.WriteLine();
Console.WriteLine("Hot bitmap-write PCs (top 15):");
foreach (var (pc, c) in bitmapHits.OrderByDescending(kv => kv.Value).Take(15))
{
Console.WriteLine($" PC=${pc:X4} {c,6} writes");
}
if (writtenBitmapAddresses.Count > 0)
{
ushort lo = writtenBitmapAddresses.Min;
ushort hi = writtenBitmapAddresses.Max;
Console.WriteLine();
Console.WriteLine($"Bitmap addresses touched: ${lo:X4}..${hi:X4}");
}
if (spriteBytes.Count > 0)
{
Console.WriteLine();
Console.WriteLine($"Sprite source bytes captured at $E1E1 (raw A in XOR-draw, {spriteBytes.Count} total):");
for (int i = 0; i < spriteBytes.Count; i += 16)
{
int len = Math.Min(16, spriteBytes.Count - i);
var sb = new System.Text.StringBuilder();
sb.Append($" {i,4}: ");
for (int j = 0; j < len; j++)
{
sb.Append(spriteBytes[i + j].ToString("X2"));
sb.Append(' ');
}
Console.WriteLine(sb.ToString());
}
}
Console.WriteLine();
Console.WriteLine($"First {bitmapTrace.Count} bitmap writes (chronological):");
Console.WriteLine(" # PC screen byte sx,sy");
for (int i = 0; i < bitmapTrace.Count; i++)
{
var (pc, addr, _, val) = bitmapTrace[i];
var (sx, sy) = ScreenToXY(addr);
Console.WriteLine($" {i,3} ${pc:X4} ${addr:X4} ${val:X2} ({sx,3},{sy,3})");
}
return 0;
}
private static (int x, int y) ScreenToXY(ushort addr)
{
// Inverse of SpectrumScreen.BitmapAddress. offset = addr - 0x4000
// bits 12-11 = band, 10-8 = pixel row, 7-5 = char row, 4-0 = x byte.
int o = addr - 0x4000;
int band = (o >> 11) & 3;
int pixelRow = (o >> 8) & 7;
int charRow = (o >> 5) & 7;
int col = o & 0x1F;
int y = band * 64 + charRow * 8 + pixelRow;
int x = col * 8;
return (x, y);
}
private static void ApplyKeys(Spectrum48 sys, string keys, int frame)
{
sys.ReleaseAllKeys();
if (string.IsNullOrEmpty(keys)) return;
foreach (var part in keys.Split(','))
{
var bits = part.Split(':');
if (bits.Length != 2) continue;
string range = bits[0];
int dash = range.IndexOf('-');
int start, end;
if (dash > 0)
{
if (!int.TryParse(range[..dash], out start) || !int.TryParse(range[(dash + 1)..], out end)) continue;
}
else
{
if (!int.TryParse(range, out start)) continue;
end = start;
}
if (frame < start || frame > end) continue;
if (SpectrumKey.FromName(bits[1]) is { } key) sys.PressKey(key);
}
}
}