mirror of
https://github.com/fadden/6502bench.git
synced 2026-04-19 12:25:05 +00:00
Add pre-processing of VICE CRT files
This adds a new system definition that, when selected, causes some pre-processing to be done on VICE CRT (cartridge) files. These have multiple chunks representing potentially overlapping ROM regions. Also, pulled RawData.cs from CiderPress2, as it has some newer stuff that we want. (It would've been handy for the OMF code.) Also, renamed SystemDefs.cs to SystemDef.cs to match the class name. (issue #183)
This commit is contained in:
+783
-36
@@ -15,87 +15,834 @@
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace CommonUtil {
|
||||
public class RawData {
|
||||
/// <summary>
|
||||
/// Utility functions for manipulating streams of primitive data types.
|
||||
/// </summary>
|
||||
public static class RawData {
|
||||
/// <summary>
|
||||
/// Extracts an integer from the data stream. Integers less than 4 bytes wide
|
||||
/// Zero-length array, useful for initializing properties to a non-null value without
|
||||
/// allocating anything.
|
||||
/// </summary>
|
||||
public static readonly byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||
public static readonly ushort[] EMPTY_USHORT_ARRAY = new ushort[0];
|
||||
public static readonly int[] EMPTY_INT_ARRAY = new int[0];
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a null-terminated ASCII string from byte data.
|
||||
/// </summary>
|
||||
/// <param name="data">Data buffer.</param>
|
||||
/// <param name="offset">Start offset in data buffer.</param>
|
||||
/// <param name="maxLen">Maximum length of string.</param>
|
||||
/// <param name="andMask">AND mask to apply to bytes.</param>
|
||||
/// <returns>String found. May be empty.</returns>
|
||||
public static string GetNullTermString(byte[] data, int offset, int maxLen, byte andMask) {
|
||||
Debug.Assert(offset + maxLen <= data.Length);
|
||||
StringBuilder sb = new StringBuilder(maxLen);
|
||||
for (int i = 0; i < maxLen; i++) {
|
||||
byte val = data[offset + i];
|
||||
if (val == 0) {
|
||||
break;
|
||||
}
|
||||
sb.Append((char)(val & andMask));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an integer from the data stream. Integers less than 4 bytes wide
|
||||
/// are not sign-extended.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw data stream.</param>
|
||||
/// <param name="offset">Start offset.</param>
|
||||
/// <param name="width">Word width, which may be 1-4 bytes.</param>
|
||||
/// <param name="width">Word width in bytes (1-4).</param>
|
||||
/// <param name="isBigEndian">True if word is in big-endian order.</param>
|
||||
/// <returns>Value found.</returns>
|
||||
/// <returns>Value read.</returns>
|
||||
public static int GetWord(byte[] data, int offset, int width, bool isBigEndian) {
|
||||
if (width < 1 || width > 4 || offset + width > data.Length) {
|
||||
throw new ArgumentOutOfRangeException("GetWord(offset=" + offset + " width=" +
|
||||
width + "), data.Length=" + data.Length);
|
||||
return (int)GetUWord(data, offset, width, isBigEndian);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned integer from the data stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Raw data stream.</param>
|
||||
/// <param name="offset">Start offset.</param>
|
||||
/// <param name="width">Word width in bytes (1-4).</param>
|
||||
/// <param name="isBigEndian">True if word is in big-endian order.</param>
|
||||
/// <returns>Value read.</returns>
|
||||
public static uint GetUWord(byte[] data, int offset, int width, bool isBigEndian) {
|
||||
if (width < 1 || width > 4 || offset > data.Length - width) {
|
||||
throw new ArgumentOutOfRangeException(nameof(offset),
|
||||
"GetUWord(offset=" + offset + " width=" + width +
|
||||
"), data.Length=" + data.Length);
|
||||
}
|
||||
if (isBigEndian) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return data[offset];
|
||||
case 2:
|
||||
return (data[offset] << 8) | data[offset + 1];
|
||||
return (uint)((data[offset] << 8) | data[offset + 1]);
|
||||
case 3:
|
||||
return (data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2];
|
||||
return (uint)((data[offset] << 16) | (data[offset + 1] << 8) |
|
||||
data[offset + 2]);
|
||||
case 4:
|
||||
return (data[offset] << 24) | (data[offset + 1] << 16) |
|
||||
(data[offset + 2] << 8) | data[offset + 3];
|
||||
return (uint)((data[offset] << 24) | (data[offset + 1] << 16) |
|
||||
(data[offset + 2] << 8) | data[offset + 3]);
|
||||
}
|
||||
} else {
|
||||
switch (width) {
|
||||
case 1:
|
||||
return data[offset];
|
||||
case 2:
|
||||
return data[offset] | (data[offset + 1] << 8);
|
||||
return (uint)(data[offset] | (data[offset + 1] << 8));
|
||||
case 3:
|
||||
return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
|
||||
return (uint)(data[offset] | (data[offset + 1] << 8) |
|
||||
(data[offset + 2] << 16));
|
||||
case 4:
|
||||
return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) |
|
||||
data[offset + 3] << 24;
|
||||
return (uint)(data[offset] | (data[offset + 1] << 8) |
|
||||
(data[offset + 2] << 16) | data[offset + 3] << 24);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("GetWord(): should not be here");
|
||||
throw new Exception("GetUWord should not be here");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a two-byte little-endian value from a byte stream, advancing the offset.
|
||||
/// Reads an unsigned 8-bit value from a byte stream, advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 1.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static byte ReadU8(byte[] data, ref int offset) {
|
||||
return data[offset++];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 8-bit value to a byte stream, advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 1.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU8(byte[] data, ref int offset, byte val) {
|
||||
data[offset++] = val;
|
||||
}
|
||||
|
||||
#region Little-Endian
|
||||
|
||||
/// <summary>
|
||||
/// Reads a little-endian value from a file stream.
|
||||
/// </summary>
|
||||
private static int ReadLE(Stream stream, int width, out bool ok) {
|
||||
int result = 0;
|
||||
int okCheck = 0;
|
||||
for (int i = 0; i < width; i++) {
|
||||
int val = stream.ReadByte(); // returns -1 on EOF
|
||||
okCheck |= val; // sign bit set after bad read
|
||||
result |= val << (i * 8);
|
||||
}
|
||||
ok = (okCheck >= 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a little-endian value to a file stream.
|
||||
/// </summary>
|
||||
private static void WriteLE(Stream stream, int width, int value) {
|
||||
for (int i = 0; i < width; i++) {
|
||||
stream.WriteByte((byte)value);
|
||||
value >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned 16-bit little-endian value from a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static ushort GetU16LE(byte[] data, int offset) {
|
||||
return (ushort)(data[offset] | (data[offset + 1] << 8));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an unsigned 16-bit little-endian value in a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void SetU16LE(byte[] data, int offset, ushort val) {
|
||||
data[offset] = (byte)val;
|
||||
data[offset + 1] = (byte)(val >> 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned 16-bit little-endian value from a byte stream,
|
||||
/// advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 2.</param>
|
||||
/// <returns>Value fetched.</returns>
|
||||
public static ushort FetchLittleUshort(byte[] data, ref int offset) {
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static ushort ReadU16LE(byte[] data, ref int offset) {
|
||||
ushort val = (ushort)(data[offset] | (data[offset + 1] << 8));
|
||||
offset += 2;
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare parts of two arrays for equality.
|
||||
/// Reads an unsigned 16-bit little-endian value from a file stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We can do this faster with unsafe code. For byte arrays, see
|
||||
/// https://stackoverflow.com/q/43289/294248 .
|
||||
/// </remarks>
|
||||
/// <param name="ar1">First array.</param>
|
||||
/// <param name="offset1">Initial offset in first array.</param>
|
||||
/// <param name="ar2">Second array.</param>
|
||||
/// <param name="offset2">Initial offset in second array.</param>
|
||||
/// <param name="count">Number of elements to compare.</param>
|
||||
/// <returns>True if the array elements are equal.</returns>
|
||||
public static bool CompareArrays<T>(T[] ar1, int offset1, T[] ar2, int offset2,
|
||||
int count) {
|
||||
Debug.Assert(count > 0);
|
||||
/// <param name="stream">File stream.</param>
|
||||
/// <param name="ok">Result: true if file read succeeded.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static ushort ReadU16LE(Stream stream, out bool ok) {
|
||||
return (ushort)ReadLE(stream, sizeof(ushort), out ok);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!ar1[offset1 + i].Equals(ar2[offset2 + i])) {
|
||||
/// <summary>
|
||||
/// Writes an unsigned 16-bit little-endian value to a byte stream, advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 2.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU16LE(byte[] data, ref int offset, ushort val) {
|
||||
data[offset++] = (byte)val;
|
||||
data[offset++] = (byte)(val >> 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 16-bit little-endian value to a file stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">File stream.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU16LE(Stream stream, ushort val) {
|
||||
stream.WriteByte((byte)val);
|
||||
stream.WriteByte((byte)(val >> 8));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned 24-bit little-endian value from a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static uint GetU24LE(byte[] data, int offset) {
|
||||
uint val = (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16));
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 24-bit little-endian value to a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <param name="val">Value to write</param>
|
||||
public static void SetU24LE(byte[] data, int offset, uint val) {
|
||||
data[offset] = (byte)val;
|
||||
data[offset + 1] = (byte)(val >> 8);
|
||||
data[offset + 2] = (byte)(val >> 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned 24-bit little-endian value from a byte stream,
|
||||
/// advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 3.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static uint ReadU24LE(byte[] data, ref int offset) {
|
||||
uint val = (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16));
|
||||
offset += 3;
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 24-bit little-endian value to a byte stream, advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 3.</param>
|
||||
/// <param name="val">Value to write</param>
|
||||
public static void WriteU24LE(byte[] data, ref int offset, uint val) {
|
||||
data[offset++] = (byte)val;
|
||||
data[offset++] = (byte)(val >> 8);
|
||||
data[offset++] = (byte)(val >> 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned 32-bit little-endian value from a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static uint GetU32LE(byte[] data, int offset) {
|
||||
return (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) |
|
||||
(data[offset + 3] << 24));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an unsigned 32-bit little-endian value in a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void SetU32LE(byte[] data, int offset, uint val) {
|
||||
data[offset] = (byte)val;
|
||||
data[offset + 1] = (byte)(val >> 8);
|
||||
data[offset + 2] = (byte)(val >> 16);
|
||||
data[offset + 3] = (byte)(val >> 24);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned 32-bit little-endian value from a byte stream,
|
||||
/// advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 4.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static uint ReadU32LE(byte[] data, ref int offset) {
|
||||
uint val = (uint)(data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) |
|
||||
(data[offset + 3] << 24));
|
||||
offset += 4;
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned 32-bit little-endian value from a file stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">File stream.</param>
|
||||
/// <param name="ok">Result: true if file read succeeded.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static uint ReadU32LE(Stream stream, out bool ok) {
|
||||
return (uint)ReadLE(stream, sizeof(uint), out ok);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 32-bit little-endian value to a byte stream, advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 4.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU32LE(byte[] data, ref int offset, uint val) {
|
||||
data[offset++] = (byte)val;
|
||||
data[offset++] = (byte)(val >> 8);
|
||||
data[offset++] = (byte)(val >> 16);
|
||||
data[offset++] = (byte)(val >> 24);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 32-bit little-endian value to a file stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">File stream.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU32LE(Stream stream, uint val) {
|
||||
WriteLE(stream, sizeof(uint), (int)val);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned 64-bit little-endian value from a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static ulong GetU64LE(byte[] data, int offset) {
|
||||
ulong val = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
val |= (ulong)data[offset + i] << (i * 8);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an unsigned 64-bit little-endian value in a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void SetU64LE(byte[] data, int offset, ulong val) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
data[offset + i] = (byte)val;
|
||||
val >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned 64-bit little-endian value from a byte stream,
|
||||
/// advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 8.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static ulong ReadU64LE(byte[] data, ref int offset) {
|
||||
ulong val = GetU64LE(data, offset);
|
||||
offset += 8;
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 64-bit little-endian value to a byte stream, advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 8.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU64LE(byte[] data, ref int offset, ulong val) {
|
||||
SetU64LE(data, offset, val);
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
#endregion Little-Endian
|
||||
|
||||
#region Big-Endian
|
||||
|
||||
/// <summary>
|
||||
/// Reads a big-endian value from a file stream.
|
||||
/// </summary>
|
||||
private static int ReadBE(Stream stream, int width, out bool ok) {
|
||||
int result = 0;
|
||||
int okCheck = 0;
|
||||
for (int i = 0; i < width; i++) {
|
||||
int val = stream.ReadByte(); // returns -1 on EOF
|
||||
okCheck |= val; // sign bit set after bad read
|
||||
result = (result << 8) | val;
|
||||
}
|
||||
ok = (okCheck >= 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a big-endian value to a file stream.
|
||||
/// </summary>
|
||||
private static void WriteBE(Stream stream, int width, int value) {
|
||||
for (int i = 0; i < width; i++) {
|
||||
stream.WriteByte((byte)(value >> 24));
|
||||
value <<= 8;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned 16-bit big-endian value from a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static ushort GetU16BE(byte[] data, int offset) {
|
||||
return (ushort)((data[offset] << 8) | data[offset + 1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an unsigned 16-bit big-endian value in a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void SetU16BE(byte[] data, int offset, ushort val) {
|
||||
data[offset] = (byte)(val >> 8);
|
||||
data[offset + 1] = (byte)val;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned 16-bit big-endian value from a byte stream,
|
||||
/// advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 2.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static ushort ReadU16BE(byte[] data, ref int offset) {
|
||||
ushort val = (ushort)((data[offset] << 8) | data[offset + 1]);
|
||||
offset += 2;
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 16-bit big-endian value to a byte stream, advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 2.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU16BE(byte[] data, ref int offset, ushort val) {
|
||||
data[offset++] = (byte)(val >> 8);
|
||||
data[offset++] = (byte)val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned 24-bit big-endian value from a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static uint GetU24BE(byte[] data, int offset) {
|
||||
return (uint)((data[offset] << 16) | (data[offset + 1] << 8) | data[offset + 2]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 24-bit big-endian value to a file stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">File stream.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU24BE(Stream stream, uint val) {
|
||||
WriteBE(stream, 3, (int)(val << 8));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned 32-bit big-endian value from a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static uint GetU32BE(byte[] data, int offset) {
|
||||
return (uint)((data[offset] << 24) | (data[offset + 1] << 16) |
|
||||
(data[offset + 2] << 8) | data[offset + 3]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an unsigned 32-bit big-endian value in a byte stream.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Data offset.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void SetU32BE(byte[] data, int offset, uint val) {
|
||||
data[offset] = (byte)(val >> 24);
|
||||
data[offset + 1] = (byte)(val >> 16);
|
||||
data[offset + 2] = (byte)(val >> 8);
|
||||
data[offset + 3] = (byte)val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned 32-bit big-endian value from a byte stream,
|
||||
/// advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 4.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static uint ReadU32BE(byte[] data, ref int offset) {
|
||||
uint val = (uint)((data[offset] << 24) | (data[offset + 1] << 16) |
|
||||
(data[offset + 2] << 8) | data[offset + 3]);
|
||||
offset += 4;
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned 32-bit big-endian value from a file stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">File stream.</param>
|
||||
/// <param name="ok">Result: true if file read succeeded.</param>
|
||||
/// <returns>Value retrieved.</returns>
|
||||
public static uint ReadU32BE(Stream stream, out bool ok) {
|
||||
return (uint)ReadBE(stream, sizeof(uint), out ok);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 32-bit big-endian value to a byte stream, advancing the offset.
|
||||
/// </summary>
|
||||
/// <param name="data">Byte stream.</param>
|
||||
/// <param name="offset">Initial offset. Value will be incremented by 4.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU32BE(byte[] data, ref int offset, uint val) {
|
||||
data[offset++] = (byte)(val >> 24);
|
||||
data[offset++] = (byte)(val >> 16);
|
||||
data[offset++] = (byte)(val >> 8);
|
||||
data[offset++] = (byte)val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 32-bit big-endian value to a file stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">File stream.</param>
|
||||
/// <param name="val">Value to write.</param>
|
||||
public static void WriteU32BE(Stream stream, uint val) {
|
||||
WriteBE(stream, sizeof(uint), (int)val);
|
||||
}
|
||||
|
||||
#endregion Big-Endian
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a region of data is entirely zeroes.
|
||||
/// </summary>
|
||||
/// <param name="data">Data buffer.</param>
|
||||
/// <param name="offset">Starting offset.</param>
|
||||
/// <param name="length">Length of data to check.</param>
|
||||
/// <returns>True if all values are zero.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Invalid length.</exception>
|
||||
public static bool IsAllZeroes(byte[] data, int offset, int length) {
|
||||
return IsAllValue(data, offset, length, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a region of data is entirely one specific value.
|
||||
/// </summary>
|
||||
/// <param name="data">Data buffer.</param>
|
||||
/// <param name="offset">Starting offset.</param>
|
||||
/// <param name="length">Length of data to check.</param>
|
||||
/// <param name="value">Value to compare to.</param>
|
||||
/// <returns>True if all values in the buffer match the argument.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Invalid length.</exception>
|
||||
public static bool IsAllValue(byte[] data, int offset, int length, byte value) {
|
||||
if (length <= 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(length));
|
||||
}
|
||||
while (length-- != 0) {
|
||||
if (data[offset++] != value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the first zero byte in a region of data.
|
||||
/// </summary>
|
||||
/// <param name="data">Data buffer.</param>
|
||||
/// <param name="offset">Starting offset.</param>
|
||||
/// <param name="length">Length of data to check.</param>
|
||||
/// <returns>Index of first zero byte.</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public static int FirstZero(byte[] data, int offset, int length) {
|
||||
if (length <= 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(length));
|
||||
}
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (data[offset + i] == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two byte arrays.
|
||||
/// </summary>
|
||||
/// <returns>True if they match, false if they're different.</returns>
|
||||
public static bool CompareBytes(byte[] ar1, byte[] ar2, int count) {
|
||||
if (ar1.Length < count || ar2.Length < count) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (ar1[i] != ar2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two byte arrays.
|
||||
/// </summary>
|
||||
/// <returns>True if they match, false if they're different.</returns>
|
||||
public static bool CompareBytes(byte[] ar1, int offset1, byte[] ar2, int offset2,
|
||||
int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (ar1[offset1 + i] != ar2[offset2 + i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the contents of two byte arrays.
|
||||
/// </summary>
|
||||
/// <param name="ar1">First array.</param>
|
||||
/// <param name="ar2">Second array.</param>
|
||||
/// <param name="count">Number of bytes to compare. May be zero.</param>
|
||||
/// <returns>A value less than, equal to, or greater than zero depending on whether the
|
||||
/// first "count" bytes of ar1 are less then, equal to, or greater than those in
|
||||
/// ar2.</returns>
|
||||
public static int MemCmp(byte[] ar1, byte[] ar2, int count) {
|
||||
if (count < 0) {
|
||||
throw new ArgumentOutOfRangeException(nameof(count), "must be positive");
|
||||
}
|
||||
if (ar1.Length < count || ar2.Length < count) {
|
||||
throw new ArgumentException("invalid count: len1=" + ar1.Length +
|
||||
" len2=" + ar2.Length + " count=" + count);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (ar1[i] != ar2[i]) {
|
||||
if (ar1[i] < ar2[i]) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a range of bytes to a value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// cf. https://stackoverflow.com/q/1897555/294248
|
||||
/// </remarks>
|
||||
/// <param name="array">Array to operate on.</param>
|
||||
/// <param name="offset">Starting offset.</param>
|
||||
/// <param name="length">Length of region.</param>
|
||||
/// <param name="value">Value to store.</param>
|
||||
public static void MemSet(byte[] array, int offset, int length, byte value) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
array[offset + i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a 32-bit little-endian character constant to a 4-character string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Assumes ASCII; does not restrict characters to printable values.
|
||||
/// </remarks>
|
||||
/// <param name="val">Constant.</param>
|
||||
/// <returns>Four-character string.</returns>
|
||||
public static string StringifyU32LE(uint val) {
|
||||
char[] arr = new char[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
arr[i] = (char)((byte)(val >> i * 8));
|
||||
}
|
||||
return new string(arr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a 32-bit big-endian character constant to a 4-character string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Assumes ASCII; does not restrict characters to printable values.
|
||||
/// </remarks>
|
||||
/// <param name="val">Constant.</param>
|
||||
/// <returns>Four-character string.</returns>
|
||||
public static string StringifyU32BE(uint val) {
|
||||
uint leVal = (val >> 16) | ((val << 16) & 0xffff0000);
|
||||
leVal = ((leVal >> 8) & 0x00ff00ff) | ((leVal << 8) & 0xff00ff00);
|
||||
return StringifyU32LE(leVal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a 4-character ASCII string to a 32-bit integer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Results are undefined if the argument includes non-ASCII chars.
|
||||
/// </remarks>
|
||||
/// <param name="str">String to convert.</param>
|
||||
/// <returns>Integer representation.</returns>
|
||||
public static int IntifyASCII(string str) {
|
||||
if (str.Length != 4) {
|
||||
throw new ArgumentException("String must be four characters: '" + str + "'");
|
||||
}
|
||||
return (str[0] << 24) | (str[1] << 16) | (str[2] << 8) | str[3];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a simple hex dump from a byte array.
|
||||
/// </summary>
|
||||
/// <param name="array">Data to dump.</param>
|
||||
/// <param name="offset">Start offset.</param>
|
||||
/// <param name="len">Length of data.</param>
|
||||
/// <returns>Hex dump in string.</returns>
|
||||
public static string SimpleHexDump(byte[] array, int offset, int len) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
string addrFmt;
|
||||
if (len >= 0x100) {
|
||||
addrFmt = "x4";
|
||||
} else {
|
||||
addrFmt = "x2";
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
if ((i & 0x0f) == 0) {
|
||||
if (i != 0) {
|
||||
sb.AppendLine();
|
||||
}
|
||||
sb.Append(i.ToString(addrFmt));
|
||||
sb.Append(':');
|
||||
}
|
||||
|
||||
sb.Append(' ');
|
||||
sb.Append(array[offset + i].ToString("x2"));
|
||||
}
|
||||
sb.AppendLine();
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a simple hex dump from a byte array.
|
||||
/// </summary>
|
||||
/// <param name="array">Data to dump.</param>
|
||||
/// <returns>Hex dump in string.</returns>
|
||||
public static string SimpleHexDump(byte[] array) {
|
||||
return SimpleHexDump(array, 0, array.Length);
|
||||
}
|
||||
|
||||
#if false
|
||||
// The compiler is apparently clever enough to figure out what CompareBytes() is
|
||||
// doing and make it very efficient, so there's no value in getting fancy. In fact,
|
||||
// the fancy versions are slightly slower.
|
||||
// cf. https://stackoverflow.com/a/48599119/294248
|
||||
|
||||
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
static extern int memcmp(byte[] b1, byte[] b2, long count);
|
||||
|
||||
static bool CompareBytesM(byte[] b1, byte[] b2) {
|
||||
// Validate buffers are the same length.
|
||||
// This also ensures that the count does not exceed the length of either buffer.
|
||||
return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0;
|
||||
}
|
||||
|
||||
public static bool CompareBytesS(byte[] ar1, int offset1, byte[] ar2, int offset2,
|
||||
int count) {
|
||||
ReadOnlySpan<byte> span1 = new ReadOnlySpan<byte>(ar1, offset1, count);
|
||||
ReadOnlySpan<byte> span2 = new ReadOnlySpan<byte>(ar2, offset2, count);
|
||||
|
||||
return span1.SequenceEqual(span2);
|
||||
}
|
||||
|
||||
public static void TestSpeed() {
|
||||
int count = 1024 * 1024 + 9; // just over 1MB
|
||||
|
||||
byte[] array1 = new byte[count];
|
||||
byte[] array2 = new byte[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
array1[i] = array2[i] = (byte)i;
|
||||
}
|
||||
|
||||
Debug.Assert(CompareBytes(array1, array2));
|
||||
Debug.Assert(CompareBytesM(array1, array2));
|
||||
Debug.Assert(CompareBytes(array1, 0, array2, 0, array1.Length));
|
||||
Debug.Assert(CompareBytesS(array1, 0, array2, 0, array1.Length));
|
||||
|
||||
const int RUNS = 100;
|
||||
Stopwatch stopWatch = new Stopwatch();
|
||||
int good;
|
||||
|
||||
good = 0;
|
||||
stopWatch.Start();
|
||||
for (int i = 0; i < RUNS; i++) {
|
||||
good += CompareBytes(array1, array2) ? 1 : 0;
|
||||
}
|
||||
stopWatch.Stop();
|
||||
Debug.WriteLine("Simple (" + good + "): " + stopWatch.ElapsedMilliseconds);
|
||||
|
||||
good = 0;
|
||||
stopWatch.Start();
|
||||
for (int i = 0; i < RUNS; i++) {
|
||||
good += CompareBytesM(array1, array2) ? 1 : 0;
|
||||
}
|
||||
stopWatch.Stop();
|
||||
Debug.WriteLine("Memcmp (" + good + "): " + stopWatch.ElapsedMilliseconds);
|
||||
|
||||
good = 0;
|
||||
stopWatch.Start();
|
||||
for (int i = 0; i < RUNS; i++) {
|
||||
good += CompareBytes(array1, 3, array2, 3, array1.Length - 3) ? 1 : 0;
|
||||
}
|
||||
stopWatch.Stop();
|
||||
Debug.WriteLine("Offsets (" + good + "): " + stopWatch.ElapsedMilliseconds);
|
||||
|
||||
good = 0;
|
||||
stopWatch.Start();
|
||||
for (int i = 0; i < RUNS; i++) {
|
||||
good += CompareBytesS(array1, 3, array2, 3, array1.Length - 3) ? 1 : 0;
|
||||
}
|
||||
stopWatch.Stop();
|
||||
Debug.WriteLine("Spans (" + good + "): " + stopWatch.ElapsedMilliseconds);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace CommonUtil {
|
||||
gce.DisposalMethod = (byte)((pak >> 2) & 0x07);
|
||||
gce.UserInputFlag = (pak & 0x02) != 0;
|
||||
gce.TransparencyFlag = (pak & 0x01) != 0;
|
||||
gce.DelayTime = RawData.FetchLittleUshort(data, ref offset);
|
||||
gce.DelayTime = RawData.ReadU16LE(data, ref offset);
|
||||
gce.TransparentColorIndex = data[offset++];
|
||||
if (data[offset++] != 0) {
|
||||
Debug.WriteLine("Missing termination in GCE data");
|
||||
@@ -166,10 +166,10 @@ namespace CommonUtil {
|
||||
//
|
||||
Debug.Assert(data[offset] == IMAGE_SEPARATOR);
|
||||
offset++;
|
||||
grb.ImageLeftPosition = RawData.FetchLittleUshort(data, ref offset);
|
||||
grb.ImageTopPosition = RawData.FetchLittleUshort(data, ref offset);
|
||||
grb.ImageWidth = RawData.FetchLittleUshort(data, ref offset);
|
||||
grb.ImageHeight = RawData.FetchLittleUshort(data, ref offset);
|
||||
grb.ImageLeftPosition = RawData.ReadU16LE(data, ref offset);
|
||||
grb.ImageTopPosition = RawData.ReadU16LE(data, ref offset);
|
||||
grb.ImageWidth = RawData.ReadU16LE(data, ref offset);
|
||||
grb.ImageHeight = RawData.ReadU16LE(data, ref offset);
|
||||
byte pak = data[offset++];
|
||||
grb.LocalColorTableFlag = (pak & 0x80) != 0;
|
||||
grb.InterlaceFlag = (pak & 0x40) != 0;
|
||||
@@ -240,9 +240,9 @@ namespace CommonUtil {
|
||||
//
|
||||
// Header. Signature ("GIF") + version ("87a" or "89a").
|
||||
//
|
||||
if (RawData.CompareArrays(gifData, 0, GIF87A, 0, GIF87A.Length)) {
|
||||
if (RawData.CompareBytes(gifData, 0, GIF87A, 0, GIF87A.Length)) {
|
||||
FileVer = FileVersion.Gif87a;
|
||||
} else if (RawData.CompareArrays(gifData, 0, GIF89A, 0, GIF87A.Length)) {
|
||||
} else if (RawData.CompareBytes(gifData, 0, GIF89A, 0, GIF87A.Length)) {
|
||||
FileVer = FileVersion.Gif89a;
|
||||
} else {
|
||||
Debug.WriteLine("GIF signature not found");
|
||||
@@ -256,8 +256,8 @@ namespace CommonUtil {
|
||||
// Logical screen descriptor.
|
||||
//
|
||||
int offset = GIF87A.Length;
|
||||
LogicalScreenWidth = RawData.FetchLittleUshort(gifData, ref offset);
|
||||
LogicalScreenHeight = RawData.FetchLittleUshort(gifData, ref offset);
|
||||
LogicalScreenWidth = RawData.ReadU16LE(gifData, ref offset);
|
||||
LogicalScreenHeight = RawData.ReadU16LE(gifData, ref offset);
|
||||
pak = gifData[offset++];
|
||||
GlobalColorTableFlag = (pak & 0x80) != 0;
|
||||
ColorResolution = (byte)((pak >> 4) & 0x07);
|
||||
|
||||
@@ -410,8 +410,17 @@ namespace SourceGen {
|
||||
|
||||
// Configure the load address.
|
||||
AddrMap.Clear();
|
||||
if (SystemDefaults.GetFirstWordIsLoadAddr(sysDef) && FileDataLength > 2 &&
|
||||
if (SystemDefaults.GetFileFormat(sysDef) == SystemDefaults.FILE_FMT_VICE_CRT) {
|
||||
string failMsg = ViceCrt.ConfigureProject(this);
|
||||
if (!string.IsNullOrEmpty(failMsg)) {
|
||||
string cmt = "Unable to process as VICE CRT: " + failMsg;
|
||||
LongComments[LineListGen.Line.HEADER_COMMENT_OFFSET] =
|
||||
new MultiLineComment(cmt);
|
||||
}
|
||||
} else if (SystemDefaults.GetFirstWordIsLoadAddr(sysDef) && FileDataLength > 2 &&
|
||||
mFileData != null) {
|
||||
// Used for C64 .PRG files.
|
||||
//
|
||||
// First two bytes are the load address, with the actual file data starting
|
||||
// at +000002. The first two bytes are non-addressable, so we leave them
|
||||
// out of the map.
|
||||
|
||||
@@ -29,18 +29,23 @@ The following fields are mandatory:
|
||||
|
||||
The currently-supported parameters are:
|
||||
|
||||
* `load-address=<addr>` - Specify the initial load address. The default
|
||||
is 0x1000.
|
||||
* `default-text-encoding=<mode>` - Specify default character encoding.
|
||||
Use `c64-petscii` for PETSCII. The default is low/high ASCII.
|
||||
* `entry-flags=<flag-set>` - Specify the processor status flag values to
|
||||
use at entry points. This is intended for use on the 65802/65816, and
|
||||
may be one of `emulation`, `native-short`, and `native-long`. The
|
||||
default is `emulation`.
|
||||
* `undocumented-opcodes={true|false}` - Enable or disable undocumented
|
||||
opcodes. They are disabled by default.
|
||||
* `file-format=<format>` - Indicates that the input file is expected
|
||||
to be in a format that requires additional processing. Currently,
|
||||
this may be `vice-crt`.
|
||||
* `first-word-is-load-addr={true|false}` - If true, the first two bytes of
|
||||
the file contain the load address.
|
||||
* `default-text-encoding=<mode>` - Specify default character encoding.
|
||||
Use `c64-petscii` for PETSCII. The default is low/high ASCII.
|
||||
* `load-address=<addr>` - Specify the initial load address. The default
|
||||
is 0x1000.
|
||||
* `two-byte-brk={true|false}` - If true, treat the BRK instruction as
|
||||
two bytes instead of one.
|
||||
* `undocumented-opcodes={true|false}` - Enable or disable undocumented
|
||||
opcodes. They are disabled by default.
|
||||
|
||||
All of these things can be changed after the project has begun, but it's
|
||||
nice to have them configured in advance.
|
||||
|
||||
@@ -211,6 +211,23 @@
|
||||
"default-text-encoding":"c64-petscii"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name" : "Commodore 64 (CRT)",
|
||||
"GroupName" : "Commodore",
|
||||
"Cpu" : "6510",
|
||||
"Speed" : "1.023",
|
||||
"Description" : "Commodore 64 home computer. The file is in VICE CRT (cartridge) format.",
|
||||
"SymbolFiles" : [
|
||||
"RT:Commodore/C64-Kernal.sym65"
|
||||
],
|
||||
"ExtensionScripts" : [
|
||||
"RT:Commodore/VisC64.cs"
|
||||
],
|
||||
"Parameters" : {
|
||||
"file-format":"vice-crt",
|
||||
"default-text-encoding":"c64-petscii"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name" : "Commodore 128",
|
||||
"GroupName" : "Commodore",
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
</Compile>
|
||||
<Compile Include="LocalVariableTable.cs" />
|
||||
<Compile Include="RenderAddressMap.cs" />
|
||||
<Compile Include="ViceCrt.cs" />
|
||||
<Compile Include="Visualization.cs" />
|
||||
<Compile Include="VisBitmapAnimation.cs" />
|
||||
<Compile Include="VisualizationSet.cs" />
|
||||
@@ -235,7 +236,7 @@
|
||||
<Compile Include="Symbol.cs" />
|
||||
<Compile Include="SymbolTable.cs" />
|
||||
<Compile Include="SystemDefaults.cs" />
|
||||
<Compile Include="SystemDefs.cs" />
|
||||
<Compile Include="SystemDef.cs" />
|
||||
<Compile Include="UndoableChange.cs" />
|
||||
<Compile Include="DisplayListSelection.cs" />
|
||||
<Compile Include="WeakSymbolRef.cs" />
|
||||
|
||||
@@ -17,7 +17,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web.Script.Serialization;
|
||||
|
||||
@@ -22,22 +22,27 @@ using TextScanMode = SourceGen.ProjectProperties.AnalysisParameters.TextScanMode
|
||||
|
||||
namespace SourceGen {
|
||||
/// <summary>
|
||||
/// Helper functions for extracting values from a SystemDef instance.
|
||||
/// Helper functions for extracting values from the "parameters" section of a SystemDef object.
|
||||
/// </summary>
|
||||
public static class SystemDefaults {
|
||||
private const string LOAD_ADDRESS = "load-address";
|
||||
private const string ENTRY_FLAGS = "entry-flags";
|
||||
private const string UNDOCUMENTED_OPCODES = "undocumented-opcodes";
|
||||
private const string TWO_BYTE_BRK = "two-byte-brk";
|
||||
private const string FIRST_WORD_IS_LOAD_ADDR = "first-word-is-load-addr";
|
||||
// Parameter names.
|
||||
private const string DEFAULT_TEXT_ENCODING = "default-text-encoding";
|
||||
private const string ENTRY_FLAGS = "entry-flags";
|
||||
private const string FILE_FORMAT = "file-format";
|
||||
private const string FIRST_WORD_IS_LOAD_ADDR = "first-word-is-load-addr";
|
||||
private const string LOAD_ADDRESS = "load-address";
|
||||
private const string TWO_BYTE_BRK = "two-byte-brk";
|
||||
private const string UNDOCUMENTED_OPCODES = "undocumented-opcodes";
|
||||
|
||||
// Recognized values for specific parameters.
|
||||
private const string ENTRY_FLAG_EMULATION = "emulation";
|
||||
private const string ENTRY_FLAG_NATIVE_LONG = "native-long";
|
||||
private const string ENTRY_FLAG_NATIVE_SHORT = "native-short";
|
||||
|
||||
private const string TEXT_ENCODING_C64_PETSCII = "c64-petscii";
|
||||
|
||||
public const string FILE_FMT_VICE_CRT = "vice-crt";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default load address.
|
||||
@@ -146,6 +151,15 @@ namespace SourceGen {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the file format parameter.
|
||||
/// </summary>
|
||||
/// <param name="sysDef"></param>
|
||||
/// <returns>File format string, or empty string if not defined.</returns>
|
||||
public static string GetFileFormat(SystemDef sysDef) {
|
||||
return GetStringParam(sysDef, FILE_FORMAT, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a parameter with a matching name and a boolean value.
|
||||
/// </summary>
|
||||
@@ -167,5 +181,22 @@ namespace SourceGen {
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a parameter with a matching name and a string value.
|
||||
/// </summary>
|
||||
/// <param name="sysDef">SystemDef reference.</param>
|
||||
/// <param name="paramName">Name of parameter to look for.</param>
|
||||
/// <param name="defVal">Default value.</param>
|
||||
/// <returns>Parsed value, or defVal if the parameter doesn't exist.</returns>
|
||||
private static string GetStringParam(SystemDef sysDef, string paramName, string defVal) {
|
||||
Dictionary<string, string> parms = sysDef.Parameters;
|
||||
string retVal = defVal;
|
||||
|
||||
if (parms.TryGetValue(paramName, out string valueStr)) {
|
||||
retVal = valueStr;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright 2025 faddenSoft
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using CommonUtil;
|
||||
|
||||
namespace SourceGen {
|
||||
/// <summary>
|
||||
/// Special handling for VICE .CRT cartridge files.
|
||||
/// </summary>
|
||||
public static class ViceCrt {
|
||||
private static readonly byte[] CBM80 = new byte[] { 0xc3, 0xc2, 0xcd, 0x38, 0x30 };
|
||||
|
||||
/// <summary>
|
||||
/// Cartridge file header.
|
||||
/// </summary>
|
||||
private class Header {
|
||||
public const int MIN_LENGTH = 64;
|
||||
public const int CART_NAME_BUF_LEN = 32;
|
||||
|
||||
// Known header signatures.
|
||||
private static readonly byte[][] SIGNATURES = new byte[][] {
|
||||
Encoding.ASCII.GetBytes("C64 CARTRIDGE "),
|
||||
Encoding.ASCII.GetBytes("C128 CARTRIDGE "),
|
||||
Encoding.ASCII.GetBytes("CBM2 CARTRIDGE "),
|
||||
Encoding.ASCII.GetBytes("VIC20 CARTRIDGE "),
|
||||
Encoding.ASCII.GetBytes("PLUS4 CARTRIDGE "),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Cartridge signature string. 16 chars, padded with spaces.
|
||||
/// </summary>
|
||||
public string Signature { get { return Encoding.ASCII.GetString(mSignature); } }
|
||||
|
||||
/// <summary>
|
||||
/// True if the signature is one we recognize.
|
||||
/// </summary>
|
||||
public bool HasValidSignature {
|
||||
get {
|
||||
foreach (byte[] sig in SIGNATURES) {
|
||||
if (RawData.CompareBytes(sig, mSignature, mSignature.Length)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cartridge name. Upper case, padded with null bytes.
|
||||
/// </summary>
|
||||
public string CartName {
|
||||
get {
|
||||
int len;
|
||||
for (len = 0; len < mCartName.Length; len++) {
|
||||
if (mCartName[len] == 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Encoding.ASCII.GetString(mCartName, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
public int ActualLength {
|
||||
get { return mHeaderLen < MIN_LENGTH ? MIN_LENGTH : (int)mHeaderLen; }
|
||||
}
|
||||
|
||||
public byte[] mSignature = new byte[16];
|
||||
public uint mHeaderLen;
|
||||
public ushort mCartVersion;
|
||||
public ushort mCartHardwareType;
|
||||
public byte mCartExromLine;
|
||||
public byte mCartGameLine;
|
||||
public byte mCartHardwareRevision;
|
||||
public byte[] mReserved = new byte[5];
|
||||
public byte[] mCartName = new byte[CART_NAME_BUF_LEN];
|
||||
|
||||
public void Load(byte[] buf, ref int offset) {
|
||||
if (offset >= buf.Length - MIN_LENGTH) {
|
||||
return; // too short
|
||||
}
|
||||
int startOffset = offset;
|
||||
Array.Copy(buf, offset, mSignature, 0, mSignature.Length);
|
||||
offset += mSignature.Length;
|
||||
mHeaderLen = RawData.ReadU32BE(buf, ref offset);
|
||||
mCartVersion = RawData.ReadU16BE(buf, ref offset);
|
||||
mCartHardwareType = RawData.ReadU16BE(buf, ref offset);
|
||||
mCartExromLine = RawData.ReadU8(buf, ref offset);
|
||||
mCartGameLine = RawData.ReadU8(buf, ref offset);
|
||||
mCartHardwareRevision = RawData.ReadU8(buf, ref offset);
|
||||
Array.Copy(buf, offset, mReserved, 0, mReserved.Length);
|
||||
offset += mReserved.Length;
|
||||
Array.Copy(buf, offset, mCartName, 0, mCartName.Length);
|
||||
offset += mCartName.Length;
|
||||
Debug.Assert(offset == startOffset + MIN_LENGTH);
|
||||
|
||||
// Skip past any additional bytes. Not expected.
|
||||
offset += ActualLength - MIN_LENGTH;
|
||||
}
|
||||
|
||||
public bool Validate() {
|
||||
if (!HasValidSignature) {
|
||||
return false;
|
||||
}
|
||||
if (mHeaderLen > 1024) {
|
||||
// Arbitrary limit. In practice, should always be 0x40, though the docs
|
||||
// say some (malformed) files have 0x20.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "CHIP" chunk header.
|
||||
/// </summary>
|
||||
private class Chip {
|
||||
public const int LENGTH = 16;
|
||||
private static byte[] SIGNATURE = Encoding.ASCII.GetBytes("CHIP");
|
||||
|
||||
public bool HasValidSignature {
|
||||
get { return RawData.CompareBytes(SIGNATURE, mSignature, SIGNATURE.Length); }
|
||||
}
|
||||
|
||||
public byte[] mSignature = new byte[4];
|
||||
public uint mPacketLength;
|
||||
public ushort mChipType;
|
||||
public ushort mBankNumber;
|
||||
public ushort mLoadAddr;
|
||||
public ushort mImageSize;
|
||||
|
||||
public int mHdrOffset;
|
||||
public int mDataOffset;
|
||||
|
||||
public void Load(byte[] buf, ref int offset) {
|
||||
if (offset >= buf.Length - LENGTH) {
|
||||
return; // too short
|
||||
}
|
||||
int startOffset = offset;
|
||||
mHdrOffset = offset;
|
||||
Array.Copy(buf, offset, mSignature, 0, mSignature.Length);
|
||||
offset += mSignature.Length;
|
||||
mPacketLength = RawData.ReadU32BE(buf, ref offset);
|
||||
mChipType = RawData.ReadU16BE(buf, ref offset);
|
||||
mBankNumber = RawData.ReadU16BE(buf, ref offset);
|
||||
mLoadAddr = RawData.ReadU16BE(buf, ref offset);
|
||||
mImageSize = RawData.ReadU16BE(buf, ref offset);
|
||||
Debug.Assert(offset == startOffset + LENGTH);
|
||||
|
||||
mDataOffset = offset;
|
||||
}
|
||||
|
||||
public bool Validate(byte[] buf) {
|
||||
if (!HasValidSignature) {
|
||||
Debug.WriteLine("Invalid CHIP signature");
|
||||
return false;
|
||||
}
|
||||
if (mImageSize == 0 || (mImageSize & 0x00ff) != 0) {
|
||||
Debug.WriteLine("Bad CHIP image size: " + mImageSize);
|
||||
return false;
|
||||
}
|
||||
if (mPacketLength != mImageSize + LENGTH) {
|
||||
// Docs say "should always be equal to ROM image size + $10".
|
||||
Debug.WriteLine("Bad CHIP length: pkt=" + mPacketLength +
|
||||
", img=" + mImageSize);
|
||||
return false;
|
||||
}
|
||||
if (mDataOffset > buf.Length - mImageSize) {
|
||||
Debug.WriteLine("CHIP extends off end of file: off=" + mDataOffset +
|
||||
" size=" + mImageSize + " bufLen=" + buf.Length);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the project based on the contents of the data file, which must be in
|
||||
/// VICE CRT format.
|
||||
/// </summary>
|
||||
/// <param name="project">Project instance.</param>
|
||||
/// <returns>Failure message, or empty string on success.</returns>
|
||||
public static string ConfigureProject(DisasmProject project) {
|
||||
byte[] dataBuf = project.FileData;
|
||||
int offset = 0;
|
||||
|
||||
//
|
||||
// Read the file header and all CHIPs. Validate everything before we make any
|
||||
// changes to the project.
|
||||
//
|
||||
Header hdr = new Header();
|
||||
hdr.Load(dataBuf, ref offset);
|
||||
if (!hdr.Validate()) {
|
||||
return "invalid CRT header";
|
||||
}
|
||||
|
||||
List<Chip> chips = new List<Chip>();
|
||||
while (offset < dataBuf.Length - Chip.LENGTH) {
|
||||
Chip chip = new Chip();
|
||||
chip.Load(dataBuf, ref offset);
|
||||
if (!chip.Validate(dataBuf)) {
|
||||
return "invalid CHIP #" + chips.Count;
|
||||
}
|
||||
chips.Add(chip);
|
||||
offset += chip.mImageSize;
|
||||
}
|
||||
if (chips.Count == 0) {
|
||||
return "no CHIPs found";
|
||||
}
|
||||
if (offset != dataBuf.Length) {
|
||||
Debug.WriteLine("Warning: found extra bytes at end: " + (dataBuf.Length - offset));
|
||||
// keep going
|
||||
}
|
||||
|
||||
Debug.WriteLine("name='" + hdr.CartName + "' chips=" + chips.Count);
|
||||
|
||||
// Configure address map.
|
||||
project.AddrMap.Clear();
|
||||
project.AddrMap.AddEntry(0, hdr.ActualLength, AddressMap.NON_ADDR);
|
||||
offset = hdr.ActualLength;
|
||||
foreach (Chip chip in chips) {
|
||||
project.AddrMap.AddEntry(offset, Chip.LENGTH, AddressMap.NON_ADDR);
|
||||
offset += Chip.LENGTH;
|
||||
Debug.Assert(offset == chip.mDataOffset);
|
||||
project.AddrMap.AddEntry(offset, chip.mImageSize, chip.mLoadAddr);
|
||||
offset += chip.mImageSize;
|
||||
}
|
||||
|
||||
// Generate a file comment.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendFormat(Res.Strings.DEFAULT_HEADER_COMMENT_FMT, App.ProgramVersion);
|
||||
sb.AppendLine();
|
||||
sb.AppendLine();
|
||||
sb.Append("File signature: ");
|
||||
sb.AppendLine(hdr.Signature.Trim());
|
||||
sb.Append("Cartridge name: ");
|
||||
sb.AppendLine(hdr.CartName);
|
||||
sb.AppendLine("CHIP packets:");
|
||||
|
||||
// Format data items in file header.
|
||||
project.OperandFormats[0x00] = FormatDescriptor.Create(16,
|
||||
FormatDescriptor.Type.StringGeneric, FormatDescriptor.SubType.Ascii);
|
||||
project.OperandFormats[0x10] = FormatDescriptor.Create(4,
|
||||
FormatDescriptor.Type.NumericBE, FormatDescriptor.SubType.None);
|
||||
project.OperandFormats[0x14] = FormatDescriptor.Create(2,
|
||||
FormatDescriptor.Type.NumericBE, FormatDescriptor.SubType.None);
|
||||
project.OperandFormats[0x16] = FormatDescriptor.Create(2,
|
||||
FormatDescriptor.Type.NumericBE, FormatDescriptor.SubType.None);
|
||||
project.OperandFormats[0x1b] = FormatDescriptor.Create(5,
|
||||
FormatDescriptor.Type.Dense, FormatDescriptor.SubType.None);
|
||||
int cartNameLen = hdr.CartName.Length;
|
||||
project.OperandFormats[0x20] = FormatDescriptor.Create(cartNameLen,
|
||||
FormatDescriptor.Type.StringGeneric, FormatDescriptor.SubType.Ascii);
|
||||
if (cartNameLen < Header.CART_NAME_BUF_LEN) {
|
||||
project.OperandFormats[0x20 + cartNameLen] =
|
||||
FormatDescriptor.Create(Header.CART_NAME_BUF_LEN - cartNameLen,
|
||||
FormatDescriptor.Type.Fill, FormatDescriptor.SubType.None);
|
||||
}
|
||||
|
||||
// Format data items in CHIP headers.
|
||||
for (int i = 0; i < chips.Count; i++) {
|
||||
Chip chip = chips[i];
|
||||
offset = chip.mHdrOffset;
|
||||
|
||||
project.Notes[offset] = new MultiLineComment("CHIP #" + i);
|
||||
|
||||
project.OperandFormats[offset + 0x00] = FormatDescriptor.Create(4,
|
||||
FormatDescriptor.Type.StringGeneric, FormatDescriptor.SubType.Ascii);
|
||||
project.OperandFormats[offset + 0x04] = FormatDescriptor.Create(4,
|
||||
FormatDescriptor.Type.NumericBE, FormatDescriptor.SubType.None);
|
||||
project.OperandFormats[offset + 0x08] = FormatDescriptor.Create(2,
|
||||
FormatDescriptor.Type.NumericBE, FormatDescriptor.SubType.None);
|
||||
project.OperandFormats[offset + 0x0a] = FormatDescriptor.Create(2,
|
||||
FormatDescriptor.Type.NumericBE, FormatDescriptor.SubType.None);
|
||||
project.OperandFormats[offset + 0x0c] = FormatDescriptor.Create(2,
|
||||
FormatDescriptor.Type.NumericBE, FormatDescriptor.SubType.None);
|
||||
project.OperandFormats[offset + 0x0e] = FormatDescriptor.Create(2,
|
||||
FormatDescriptor.Type.NumericBE, FormatDescriptor.SubType.None);
|
||||
|
||||
sb.AppendFormat(" #{0:D2}: addr=${1:X4} len=${2:X4}",
|
||||
i, chip.mLoadAddr, chip.mImageSize);
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
// Remove the code start tag added by the new-project code.
|
||||
project.AnalyzerTags[0] = CodeAnalysis.AnalyzerTag.None;
|
||||
|
||||
// Look for CBM80 in the first chunk. If found, do additional formatting.
|
||||
offset = chips[0].mDataOffset;
|
||||
if (RawData.CompareBytes(CBM80, 0, dataBuf, offset + 4, CBM80.Length)) {
|
||||
// First two words are the addresses of the hard-reset and soft-reset handlers.
|
||||
project.OperandFormats[offset + 0x00] = FormatDescriptor.Create(2,
|
||||
FormatDescriptor.Type.NumericLE, FormatDescriptor.SubType.Address);
|
||||
project.OperandFormats[offset + 0x02] = FormatDescriptor.Create(2,
|
||||
FormatDescriptor.Type.NumericLE, FormatDescriptor.SubType.Address);
|
||||
project.OperandFormats[offset + 0x04] = FormatDescriptor.Create(5,
|
||||
FormatDescriptor.Type.StringGeneric, FormatDescriptor.SubType.C64Petscii);
|
||||
|
||||
// If the handler addresses fall within this chunk, tag them as code starts.
|
||||
TryTagChunkAddr(project, chips[0], RawData.GetU16LE(dataBuf, offset + 0x00));
|
||||
TryTagChunkAddr(project, chips[0], RawData.GetU16LE(dataBuf, offset + 0x02));
|
||||
|
||||
// The first byte past "CBM80" is sometimes code and sometimes not.
|
||||
//project.AnalyzerTags[offset + 0x09] = CodeAnalysis.AnalyzerTag.Code;
|
||||
}
|
||||
|
||||
// Replace the project-header comment.
|
||||
sb.AppendLine();
|
||||
project.LongComments[LineListGen.Line.HEADER_COMMENT_OFFSET] =
|
||||
new MultiLineComment(sb.ToString());
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private static void TryTagChunkAddr(DisasmProject project, Chip chip, ushort addr) {
|
||||
if (addr < chip.mLoadAddr || addr >= chip.mLoadAddr + chip.mImageSize) {
|
||||
return;
|
||||
}
|
||||
int offset = addr - chip.mLoadAddr;
|
||||
project.AnalyzerTags[chip.mDataOffset + offset] = CodeAnalysis.AnalyzerTag.Code;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user