Add multi-inline extension script to tutorial
It's useful to have an example of an extension script that handles multiple types of things. It's also good to show that scripts can handle data types other than strings, and can chase an address to format data items elsewhere in the code. This required updating the tutorial binary, adding the new script, and updating the tutorial text and associated screen shots.
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using PluginCommon;
|
||||
|
||||
namespace ExtensionScriptSample {
|
||||
@ -15,7 +14,7 @@ namespace ExtensionScriptSample {
|
||||
private IApplication mAppRef;
|
||||
private byte[] mFileData;
|
||||
|
||||
// Only one call.
|
||||
// Only one label.
|
||||
private const string CALL_LABEL = "PrintInlineL1String";
|
||||
private int mInlineL1StringAddr; // jsr
|
||||
|
||||
@ -62,7 +61,7 @@ namespace ExtensionScriptSample {
|
||||
if (offset + 3 >= mFileData.Length) {
|
||||
return; // length byte is off end
|
||||
}
|
||||
int len = mFileData[3]; // first byte past JSR
|
||||
int len = mFileData[offset + 3]; // first byte past JSR
|
||||
if (offset + 4 + len > mFileData.Length) {
|
||||
mAppRef.DebugLog("L1 string ran off end of file at +" +
|
||||
(offset + 4).ToString("x6"));
|
||||
|
153
SourceGen/Examples/Tutorial/InlineMulti.cs
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2021 faddenSoft. All Rights Reserved.
|
||||
// See the LICENSE.txt file for distribution terms (Apache 2.0).
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PluginCommon;
|
||||
|
||||
namespace ExtensionScriptSample {
|
||||
/// <summary>
|
||||
/// Formats three different kinds of inline functions:
|
||||
///
|
||||
/// PrintInlineL1String: format following data as L1 string
|
||||
/// PrintInlineNullString*: format following data as null-term string.
|
||||
/// PrintInlineAddrString*: format following data as 16-bit pointer to null-term string.
|
||||
/// </summary>
|
||||
public class InlineMultiData : MarshalByRefObject, IPlugin, IPlugin_SymbolList,
|
||||
IPlugin_InlineJsr {
|
||||
private IApplication mAppRef;
|
||||
private byte[] mFileData;
|
||||
private AddressTranslate mAddrTrans;
|
||||
|
||||
private const string L1STR_NAME = "PrintInlineL1String";
|
||||
private const string NULLSTR_PREFIX = "PrintInlineNullString";
|
||||
private const string ADDRSTR_PREFIX = "PrintInlineAddrString";
|
||||
private enum InlineKind { Unknown = 0, L1Str, NullStr, AddrStr };
|
||||
|
||||
private Dictionary<int, InlineKind> mInlineLabels = new Dictionary<int, InlineKind>();
|
||||
|
||||
public string Identifier {
|
||||
get {
|
||||
return "Inline multi-data formatter";
|
||||
}
|
||||
}
|
||||
|
||||
public void Prepare(IApplication appRef, byte[] fileData, AddressTranslate addrTrans) {
|
||||
mAppRef = appRef;
|
||||
mFileData = fileData;
|
||||
mAddrTrans = addrTrans;
|
||||
|
||||
mAppRef.DebugLog("InlineMultiData(id=" +
|
||||
AppDomain.CurrentDomain.Id + "): prepare()");
|
||||
}
|
||||
|
||||
public void Unprepare() {
|
||||
mAppRef = null;
|
||||
mFileData = null;
|
||||
mAddrTrans = null;
|
||||
}
|
||||
|
||||
public void UpdateSymbolList(List<PlSymbol> 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) {
|
||||
if (sym.Label.Equals(L1STR_NAME)) {
|
||||
mInlineLabels.Add(sym.Value, InlineKind.L1Str);
|
||||
} else if (sym.Label.StartsWith(NULLSTR_PREFIX)) {
|
||||
mInlineLabels.Add(sym.Value, InlineKind.NullStr);
|
||||
} else if (sym.Label.StartsWith(ADDRSTR_PREFIX)) {
|
||||
mInlineLabels.Add(sym.Value, InlineKind.AddrStr);
|
||||
}
|
||||
}
|
||||
mAppRef.DebugLog("Found matches for " + mInlineLabels.Count + " labels");
|
||||
}
|
||||
|
||||
public bool IsLabelSignificant(string beforeLabel, string afterLabel) {
|
||||
return DoesLabelMatch(beforeLabel) || DoesLabelMatch(afterLabel);
|
||||
}
|
||||
private static bool DoesLabelMatch(string label) {
|
||||
return (label.Equals(L1STR_NAME) ||
|
||||
label.StartsWith(NULLSTR_PREFIX) ||
|
||||
label.StartsWith(ADDRSTR_PREFIX));
|
||||
}
|
||||
|
||||
public void CheckJsr(int offset, int operand, out bool noContinue) {
|
||||
noContinue = false;
|
||||
|
||||
InlineKind kind;
|
||||
if (!mInlineLabels.TryGetValue(operand, out kind)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (kind) {
|
||||
case InlineKind.L1Str:
|
||||
// Length-delimited ASCII string
|
||||
FormatL1String(offset + 3);
|
||||
break;
|
||||
case InlineKind.NullStr:
|
||||
// Null-terminated ASCII string.
|
||||
FormatNullTermString(offset + 3);
|
||||
break;
|
||||
case InlineKind.AddrStr:
|
||||
// Pointer to data. Format as address. Start by confirming next two
|
||||
// bytes are inside the file bounds.
|
||||
if (!Util.IsInBounds(mFileData, offset + 3, 2)) {
|
||||
return;
|
||||
}
|
||||
// Format 16-bit value as an address.
|
||||
mAppRef.SetInlineDataFormat(offset + 3, 2,
|
||||
DataType.NumericLE, DataSubType.Address, null);
|
||||
|
||||
// Now format the string that the address points to. Extract the
|
||||
// address from the operand.
|
||||
int strAddr = Util.GetWord(mFileData, offset + 3, 2, false);
|
||||
// Convert the address to a file offset. Returns -1 if not in file bounds.
|
||||
int strOff = mAddrTrans.AddressToOffset(offset, strAddr);
|
||||
// Format it if it's in bounds.
|
||||
FormatNullTermString(strOff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void FormatL1String(int offset) {
|
||||
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 + 1).ToString("x6"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Assuming ASCII.
|
||||
mAppRef.SetInlineDataFormat(offset, len + 1,
|
||||
DataType.StringL8, DataSubType.Ascii, null);
|
||||
}
|
||||
|
||||
private void FormatNullTermString(int offset) {
|
||||
if (offset < 0 || offset >= mFileData.Length) {
|
||||
return; // start 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;
|
||||
}
|
||||
|
||||
// Assuming ASCII.
|
||||
mAppRef.SetInlineDataFormat(offset, nullOff - offset + 1,
|
||||
DataType.StringNullTerm, DataSubType.Ascii, null);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using PluginCommon;
|
||||
|
||||
namespace ExtensionScriptSample {
|
||||
@ -41,6 +40,7 @@ namespace ExtensionScriptSample {
|
||||
public void UpdateSymbolList(List<PlSymbol> plSyms) {
|
||||
mNullStringAddrs.Clear();
|
||||
|
||||
// Find matching symbols.
|
||||
foreach (PlSymbol sym in plSyms) {
|
||||
if (sym.Label.StartsWith(LABEL_PREFIX)) {
|
||||
mNullStringAddrs.Add(sym.Value, sym);
|
||||
@ -61,7 +61,7 @@ namespace ExtensionScriptSample {
|
||||
// search for the terminating null byte
|
||||
int nullOff = offset + 3;
|
||||
while (nullOff < mFileData.Length) {
|
||||
if (mFileData[nullOff] == 0) {
|
||||
if (mFileData[nullOff] == 0x00) {
|
||||
break;
|
||||
}
|
||||
nullOff++;
|
||||
|
@ -13,11 +13,18 @@
|
||||
|
||||
jsr PrintInlineZString2
|
||||
asc 'Test two',00
|
||||
|
||||
jsr PrintInlineAddr
|
||||
dw test_three
|
||||
rts
|
||||
|
||||
test_three asc 'Test three',00
|
||||
|
||||
PrintInlineL1String
|
||||
rts
|
||||
PrintInlineZString1
|
||||
rts
|
||||
PrintInlineZString2
|
||||
rts
|
||||
PrintInlineAddr
|
||||
rts
|
||||
|
@ -131,11 +131,11 @@
|
||||
<div class="grid-container">
|
||||
<div class="grid-item-text">
|
||||
<p>Double-click the <code>JSR</code> opcode on line $1000
|
||||
to jump to address $1026. The only thing there is an <code>RTS</code>.
|
||||
to jump to address $1036. The only thing there is an <code>RTS</code>.
|
||||
It's supposed to be a routine that prints a string with a leading length
|
||||
byte, but for the sake of keeping the example code short it's just a
|
||||
place-holder. Use the curly toolbar arrow
|
||||
(or <kbd class="key">Alt+LeftArrow</kbd>) to jump back to $1026.</p>
|
||||
(or <kbd class="key">Alt+LeftArrow</kbd>) to jump back to $1000.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -145,7 +145,7 @@
|
||||
</div>
|
||||
<div class="grid-item-text">
|
||||
<p>This time, double-click the <code>JSR</code> <i>operand</i>
|
||||
("<samp>L1026</samp>") to edit the operand.
|
||||
("<samp>L1036</samp>") to edit the operand.
|
||||
Click <samp>Create Label</samp>, and enter <kbd>PrintInlineL1String</kbd>.
|
||||
Remember that labels are case-sensitive;
|
||||
you must enter it exactly as shown. Hit <samp>OK</samp> to accept the label,
|
||||
@ -189,12 +189,56 @@
|
||||
<img src="images/t4-inlinenull-done.png" alt="t4-inlinenull-done"/>
|
||||
</div>
|
||||
<div class="grid-item-text">
|
||||
<p>Double-click the operand on line $100D ("<code>L1027</code>"),
|
||||
<p>Double-click the operand on line $100D ("<code>L1037</code>"),
|
||||
click <samp>Create Label</samp>,
|
||||
and set the label to "<kbd>PrintInlineNullStringOne</kbd>".
|
||||
Hit <samp>OK</samp> twice. That formatted the first one and got us
|
||||
to the next <code>JSR</code>. Repeat the process on line $1019
|
||||
("<code>L1028</code>"), setting the label to "<kbd>PrintInlineNullStringTwo</kbd>".</p>
|
||||
("<code>L1038</code>"), setting the label to "<kbd>PrintInlineNullStringTwo</kbd>".</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-container">
|
||||
<div class="grid-item-image">
|
||||
<img src="images/t4-inlinemulti-before.png" alt="t4-inlinemulti-before"/>
|
||||
</div>
|
||||
<div class="grid-item-text">
|
||||
<p>Things are looking good, but we've got one left, and it's got a
|
||||
twist. Instead of a string, the <code>JSR</code> at $1025 is followed by a
|
||||
pointer to a string. We'd like to format the pointer as an address,
|
||||
and then format the string it points to.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-container">
|
||||
<div class="grid-item-image">
|
||||
<img src="images/t4-inlinemulti-src.png" alt="t4-inlinemulti-src"/>
|
||||
</div>
|
||||
<div class="grid-item-text">
|
||||
<p>We're going to add a third extension script. Sometimes it's
|
||||
convenient to collect all inline data handlers for a project into a
|
||||
single script, so we'll demonstrate that here. The new script handles
|
||||
the previous two cases as well as this new case.
|
||||
Go into Project
|
||||
Properties, click the Extension Scripts tab, select the two scripts
|
||||
you added earlier, and click Remove. Then add the script "InlineMulti.cs"
|
||||
from the project directory. Click <samp>OK</samp> twice. Note that
|
||||
the strings formatted earlier remain formatted.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-container">
|
||||
<div class="grid-item-image">
|
||||
<img src="images/t4-inlinemulti-done.png" alt="t4-inlinemulti-done"/>
|
||||
</div>
|
||||
<div class="grid-item-text">
|
||||
<p>Double-click the operand on line $1025 ("<code>L1039</code>"),
|
||||
click <samp>Create Label</samp>,
|
||||
and set the label to "<kbd>PrintInlineAddrString</kbd>".
|
||||
Hit <samp>OK</samp> twice. This formatted the address at $1028,
|
||||
and also formatted the string at $102b as a null-terminated string.
|
||||
Because the bytes were formatted as an address and not a just a
|
||||
16-bit value, a label was generated automatically.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 53 KiB |
BIN
docs/sgtutorial/images/t4-inlinemulti-before.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
docs/sgtutorial/images/t4-inlinemulti-done.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
docs/sgtutorial/images/t4-inlinemulti-src.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |