From 97e72f798949c3221ea1cae2a4d1692c6fdc23cc Mon Sep 17 00:00:00 2001 From: Josh Dersch Date: Fri, 25 Mar 2016 15:52:23 -0700 Subject: [PATCH] Fixup for "1.0" release. --- Contralto/AltoSystem.cs | 32 +++ Contralto/CPU/Shifter.cs | 5 +- Contralto/CPU/Tasks/EmulatorTask.cs | 8 +- Contralto/CPU/UCodeDisassembler.cs | 8 +- Contralto/CPU/UCodeMemory.cs | 27 +- Contralto/Contralto.csproj | 3 +- Contralto/Disk/allgames.dsk | Bin 2601648 -> 2601648 bytes Contralto/Display/FakeDisplayController.cs | 155 ----------- Contralto/IO/DiskController.cs | 6 +- Contralto/IO/EthernetController.cs | 5 + Contralto/IO/HostEthernetEncapsulation.cs | 17 +- Contralto/IO/IPacketEncapsulation.cs | 5 + Contralto/IO/Mouse.cs | 7 +- Contralto/IO/UDPEncapsulation.cs | 15 ++ Contralto/Memory/MemoryBus.cs | 6 +- Contralto/Program.cs | 56 ++-- Contralto/Properties/Resources.Designer.cs | 10 + Contralto/Properties/Resources.resx | 4 + Contralto/Resources/dragon.png | Bin 0 -> 1202 bytes Contralto/UI/AboutBox.Designer.cs | 36 ++- Contralto/UI/AltoWindow.Designer.cs | 14 +- Contralto/UI/AltoWindow.cs | 106 +++++--- Contralto/UI/Debugger.Designer.cs | 282 +++++++-------------- Contralto/UI/Debugger.cs | 70 ++--- Contralto/UI/Debugger.resx | 6 - Contralto/UI/SystemOptions.cs | 2 +- Contralto/dragon.png | Bin 0 -> 1202 bytes 27 files changed, 390 insertions(+), 495 deletions(-) delete mode 100644 Contralto/Display/FakeDisplayController.cs create mode 100644 Contralto/Resources/dragon.png create mode 100644 Contralto/dragon.png diff --git a/Contralto/AltoSystem.cs b/Contralto/AltoSystem.cs index dad4d3e..4904311 100644 --- a/Contralto/AltoSystem.cs +++ b/Contralto/AltoSystem.cs @@ -82,6 +82,15 @@ namespace Contralto _displayController.DetachDisplay(); } + public void Shutdown() + { + // Kill any host interface threads that are running. + if (_ethernetController.HostInterface != null) + { + _ethernetController.HostInterface.Shutdown(); + } + } + public void SingleStep() { // Run every device that needs attention for a single clock cycle. @@ -140,6 +149,29 @@ namespace Contralto _diskController.Drives[drive].UnloadPack(); } + // + // Disk handling + // + public void CommitDiskPack(int driveId) + { + DiabloDrive drive = _diskController.Drives[driveId]; + if (drive.IsLoaded) + { + using (FileStream fs = new FileStream(drive.Pack.PackName, FileMode.Create, FileAccess.Write)) + { + try + { + drive.Pack.Save(fs); + } + catch (Exception e) + { + // TODO: this does not really belong here. + System.Windows.Forms.MessageBox.Show(String.Format("Unable to save disk {0}'s contents. Error {0}. Any changes have been lost.", e.Message), "Disk save error"); + } + } + } + } + public void PressBootKeys(AlternateBootType bootType) { switch(bootType) diff --git a/Contralto/CPU/Shifter.cs b/Contralto/CPU/Shifter.cs index 4887b02..2160a41 100644 --- a/Contralto/CPU/Shifter.cs +++ b/Contralto/CPU/Shifter.cs @@ -15,10 +15,9 @@ namespace Contralto.CPU // NOTE: FOR NOVA (NOVEL) SHIFTS (from aug '76 manual): // The emulator has two additional bits of state, the SKIP and CARRY flip flops.CARRY is identical // to the Nova carry bit, and is set or cleared as appropriate when the DNS+- (do Nova shifts) - // function is executed.DNS also addresses R from(1R[3 - 4] XOR 3), and sets the SKIP flip flop if + // function is executed. DNS also addresses R from (R[3 - 4] XOR 3), and sets the SKIP flip flop if // appropriate.The PC is incremented by 1 at the beginning of the next emulated instruction if - // SKIP is set, using ALUF DB.IR4- clears SKIP. - + // SKIP is set, using ALUF DB. IR<- clears SKIP. public static class Shifter { static Shifter() diff --git a/Contralto/CPU/Tasks/EmulatorTask.cs b/Contralto/CPU/Tasks/EmulatorTask.cs index 3444e1a..7bd636e 100644 --- a/Contralto/CPU/Tasks/EmulatorTask.cs +++ b/Contralto/CPU/Tasks/EmulatorTask.cs @@ -21,7 +21,9 @@ namespace Contralto.CPU public override void Reset() { base.Reset(); - _wakeup = true; + _wakeup = true; + + _systemType = Configuration.SystemType; } public override void BlockTask() @@ -153,7 +155,7 @@ namespace Contralto.CPU case EmulatorF1.LoadESRB: _rb = (ushort)((_busData & 0xe) >> 1); - if (_rb != 0 && Configuration.SystemType != SystemType.ThreeKRam) + if (_rb != 0 && _systemType != SystemType.ThreeKRam) { // Force bank 0 for machines with only 1K RAM. _rb = 0; @@ -455,6 +457,8 @@ namespace Contralto.CPU // // NB: _skip is in the encapsulating AltoCPU class to make it easier to reference since the ALU needs to know about it. private int _carry; + + private SystemType _systemType; } } } diff --git a/Contralto/CPU/UCodeDisassembler.cs b/Contralto/CPU/UCodeDisassembler.cs index 037cb82..1a77fa9 100644 --- a/Contralto/CPU/UCodeDisassembler.cs +++ b/Contralto/CPU/UCodeDisassembler.cs @@ -2,8 +2,7 @@ using System.Text; namespace Contralto.CPU -{ - // BUG: register assignments should come from L (not 0) +{ public static class UCodeDisassembler { @@ -150,9 +149,8 @@ namespace Contralto.CPU f1 = "TASK "; break; - case SpecialFunction1.Block: - // "...this function is reserved by convention only; it is *not* done by the microprocessor" - f1 = "BLOCK "; // throw new InvalidOperationException("BLOCK should never be invoked by microcode."); + case SpecialFunction1.Block: + f1 = "BLOCK "; break; case SpecialFunction1.LLSH1: diff --git a/Contralto/CPU/UCodeMemory.cs b/Contralto/CPU/UCodeMemory.cs index edc9be4..9b6c0ae 100644 --- a/Contralto/CPU/UCodeMemory.cs +++ b/Contralto/CPU/UCodeMemory.cs @@ -65,6 +65,9 @@ namespace Contralto.CPU _ramBank = 0; _ramSelect = true; _lowHalfsel = true; + + // Cache the system type from the configuration + _systemType = Configuration.SystemType; } public static void LoadBanksFromRMR(ushort rmr) @@ -97,25 +100,19 @@ namespace Contralto.CPU } public static void LoadControlRAMAddress(ushort address) - { - _ramBank = (address & 0x3000) >> 12; + { _ramSelect = (address & 0x0800) == 0; _lowHalfsel = (address & 0x0400) == 0; _ramAddr = (address & 0x3ff); // Clip RAM bank into range, it's always 0 unless we have a 3K uCode RAM system - switch (Configuration.SystemType) + if (_systemType != SystemType.ThreeKRam) { - case SystemType.OneKRom: - case SystemType.TwoKRom: - _ramBank = 0; - break; - case SystemType.ThreeKRam: - if (_ramBank > 2) - { - _ramBank = 2; - } - break; + _ramBank = 0; + } + else + { + _ramBank = (address & 0x3000) >> 12; } } @@ -129,7 +126,7 @@ namespace Contralto.CPU { // Log.Write(Logging.LogComponent.Microcode, "SWMODE: Current Bank {0}", _microcodeBank[(int)task]); - switch (Configuration.SystemType) + switch (_systemType) { case SystemType.OneKRom: _microcodeBank[(int)task] = _microcodeBank[(int)task] == MicrocodeBank.ROM0 ? MicrocodeBank.RAM0 : MicrocodeBank.ROM0; @@ -377,5 +374,7 @@ namespace Contralto.CPU private static bool _lowHalfsel; private static int _ramAddr; + private static SystemType _systemType; + } } diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj index 825ec0c..6618755 100644 --- a/Contralto/Contralto.csproj +++ b/Contralto/Contralto.csproj @@ -163,7 +163,6 @@ Debugger.cs - @@ -393,6 +392,8 @@ + + diff --git a/Contralto/Disk/allgames.dsk b/Contralto/Disk/allgames.dsk index d25af00f61fe311ae4fe5b443bae3494b8997a6a..ee49ceb03a594d94f1ab69212bde66aacfd61aca 100644 GIT binary patch delta 188 zcmXBFxemx6twNZh61Q#C0@G-#@0cMyZ!~#o1SYh26ji0^dQa3}44Yo+ILyA2P$dIEzi6bhU OaK;5!+`4ACSKmLU`$5eB diff --git a/Contralto/Display/FakeDisplayController.cs b/Contralto/Display/FakeDisplayController.cs deleted file mode 100644 index 258defb..0000000 --- a/Contralto/Display/FakeDisplayController.cs +++ /dev/null @@ -1,155 +0,0 @@ -namespace Contralto.Display -{ - /// - /// FakeDisplayController draws the display without the aid of the - /// display microcode tasks (i.e. it cheats). It reads the displaylist - /// starting at DASTART and renders the display from there. - /// - public class FakeDisplayController : IClockable - { - public FakeDisplayController(AltoSystem system) - { - _system = system; - Reset(); - } - - public void AttachDisplay(IAltoDisplay display) - { - _display = display; - } - - public void Reset() - { - - } - - public void Clock() - { - _clocks++; - - if (_clocks > _frameClocks) - { - _clocks -= _frameClocks; - - RenderDisplay(); - _display.Render(); - } - } - - private void RenderDisplay() - { - // pick up DASTART; if zero we render a blank screen. - ushort daStart = _system.MemoryBus.DebugReadWord(0x110); - - if (daStart == 0) - { - for (int scanline = 0; scanline < 808; scanline++) - { - for (int word = 0; word < 38; word++) - { - _display.DrawDisplayWord(scanline, word, 0xffff, false); - } - } - - _display.Render(); - return; - } - - DCB dcb = GetNextDCB(daStart); - int dcbScanline = 0; - - for (int scanline = 0; scanline < 808; scanline++) - { - int wordOffset = 0; - - // fill in HTAB - for(int htab = 0;htab> 8; - dcb.nWrds = (mode & 0xff); - - dcb.startAddress = _system.MemoryBus.DebugReadWord((ushort)(address + 2)); - dcb.scanlineCount = _system.MemoryBus.DebugReadWord((ushort)(address + 3)) * 2; - - return dcb; - } - - - private struct DCB - { - public ushort daNext; - public bool lowRes; - public bool whiteOnBlack; - public int hTab; - public int nWrds; - public ushort startAddress; - public int scanlineCount; - } - - private double _clocks; - - private AltoSystem _system; - private IAltoDisplay _display; - - // Timing constants - // 38uS per scanline; 4uS for hblank. - // ~35 scanlines for vblank (1330uS) - private const double _scanlineClocks = 38.0 / 0.017; - private const double _frameClocks = _scanlineClocks * 850; // approx. - } -} diff --git a/Contralto/IO/DiskController.cs b/Contralto/IO/DiskController.cs index 96aea6f..8337fb5 100644 --- a/Contralto/IO/DiskController.cs +++ b/Contralto/IO/DiskController.cs @@ -351,8 +351,7 @@ namespace Contralto.IO if (_seclateEnable) { _seclate = true; - _kStat |= SECLATE; - Console.WriteLine("SECLATE for sector {0}.", _sector); + _kStat |= SECLATE; Log.Write(LogComponent.DiskSectorTask, "SECLATE for sector {0}.", _sector); } } @@ -500,8 +499,7 @@ namespace Contralto.IO // Debugging: on a read/check, if we are overwriting a word that was never read by the // microcode via KDATA, log it. if (_debugRead) - { - Console.WriteLine("--- missed sector word {0}({1}) ---", _sectorWordIndex, _kDataRead); + { Log.Write(LogType.Warning, LogComponent.DiskController, "--- missed sector word {0}({1}) ---", _sectorWordIndex, _kDataRead); } diff --git a/Contralto/IO/EthernetController.cs b/Contralto/IO/EthernetController.cs index 34e7618..db620fa 100644 --- a/Contralto/IO/EthernetController.cs +++ b/Contralto/IO/EthernetController.cs @@ -111,6 +111,11 @@ namespace Contralto.IO set { _countdownWakeup = value; } } + public IPacketEncapsulation HostInterface + { + get { return _hostInterface; } + } + public void ResetInterface() { // Latch status before resetting diff --git a/Contralto/IO/HostEthernetEncapsulation.cs b/Contralto/IO/HostEthernetEncapsulation.cs index a9785a1..1c9acb8 100644 --- a/Contralto/IO/HostEthernetEncapsulation.cs +++ b/Contralto/IO/HostEthernetEncapsulation.cs @@ -84,12 +84,24 @@ namespace Contralto.IO BeginReceive(); } + public void Shutdown() + { + if (_communicator != null) + { + _communicator.Break(); + } + + if (_receiveThread != null) + { + _receiveThread.Abort(); + } + } /// /// Sends an array of bytes over the ethernet as a 3mbit packet encapsulated in a 10mbit packet. /// /// - /// + /// public void Send(ushort[] packet, int length) { // Sanity check. @@ -222,7 +234,8 @@ namespace Contralto.IO // (probably need to make this more elegant so we can tear down the thread // properly.) Log.Write(LogComponent.HostNetworkInterface, "Receiver thread started."); - _communicator.ReceivePackets(-1, ReceiveCallback); + + _communicator.ReceivePackets(-1, ReceiveCallback); } private MacAddress Get10mbitDestinationMacFrom3mbit(byte destinationHost) diff --git a/Contralto/IO/IPacketEncapsulation.cs b/Contralto/IO/IPacketEncapsulation.cs index 8e862c6..b702b2e 100644 --- a/Contralto/IO/IPacketEncapsulation.cs +++ b/Contralto/IO/IPacketEncapsulation.cs @@ -19,5 +19,10 @@ namespace Contralto.IO /// /// void Send(ushort[] packet, int length); + + /// + /// Shuts down the encapsulation provider + /// + void Shutdown(); } } diff --git a/Contralto/IO/Mouse.cs b/Contralto/IO/Mouse.cs index e4fe479..6adc200 100644 --- a/Contralto/IO/Mouse.cs +++ b/Contralto/IO/Mouse.cs @@ -53,8 +53,7 @@ namespace Contralto.IO _ySteps = Math.Abs(dy); _yDir = Math.Sign(dy); - - //Console.WriteLine("Mouse move from ({0},{1}) to ({2},{3}).", _mouseX, _mouseY, _destX, _destY); + _lock.ExitWriteLock(); } @@ -122,9 +121,7 @@ namespace Contralto.IO else if (_yDir == 1 && _xDir == 1) { bits = 8; - } - - //Console.WriteLine("Mouse poll: xdir {0}, ydir {1}, bits {2}", _xDir, _yDir, Conversion.ToOctal(bits)); + } // Move the mouse closer to its destination if (_xSteps > 0) diff --git a/Contralto/IO/UDPEncapsulation.cs b/Contralto/IO/UDPEncapsulation.cs index 9cd6956..087e37a 100644 --- a/Contralto/IO/UDPEncapsulation.cs +++ b/Contralto/IO/UDPEncapsulation.cs @@ -91,6 +91,21 @@ namespace Contralto.IO BeginReceive(); } + public void Shutdown() + { + // Shut down the reciever thread. + + if (_receiveThread != null) + { + _receiveThread.Abort(); + } + + if (_udpClient != null) + { + _udpClient.Close(); + } + } + /// /// Sends an array of bytes over the ethernet as a 3mbit packet encapsulated in a 10mbit packet. diff --git a/Contralto/Memory/MemoryBus.cs b/Contralto/Memory/MemoryBus.cs index f1d4c5a..e9bf057 100644 --- a/Contralto/Memory/MemoryBus.cs +++ b/Contralto/Memory/MemoryBus.cs @@ -337,8 +337,7 @@ namespace Contralto.Memory } else { - //throw new NotImplementedException(String.Format("Read from unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address))); - //Console.WriteLine("Read from unimplemented memory-mapped I/O device at {0}.", Conversion.ToOctal(address)); + //throw new NotImplementedException(String.Format("Read from unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address))); return 0; } } @@ -369,8 +368,7 @@ namespace Contralto.Memory } else { - // throw new NotImplementedException(String.Format("Write to unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address))); - //Console.WriteLine("Write to unimplemented memory-mapped I/O device at {0}.", Conversion.ToOctal(address)); + // throw new NotImplementedException(String.Format("Write to unimplemented memory-mapped I/O device at {0}.", OctalHelpers.ToOctal(address))); } } } diff --git a/Contralto/Program.cs b/Contralto/Program.cs index 224e2f7..598d670 100644 --- a/Contralto/Program.cs +++ b/Contralto/Program.cs @@ -3,6 +3,9 @@ using Contralto.IO; using System; using System.Net; using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; +using System.Threading; namespace Contralto { @@ -17,18 +20,18 @@ namespace Contralto // See if WinPCap is installed and working TestPCap(); - AltoSystem system = new AltoSystem(); + _system = new AltoSystem(); if (!String.IsNullOrEmpty(Configuration.Drive0Image)) { try { - system.LoadDrive(0, Configuration.Drive0Image); + _system.LoadDrive(0, Configuration.Drive0Image); } catch(Exception e) { Console.WriteLine("Could not load image '{0}' for drive 0. Error '{1}'.", Configuration.Drive0Image, e.Message); - system.UnloadDrive(0); + _system.UnloadDrive(0); } } @@ -36,27 +39,51 @@ namespace Contralto { try { - system.LoadDrive(1, Configuration.Drive0Image); + _system.LoadDrive(1, Configuration.Drive1Image); } catch (Exception e) { Console.WriteLine("Could not load image '{0}' for drive 1. Error '{1}'.", Configuration.Drive1Image, e.Message); - system.UnloadDrive(1); + _system.UnloadDrive(1); } } - AltoWindow mainWindow = new AltoWindow(); + // + // Attach handlers so that we can properly flush state if we're terminated. + // + AppDomain.CurrentDomain.ProcessExit += OnProcessExit; - mainWindow.AttachSystem(system); - - mainWindow.ShowDialog(); + // + // Invoke the main UI window; this will run until the user closes it, at which + // point we are done. + // + using (AltoWindow mainWindow = new AltoWindow()) + { + mainWindow.AttachSystem(_system); + Application.Run(mainWindow); + } + } + private static void OnProcessExit(object sender, EventArgs e) + { + Console.WriteLine("Exiting..."); + + // + // Save disk contents + // + _system.CommitDiskPack(0); + _system.CommitDiskPack(1); + + // + // Commit current configuration to disk + // + Configuration.WriteConfiguration(); } private static void PrintHerald() { - Console.WriteLine("ContrAlto v0.1 (c) 2015, 2016 Living Computer Museum."); + Console.WriteLine("ContrAlto v1.0 (c) 2015, 2016 Living Computer Museum."); Console.WriteLine("Bug reports to joshd@livingcomputermuseum.org"); Console.WriteLine(); } @@ -72,12 +99,11 @@ namespace Contralto } catch { - Console.WriteLine("WARNING: WinPCAP does not appear to be properly installed."); - Console.WriteLine(" Raw Ethernet functionality will be disabled."); - Console.WriteLine(" Please install WinPCAP from: http://www.winpcap.org/"); - Configuration.HostRawEthernetInterfacesAvailable = false; } - } + } + + private static AltoSystem _system; + private static ManualResetEvent _closeEvent; } } diff --git a/Contralto/Properties/Resources.Designer.cs b/Contralto/Properties/Resources.Designer.cs index 0235d95..cd06c33 100644 --- a/Contralto/Properties/Resources.Designer.cs +++ b/Contralto/Properties/Resources.Designer.cs @@ -59,5 +59,15 @@ namespace Contralto.Properties { resourceCulture = value; } } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap dragon { + get { + object obj = ResourceManager.GetObject("dragon", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } } } diff --git a/Contralto/Properties/Resources.resx b/Contralto/Properties/Resources.resx index 1af7de1..2c3c270 100644 --- a/Contralto/Properties/Resources.resx +++ b/Contralto/Properties/Resources.resx @@ -117,4 +117,8 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\dragon.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/Contralto/Resources/dragon.png b/Contralto/Resources/dragon.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3c52c684c743e95d105c841956bbbf40c9662c GIT binary patch literal 1202 zcmeAS@N?(olHy`uVBq!ia0vp^vw`?E2OE&AbAI9rq!^2X+?^QKos)S9a~60+7BevL9RguSQ4OyKpc;2i7srr_xVN`2<~=qOaJabS|Nry3%sFD7 z+&OM+rJB*t_vFZP%t$6L((zd82G@87@I|NnLKiF(Z6%6@^{ zuC+%Ddozyja<_0h+0ol`;{*z^FfZq;%KsD3edkVXxsjCa_u_xb_3w30B~+EojFOa5 zh_+oDubOiNTj%T9+56|z81K!jNST{F1>M9OCnR3oJ2J8Jf7blY&bMmk_DIes>X{@c zExB;%;|#0h`rSV5M}p5A z`P}=*cPMN1_wQP-*BvnQLJJolS5E4c-&z?nxu5rTS58~_?%|EHo2Hfff9*5@pwk6h_clGs;SOe;(?Ulb0aelt-3)deCDDTY&n!4+<)wM!wHn-;q#I-KW zd;P(hBYgf3|LYfvv8f8q7g*+e<7!;uis~otKk1yWzGcm^`t7fKvFrX-+Z@6cQrqq; z_{iS8Um{_(H94ayWrg+D$CmqUL4tStYs;|Rhl;UfjWf>9cUMkzKB9bo_pPbzyLQ+0 zyUXSfQdhTd|iSMQ8~A+z@1o|Sj)zg|9KW;%C8^mVL0i3Yl>ec|pk z_wK)&|7#mCag=@i8{3F2Y3y)}c=GD^Zl?DNN7n7zZ)}MrBcJi+D_vg3Te>_hcg63b jjfJs&flB%L{}~LG{xev True - - True - - - True - True diff --git a/Contralto/UI/SystemOptions.cs b/Contralto/UI/SystemOptions.cs index dc4eacd..4e6b08f 100644 --- a/Contralto/UI/SystemOptions.cs +++ b/Contralto/UI/SystemOptions.cs @@ -215,7 +215,7 @@ namespace Contralto.UI Configuration.HostPacketInterfaceType != _selectedInterfaceType || Configuration.SystemType != _selectedSystemType) { - MessageBox.Show("Changes to CPU or Ethernet configuration will not take effect until ContrAlto is restarted."); + MessageBox.Show("Changes to CPU or host Ethernet configuration will not take effect until ContrAlto is restarted."); } // System diff --git a/Contralto/dragon.png b/Contralto/dragon.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3c52c684c743e95d105c841956bbbf40c9662c GIT binary patch literal 1202 zcmeAS@N?(olHy`uVBq!ia0vp^vw`?E2OE&AbAI9rq!^2X+?^QKos)S9a~60+7BevL9RguSQ4OyKpc;2i7srr_xVN`2<~=qOaJabS|Nry3%sFD7 z+&OM+rJB*t_vFZP%t$6L((zd82G@87@I|NnLKiF(Z6%6@^{ zuC+%Ddozyja<_0h+0ol`;{*z^FfZq;%KsD3edkVXxsjCa_u_xb_3w30B~+EojFOa5 zh_+oDubOiNTj%T9+56|z81K!jNST{F1>M9OCnR3oJ2J8Jf7blY&bMmk_DIes>X{@c zExB;%;|#0h`rSV5M}p5A z`P}=*cPMN1_wQP-*BvnQLJJolS5E4c-&z?nxu5rTS58~_?%|EHo2Hfff9*5@pwk6h_clGs;SOe;(?Ulb0aelt-3)deCDDTY&n!4+<)wM!wHn-;q#I-KW zd;P(hBYgf3|LYfvv8f8q7g*+e<7!;uis~otKk1yWzGcm^`t7fKvFrX-+Z@6cQrqq; z_{iS8Um{_(H94ayWrg+D$CmqUL4tStYs;|Rhl;UfjWf>9cUMkzKB9bo_pPbzyLQ+0 zyUXSfQdhTd|iSMQ8~A+z@1o|Sj)zg|9KW;%C8^mVL0i3Yl>ec|pk z_wK)&|7#mCag=@i8{3F2Y3y)}c=GD^Zl?DNN7n7zZ)}MrBcJi+D_vg3Te>_hcg63b jjfJs&flB%L{}~LG{xev