コード例 #1
0
    // writes the TZX file to standard output
    static void WriteTzx()
    {
        using (var output = OpenOutput()) {
            FortySevenLoader.Tzx.FileHeader.Standard.Write(output);
            output.WriteByte(0x10); // standard speed block
            output.WriteByte(0);    // two-byte pause length, 0ms
            output.WriteByte(0);
            output.WriteByte(19);   // two-byte length of following data
            output.WriteByte(0);

            // write program header
            var headerData = new byte[19];
            // space-padded ten-byte file name at offset 2
            var progName = _progName;
            if (_alkatraz)
            {
                // AT 1,0;progName,
                // add a leading space if fewer than 6 chars
                progName = string.Format("{0}{1}{2}{3}{4}{5}",
                                         (char)0x16, // AT
                                         (char)1,
                                         (char)0,
                                         progName.Length < 6 ? " " : string.Empty,
                                         progName,
                                         (char)6); // COMMA
            }
            Encoding.ASCII.GetBytes(progName).CopyTo(headerData, 2);
            for (int i = 11; (i > 1) && (headerData[i] == 0); i--)
            {
                headerData[i] = (byte)' ';
            }
            // length of BASIC program + variables at offset 12
            HighLow16 basicLen = (ushort)_data.Count;
            headerData[12] = basicLen.Low;
            headerData[13] = basicLen.High;
            // autostart line at offset 14
            var autostart = new HighLow16(BasicLine.FirstLine);
            headerData[14] = autostart.Low;
            headerData[15] = autostart.High;
            // length of BASIC program without variables at offset 16
            headerData[16] = basicLen.Low;
            headerData[17] = basicLen.High;
            // XOR checksum at offset 18
            headerData[18] = headerData.Aggregate((x, n) => (byte)(x ^ n));
            output.Write(headerData, 0, headerData.Length);

            // write data block
            output.WriteByte(0x10); // standard speed block
            output.WriteByte(250);  // two-byte pause length, 250ms
            output.WriteByte(0);
            HighLow16 dataBlockLen = (ushort)(2 + _data.Count);
            output.WriteByte(dataBlockLen.Low);
            output.WriteByte(dataBlockLen.High);
            _data.Insert(0, 255);                                       // flag byte
            output.Write(_data.ToArray(), 0, _data.Count);
            output.WriteByte(_data.Aggregate((x, n) => (byte)(x ^ n))); // checksum
        }
    }
コード例 #2
0
ファイル: DynamicTable.cs プロジェクト: tomsim/47loader
            /// <summary>
            /// Initializes a new instance of the
            /// <see cref="FortySevenLoader.DynamicTable+Entry"/> class.
            /// </summary>
            /// <param name="address">
            /// The address at which to load the block.
            /// </param>
            /// <param name="length">
            /// The length of the block, at most 32767 bytes.
            /// </param>
            /// <param name="changeDirection">
            /// Set if the loader should change direction before loading the
            /// block.
            /// </param>
            public Entry(HighLow16 address,
                         HighLow16 length,
                         bool changeDirection = false)
            {
                // bit 15 of the length is used for the direction change flag
                if (length > 32767)
                {
                    throw new ArgumentOutOfRangeException("length");
                }

                Address         = address;
                Length          = length;
                ChangeDirection = changeDirection;
            }
コード例 #3
0
ファイル: FancyScreen.cs プロジェクト: tomsim/47loader
        AttrSquares(byte size, bool initiallyBackwards = false)
        {
            // squares can have 1, 2, 4 or 8 character cells per side
            switch (size)
            {
            case 1:
            case 2:
            case 4:
            case 8:
                break;

            default:
                throw new ArgumentOutOfRangeException("size");
            }

            int squaresPerRow = 32 / size;
            // divvy the attribute memory up into strips of length size
            var chunks = new List <DynamicTable.Entry>();

            for (HighLow16 start = 16384 + 6144, addr = start; addr < 23296; addr += size)
            {
                chunks.Add(new DynamicTable.Entry
                               (addr, size, (addr == start) && initiallyBackwards));
            }

            var firstHalf  = new List <DynamicTable.Entry>();
            var secondHalf = new List <DynamicTable.Entry>();

            // imagine a chessboard, black-white-black-white etc.
            // we draw all the black squares top-to-bottom, as firstHalf;
            // we draw all the white squares bottom-to-top, as secondHalf
            bool stagger = false;

            do
            {
                for (int i = 0; i < (squaresPerRow / 2) * size; i++)
                {
                    firstHalf.Add(chunks[stagger ? 1 : 0]);
                    secondHalf.Insert(0, chunks[stagger ? 0 : 1]);
                    chunks.RemoveRange(0, 2);
                }

                stagger = !stagger;
            }while (chunks.Count > 0);

            return(firstHalf.Concat(secondHalf));
        }
コード例 #4
0
ファイル: BasicLine.cs プロジェクト: tomsim/47loader
        /// <summary>
        /// Returns an enumerator over the bytes comprising the line of
        /// BASIC.
        /// </summary>
        /// <returns>
        /// The enumerator.
        /// </returns>
        public IEnumerator <byte> GetEnumerator()
        {
            // length includes trailing ENTER
            HighLow16 len = (ushort)(_lineData.Count + 1);

            // line number is big-endian
            yield return(_lineNumber.High);

            yield return(_lineNumber.Low);

            yield return(len.Low);

            yield return(len.High);

            foreach (var b in _lineData)
            {
                yield return(b);
            }
            yield return((byte)13);
        }
コード例 #5
0
ファイル: BasicLine.cs プロジェクト: tomsim/47loader
 /// <summary>
 /// Initializes a new instance of the
 /// <see cref="FortySevenLoader.Basic.BasicLine"/> class.
 /// </summary>
 public BasicLine()
 {
     _lineNumber = _nextLineNumber++;
 }
コード例 #6
0
        // parses options, returns array of file names
        static string[] ParseArguments(string[] args)
        {
            Action <sbyte> doExtraCycles = delegate(sbyte cycles)
            {
                HighLow16 new0 = Pulse.Zero(cycles);
                HighLow16 new1 = Pulse.One(cycles);
                _blockHeader.PilotPulse = Pulse.Pilot;
                _blockHeader.SyncPulse0 = _blockHeader.ZeroPulse = new0;
                _blockHeader.SyncPulse1 = _blockHeader.OnePulse = new1;
            };
            string pilot         = null;
            bool   noFixedLength = false;

            for (int i = 0; i < args.Length; i++)
            {
                if (args[i].FirstOrDefault() != '-')
                {
                    // we've come to the end of the options, all the rest
                    // are files

                    if (noFixedLength && (_dynamicTable != null))
                    {
                        _dynamicTable.DisableFixedLength();
                    }

                    if (!string.IsNullOrWhiteSpace(pilot))
                    {
                        checked
                        {
                            HighLow16 pilotPulses, pilotClicks = 0;
                            pilot = pilot.ToLower();
                            switch (pilot)
                            {
                            case "resume":
                                pilotPulses = TinyPilotPulseCount;
                                break;

                            case "short":
                                pilotPulses = 600;
                                break;

                            case "standard":
                                pilot = "1000"; // one second
                                goto default;   // fall through

                            default:
                                if (pilot.StartsWith("click"))
                                {
                                    if (_blockHeader.PilotPulse == Pulse.Rom.Pilot)
                                    {
                                        Console.Error.WriteLine
                                            ("clicking pilots may not be used with ROM timings");
                                        Die();
                                    }

                                    // see if a click count was appended, default to 8 if not
                                    var clickStr = pilot.Substring(5).Trim();
                                    if (clickStr.Length > 0)
                                    {
                                        pilotClicks = ParseInteger <ushort>(clickStr);
                                    }
                                    if (pilotClicks < 1)
                                    {
                                        pilotClicks = 8;
                                    }
                                    pilotPulses = 300;
                                    break;
                                }

                                int ms      = ParseInteger <int>(pilot);
                                int tstates = ms * TStatesPerMillisecond;
                                pilotPulses =
                                    (ushort)(tstates / _blockHeader.PilotPulse);
                                if ((pilotPulses % 2) == 1)
                                {
                                    pilotPulses += 1;
                                }
                                break;
                            }
                            _blockHeader.PilotPulseCount = pilotPulses;
                            _blockHeader.PilotClickCount = pilotClicks;
                        }
                    }

                    return(args.Skip(i).ToArray());
                }

                switch (args[i].TrimStart('-'))
                {
                case "noheader":
                    _noHeader = true;
                    break;

                case "nofixedlength":
                    noFixedLength = true;
                    break;

                case "reverse":
                    _reverse = true;
                    break;

                case "pilot":
                    // next arg is pilot length in milliseconds, or a string, or
                    // click count
                    pilot = args[++i];
                    // defer calculation until end of option parsing in case
                    // ROM timings are requested and we're going to change
                    // the pilot pulse length
                    break;

                case "pause":
                    // next arg is pause length in milliseconds
                    checked
                    {
                        HighLow16 ms = ParseInteger <ushort>(args[++i]);
                        _blockHeader.Pause = ms;
                    }
                    break;

                case "extracycles":
                    sbyte cycles = ParseInteger <sbyte>(args[++i]);
                    doExtraCycles(cycles);
                    break;

                case "instascreen":
                    WriteData = WriteData_Instascreen;
                    break;

                case "fancyscreen":
                    // next arg is a fancy screen load type; one of the fields
                    // in the FancyScreen class
                    var loadTypeName = args[++i];
                    _dynamicTable = (from field in typeof(FancyScreen).GetFields
                                         (BindingFlags.Static | BindingFlags.NonPublic)
                                     where field.FieldType == typeof(DynamicTable)
                                     where field.Name.Equals(loadTypeName,
                                                             StringComparison.OrdinalIgnoreCase)
                                     select(DynamicTable) field.GetValue(null))
                                    .FirstOrDefault();
                    if (_dynamicTable == null)
                    {
                        Console.Error.WriteLine
                            ("unknown fancy screen type \"{0}\"", loadTypeName);
                        goto die;
                    }
                    WriteData = WriteData_FancyScreen;
                    break;

                case "progressive":
                    // next arg is number of progressive chunks
                    checked
                    {
                        _progressive = ParseInteger <ushort>(args[++i]);
                    }
                    WriteData = WriteData_Progressive;
                    break;

                case "countdown":
                    // next arg is number of progressive chunks, optionally
                    // followed by the number at which to start the countdown
                    var split = args[++i].Split(':');
                    if (split.Length < 1 || split.Length > 2)
                    {
                        goto badCountdown;
                    }
                    switch (split.Length)
                    {
                    case 1:
                        // start countdown at the first specified block
                        split = new[] { split[0], split[0] };
                        break;

                    case 2:
                        break;

                    default:
                        goto badCountdown;
                    }
                    checked
                    {
                        int countdownBlocks = ParseInteger <int>(split[0]);
                        int countdownStart  = ParseInteger <int>(split[1]);

                        if (countdownBlocks < 1 || countdownBlocks > 99)
                        {
                            goto badCountdown;
                        }
                        if (countdownStart < countdownBlocks || countdownStart > 99)
                        {
                            goto badCountdown;
                        }
                        _progressive    = (byte)countdownBlocks;
                        _countdownStart = (byte)countdownStart;
                    }
                    WriteData = WriteData_Progressive;
                    break;
badCountdown:
                    Console.Error.WriteLine("bad countdown argument");
                    goto die;

                case "bleep":
                    _bleep = true;
                    break;

                case "output":
                    // next arg is name of file to which to write
                    _outputFileName = args[++i];
                    if (File.Exists(_outputFileName))
                    {
                        _noHeader = true;
                    }
                    break;

                case "speed":
                    // next arg is a named timing
                    var    speedName = args[++i];
                    Timing speed;
                    if (!Enum.TryParse(speedName, true, out speed))
                    {
                        Console.Error.WriteLine("unknown speed \"{0}\"", speedName);
                        goto die;
                    }
                    if (speed != Timing.Rom)
                    {
                        doExtraCycles((sbyte)speed);
                    }
                    else
                    {
                        // convert block to ROM timings
                        _blockHeader.PilotPulse = Pulse.Rom.Pilot;
                        _blockHeader.SyncPulse0 = Pulse.Rom.Sync0;
                        _blockHeader.SyncPulse1 = Pulse.Rom.Sync1;
                        _blockHeader.ZeroPulse  = Pulse.Rom.Zero;
                        _blockHeader.OnePulse   = Pulse.Rom.One;
                    }
                    // recompute pilot pulse count
                    if (pilot == null)
                    {
                        pilot = "standard";
                    }
                    break;

                default:
                    // unknown option
                    Console.Error.WriteLine("Unknown option \"{0}\"", args[i]);
                    goto die;
                }
            }

            // still here?  Bad option or no files...
die:
            Die();
            return(null);
        }
コード例 #7
0
ファイル: FancyScreen.cs プロジェクト: tomsim/47loader
        /// <summary>
        /// Writes data blocks for a fancy screen load.
        /// </summary>
        /// <param name='output'>
        /// The stream to which to write the blocks.
        /// </param>
        /// <param name='table'>
        /// The dynamic table defining the blocks to load.
        /// </param>
        /// <param name='screenData'>
        /// 6912 bytes of screen data.
        /// </param>
        /// <param name='blockHeaderTemplate'>
        /// Template values to use when writing block headers.
        /// </param>
        internal static void WriteData(Stream output,
                                       DynamicTable table,
                                       IList <byte> screenData,
                                       TurboBlockHeader blockHeaderTemplate)
        {
            if (screenData.Count != 6912)
            {
                Console.Error.WriteLine("not a 6912 byte screen");
                Environment.Exit(1);
            }

            var fixedLength = table.IsFixedLength;
            var tableBytes  = ((IEnumerable <byte>)table).ToArray();

            // first, write a block containg the length of the table
            var tableLengthBlockData =
                new HighLow16((ushort)tableBytes.Length).ToArray();
            var block = new Block
                            (blockHeaderTemplate.DeepCopy(), tableLengthBlockData);

            // all the blocks comprising the screen must be glued
            block.BlockHeader.Pause = 0;
            block.WriteBlock(output);

            // next, write the table itself
            block = new Block(blockHeaderTemplate.DeepCopy(), tableBytes);
            block.BlockHeader.Pause           = 0;
            block.BlockHeader.PilotPulseCount =
                FortySevenLoaderTzx.TinyPilotPulseCount; // for glued block
            block.WriteBlock(output);

            // now we write the blocks as specified by the table
            bool backwards = false;

            foreach (DynamicTable.Entry entry in table)
            {
                int offset = entry.Address - 16384;
                if (entry.ChangeDirection)
                {
                    backwards = !backwards;
                }
                // for backwards loading, the address is the last byte
                if (backwards)
                {
                    offset -= (entry.Length - 1);
                }
                var blockData = screenData.Skip(offset).Take(entry.Length);
                if (backwards)
                {
                    blockData = blockData.Reverse();
                }
                block = new Block(blockHeaderTemplate.DeepCopy(), blockData);
                block.BlockHeader.PilotPulseCount =
                    FortySevenLoaderTzx.TinyPilotPulseCount;
                // leave the pause length alone for the very last block
                if (!ReferenceEquals(entry, table.Last <DynamicTable.Entry>()))
                {
                    block.BlockHeader.Pause = 0;
                }
                block.WriteBlock(output);
            }

            Console.Error.WriteLine
                ("Dynamic table length: {0} bytes", tableBytes.Length);
            if (fixedLength)
            {
                Console.Error.WriteLine("Define LOADER_DYNAMIC_FIXED_LENGTH EQU " +
                                        (ushort)table[0].Length);
                return;
            }
            if (!table.Any <DynamicTable.Entry>(entry => entry.Length > 127))
            {
                // compact table -- need to define this
                Console.Error.WriteLine("Define LOADER_DYNAMIC_ONE_BYTE_LENGTHS");
            }
            if (!table.Any <DynamicTable.Entry>(entry => entry.ChangeDirection))
            {
                // defining this saves us a few bytes
                Console.Error.WriteLine("Define LOADER_DYNAMIC_FORWARDS_ONLY");
            }
            else
            {
                // remind the user to define this because I forget otherwise
                Console.Error.WriteLine("Define LOADER_CHANGE_DIRECTION");
            }
        }