mirror of
https://github.com/fadden/6502bench.git
synced 2026-04-20 04:16:47 +00:00
f059705fb4
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)
340 lines
15 KiB
C#
340 lines
15 KiB
C#
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|