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