feat(analysis): add sprite scanner
This commit is contained in:
1 parent
c9c9bbe68b
commit
f7c48098ce
2 files changed
+172
No files matched your search
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Chip8.Analysis;
|
||||||
|
|
||||||
|
public sealed class SpriteCandidate
|
||||||
|
{
|
||||||
|
public SpriteCandidate(ushort address, int rows, int bytesPerRow, byte[] bytes, ushort loadAddress, ushort drawAddress)
|
||||||
|
{
|
||||||
|
this.address = address;
|
||||||
|
this.rows = rows;
|
||||||
|
this.bytesPerRow = bytesPerRow;
|
||||||
|
this.bytes = bytes;
|
||||||
|
this.loadAddress = loadAddress;
|
||||||
|
this.drawAddress = drawAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort address { get; }
|
||||||
|
public int rows { get; }
|
||||||
|
public int bytesPerRow { get; }
|
||||||
|
public byte[] bytes { get; }
|
||||||
|
public ushort loadAddress { get; }
|
||||||
|
public ushort drawAddress { get; }
|
||||||
|
|
||||||
|
public int widthPixels => bytesPerRow * 8;
|
||||||
|
|
||||||
|
public int setPixelCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
foreach (byte value in bytes)
|
||||||
|
{
|
||||||
|
int bits = value;
|
||||||
|
while (bits != 0)
|
||||||
|
{
|
||||||
|
count += bits & 1;
|
||||||
|
bits >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool isBlank => setPixelCount == 0;
|
||||||
|
|
||||||
|
public bool pixel(int row, int column)
|
||||||
|
{
|
||||||
|
int byteIndex = row * bytesPerRow + column / 8;
|
||||||
|
if (byteIndex >= bytes.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int bit = 7 - column % 8;
|
||||||
|
return (bytes[byteIndex] & (1 << bit)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string renderAscii(char on = '#', char off = '.')
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
for (int row = 0; row < rows; row++)
|
||||||
|
{
|
||||||
|
for (int column = 0; column < widthPixels; column++)
|
||||||
|
{
|
||||||
|
builder.Append(pixel(row, column) ? on : off);
|
||||||
|
}
|
||||||
|
builder.Append('\n');
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() =>
|
||||||
|
"sprite@0x" + address.ToString("X4", CultureInfo.InvariantCulture)
|
||||||
|
+ " " + widthPixels + "x" + rows
|
||||||
|
+ " (" + setPixelCount + " px, drawn at 0x" + drawAddress.ToString("X4", CultureInfo.InvariantCulture) + ")";
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Chip8.Analysis;
|
||||||
|
|
||||||
|
public sealed class SpriteScanner
|
||||||
|
{
|
||||||
|
public SpriteScanner(ushort entry = InstructionDecoder.defaultBaseAddress, ushort baseAddress = InstructionDecoder.defaultBaseAddress)
|
||||||
|
{
|
||||||
|
this.entry = entry;
|
||||||
|
this.baseAddress = baseAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort entry { get; }
|
||||||
|
public ushort baseAddress { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<SpriteCandidate> scan(byte[] program)
|
||||||
|
{
|
||||||
|
var traversal = FlowTraversal.explore(program, entry, baseAddress);
|
||||||
|
var ordered = traversal.decoded.Values.OrderBy(info => info.address).ToList();
|
||||||
|
|
||||||
|
var candidates = new List<SpriteCandidate>();
|
||||||
|
ushort? lastIndex = null;
|
||||||
|
ushort lastIndexAddress = 0;
|
||||||
|
|
||||||
|
foreach (var info in ordered)
|
||||||
|
{
|
||||||
|
if (info.loadsIndex && info.indexValue.HasValue)
|
||||||
|
{
|
||||||
|
lastIndex = info.indexValue.Value;
|
||||||
|
lastIndexAddress = info.address;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.kind == InstructionKind.LoadFont || info.kind == InstructionKind.LoadBigFont || info.kind == InstructionKind.AddIndex)
|
||||||
|
{
|
||||||
|
lastIndex = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.kind == InstructionKind.Draw && lastIndex.HasValue)
|
||||||
|
{
|
||||||
|
int rows = info.spriteRows!.Value;
|
||||||
|
int bytesPerRow = info.n == 0 ? 2 : 1;
|
||||||
|
byte[] bytes = readBytes(program, lastIndex.Value, rows * bytesPerRow);
|
||||||
|
candidates.Add(new SpriteCandidate(lastIndex.Value, rows, bytesPerRow, bytes, lastIndexAddress, info.address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<SpriteCandidate> scanDistinct(byte[] program)
|
||||||
|
{
|
||||||
|
var seen = new HashSet<ushort>();
|
||||||
|
var distinct = new List<SpriteCandidate>();
|
||||||
|
foreach (var candidate in scan(program))
|
||||||
|
{
|
||||||
|
if (seen.Add(candidate.address))
|
||||||
|
{
|
||||||
|
distinct.Add(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return distinct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpriteCandidate scanRegion(byte[] program, ushort address, int rows, int bytesPerRow = 1)
|
||||||
|
{
|
||||||
|
byte[] bytes = readBytes(program, address, rows * bytesPerRow);
|
||||||
|
return new SpriteCandidate(address, rows, bytesPerRow, bytes, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] readBytes(byte[] program, ushort address, int count)
|
||||||
|
{
|
||||||
|
int offset = address - baseAddress;
|
||||||
|
var bytes = new byte[count];
|
||||||
|
for (int index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
int source = offset + index;
|
||||||
|
bytes[index] = source >= 0 && source < program.Length ? program[source] : (byte)0;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string render(IReadOnlyList<SpriteCandidate> candidates)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
foreach (var candidate in candidates)
|
||||||
|
{
|
||||||
|
builder.Append(candidate).Append('\n');
|
||||||
|
builder.Append(candidate.renderAscii());
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user