public void Parameter2PropertyAsExpected(int value, int chk) { // --- Arrange var head = new SpectrumTapeHeader(); // --- Act head.Parameter2 = (ushort)value; // --- Assert head.Parameter2.ShouldBe((ushort)value); head.Checksum.ShouldBe((byte)chk); }
public void NamePropertyAsExpected(string value, string expected, int chk) { // --- Arrange var head = new SpectrumTapeHeader(); // --- Act head.Name = value; // --- Assert head.Name.ShouldBe(expected); head.Checksum.ShouldBe((byte)chk); }
public void TypePropertyAsExpected(int value, int expected, int chk) { // --- Arrange var head = new SpectrumTapeHeader(); // --- Act head.Type = (byte)value; // --- Assert head.Type.ShouldBe((byte)expected); head.Checksum.ShouldBe((byte)chk); }
public void ConstructionWorksAsExpected() { // --- Act var head = new SpectrumTapeHeader(); // --- Assert head.Type.ShouldBe((byte)0); head.Name.Length.ShouldBe(10); head.DataLength.ShouldBe((ushort)0); head.Parameter1.ShouldBe((ushort)0); head.Parameter2.ShouldBe((ushort)0); head.Checksum.ShouldBe((byte)0); }
/// <summary> /// Creates auto start block (header+data) to save /// </summary> /// <param name="name">Program name</param> /// <param name="blockNo">Number of blocks to load</param> /// <param name="startAddr">Auto start address</param> /// <param name="clearAddr">Optional CLEAR address</param> /// <returns></returns> public List <byte[]> CreateAutoStartBlock(string name, int blockNo, ushort startAddr, ushort?clearAddr = null) { if (blockNo > 128) { throw new ArgumentException("The number of blocks cannot be more than 128.", nameof(blockNo)); } var result = new List <byte[]>(); // --- Step #1: Create the code line for auto start var codeLine = new List <byte>(100); if (clearAddr.HasValue && clearAddr.Value >= 0x6200) { // --- Add clear statement codeLine.Add(CLEAR_TKN); WriteNumber(codeLine, (ushort)(clearAddr.Value - RAMTOP_GAP)); codeLine.Add(COLON); } // --- Add 'LOAD "" CODE' for each block for (int i = 0; i < blockNo; i++) { codeLine.Add(LOAD_TKN); codeLine.Add(DQUOTE); codeLine.Add(DQUOTE); codeLine.Add(CODE_TKN); codeLine.Add(COLON); } // --- Add 'RANDOMIZE USR addr' codeLine.Add(RAND_TKN); codeLine.Add(USER_TKN); WriteNumber(codeLine, startAddr); // --- Complete the line codeLine.Add(NEW_LINE); // --- Step #2: Now, complete the data block // --- Allocate extra 6 bytes: 1 byte - header, 2 byte - line number // --- 2 byte - line lenght, 1 byte - checksum var dataBlock = new byte[codeLine.Count + 6]; codeLine.CopyTo(dataBlock, 5); dataBlock[0] = 0xff; // --- Set line number to 10. Line number uses MSB/LSB order dataBlock[1] = 0x00; dataBlock[2] = 10; // --- Set line length dataBlock[3] = (byte)codeLine.Count; dataBlock[4] = (byte)(codeLine.Count >> 8); SetTapeCheckSum(dataBlock); // --- Step #3: Create the header var header = new SpectrumTapeHeader { // --- Program block Type = 0, Name = name, DataLength = (ushort)(dataBlock.Length - 2), // --- Autostart at Line 10 Parameter1 = 10, // --- Variable area offset Parameter2 = (ushort)(dataBlock.Length - 2) }; // --- Step #4: Retrieve the auto start header and data block for save result.Add(header.HeaderBytes); result.Add(dataBlock); return(result); void WriteNumber(ICollection <byte> codeArray, ushort number) { // --- Number in string form foreach (var ch in number.ToString()) { codeArray.Add((byte)ch); } codeArray.Add(NUMB_SIGN); // --- Five bytes as the short form of an integer codeArray.Add(0x00); codeArray.Add(0x00); codeArray.Add((byte)number); codeArray.Add((byte)(number >> 8)); codeArray.Add(0x00); } }
/// <summary> /// Creates tape blocks from the assembler output. /// </summary> /// <param name="name">Program name</param> /// <param name="output">Assembler output</param> /// <param name="singleBlock"> /// Indicates if a single block should be created from all segments /// </param> /// <returns>The list that contains headers and data blocks to save</returns> public List <byte[]> CreateTapeBlocks(string name, AssemblerOutput output, bool singleBlock) { var result = new List <byte[]>(); if (output.Segments.Sum(s => s.EmittedCode.Count) == 0) { // --- No code to return return(null); } if (singleBlock) { // --- Merge all blocks together var startAddr = output.Segments.Min(s => s.StartAddress); var endAddr = output.Segments.Max(s => s.StartAddress + s.EmittedCode.Count - 1); var mergedSegment = new byte[endAddr - startAddr + 3]; foreach (var segment in output.Segments) { segment.EmittedCode.CopyTo(mergedSegment, segment.StartAddress - startAddr + 1); } // --- The first byte of the merged segment is 0xFF (Data block) mergedSegment[0] = 0xff; SetTapeCheckSum(mergedSegment); // --- Create the single header var singleHeader = new SpectrumTapeHeader { Type = 3, // --- Code block Name = name, DataLength = (ushort)(mergedSegment.Length - 2), Parameter1 = startAddr, Parameter2 = 0x8000 }; // --- Create the two tape blocks (header + data) result.Add(singleHeader.HeaderBytes); result.Add(mergedSegment); } else { // --- Create separate block for each segment var segmentIdx = 0; foreach (var segment in output.Segments) { segmentIdx++; var startAddr = segment.StartAddress; var endAddr = segment.StartAddress + segment.EmittedCode.Count - 1; var codeSegment = new byte[endAddr - startAddr + 3]; segment.EmittedCode.CopyTo(codeSegment, segment.StartAddress - startAddr + 1); // --- The first byte of the code segment is 0xFF (Data block) codeSegment[0] = 0xff; SetTapeCheckSum(codeSegment); // --- Create the single header var header = new SpectrumTapeHeader { Type = 3, // --- Code block Name = $"{segmentIdx}_{name}", DataLength = (ushort)(codeSegment.Length - 2), Parameter1 = startAddr, Parameter2 = 0x8000 }; // --- Create the two tape blocks (header + data) result.Add(header.HeaderBytes); result.Add(codeSegment); } } return(result); }
/// <summary> /// Creates an auto start block for Spectrum 128K /// </summary> /// <param name="name">Program name</param> /// <param name="useScreenFile">Indicates if a screen file is used</param> /// <param name="addPause0">Indicates if a "PAUSE 0" should be added</param> /// <param name="borderColor">Border color ("0"-"7")</param> /// <param name="startAddr">Auto start address</param> /// <param name="clearAddr">Optional CLEAR address</param> /// <returns>Block contents</returns> private List <byte[]> CreateSpectrum128StartBlock(string name, bool useScreenFile, bool addPause0, string borderColor, ushort startAddr, ushort?clearAddr = null) { var result = new List <byte[]>(); // --- We keep the code lines here var lines = new List <List <byte> >(); // --- Create placeholder for the paging code (line 10) var codeLine = new List <byte>(100) { REM_TKN }; WriteString(codeLine, "012345678901234567890"); codeLine.Add(NEW_LINE); lines.Add(codeLine); // --- Create code for CLEAR/PEEK program address (line 20) codeLine = new List <byte>(100); if (clearAddr.HasValue && clearAddr.Value >= 0x6000) { // --- Add clear statement codeLine.Add(CLEAR_TKN); WriteNumber(codeLine, (ushort)(clearAddr.Value - 1)); codeLine.Add(COLON); } // --- Add "LET c=(PEEK 23635 + 256*PEEK 23636)+5 codeLine.Add(LET_TKN); WriteString(codeLine, "c=("); codeLine.Add(PEEK_TKN); WriteNumber(codeLine, 23635); WriteString(codeLine, "+"); WriteNumber(codeLine, 256); WriteString(codeLine, "*"); codeLine.Add(PEEK_TKN); WriteNumber(codeLine, 23636); WriteString(codeLine, ")+"); WriteNumber(codeLine, 5); codeLine.Add(NEW_LINE); lines.Add(codeLine); // --- Setup the machine code codeLine = new List <byte>(100) { FOR_TKN }; WriteString(codeLine, "i="); WriteNumber(codeLine, 0); codeLine.Add(TO_TKN); WriteNumber(codeLine, 20); codeLine.Add(COLON); codeLine.Add(READ_TKN); WriteString(codeLine, "d:"); codeLine.Add(POKE_TKN); WriteString(codeLine, "c+i,d:"); codeLine.Add(NEXT_TKN); WriteString(codeLine, "i"); codeLine.Add(NEW_LINE); lines.Add(codeLine); // --- Create code for BORDER/SCREEN and loading normal code blocks (line 30) codeLine = new List <byte>(100); if (borderColor != null) { var border = int.Parse(borderColor); codeLine.Add(BORDER_TKN); WriteNumber(codeLine, (ushort)border); codeLine.Add(COLON); } // --- Add optional screen loader, LET o = PEEK 23739:LOAD "" SCREEN$ : POKE 23739,111 if (useScreenFile) { codeLine.Add(LET_TKN); WriteString(codeLine, "o="); codeLine.Add(PEEK_TKN); WriteNumber(codeLine, 23739); codeLine.Add(COLON); codeLine.Add(LOAD_TKN); codeLine.Add(DQUOTE); codeLine.Add(DQUOTE); codeLine.Add(SCREEN_TKN); codeLine.Add(COLON); codeLine.Add(POKE_TKN); WriteNumber(codeLine, 23739); codeLine.Add(COMMA); WriteNumber(codeLine, 111); codeLine.Add(COLON); } // --- Add 'LOAD "" CODE' for each block for (var i = 0; i < Output.Segments.Count(s => s.Bank == null); i++) { if (i > 0) { codeLine.Add(COLON); } codeLine.Add(LOAD_TKN); codeLine.Add(DQUOTE); codeLine.Add(DQUOTE); codeLine.Add(CODE_TKN); } codeLine.Add(NEW_LINE); lines.Add(codeLine); // --- Code for reading banks codeLine = new List <byte>(100) { READ_TKN }; WriteString(codeLine, "b"); codeLine.Add(NEW_LINE); lines.Add(codeLine); // --- "IF b = 8 THEN GO TO 80"; codeLine = new List <byte>(100) { IF_TKN }; WriteString(codeLine, "b="); WriteNumber(codeLine, 8); codeLine.Add(THEN_TKN); codeLine.Add(GOTO_TKN); WriteNumber(codeLine, 80); codeLine.Add(NEW_LINE); lines.Add(codeLine); // --- "POKE 23608,b: RANDOMIZE USR c: LOAD "" CODE: GO TO 50" codeLine = new List <byte>(100) { POKE_TKN }; WriteNumber(codeLine, 23608); WriteString(codeLine, ",b:"); codeLine.Add(RAND_TKN); codeLine.Add(USR_TKN); WriteString(codeLine, "c:"); codeLine.Add(LOAD_TKN); codeLine.Add(DQUOTE); codeLine.Add(DQUOTE); codeLine.Add(CODE_TKN); codeLine.Add(COLON); codeLine.Add(GOTO_TKN); WriteNumber(codeLine, 50); codeLine.Add(NEW_LINE); lines.Add(codeLine); // --- PAUSE and START codeLine = new List <byte>(100); if (addPause0) { codeLine.Add(PAUSE_TKN); WriteNumber(codeLine, 0); codeLine.Add(COLON); } if (useScreenFile) { codeLine.Add(POKE_TKN); WriteNumber(codeLine, 23739); WriteString(codeLine, ",o:"); } // --- Add 'RANDOMIZE USR address: STOP' codeLine.Add(RAND_TKN); codeLine.Add(USR_TKN); WriteNumber(codeLine, startAddr); codeLine.Add(COLON); codeLine.Add(STOP_TKN); codeLine.Add(NEW_LINE); lines.Add(codeLine); // --- Add data lines with the machine code subroutine codeLine = new List <byte>(100); WriteDataStatement(codeLine, new ushort[] { 243, 58, 92, 91, 230, 248, 71, 58, 56, 92, 176, 50, 92, 91, 1, 253, 127, 237, 121, 251, 201 }); lines.Add(codeLine); // --- Add data lines with used banks and terminating 8 codeLine = new List <byte>(100); var banks = Output .Segments .Where(s => s.Bank != null) .Select(s => (ushort)s.Bank) .OrderBy(n => n) .ToList(); banks.Add(8); WriteDataStatement(codeLine, banks.ToArray()); lines.Add(codeLine); // --- All code lines are set up, create the file blocks var dataBlock = CreateDataBlockForCodeLines(lines); var header = new SpectrumTapeHeader { // --- Program block Type = 0, Name = name, DataLength = (ushort)(dataBlock.Length - 2), // --- Auto-start at Line 10 Parameter1 = 10, // --- Variable area offset Parameter2 = (ushort)(dataBlock.Length - 2) }; // --- Step #4: Retrieve the auto start header and data block for save result.Add(header.HeaderBytes); result.Add(dataBlock); return(result); }
/// <summary> /// Creates an auto start block for Spectrum 48K /// </summary> /// <param name="name">Program name</param> /// <param name="useScreenFile">Indicates if a screen file is used</param> /// <param name="addPause0">Indicates if a "PAUSE 0" should be added</param> /// <param name="borderColor">Border color ("0"-"7")</param> /// <param name="startAddr">Auto start address</param> /// <param name="clearAddr">Optional CLEAR address</param> /// <returns>Block contents</returns> private List <byte[]> CreateSpectrum48StartBlock(string name, bool useScreenFile, bool addPause0, string borderColor, ushort startAddr, ushort?clearAddr = null) { var result = new List <byte[]>(); // --- Step #1: Create the code line for auto start var codeLine = new List <byte>(100); if (clearAddr.HasValue && clearAddr.Value >= 0x6000) { // --- Add clear statement codeLine.Add(CLEAR_TKN); WriteNumber(codeLine, (ushort)(clearAddr.Value - 1)); codeLine.Add(COLON); } // --- Add optional border color if (borderColor != null) { var border = int.Parse(borderColor); codeLine.Add(BORDER_TKN); WriteNumber(codeLine, (ushort)border); codeLine.Add(COLON); } // --- Add optional screen loader, LET o = PEEK 23739 : LOAD "" SCREEN$ : POKE 23739,111 if (useScreenFile) { codeLine.Add(LET_TKN); WriteString(codeLine, "o="); codeLine.Add(PEEK_TKN); WriteNumber(codeLine, 23739); codeLine.Add(COLON); codeLine.Add(LOAD_TKN); codeLine.Add(DQUOTE); codeLine.Add(DQUOTE); codeLine.Add(SCREEN_TKN); codeLine.Add(COLON); codeLine.Add(POKE_TKN); WriteNumber(codeLine, 23739); codeLine.Add(COMMA); WriteNumber(codeLine, 111); codeLine.Add(COLON); } // --- Add 'LOAD "" CODE' for each block for (var i = 0; i < Output.Segments.Count; i++) { codeLine.Add(LOAD_TKN); codeLine.Add(DQUOTE); codeLine.Add(DQUOTE); codeLine.Add(CODE_TKN); codeLine.Add(COLON); } // --- Add 'PAUSE 0' if (addPause0) { codeLine.Add(PAUSE_TKN); WriteNumber(codeLine, 0); codeLine.Add(COLON); } // --- Some SCREEN$ related poking if (useScreenFile) { codeLine.Add(POKE_TKN); WriteNumber(codeLine, 23739); WriteString(codeLine, ",o:"); } // --- Add 'RANDOMIZE USR address' codeLine.Add(RAND_TKN); codeLine.Add(USR_TKN); WriteNumber(codeLine, startAddr); // --- Complete the line codeLine.Add(NEW_LINE); // --- Step #2: Now, complete the data block // --- Allocate extra 6 bytes: 1 byte - header, 2 byte - line number // --- 2 byte - line length, 1 byte - checksum var dataBlock = new byte[codeLine.Count + 6]; codeLine.CopyTo(dataBlock, 5); dataBlock[0] = 0xff; // --- Set line number to 10. Line number uses MSB/LSB order dataBlock[1] = 0x00; dataBlock[2] = 10; // --- Set line length dataBlock[3] = (byte)codeLine.Count; dataBlock[4] = (byte)(codeLine.Count >> 8); SetTapeCheckSum(dataBlock); // --- Step #3: Create the header var header = new SpectrumTapeHeader { // --- Program block Type = 0, Name = name, DataLength = (ushort)(dataBlock.Length - 2), // --- Auto-start at Line 10 Parameter1 = 10, // --- Variable area offset Parameter2 = (ushort)(dataBlock.Length - 2) }; // --- Step #4: Retrieve the auto start header and data block for save result.Add(header.HeaderBytes); result.Add(dataBlock); return(result); }
/// <summary> /// Creates tape blocks from the assembler output. /// </summary> /// <param name="name">Program name</param> /// <param name="singleBlock"> /// Indicates if a single block should be created from all segments /// </param> /// <returns>The list that contains headers and data blocks to save</returns> private List <byte[]> CreateTapeBlocks(string name, bool singleBlock) { var result = new List <byte[]>(); if (Output.Segments.Sum(s => s.EmittedCode.Count) == 0) { // --- No code to return return(null); } if (singleBlock) { // --- Merge all blocks together var startAddr = Output.Segments.Min(s => s.StartAddress); var endAddr = Output.Segments .Where(s => s.Bank == null) .Max(s => s.StartAddress + s.EmittedCode.Count - 1); // --- Normal code segments var mergedSegment = new byte[endAddr - startAddr + 3]; foreach (var segment in Output.Segments.Where(s => s.Bank == null)) { segment.EmittedCode.CopyTo(mergedSegment, segment.StartAddress - startAddr + 1); } // --- The first byte of the merged segment is 0xFF (Data block) mergedSegment[0] = 0xff; SetTapeCheckSum(mergedSegment); // --- Create the single header var singleHeader = new SpectrumTapeHeader { Type = 3, // --- Code block Name = name, DataLength = (ushort)(mergedSegment.Length - 2), Parameter1 = startAddr, Parameter2 = 0x8000 }; // --- Create the two tape blocks (header + data) result.Add(singleHeader.HeaderBytes); result.Add(mergedSegment); } else { // --- Create separate block for each segment var segmentIdx = 0; // --- Normal code segments foreach (var segment in Output.Segments.Where(s => s.Bank == null)) { segmentIdx++; var startAddr = segment.StartAddress; var endAddr = segment.StartAddress + segment.EmittedCode.Count - 1; var codeSegment = new byte[endAddr - startAddr + 3]; segment.EmittedCode.CopyTo(codeSegment, segment.StartAddress - startAddr + 1); // --- The first byte of the code segment is 0xFF (Data block) codeSegment[0] = 0xff; SetTapeCheckSum(codeSegment); // --- Create the single header var header = new SpectrumTapeHeader { Type = 3, // --- Code block Name = $"{segmentIdx}_{name}", DataLength = (ushort)(codeSegment.Length - 2), Parameter1 = startAddr, Parameter2 = 0x8000 }; // --- Create the two tape blocks (header + data) result.Add(header.HeaderBytes); result.Add(codeSegment); } } // --- Create blocks for the banks foreach (var bankSegment in Output.Segments.Where(s => s.Bank != null).OrderBy(s => s.Bank)) { var startAddr = (ushort)(0xC000 + bankSegment.BankOffset); var endAddr = startAddr + bankSegment.EmittedCode.Count - 1; var codeSegment = new byte[endAddr - startAddr + 3]; bankSegment.EmittedCode.CopyTo(codeSegment, 1); // --- The first byte of the code segment is 0xFF (Data block) codeSegment[0] = 0xff; SetTapeCheckSum(codeSegment); // --- Create the single header var header = new SpectrumTapeHeader { Type = 3, // --- Code block Name = $"bank{bankSegment.Bank}.code", DataLength = (ushort)(codeSegment.Length - 2), Parameter1 = startAddr, Parameter2 = 0x8000 }; // --- Create the two tape blocks (header + data) result.Add(header.HeaderBytes); result.Add(codeSegment); } return(result); }