private void OutputString(int offset, string labelStr, string commentStr) { // This gets complicated. // // For Dci, L8String, and L16String, the entire string needs to fit in the // operand of one line. If it can't, we need to separate the length byte/word // or inverted character out, and just dump the rest as ASCII. Computing the // line length requires factoring delimiter character escapes. (NOTE: contrary // to the documentation, STR and STRL do include trailing hex characters in the // length calculation, so it's possible to escape delimiters.) // // For Reverse, we can span lines, but only if we emit the lines in // backward order. Also, Merlin doesn't allow hex to be embedded in a REV // operation, so we can't use REV if the string contains a delimiter. // // For aesthetic purposes, zero-length CString, L8String, and L16String // should be output as DFB/DW zeroes rather than an empty string -- makes // it easier to read. // // NOTE: we generally assume that the input is in the correct format, e.g. // the length byte in a StringL8 matches dfd.Length, and the high bits in DCI strings // have the right pattern. If not, we will generate bad output. This would need // to be scanned and corrected at a higher level. Anattrib attr = Project.GetAnattrib(offset); FormatDescriptor dfd = attr.DataDescriptor; Debug.Assert(dfd != null); Debug.Assert(dfd.IsString); Debug.Assert(dfd.Length > 0); // We can sort of do parts of C64 stuff, but it's probably more readable to just // output a commented blob than something where only the capital letters are readable. switch (dfd.FormatSubType) { case FormatDescriptor.SubType.Ascii: case FormatDescriptor.SubType.HighAscii: break; case FormatDescriptor.SubType.C64Petscii: case FormatDescriptor.SubType.C64Screen: default: OutputNoJoy(offset, dfd.Length, labelStr, commentStr); return; } Formatter formatter = SourceFormatter; byte[] data = Project.FileData; StringOpFormatter.ReverseMode revMode = StringOpFormatter.ReverseMode.Forward; int leadingBytes = 0; string opcodeStr; switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: opcodeStr = sDataOpNames.StrGeneric; break; case FormatDescriptor.Type.StringReverse: opcodeStr = sDataOpNames.StrReverse; revMode = StringOpFormatter.ReverseMode.LineReverse; break; case FormatDescriptor.Type.StringNullTerm: opcodeStr = sDataOpNames.StrGeneric; // no pseudo-op for this if (dfd.Length == 1) { // Empty string. Just output the length byte(s) or null terminator. GenerateShortSequence(offset, 1, out string opcode, out string operand); OutputLine(labelStr, opcode, operand, commentStr); return; } break; case FormatDescriptor.Type.StringL8: opcodeStr = sDataOpNames.StrLen8; leadingBytes = 1; break; case FormatDescriptor.Type.StringL16: opcodeStr = sDataOpNames.StrLen16; leadingBytes = 2; break; case FormatDescriptor.Type.StringDci: opcodeStr = sDataOpNames.StrDci; break; default: Debug.Assert(false); return; } // Merlin 32 uses single-quote for low ASCII, double-quote for high ASCII. CharEncoding.Convert charConv; char delim; if (dfd.FormatSubType == FormatDescriptor.SubType.HighAscii) { charConv = CharEncoding.ConvertHighAscii; delim = '"'; } else { charConv = CharEncoding.ConvertAscii; delim = '\''; } StringOpFormatter stropf = new StringOpFormatter(SourceFormatter, new Formatter.DelimiterDef(delim), StringOpFormatter.RawOutputStyle.DenseHex, charConv); if (dfd.FormatType == FormatDescriptor.Type.StringDci) { // DCI is awkward because the character encoding flips on the last byte. Rather // than clutter up StringOpFormatter for this rare item, we just accept low/high // throughout. stropf.CharConv = CharEncoding.ConvertLowAndHighAscii; } // Feed bytes in, skipping over the leading length bytes. stropf.FeedBytes(data, offset + leadingBytes, dfd.Length - leadingBytes, 0, revMode); Debug.Assert(stropf.Lines.Count > 0); // See if we need to do this over. bool redo = false; switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: case FormatDescriptor.Type.StringNullTerm: break; case FormatDescriptor.Type.StringReverse: if (stropf.HasEscapedText) { // can't include escaped characters in REV opcodeStr = sDataOpNames.StrGeneric; revMode = StringOpFormatter.ReverseMode.Forward; redo = true; } break; case FormatDescriptor.Type.StringL8: if (stropf.Lines.Count != 1) { // single-line only opcodeStr = sDataOpNames.StrGeneric; leadingBytes = 1; redo = true; } break; case FormatDescriptor.Type.StringL16: if (stropf.Lines.Count != 1) { // single-line only opcodeStr = sDataOpNames.StrGeneric; leadingBytes = 2; redo = true; } break; case FormatDescriptor.Type.StringDci: if (stropf.Lines.Count != 1) { // single-line only opcodeStr = sDataOpNames.StrGeneric; stropf.CharConv = charConv; redo = true; } break; default: Debug.Assert(false); return; } if (redo) { //Debug.WriteLine("REDO off=+" + offset.ToString("x6") + ": " + dfd.FormatType); // This time, instead of skipping over leading length bytes, we include them // explicitly. stropf.Reset(); stropf.FeedBytes(data, offset, dfd.Length, leadingBytes, revMode); } opcodeStr = formatter.FormatPseudoOp(opcodeStr); foreach (string str in stropf.Lines) { OutputLine(labelStr, opcodeStr, str, commentStr); labelStr = commentStr = string.Empty; // only show on first } }
/// <summary> /// Converts a collection of bytes that represent a string into an array of formatted /// string operands. /// </summary> /// <param name="formatter">Formatter object.</param> /// <param name="opNames">Pseudo-opcode name table.</param> /// <param name="dfd">Format descriptor.</param> /// <param name="data">File data.</param> /// <param name="offset">Offset, within data, of start of string.</param> /// <param name="popcode">Pseudo-opcode string.</param> /// <returns>Array of operand strings.</returns> public static List <string> FormatStringOp(Formatter formatter, PseudoOpNames opNames, FormatDescriptor dfd, byte[] data, int offset, out string popcode) { int hiddenLeadingBytes = 0; int trailingBytes = 0; StringOpFormatter.ReverseMode revMode = StringOpFormatter.ReverseMode.Forward; Formatter.DelimiterSet delSet = formatter.Config.mStringDelimiters; Formatter.DelimiterDef delDef; CharEncoding.Convert charConv; switch (dfd.FormatSubType) { case FormatDescriptor.SubType.Ascii: if (dfd.FormatType == FormatDescriptor.Type.StringDci) { charConv = CharEncoding.ConvertLowAndHighAscii; } else { charConv = CharEncoding.ConvertAscii; } delDef = delSet.Get(CharEncoding.Encoding.Ascii); break; case FormatDescriptor.SubType.HighAscii: if (dfd.FormatType == FormatDescriptor.Type.StringDci) { charConv = CharEncoding.ConvertLowAndHighAscii; } else { charConv = CharEncoding.ConvertHighAscii; } delDef = delSet.Get(CharEncoding.Encoding.HighAscii); break; case FormatDescriptor.SubType.C64Petscii: if (dfd.FormatType == FormatDescriptor.Type.StringDci) { charConv = CharEncoding.ConvertLowAndHighC64Petscii; } else { charConv = CharEncoding.ConvertC64Petscii; } delDef = delSet.Get(CharEncoding.Encoding.C64Petscii); break; case FormatDescriptor.SubType.C64Screen: if (dfd.FormatType == FormatDescriptor.Type.StringDci) { charConv = CharEncoding.ConvertLowAndHighC64ScreenCode; } else { charConv = CharEncoding.ConvertC64ScreenCode; } delDef = delSet.Get(CharEncoding.Encoding.C64ScreenCode); break; default: Debug.Assert(false); charConv = CharEncoding.ConvertAscii; delDef = delSet.Get(CharEncoding.Encoding.Ascii); break; } if (delDef == null) { delDef = Formatter.DOUBLE_QUOTE_DELIM; } switch (dfd.FormatType) { case FormatDescriptor.Type.StringGeneric: // Generic character data. popcode = opNames.StrGeneric; break; case FormatDescriptor.Type.StringReverse: // Character data, full width specified by formatter. Show characters // in reverse order. popcode = opNames.StrReverse; revMode = StringOpFormatter.ReverseMode.FullReverse; break; case FormatDescriptor.Type.StringNullTerm: // Character data with a terminating null. Don't show the null byte. popcode = opNames.StrNullTerm; trailingBytes = 1; //if (strLen == 0) { // showHexZeroes = 1; //} break; case FormatDescriptor.Type.StringL8: // Character data with a leading length byte. Don't show the length. hiddenLeadingBytes = 1; //if (strLen == 0) { // showHexZeroes = 1; //} popcode = opNames.StrLen8; break; case FormatDescriptor.Type.StringL16: // Character data with a leading length word. Don't show the length. Debug.Assert(dfd.Length > 1); hiddenLeadingBytes = 2; //if (strLen == 0) { // showHexZeroes = 2; //} popcode = opNames.StrLen16; break; case FormatDescriptor.Type.StringDci: // High bit on last byte is flipped. popcode = opNames.StrDci; break; default: Debug.Assert(false); popcode = ".!!!"; break; } StringOpFormatter stropf = new StringOpFormatter(formatter, delDef, StringOpFormatter.RawOutputStyle.CommaSep, MAX_OPERAND_LEN, charConv); stropf.FeedBytes(data, offset + hiddenLeadingBytes, dfd.Length - hiddenLeadingBytes - trailingBytes, 0, revMode); return(stropf.Lines); }