// 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 } }
/// <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; }
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)); }
/// <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); }
/// <summary> /// Initializes a new instance of the /// <see cref="FortySevenLoader.Basic.BasicLine"/> class. /// </summary> public BasicLine() { _lineNumber = _nextLineNumber++; }
// 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); }
/// <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"); } }