Esempio n. 1
0
        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);
        }
Esempio n. 2
0
        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);
        }
Esempio n. 3
0
        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);
        }
Esempio n. 4
0
        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);
        }
Esempio n. 5
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);
            }
        }
Esempio n. 6
0
        /// <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);
        }