/* * Copyright 2021 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 PluginCommon; namespace RuntimeData.Common { /// /// Performs inline data formatting for various common situations: /// /// InAZ_* - inline ASCII null-terminated string /// InA1_* - inline ASCII length-delimited string /// InPZ_* - inline PETSCII null-terminated string /// InP1_* - inline PETSCII length-delimited string /// InW_* - inline 16-bit word /// InWA_* - inline 16-bit address /// InNR_* - non-returning call /// /// Put a label with the appropriate prefix on the address of the subroutine, and all /// calls to it will be formatted automatically. For example, JSRs to the label /// "InAZ_PrintString" will be expected to be followed by null-terminated ASCII string data. /// /// ASCII functions work for standard and high ASCII, auto-detecting the encoding based on /// the first character. /// public class StdInline : MarshalByRefObject, IPlugin, IPlugin_SymbolList, IPlugin_InlineJsr { private IApplication mAppRef; private byte[] mFileData; private class NameMap { public string Prefix { get; private set; } public InlineKind Kind { get; private set; } public NameMap(string prefix, InlineKind kind) { Prefix = prefix; Kind = kind; } }; private enum InlineKind { Unknown = 0, InAZ, InA1, InPZ, InP1, InW, InWA, InNR }; private static NameMap[] sMap = { new NameMap("InNR_", InlineKind.InNR), new NameMap("InAZ_", InlineKind.InAZ), new NameMap("InA1_", InlineKind.InA1), new NameMap("InPZ_", InlineKind.InPZ), new NameMap("InP1_", InlineKind.InP1), new NameMap("InW_", InlineKind.InW), new NameMap("InWA_", InlineKind.InWA), }; // Map of addresses (not offsets) in project to inline data handled by code there. private Dictionary mInlineLabels = new Dictionary(); // IPlugin public string Identifier { get { return "Standard inline data formatter"; } } // IPlugin public void Prepare(IApplication appRef, byte[] fileData, AddressTranslate unused) { mAppRef = appRef; mFileData = fileData; mAppRef.DebugLog("StdInline(id=" + AppDomain.CurrentDomain.Id + "): prepare()"); } // IPlugin public void Unprepare() { mAppRef = null; mFileData = null; } // IPlugin_SymbolList public void UpdateSymbolList(List plSyms) { mInlineLabels.Clear(); // Find matching symbols. Save the symbol's value (its address) and the type. // We want an exact match on L1STR_NAME, and prefix matches on the other two. foreach (PlSymbol sym in plSyms) { // We might want to ignore user labels in non-addressable regions, which all // show up with NON_ADDR as their address. In practice it doesn't matter. foreach (NameMap map in sMap) { if (sym.Label.StartsWith(map.Prefix)) { // Multiple offsets could have the same address. Map the first. if (!mInlineLabels.ContainsKey(sym.Value)) { mInlineLabels.Add(sym.Value, map.Kind); } else { mAppRef.DebugLog("Ignoring duplicate address " + sym.Value.ToString("x4")); } break; } } } mAppRef.DebugLog("Found matches for " + mInlineLabels.Count + " labels"); } // IPlugin_SymbolList public bool IsLabelSignificant(string beforeLabel, string afterLabel) { return DoesLabelMatch(beforeLabel) || DoesLabelMatch(afterLabel); } private static bool DoesLabelMatch(string label) { foreach (NameMap map in sMap) { if (label.StartsWith(map.Prefix)) { return true; } } return false; } // IPlugin_InlineJsr public void CheckJsr(int offset, int operand, out bool noContinue) { noContinue = false; InlineKind kind; if (!mInlineLabels.TryGetValue(operand, out kind)) { // JSR destination address not recognized. return; } offset += 3; // move past JSR switch (kind) { case InlineKind.InAZ: // Null-terminated ASCII string. FormatNullTermString(offset, false); break; case InlineKind.InA1: // Length-delimited ASCII string FormatL1String(offset, false); break; case InlineKind.InPZ: // Null-terminated PETSCII string. FormatNullTermString(offset, true); break; case InlineKind.InP1: // Length-delimited PETSCII string FormatL1String(offset, true); break; case InlineKind.InW: case InlineKind.InWA: // 16-bit value. Start by confirming next two bytes are inside the file bounds. if (!Util.IsInBounds(mFileData, offset, 2)) { return; } if (kind == InlineKind.InW) { // Format 16-bit value as default (hex). mAppRef.SetInlineDataFormat(offset, 2, DataType.NumericLE, DataSubType.None, null); } else { // Format 16-bit value as an address. mAppRef.SetInlineDataFormat(offset, 2, DataType.NumericLE, DataSubType.Address, null); } break; case InlineKind.InNR: // Non-returning call. noContinue = true; break; } } private void FormatNullTermString(int offset, bool isPetscii) { if (offset < 0 || offset >= mFileData.Length) { return; // first byte is not inside file } // search for the terminating null byte int nullOff = offset; while (nullOff < mFileData.Length) { if (mFileData[nullOff] == 0x00) { break; } nullOff++; } if (nullOff == mFileData.Length) { mAppRef.DebugLog("Unable to find end of null-terminated string at +" + offset.ToString("x6")); return; } DataSubType stype; if (isPetscii) { stype = DataSubType.C64Petscii; } else if (mFileData[offset] >= 0x80) { stype = DataSubType.HighAscii; } else { stype = DataSubType.Ascii; } mAppRef.SetInlineDataFormat(offset, nullOff - offset + 1, DataType.StringNullTerm, stype, null); } private void FormatL1String(int offset, bool isPetscii) { if (offset < 0 || offset >= mFileData.Length) { return; // length byte is not inside file } int len = mFileData[offset]; if (offset + 1 + len > mFileData.Length) { mAppRef.DebugLog("L1 string ran off end of file at +" + offset.ToString("x6")); return; } DataSubType stype; if (isPetscii) { stype = DataSubType.C64Petscii; } else if (len > 0 && mFileData[offset + 1] >= 0x80) { stype = DataSubType.HighAscii; } else { stype = DataSubType.Ascii; } mAppRef.SetInlineDataFormat(offset, len + 1, DataType.StringL8, stype, null); } } }