// 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"); } }