From 7e8c4a23edbe643b9b943ea360893dae17f87c55 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Sun, 7 Aug 2016 04:35:32 +0100 Subject: [PATCH] * README.md: * DiscImageChef.DiscImages/CopyQM.cs: * DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj: Added support for Sydex CopyQM disk image format. * DiscImageChef.CommonTypes/MediaType.cs: Corrected typo. * DiscImageChef.DiscImages/DiskCopy42.cs: Misplacement of XML media type setting. * DiscImageChef.Helpers/DateHandlers.cs: Added DOS to C# date converter. --- DiscImageChef.CommonTypes/ChangeLog | 4 + DiscImageChef.CommonTypes/MediaType.cs | 4 +- DiscImageChef.DiscImages/ChangeLog | 8 + DiscImageChef.DiscImages/CopyQM.cs | 671 ++++++++++++++++++ .../DiscImageChef.DiscImages.csproj | 1 + DiscImageChef.DiscImages/DiskCopy42.cs | 4 +- DiscImageChef.Helpers/ChangeLog | 4 + DiscImageChef.Helpers/DateHandlers.cs | 14 + README.md | 1 + docs/cqm-format.pdf | Bin 0 -> 29316 bytes 10 files changed, 707 insertions(+), 4 deletions(-) create mode 100644 DiscImageChef.DiscImages/CopyQM.cs create mode 100644 docs/cqm-format.pdf diff --git a/DiscImageChef.CommonTypes/ChangeLog b/DiscImageChef.CommonTypes/ChangeLog index b592da80..c106e3bf 100644 --- a/DiscImageChef.CommonTypes/ChangeLog +++ b/DiscImageChef.CommonTypes/ChangeLog @@ -1,3 +1,7 @@ +2016-08-07 Natalia Portillo + + * MediaType.cs: Corrected typo. + 2016-08-02 Natalia Portillo * MediaType.cs: Added Priam DataTower. diff --git a/DiscImageChef.CommonTypes/MediaType.cs b/DiscImageChef.CommonTypes/MediaType.cs index 8b553739..f87f15bd 100644 --- a/DiscImageChef.CommonTypes/MediaType.cs +++ b/DiscImageChef.CommonTypes/MediaType.cs @@ -281,9 +281,9 @@ namespace DiscImageChef.CommonTypes #endregion IBM/Microsoft PC standard floppy formats #region Microsoft non standard floppy formats - /// 3.5", DS, DD, 80 tracks, 21 spt, 512 bytes/sector, MFM + /// 3.5", DS, HD, 80 tracks, 21 spt, 512 bytes/sector, MFM DMF, - /// 3.5", DS, DD, 82 tracks, 21 spt, 512 bytes/sector, MFM + /// 3.5", DS, HD, 82 tracks, 21 spt, 512 bytes/sector, MFM DMF_82, #endregion Microsoft non standard floppy formats diff --git a/DiscImageChef.DiscImages/ChangeLog b/DiscImageChef.DiscImages/ChangeLog index 773f764a..b3286e56 100644 --- a/DiscImageChef.DiscImages/ChangeLog +++ b/DiscImageChef.DiscImages/ChangeLog @@ -1,3 +1,11 @@ +2016-08-07 Natalia Portillo + + * CopyQM.cs: + * DiscImageChef.DiscImages.csproj: Added support for Sydex + CopyQM disk image format. + + * DiskCopy42.cs: Misplacement of XML media type setting. + 2016-08-02 Natalia Portillo * BLU.cs: diff --git a/DiscImageChef.DiscImages/CopyQM.cs b/DiscImageChef.DiscImages/CopyQM.cs new file mode 100644 index 00000000..eca53887 --- /dev/null +++ b/DiscImageChef.DiscImages/CopyQM.cs @@ -0,0 +1,671 @@ +// /*************************************************************************** +// The Disc Image Chef +// ---------------------------------------------------------------------------- +// +// Filename : CopyQM.cs +// Author(s) : Natalia Portillo +// +// Component : Disc image plugins. +// +// --[ Description ] ---------------------------------------------------------- +// +// Manages Sydex CopyQM disc images. +// +// --[ License ] -------------------------------------------------------------- +// +// This library is free software; you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation; either version 2.1 of the +// License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2011-2016 Natalia Portillo +// ****************************************************************************/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using DiscImageChef.CommonTypes; +using DiscImageChef.Console; + +namespace DiscImageChef.ImagePlugins +{ + public class CopyQM : ImagePlugin + { + #region Internal Structures + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct CopyQMHeader + { + /// 0x00 magic, "CQ" + public ushort magic; + /// 0x02 always 0x14 + public byte mark; + /// 0x03 Bytes per sector (part of FAT's BPB) + public ushort sectorSize; + /// 0x05 Sectors per cluster (part of FAT's BPB) + public byte sectorPerCluster; + /// 0x06 Reserved sectors (part of FAT's BPB) + public ushort reservedSectors; + /// 0x08 Number of FAT copies (part of FAT's BPB) + public byte fatCopy; + /// 0x09 Maximum number of entries in root directory (part of FAT's BPB) + public ushort rootEntries; + /// 0x0B Sectors on disk (part of FAT's BPB) + public ushort sectors; + /// 0x0D Media descriptor (part of FAT's BPB) + public byte mediaType; + /// 0x0E Sectors per FAT (part of FAT's BPB) + public ushort sectorsPerFat; + /// 0x10 Sectors per track (part of FAT's BPB) + public ushort sectorsPerTrack; + /// 0x12 Heads (part of FAT's BPB) + public ushort heads; + /// 0x14 Hidden sectors (part of FAT's BPB) + public uint hidden; + /// 0x18 Sectors on disk (part of FAT's BPB) + public uint sectorsBig; + /// 0x1C Description + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 60)] + public string description; + /// 0x58 Blind mode. 0 = DOS, 1 = blind, 2 = HFS + public byte blind; + /// 0x59 Density. 0 = Double, 1 = High, 2 = Quad/Extra + public byte density; + /// 0x5A Cylinders in image + public byte imageCylinders; + /// 0x5B Cylinders on disk + public byte totalCylinders; + /// 0x5C CRC32 of data + public uint crc; + /// 0x60 DOS volume label + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)] + public string volumeLabel; + /// 0x6B Modification time + public ushort time; + /// 0x6D Modification date + public ushort date; + /// 0x6F Comment length + public ushort commentLength; + /// 0x71 Sector base (first sector - 1) + public byte secbs; + /// 0x72 Unknown + public ushort unknown; + /// 0x74 Interleave + public byte interleave; + /// 0x75 Skew + public byte skew; + /// 0x76 Source drive type. 1 = 5.25" DD, 2 = 5.25" HD, 3 = 3.5" DD, 4 = 3.5" HD, 6 = 3.5" ED + public byte drive; + /// 0x77 Filling bytes + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 13)] + public byte[] fill; + /// 0x84 Header checksum + public byte headerChecksum; + } + #endregion Internal Structures + + #region Internal Constants + const ushort COPYQM_MAGIC = 0x5143; + const byte COPYQM_MARK = 0x14; + + const byte COPYQM_FAT = 0; + const byte COPYQM_BLIND = 1; + const byte COPYQM_HFS = 2; + + const byte COPYQM_525DD = 1; + const byte COPYQM_525HD = 2; + const byte COPYQM_35DD = 3; + const byte COPYQM_35HD = 4; + const byte COPYQM_35ED = 6; + + readonly uint[] copyQmCrcTable = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + #endregion Internal Constants + + #region Internal variables + CopyQMHeader header; + byte[] decodedDisk; + MemoryStream decodedImage; + + bool headerChecksumOk; + uint calculatedDataCrc; + #endregion Internal variables + + public CopyQM() + { + Name = "Sydex CopyQM"; + PluginUUID = new Guid("147E927D-3A92-4E0C-82CD-142F5A4FA76D"); + ImageInfo = new ImageInfo(); + ImageInfo.readableSectorTags = new List(); + ImageInfo.readableMediaTags = new List(); + ImageInfo.imageHasPartitions = false; + ImageInfo.imageHasSessions = false; + ImageInfo.imageVersion = null; + ImageInfo.imageApplication = null; + ImageInfo.imageApplicationVersion = null; + ImageInfo.imageCreator = null; + ImageInfo.imageComments = null; + ImageInfo.mediaManufacturer = null; + ImageInfo.mediaModel = null; + ImageInfo.mediaSerialNumber = null; + ImageInfo.mediaBarcode = null; + ImageInfo.mediaPartNumber = null; + ImageInfo.mediaSequence = 0; + ImageInfo.lastMediaSequence = 0; + ImageInfo.driveManufacturer = null; + ImageInfo.driveModel = null; + ImageInfo.driveSerialNumber = null; + } + + #region Public methods + public override bool IdentifyImage(string imagePath) + { + FileStream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + stream.Seek(0, SeekOrigin.Begin); + if(stream.Length < 133) + return false; + + byte[] hdr = new byte[133]; + stream.Read(hdr, 0, 133); + + ushort magic = BitConverter.ToUInt16(hdr, 0); + + if(magic != COPYQM_MAGIC || hdr[0x02] != COPYQM_MARK || (133 + hdr[0x6F]) >= stream.Length) + return false; + + return true; + } + + public override bool OpenImage(string imagePath) + { + FileStream stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + stream.Seek(0, SeekOrigin.Begin); + + byte[] hdr = new byte[133]; + byte[] cmt; + + stream.Read(hdr, 0, 133); + header = new CopyQMHeader(); + IntPtr hdrPtr = Marshal.AllocHGlobal(133); + Marshal.Copy(hdr, 0, hdrPtr, 133); + header = (CopyQMHeader)Marshal.PtrToStructure(hdrPtr, typeof(CopyQMHeader)); + Marshal.FreeHGlobal(hdrPtr); + + DicConsole.DebugWriteLine("CopyQM plugin", "header.magic = 0x{0:X4}", header.magic); + DicConsole.DebugWriteLine("CopyQM plugin", "header.mark = 0x{0:X2}", header.mark); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorSize = {0}", header.sectorSize); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorPerCluster = {0}", header.sectorPerCluster); + DicConsole.DebugWriteLine("CopyQM plugin", "header.reservedSectors = {0}", header.reservedSectors); + DicConsole.DebugWriteLine("CopyQM plugin", "header.fatCopy = {0}", header.fatCopy); + DicConsole.DebugWriteLine("CopyQM plugin", "header.rootEntries = {0}", header.rootEntries); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectors = {0}", header.sectors); + DicConsole.DebugWriteLine("CopyQM plugin", "header.mediaType = 0x{0:X2}", header.mediaType); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsPerFat = {0}", header.sectorsPerFat); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsPerTrack = {0}", header.sectorsPerTrack); + DicConsole.DebugWriteLine("CopyQM plugin", "header.heads = {0}", header.heads); + DicConsole.DebugWriteLine("CopyQM plugin", "header.hidden = {0}", header.hidden); + DicConsole.DebugWriteLine("CopyQM plugin", "header.sectorsBig = {0}", header.sectorsBig); + DicConsole.DebugWriteLine("CopyQM plugin", "header.description = {0}", header.description); + DicConsole.DebugWriteLine("CopyQM plugin", "header.blind = {0}", header.blind); + DicConsole.DebugWriteLine("CopyQM plugin", "header.density = {0}", header.density); + DicConsole.DebugWriteLine("CopyQM plugin", "header.imageCylinders = {0}", header.imageCylinders); + DicConsole.DebugWriteLine("CopyQM plugin", "header.totalCylinders = {0}", header.totalCylinders); + DicConsole.DebugWriteLine("CopyQM plugin", "header.crc = 0x{0:X8}", header.crc); + DicConsole.DebugWriteLine("CopyQM plugin", "header.volumeLabel = {0}", header.volumeLabel); + DicConsole.DebugWriteLine("CopyQM plugin", "header.time = 0x{0:X4}", header.time); + DicConsole.DebugWriteLine("CopyQM plugin", "header.date = 0x{0:X4}", header.date); + DicConsole.DebugWriteLine("CopyQM plugin", "header.commentLength = {0}", header.commentLength); + DicConsole.DebugWriteLine("CopyQM plugin", "header.secbs = {0}", header.secbs); + DicConsole.DebugWriteLine("CopyQM plugin", "header.unknown = 0x{0:X4}", header.unknown); + DicConsole.DebugWriteLine("CopyQM plugin", "header.interleave = {0}", header.interleave); + DicConsole.DebugWriteLine("CopyQM plugin", "header.skew = {0}", header.skew); + DicConsole.DebugWriteLine("CopyQM plugin", "header.drive = {0}", header.drive); + + cmt = new byte[header.commentLength]; + stream.Read(cmt, 0, header.commentLength); + ImageInfo.imageComments = StringHandlers.CToString(cmt); + decodedImage = new MemoryStream(); + + calculatedDataCrc = 0; + + while(stream.Position + 2 < stream.Length) + { + byte[] runLengthBytes = new byte[2]; + if(stream.Read(runLengthBytes, 0, 2) != 2) + break; + + short runLength = BitConverter.ToInt16(runLengthBytes, 0); + + if(runLength < 0) + { + byte repeatedByte = (byte)stream.ReadByte(); + byte[] repeatedArray = new byte[runLength * -1]; + ArrayHelpers.ArrayFill(repeatedArray, repeatedByte); + + for(int i = 0; i < (runLength * -1); i++) + { + decodedImage.WriteByte(repeatedByte); + calculatedDataCrc = copyQmCrcTable[(repeatedByte ^ calculatedDataCrc) & 0x3F] ^ (calculatedDataCrc >> 8); + } + } + else if(runLength > 0) + { + byte[] nonRepeated = new byte[runLength]; + stream.Read(nonRepeated, 0, runLength); + decodedImage.Write(nonRepeated, 0, runLength); + + for(int i = 0; i < nonRepeated.Length; i++) + calculatedDataCrc = copyQmCrcTable[(nonRepeated[i] ^ calculatedDataCrc) & 0x3F] ^ (calculatedDataCrc >> 8); + } + } + + // In case there is omitted data + long sectors = (header.sectorsPerTrack) * header.heads * header.imageCylinders; + + byte[] filling = new byte[(sectors * header.sectorSize) - decodedImage.Length]; + if(filling.Length > 0) + { + ArrayHelpers.ArrayFill(filling, header.mediaType); + decodedImage.Write(filling, 0, filling.Length); + } + + /*FileStream debugStream = new FileStream("debug.img", FileMode.CreateNew, FileAccess.ReadWrite); + debugStream.Write(decodedImage.ToArray(), 0, (int)decodedImage.Length); + debugStream.Close();*/ + + int sum = 0; + for(int i = 0; i < hdr.Length - 1; i++) + sum += hdr[i]; + headerChecksumOk = ((-1 * sum) & 0xFF) == header.headerChecksum; + + DicConsole.DebugWriteLine("CopyQM plugin", "Calculated header checksum = 0x{0:X2}, {1}", ((-1 * sum) & 0xFF), headerChecksumOk); + DicConsole.DebugWriteLine("CopyQM plugin", "Calculated data CRC = 0x{0:X8}, {1}", calculatedDataCrc, calculatedDataCrc == header.crc); + + ImageInfo.imageApplication = "CopyQM"; + ImageInfo.imageCreationTime = DateHandlers.DOSToDateTime(header.date, header.time); + ImageInfo.imageLastModificationTime = ImageInfo.imageCreationTime; + ImageInfo.imageName = header.volumeLabel; + ImageInfo.imageSize = (ulong)(stream.Length - 133 - header.commentLength); + ImageInfo.sectors = (ulong)(decodedImage.Length / header.sectorSize); + ImageInfo.sectorSize = header.sectorSize; + + switch(header.drive) + { + case COPYQM_525HD: + if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 15 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_525_HD; + else if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 16 && header.sectorSize == 256) + ImageInfo.mediaType = MediaType.ACORN_525_DS_DD; + else if(header.heads == 1 && header.imageCylinders == 80 && header.sectorsPerTrack == 16 && header.sectorSize == 256) + ImageInfo.mediaType = MediaType.ACORN_525_SS_DD_80; + else if(header.heads == 1 && header.imageCylinders == 80 && header.sectorsPerTrack == 10 && header.sectorSize == 256) + ImageInfo.mediaType = MediaType.ACORN_525_SS_SD_80; + else if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 8 && header.sectorSize == 1024) + ImageInfo.mediaType = MediaType.NEC_525_HD; + else if(header.heads == 2 && header.imageCylinders == 77 && header.sectorsPerTrack == 8 && header.sectorSize == 1024) + ImageInfo.mediaType = MediaType.SHARP_525; + else + goto case COPYQM_525DD; + break; + case COPYQM_525DD: + if(header.heads == 1 && header.imageCylinders == 40 && header.sectorsPerTrack == 8 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_525_SS_DD_8; + else if(header.heads == 1 && header.imageCylinders == 40 && header.sectorsPerTrack == 9 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_525_SS_DD_9; + else if(header.heads == 2 && header.imageCylinders == 40 && header.sectorsPerTrack == 8 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_525_DS_DD_8; + else if(header.heads == 2 && header.imageCylinders == 40 && header.sectorsPerTrack == 9 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_525_DS_DD_9; + else if(header.heads == 1 && header.imageCylinders == 40 && header.sectorsPerTrack == 18 && header.sectorSize == 128) + ImageInfo.mediaType = MediaType.ATARI_525_SD; + else if(header.heads == 1 && header.imageCylinders == 40 && header.sectorsPerTrack == 26 && header.sectorSize == 128) + ImageInfo.mediaType = MediaType.ATARI_525_ED; + else if(header.heads == 1 && header.imageCylinders == 40 && header.sectorsPerTrack == 18 && header.sectorSize == 256) + ImageInfo.mediaType = MediaType.ATARI_525_DD; + else + ImageInfo.mediaType = MediaType.Unknown; + break; + case COPYQM_35ED: + if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 36 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_35_ED; + else + goto case COPYQM_35HD; + break; + case COPYQM_35HD: + if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 18 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_35_HD; + else if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 21 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DMF; + else if(header.heads == 2 && header.imageCylinders == 82 && header.sectorsPerTrack == 21 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DMF_82; + else if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 8 && header.sectorSize == 1024) + ImageInfo.mediaType = MediaType.NEC_35_HD_8; + else if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 15 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.NEC_35_HD_15; + else + goto case COPYQM_35DD; + break; + case COPYQM_35DD: + if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 9 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_35_DS_DD_9; + else if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 8 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_35_DS_DD_8; + if(header.heads == 1 && header.imageCylinders == 80 && header.sectorsPerTrack == 9 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_35_SS_DD_9; + else if(header.heads == 1 && header.imageCylinders == 80 && header.sectorsPerTrack == 8 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.DOS_35_SS_DD_8; + else if(header.heads == 2 && header.imageCylinders == 80 && header.sectorsPerTrack == 10 && header.sectorSize == 512) + ImageInfo.mediaType = MediaType.ACORN_35_DS_DD; + else if(header.heads == 2 && header.imageCylinders == 77 && header.sectorsPerTrack == 8 && header.sectorSize == 1024) + ImageInfo.mediaType = MediaType.SHARP_35; + else + ImageInfo.mediaType = MediaType.Unknown; + break; + default: + ImageInfo.mediaType = MediaType.Unknown; + break; + } + + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + decodedDisk = decodedImage.ToArray(); + + decodedImage.Close(); + stream.Close(); + + return true; + } + + public override bool? VerifySector(ulong sectorAddress) + { + return null; + } + + public override bool? VerifySector(ulong sectorAddress, uint track) + { + return null; + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, out List FailingLBAs, out List UnknownLBAs) + { + FailingLBAs = new List(); + UnknownLBAs = new List(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) + UnknownLBAs.Add(i); + + return null; + } + + public override bool? VerifySectors(ulong sectorAddress, uint length, uint track, out List FailingLBAs, out List UnknownLBAs) + { + FailingLBAs = new List(); + UnknownLBAs = new List(); + + for(ulong i = sectorAddress; i < sectorAddress + length; i++) + UnknownLBAs.Add(i); + + return null; + } + + public override bool? VerifyMediaImage() + { + return (calculatedDataCrc == header.crc) && headerChecksumOk; + } + + public override bool ImageHasPartitions() + { + return ImageInfo.imageHasPartitions; + } + + public override ulong GetImageSize() + { + return ImageInfo.imageSize; + } + + public override ulong GetSectors() + { + return ImageInfo.sectors; + } + + public override uint GetSectorSize() + { + return ImageInfo.sectorSize; + } + + public override byte[] ReadSector(ulong sectorAddress) + { + return ReadSectors(sectorAddress, 1); + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length) + { + if(sectorAddress > ImageInfo.sectors - 1) + throw new ArgumentOutOfRangeException(nameof(sectorAddress), "Sector address not found"); + + if(sectorAddress + length > ImageInfo.sectors) + throw new ArgumentOutOfRangeException(nameof(length), "Requested more sectors than available"); + + byte[] buffer = new byte[length * ImageInfo.sectorSize]; + + Array.Copy(decodedDisk, (int)sectorAddress * ImageInfo.sectorSize, buffer, 0, length * ImageInfo.sectorSize); + + return buffer; + } + + public override string GetImageFormat() + { + return "Sybex CopyQM"; + } + + public override string GetImageVersion() + { + return ImageInfo.imageVersion; + } + + public override string GetImageApplication() + { + return ImageInfo.imageApplication; + } + + public override string GetImageApplicationVersion() + { + return ImageInfo.imageApplicationVersion; + } + + public override DateTime GetImageCreationTime() + { + return ImageInfo.imageCreationTime; + } + + public override DateTime GetImageLastModificationTime() + { + return ImageInfo.imageLastModificationTime; + } + + public override string GetImageName() + { + return ImageInfo.imageName; + } + + public override MediaType GetMediaType() + { + return ImageInfo.mediaType; + } + public override string GetImageCreator() + { + return ImageInfo.imageCreator; + } + + public override string GetImageComments() + { + return ImageInfo.imageComments; + } + + public override string GetMediaManufacturer() + { + return ImageInfo.mediaManufacturer; + } + + public override string GetMediaModel() + { + return ImageInfo.mediaModel; + } + + public override string GetMediaSerialNumber() + { + return ImageInfo.mediaSerialNumber; + } + + public override string GetMediaBarcode() + { + return ImageInfo.mediaBarcode; + } + + public override string GetMediaPartNumber() + { + return ImageInfo.mediaPartNumber; + } + + public override int GetMediaSequence() + { + return ImageInfo.mediaSequence; + } + + public override int GetLastDiskSequence() + { + return ImageInfo.lastMediaSequence; + } + + public override string GetDriveManufacturer() + { + return ImageInfo.driveManufacturer; + } + + public override string GetDriveModel() + { + return ImageInfo.driveModel; + } + + public override string GetDriveSerialNumber() + { + return ImageInfo.driveSerialNumber; + } + #endregion Public methods + + #region Unsupported features + + public override byte[] ReadSectorTag(ulong sectorAddress, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorLong(ulong sectorAddress) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadDiskTag(MediaTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetPartitions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetTracks() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessionTracks(Session session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessionTracks(ushort session) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override List GetSessions() + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSector(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorTag(ulong sectorAddress, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectors(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsTag(ulong sectorAddress, uint length, uint track, SectorTagType tag) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorLong(ulong sectorAddress, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + + public override byte[] ReadSectorsLong(ulong sectorAddress, uint length, uint track) + { + throw new FeatureUnsupportedImageException("Feature not supported by image format"); + } + #endregion Unsupported features + + } +} + diff --git a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj index 4e8553d5..37909d66 100644 --- a/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj +++ b/DiscImageChef.DiscImages/DiscImageChef.DiscImages.csproj @@ -46,6 +46,7 @@ + diff --git a/DiscImageChef.DiscImages/DiskCopy42.cs b/DiscImageChef.DiscImages/DiskCopy42.cs index 21e1a989..1792a2f4 100644 --- a/DiscImageChef.DiscImages/DiskCopy42.cs +++ b/DiscImageChef.DiscImages/DiskCopy42.cs @@ -228,8 +228,6 @@ namespace DiscImageChef.ImagePlugins return false; } - ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; - return true; } @@ -440,6 +438,8 @@ namespace DiscImageChef.ImagePlugins } } + ImageInfo.xmlMediaType = XmlMediaType.BlockMedia; + return true; } diff --git a/DiscImageChef.Helpers/ChangeLog b/DiscImageChef.Helpers/ChangeLog index 477776b3..8fb99fd0 100644 --- a/DiscImageChef.Helpers/ChangeLog +++ b/DiscImageChef.Helpers/ChangeLog @@ -1,3 +1,7 @@ +2016-08-07 Natalia Portillo + + * DateHandlers.cs: Added DOS to C# date converter. + 2016-08-01 Natalia Portillo * AssemblyInfo.cs: diff --git a/DiscImageChef.Helpers/DateHandlers.cs b/DiscImageChef.Helpers/DateHandlers.cs index fef56247..9ada5110 100644 --- a/DiscImageChef.Helpers/DateHandlers.cs +++ b/DiscImageChef.Helpers/DateHandlers.cs @@ -150,6 +150,20 @@ namespace DiscImageChef DicConsole.DebugWriteLine("UCSDPascalToDateTime handler", "dateRecord = 0x{0:X4}, year = {1}, month = {2}, day = {3}", dateRecord, year, month, day); return new DateTime(year, month, day); } + + public static DateTime DOSToDateTime(ushort date, ushort time) + { + int year = ((date & 0xFE00) >> 9) + 1980; + int month = (date & 0x1E0) >> 5; + int day = date & 0x1F; + int hour = (time & 0xF800) >> 11; + int minute = (time & 0x7E0) >> 5; + int second = (time & 0x1F) * 2; + + DicConsole.DebugWriteLine("DOSToDateTime handler", "date = 0x{0:X4}, year = {1}, month = {2}, day = {3}", date, year, month, day); + DicConsole.DebugWriteLine("DOSToDateTime handler", "time = 0x{0:X4}, hour = {1}, minute = {2}, second = {3}", time, hour, minute, second); + return new DateTime(year, month, day, hour, minute, second); + } } } diff --git a/README.md b/README.md index 8bea7e96..fdb7e348 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Supported disk image formats * Virtual PC fixed size, dynamic size and differencing (undo) disk images * CDRDAO TOC sheets * Dreamcast GDI +* CopyQM Supported partitioning schemes ============================== diff --git a/docs/cqm-format.pdf b/docs/cqm-format.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e00060d9b6f00fa2a336fea238958c0efac73aeb GIT binary patch literal 29316 zcmY!laBoSRJeV zyOd+@mB!KaO!Nhzm#MeZHFJ-saBa$@4F>1EgS+{{$uY|hL+y`5if+1cBt#Y47n_p@?k6)Z&^CwjI!h`GSnGK)Pb0$1J{d~6nxz3fTj4~aK z&r|ow@^G5ueJk6%{9~O)liQ(&1oP+T&+ofex6OH#%H!?l%c>=U&ROQ(e|JVea6;Gp zqQb+>M{YbfIj}s(`BvPA8_QE8vyQ$^E^U<7>x#Vpo}t}m%iVoBpYLzKv|IM=*Zt?} zgUw!=9qCf|E+JmsBGM-ETJFDtw@dpvoo8&5n>%m2ul;g1Prhx&9>znZd3)}>{VSlD zsIq~DVYB*|?1fwRPUvHXJ^`(0?_zx}=}k>T;Cwbe1Ye?20f?V73=^)l@_ySr55)U?!0 z&Ixn4y7Ek{(I?nSiU7En$`Ouq*Cq>}Sw@~fc{l$q2Sx4W7@7^n0v!rnU-GdEVz8`YQ z@_x}dYyNi4Jv4x*Oc=m-{`H-wM(QxJrcOA7u2}Rc`6~C^J>RdBfvY{hpMo;Uy znKRz7bswwYird^_4(!e*wFc!&(Hfa zW4yP$ovWOC{%Fj^K(5J!zqj6an%wFUeyTD!axz2Jy@amY^>0qFFe#O!hb+4M>A4Gk z^2rq)X19+u24%(kSu!`xhspKYtsgUMR9u#Q<@&Yc+KcGSC6bj_EX8O2{q*OJ_NRGk zyq}~b%G`bUeCsROX|H%+I=8HgV!uBpcK+YY&yV!vZU{O{otTi9)5GjnHf3A!zq2he zi^VT`eM#yPnqOKfRpazU2l)cEzYRm7o=y~#lA8S@8`1Qz1NGzIr!I^dM497*&6})T0 zu3G(=@`CZ^yj4>B-1&R>nQS*4UUSCf1G^R2T|a8KFq79}g8Jwo#}6lcx&yy|P6fA9hoBdXf!K{=1g$ zw^=3Y|66rNNbod<$#0JqwZ_dpWAgN^*N!Ito#Aa#FRiW8%v2{@$@&CuRq;Ev_;}@} zKd)EWE_8lZBo`y)-~Vc2y>Hy@IbS1`ZeJ)hi)z%n@qUFm$Cdd>JO9AJ$`q$S#$8bnx-{pOJ;tIn<#p4Z~s)*oh8Rj zG`+cBKb%p{dExi))Y+2} zNmnd4nupD8oyEYwykp(D=JyZHXB=K;c0*BDcdfv>^xn5mJ0yGx^j0W+t=zd{TbanZ zoxR8JZrQfl%x}8J*;AiSxN}V@e^eH9eg5P*9-6$5kDi-TJ)M1$`mLUoueO|#`qI0= z)N1X;tX|b7ZFk+1#mkZuWIZ~a=7#me@WgzHanWNEmlf9)KkL__>A>OSb9|NYPPI9V zwNc^zTFWBhOFS(9`s~<#N!&W?_q>Ad`#pk|pS~sU7cz0tvrisx{%>4!>|<%}(yDpcb2aV+22=t=<&gG7tgdDt9^S` zUH?y9M!@!?^WWDvB^GVykeu5YdT)8ww$Mcvr>g&3oh4f%H2HC_^qq{KiDKWSb(O3# z+&ZMM?@oBTGGu|02#b^aoE@5;oaZ9XENb#B$<|NXI;C|M>&1Bmh0@Fq&G?iobBh+9 zxgg(fIOY1>HQww%wt{;>QwYw6|YhnlLc3m0XU%jEOh^?mkmuZm;a zQ*PMU{>ggHzFCP;=^v*teo9o{A+y~~@U-%eeQ5zF4{4kI*rzFeZ>7QJQ!J`|X~pYR zowh#tsLlF$@%f;oXXh2$Z&Wq->9){j*1I^?MVnIsPft1dr!RDl{!c-lH>*lNWS&`n z^wCE__7^Ivp84Ht{Nt_rvE}v;=0!I}vOOh^G6$@mZNeyU(&pbvHvh`Cb5=&pdzbR; zZM#V-SKRC4dsAK*{o?KMPgwFpEIoFA)mvTG+k!>!nHyv~cL73YcfcVw5JJHA6*M66#h zl2agT=S!!A^Zqvf_H1rEvyFe%&2YbIwfB#t+s5;qe*GtTcc=W0*#&=We>!yBR6aAav-}R) zdowlW$$7S{n3%e^?1l#qY45&Wyh8K4>2#Kz`{pi{zR36V_;N!{kJEx4+bfjMp!zP1pWNv{leq)oxrt`f8w>Y z6lKjnp5QwyV6*am-JVlY5AT?|T$lg6H!%8Z!Mu9UWBcWk#fx(9{f_@I)x5m5O4{gs zN4fFpQ|G5oZ8usox!$(x=H=Ff8)x=OS~0FJIVoOz_1RSMK-EUmUpr@9Th}p7vgZEd z%9uyj!nMBJ7<)P!?f8_ecr2j$L_v)B%~Q80O*(s8HoH>BbnOvIG2NL**d@1Hrp}pS zx&F(2_h%YPOK#lHZ<&1g{!(4#1@deYZFdyidvr|fYG^>9Y{}QTYRtiM>2kHo1^=77 zYDy&<&x^2ZxeVW6B1z+E;j6wz8*h-2TmF9Sny`(UOHD6q>T&MAWu$jxg^u&QEs>_n=bSQ6Px!T_ z>*+(oe4QVhp56!l{!tZAU;9{+DbH--+iQW5Oln@&{P#HIX*V)59$xEkCR)JT(bLGJ z>CmE`js{E&)7HDlG8tUnvUu)SZ7~KRLn+Vc)@?TR{T&}YADgeK+AtyW0#De3_*rw0 z-gszVR#Kq;DI{#2r9pT4b-CyIItP=YZ7t_2ilkY_2p^YrX|<60s&2Bn!K+b%b?Nl~ z6MHu&tX;ue`D|*${s%7?*6E$pIhox2S@z#zkK>++U2A47^U~ZhZ;RvXyMHdOpW@0M zVPEc(W&C%6&P47fwa%wjdGGwVJ1VN(IPiVLM{ciT_n(bHZxwFk>V*Ak_bu5pcM;=O zTfhC$Z*N`xvUzjqZteMVF5b!wcxYvy71O@rP^sPP0_7J4!9fq33bkT3IJR;pgzjjF zYvn%pqCojoLHg=VS?r42DW)?91^7G4A}>m!Ti#m|Gs#`uf6(g1PRZ>$pAJXFIgdUd%Q9 zNN_8+-iiXoUasjnleom4u02${^6=S`L$0qwcDz``C4O;Lf#=(h9TL5K<_r1EUl%=H zs~3LnWzo}(lUKi5uEEZJ;bh^0^dj3Wa`JZ@=bg;Yu+?jrdH#URmwDEAqn0K9Txe~6 zKdMS*LYYm9zx$_(%%7Ej4ms9-8?6Z}Zz4Z_E zIrlp5%#5wa3`3)hRv-WPza%_PgiYtZWl^)Y+@EjTt&Qa56#V7grR`^>d70fh8+he# zp;)Wv?fcUcE~Gv_Rle+*+s1$APpuMj4EmJhs?k$+Z>9A9*=~$BmI7t7Z#mpMW-O=i z;zz%k`+c+XnL7RjPJ$@=!YYM11y0K^ors(|q{U`C|!FPV&-1e|lf zZ#0{2yJVDcbip>uv)1o;yaC%=-N}=^NLXmD@@e`dnFb z?P%KnrCUQ^zAjs~#Psm_yti-PRDDa{XnR(H|K+MnQ7#4^?_J(*atsp^iPL`DanHtN zQ~$9E2f5hytL{F>;d6ZU?cPoMr7qrm_A^wj+NW%lNYBGJ9jo>_pHRD)dt+MMN}ZSC z%ROFg2-SW(srrp)R$v&H+uTLV%uJRk?mpgHy!m>hH1m#wIj31C`pPX?^!nR`m&Xs5 z7%^+@4ojNOT>Z|nN&7)y*RC+rLbLCYZ<42%L?`G0 z3dwxBY~x?N{o09B8pUVMFTVb6%TYHcrFV{O0?C1|1QNVe>;gTPb8~AtJrn0!Gq^ZptWNJ5|;}mNFPwU>>YkYc?vK}}$PtShyMEIwVN$fdwJHL4* z5A(B+&$@M1Vf71LX`u_-ug80P)bXx(z2)!!AT8B7t9i?>eXZCl!(V7AUo7bCF9P}LN+*`J<}|oWnNzxc=4nLZ}JZ82Qwe2rRIhtLndNx>$E{n>f?tnREHy zNfdXOn0ekWiJLNgZ;FlOJTKqYJFlfDgVD;j z8!IfMYyRzWo^~QqvEqE67)RaFzCJ^q36N0I#?W*Hl1Uin?WUe+I{wK zJ>Frf{wKL>K63xItBm8`hUq&}{r6dvZN09%utI(X!{kH8T}2_*FYJoGq?eT3SD(ly zbTr1tb58yFxX+?Z_V$5?+5?sSMH1mxy60eitSbXGUITb zjKgjT_F<1A#8s*kD`y;A+Pe70)&55@-}cB_N6nh5&^g05@!1!xxsQ_9{QXlsBkY>@ zaknQHcBwP^zf5|%hwCu^rj2Q3?`AwGSt5VIw@A`tdfVoYt2);N6tUgD$teEw{(OON zK~CmZmD6@9Xz?d%Hg;~9D`XX?q`bbcIYZ(_aGwp&m(Iqz-rMIrr8O^RoKkJ$Sh!10 z&HCG~_c^cEdQV>T?6kNZ=d)QKV)$yJ)Oqu(R< z>0?hajy$nH?ax1HhGVk6!Q-NO*=&nRJmpm`s-<=UxtF#p&-Ht@Y#PsBbu-Q7Yd$Yv zQt>{h^l{3ou!6H&ckw+rn)rX^)GxljeNIR5+Ry)b(($&&p10D9Iu;%q;@d{B`5ZNj#R6=&_F3M9gF zW^R5wPiRL|<*s&b?)XdX@DqJ5YqvI1_^s5wXJ2wlHmyCA$}1yO z6vY+2@$>$@eqDiwn$N%I{5$JyhtADs9FY#Y1lJ3OaD2*T-FNTLtBk#o8&hpoem4JO z7c_hFq&nV`Ct`{XCZzQ*%j%~GLXR^D6?aVmqg)=R%Jc}j`Ex*chUuWmeb zhckQjQu`*h!_zl*nQl*PEZ@L>jHuX@jU*WCeJspuiet( zdhvUV#!IWo+rF?JJg2PsgY)0F|7VqDzx`z2Bk=xz>hq{um%N+hCR|$mvu9Ucm$q`R zOs!q|%hWwWX&(L&uu$U_scFoE>y5gVP{nN9#g4zruNrO9THs*Qd_M2v=L{F+;MnYiyY5d* zc-XUlo%4&_d4FT&PTPx}J)o~($sg2`YNKJ0=5utVtYBGP*L#i=7CXKMOapyZNXla}UN*FEL&HgspEQ z1J%6eF8hDms8T%ta&wEAmDS^C_ooYJ&NVFk{QIt2tIx*Z^2mcX>%>F4KJQmwd|~UA zx_{1cDnIJyZ_vtKp)#G>@#X?6ztRg2j1I78s$RYvv|Zrlj)Sc(efyu;K6zuWklR~u z;EC@NZ40lk-UWNP7fbtIPWfIw^Wpxtsp1E;&SX#%TsB|=$ z^Y-mb*IgDxkB!b-`~57qRdm^k|B=&C2iEPbg-aAg(*$h)vzPzYeUH&<1UCmk%|;_r zBO_R|(FoR-M9c#knG@4&d>VUm(w%Lhwe!~}JY8vB7<@zgX5p#mB=s_x)00m*Pw_gd zr8y~0r8gz|*S?wvhP)jr{L(4>#{*@0uWmPx-QK!=ctop@q7ovHzYAEu4Kl_#-+0-?+SkS=h?*!4a4bM3U=OQ{l|S;R`$$l1E=?`PlV6z^V|9%W8JH5 zJKmR;@wq!|sK2$lCw1%PG}VOW#TFwAb=Vof2(a z^P{^sH+w?SJ^ngg#n%N5T-n(#L*y32U>)eVXd@gVKw`ZAU#=q^dQzotS%dqoRi9M`xaGA74={XI%HplNX9yNY4(DX+*31ze0ICI{tu5XGM&}ZbCcIM{_xRHSsQe&C5zcydvk2f7WHJX zbIhMLR8BKbR-61H(&?PKX5~~af#e68yj`yQed)GoDNC>D9Xwvr z>ydB3)AUoVRMXr@WohTGjX$*lzwL=jvHad_TC(5pyUI=L8)3T_1+=Y-J#>dNyZ_6d z0_RWFw|IB$Yg98ZOW4@^XLUxhhn(eHtJS$8ukSp2csOLs!~K`8d{Fu(mHF!7G{033 zB#lC4>T65>yzXH;q4(8s6W_ON2MGrU3E_eX&$g+`o>h@My5r}`sKm0&ld+O7_->`O z@Cb(lpG%(JBBJoAUc9$RGs`vM%v7I%*&46!#-x9JE*WC>#(6?p=YQj+J@c0p?#n$X zVa?3ERj0~eN?}!3?o(TfuuTa&&F(o*yIlG0%hkf`WgD_ZKHISST+IG>jCEVuR@DWq zTYaXg`3bGt$?;S8+#Tr?8@l77V_NN)5+l_nu+DzsmbyW&r*Zwl)b{SuteoCONqz}; zXED#N_uL{BRClLHG@+?e^6bQ{=_2OZf4?}siQDqav*z-`d8VxCuV+T`)tA}*5nLsH zAYLIp`>529X#d6WiHCWvFK(@0ATaCJRIQ4_b9w*ntWDYy#3{aCQ?vZ!R(4K>ZP&w= z##!0CN>bPM3E1>UAoAhNj-M^pIfeczrh10Flv3L$dx(<&SM%cA;TL zLvk1olkXDVq$fPjOLZ6CynHKl8vmp6jk_c5I?BE)pJbTMId4C&r?AcJ2{ENnf9-Zk z&sSMtckdH}@wC^Stj#9g$2xjn&t99R!=;^F^y}%2>PZJ8^1Nz~JnYB1ZPA9nms z@riAm6ArICym}uqXXO6LPA7tPoHf6lU|xN-c(2gGh)R#`+8MWGy0kCwYR@TQZ_G>Q zo}70*@o|pM{D_Uy9=xyA+7NFlcKFlnwOnNZ!n5w1_RQ;@yZrR>=aF;f9egp@(Dte0 z<>0T&p6B!ntQPuq`|u>4xy4H(BrC=H7G7=EtWNc{XFBdDHTm|5pxI?rbyJ+CsZ@E+ zGFlnWJ6Y&i9Ydtsmw5#&x3!l@Ew&EY-PUk3tKRN@%7yY|BZ1YNw>P-N1!ndVco+FX-VCSTQ~(&j~!fef|HZ~;SV=q_VcquGM}fJrMdQ&` z(d!w1w8Z;^UptMyWG+?S#*^{;^u>uX7u5?~{Eg@NUJ}?C7RngawBx z&xBr#iwVEu1U>VXI)!4xbu(rzCY=CG~B&2nkRjYGP|?i-X%YRBAK_jZICTy^qd_Kyhr%g ze%asE*JYb_9}r!ysHh??zfSp%iXY?3Z%k_U%MPF1b+1L!KX}rUzNSfAv(A_)+$nOf zJj=l4X#UBDU1Q0#p3}^$4H7~fB5#}Lw|+S zSe4$+I5c@;#;?|*Y*`C{`YFD``#EWWJAxlp4~Q&M@JV2fPN-#$y(ug_() zvNtc)Gfh45nk#Y&$Lp4E)BMr`t5X<$<&e$GHd=s zE)TjUb$s^3B^#IjeDVJ6-uIk+x=&ADc&^fYE$(%F!A0S9m;7g~O!0bqCn?UCl0!}_Kr#^i#c*UMcG9xdWHbisMHsQH2I zN0wW7-RQ_v{^2@d#de?m56hQXG}~uRI%9j+P3>f4i~Lfi%P;t}zJ2}WwQ8zh>Q}4o zXB~WjMSHZ8miIe|wM|w%xY3fC{c6>uBW$|sX4rPS=)CMaTVb>#vU-Q-o#&^I)&|BF zl&sT#r?V*hQJUS0@4r>wSu+1W+VR_nvvU8Q+*iF4$|_ZfnG#j@{phA+Mw!{`4cO); zCcfF*X0ze$NiJKiZP!&&#LRA|7QV1c+n(52De``M^vTC+=XQywc*?8qeRtr(GLgco zdt-DKafX+_=j^&YCt2H|?aWRAQ^pog>1Xp?lr1!>U*CJ{&t&nsWaZgs>*qf4U_SCx zedmW)8kKdwUr1RM^2i$0d|kQPIX?87&bJdQ73ZncZ5P^kW`Bx&y%twkbNrqo7h==T z96j7|x9?{6VP^dVy>~q={L|i?*1zd}{*hJNTb*_MNmabr>CKy(7W5?hE(uq}zym@!juvw|#bp53DXXWlaS?wv*?3*W^|L6~g;O8eV84pz*YB1mN zcJrL2n=*V>Fm5UD;NS9C)^(3^$N8*@6Vx;v;#8e)slE>XKOum7YQ0yD3cJy_JnL`M zl% zZ2nOD%j0p2v*V86yEWC5mhaK?EpDhhzq705gyz=z_QpG@lM31!_gKC;cb9pO@eC=2 z^VV;UTF-sY(9!&P`R_C80&eg3|7lg3K6j-etH%Q-xuBzL26lQ^j2qOay(w8Idwcia z({8y%_vX#HWTvrPWqEX%g<07NyJ)4L#gP}LDt9jU`El=_ntY3mRVTk*Do7H@Ex00D z%DG|T_Ot_n{_FQ{4_$Wn(rWhzNBNttk9vGxp7!p?iS7wY{g3B%-kw>z`}`#59XB=~ zec!|O@yp9<+tc5FNjDkad(SAmbZ7j{nJ-IkM?O8~eOO5K_xhFp8;_sf5Z!iOG+^OA zxvSDEtS&C++`Hq&-dM+i;7=9hx2CL9U;9k=mhOhLOHVABY|yf3hV?Rytc%$ZYa6b5 zLRWpVUWr5Sa0cds13 zCMEZ%Xy?(W{gSe7;eEmU56)d%yk>f1D1*GfuDN%Xe_wNAj{j@HZ!K!gix2IX#JkRU z-^;Lu)p}Du|BD*uxoVcyU$>TB ze3(sarj|;I72i`gmO4YB7YXkkAKPShLda!Cz1gSQpf`$gXF7r(9PN4)`Y(083&)Qo z!Tbx4H(KVbOkS*&eQNS1-#HU4wO7oaG+Cq4_fyN#zJAAtwQp+{Fvm|)dGbB3c(Z%% zl7wZ~WfrzPNt@Po@b3DJJBpMK4LXeazN_4eL%mJG3C@pzNt>a~9?tWjXV0gZJBn>BkP~t!<69V&45l zs#J4^Q*2kobiowv&Rsm+im!}(DsF81wjuX%;_Yh(x6N&RQqjC?iqB#_)$1#~HAP+- zt=Y4}d*y<89=u;&q*kb3a%!Exr`7T$KvAT9()#6{B2%B;c4F6>RVgIJ{&Tb4iX?AG zyOoFL zi#=n@R=!>r(|fN`OYr_&SuumRs|4JB|0xYu-x^z1X@B&VmdZ?p#XA&t$~P6tupi-> z^T^9gR#HxRS@S~g^S7-Gf8Gq6%E-*Tqh7r7K#9Z$^SceZ--~&Q_1ImEO{}_bbyYZT z_MWc%^vvSfd=(Dg`?u-z%=dgK74&-PCf`7}t4p$^-|zUy@z~~=-0exrV_$iOzbk$9 zJT|}fg8Oir6go4c;Iab7%8649{bL#6xI?Xv8J@y}y}JB8)*w@!YmBpm##^-alkNo|z!Nc>K-L>3BEc~Js8n^bW z6OmWt3YgQ(n0aX;-<{+y&1wG`1GluwVvZ+)#)&}VNhYS2rm*oO6T~cJNM%8)en4V+ zs=jY(N@k){euY9bXhzc1(p161#3+_aKPbN>u_RT&(m+2Tu_!gKM8R0WKp}`rKPa_0 zzqBYhwb;f+KcFZ-IXJZ>T0g+WO+h~-wW1`JOW!#^C%-7TATc>r!4PC)5SPAdMTvVb z*f>Mb()J)O{eZ-hlGLI+1w+sfR}h!JTYg@Nf+5J!L0oorAVcy>Qu9iR6(H*E>|lN{ zw4jS85H2=?`Wx&*BZ!AkTnL`r1-lwL+iL`JGbrqBZ1iau&PYxKg$XPWjX-nFVCU#N z=R*S#Jdg+$gE|r9Cx{Cbz)n;Er-fn#eecYaVufffh&RDu9mECpPY_qEg1&QpDJUU; zVhC!GA!=+W=sPEtBX}(GRlZ0x1kaN*_ppA7su2vJY7nl-yu?Ex5qphR_S*LM7e6J1z8)Hd=5& z91Kmk@DvP+UYAN|Nq}_oN@YUu;K*m3ET{QWjb-7=z=-}8l~Xu4GrHQLjx{DP=OPqA7p66WoQH{ zTo5q<&U7%P7_kV>g)mtpgNzKhj6ewzhnYyxi{WYG8Z z4fAo1(Dq2pDN8NMOit8w%Fjsw7i%u5#mPmP1ts}K3WgBZff5E-ic8-uv#7YlIU})1 z!PrROClSgvG&0l=&rB)FD30bbG6%UE6jU&5Y;3Fm#ulKoZvbY4R2i9Da2Y6=nwo(z zL^X&9VS#jjSf&=B{A0icVjC!!8ySJIfr1&>aUecK2E?~82IXIKu({^u7GRZTX69hE z2$c}^P!j3_kZzFOFdAe#hz-IZw?fAU77=ci=mrR#@h<~imSXTtfxrAZ~Anhg}fhDgZ?6z=FT77*eT738PwoL^d$ znOcN3zKp=h5|Z&ju}n;SnHfMsLIFfWcpwsnVWJ?u0T+lw#voY`28khP5D$c5u?CXC zilMQJE(g}D03xB7%K({&lMONlsspA5Yy+AY+^1#+pyCT28$`wqIP;^&j<0*TyJHw4 zKY{ZLa#jMR%pfA7#?lNeD;b-DstB;x%s|xxD3O6NDA8G%D1Z_rh!3JcSqhr$zzGr` z8>(Ibl)>QG)C?5Vpe$o%4$eNHYy-b^`@+6DCwQ74@5$nUhgKfXhB;5(JeUXsHd%^-Ii6#Z$~f zYIy}+BST9C3nLQ+a}&c@Q2QahSi#tcOW(0L8Pte2H!79|BsjH1L*H57Mc+-|L*GZ=SKnVhP(MgNSU*HRR6kKavA86)D6=?QKS@7XKRG`q zKTkhJKUF`qurx78KTSVfKSMvGvLGWhPd`&XTR%rXS3gfbUq3%DRlh*L0MrY}Nlhz( zu!=zL*Dpvd%FIvEFVrv6FV-*4tk5scNi5FLF9x;#^-J_i%JcP0^~?1u^eZ*NVd$2b zlWMGB42pK7RvDNN4Sj44FO*W<8P?D-HZnIvYUzZARmO<8i@jr9w!1Zvr%zX@FwKU+ zk)3~!L#)#Ag6=MMCx>^ZA5N(5kUHQZ+;v1*W1YtI2aJLRj;@YJBodiVOg!QtWHNh( zgm0K?#h!UlOc&J=ZmoCu(e8 zamCiuclJDq`4d*o*CcnR2%;UzMDBFVb@2&cJ`SUNp?OabU-f zQqI@H&1N~bS9r#)Dv2~be$Vl#Mg2piF0Lilf;O)#ox;0y9@nBbD|oNCotk&cg*7*q?e-24!V2K6R}O?$(e$)%j_uSg^N7eU_%E!`g*9nu4jmCs%S#(M)yo zT-kXg`-+N}o36r=V6~K+b+0y_@Gl8jVqRp{Wmc7BbG)LvaOTJ7YMg3M)%JI4)Mc}HgR-#_wl_KC*^%F^$0ZS61As-5)j+pli? zKlQ+wbOz%*#mvTwMUk(s^W0PtKJr20$Rf5+2OR^X4x9cHYM-0Ck)t~4tH!betke1% zIl`Ln&k?Vfx=5mX=R@sRlG|q6-Y?*llahWdv1{#wSC5Pmh3W;=y7iixH@?fSG26a8 zJ$&;r)fd+eiRn1$^c|YKQTE*N-41PUIU?i@>e;ltL~cyeD~Z{Ca$?P)Dq9xyh)+M) zCsciHGJ7~>kJq7vB1|PMW#2U$8@PMuyyaX`Yus*As-9rtT@f&|j1?)0Fwq zYweFhdn__k#kBu5eZ8W+=8sHMDXXVv_@!ya_pVi(iqScGZe8fQUd66s5B?pAGig{i z=iiR*qbhow4x-W%pg&f+g?uMg{|$kMG9te%u(4cROPXQUCV`8 zW$$y(BY*6E^c}w6*sLdc{^-^ntXUhI1&nLV;{?-+wSVq?>}4zxs#l-Kb>GErkH67l zak-bGGh*(mx2s32oNjRA%`$O=_-*sw=gsx<|N!cNb^N%R?up9^dihRe9f{V4V`9 zgfGWRbGmb5nK!sDlv(q#`TFX9ex+}>JahMK`>fnl zO|5t8*1Xlb=iE-a+iSNn_kBX@PK~dJo0Ri68uTAqtuS-))?F^`m3w9eU9Q=;>XF;Z zJ!_Y|e9*{$%5Ia=iet@;4N`nI3xayWFLi%3QjbvUIlvsAlpmwqHucN-4WI1dG<$ed z`~pHhefCt+38cc`1|Xp(v7CRpDHHn%=_Zc zLE-j)fn{rU?GnFKx@qO(p9T?+BbBW8$-FCOHMsgxI>*;#zl$mJ@8qw|ksBm*-uE+< zyEHDp76BZiyLAN ze=kf}`CfQ(5p&;r;n%-;7M)4^kn-1H-YNd>!e^(G{%9{=9&dQ+wm+k4)ayHcxOea7;j^okGdpNk@%6Ce0;bRI&F5c>MEE^- zJ+g#-amJRn5$9&cidOsWxqqr zSwAs<5o9mC@o0q+yYT*Phr2iI?be-^@L6;*%am8Q0}a9jwks?ut@>~}_@aMP@*S1F zje92Bh5q_}>(Zu0-@TunpYc{+MdZ2CHN%?MOs(rzKhE82u2K1Zx6{wZQ|8YTob+Cr zXU-D2vc{=Pb6V!RAK5Zz;@{ooMsGqIOy}8%M?Vm)cxs!&6lVMU)b4d!cO>plC^+xS z8LIoUDCoh_iK&{sC&h(!eSSA-+m<&Erz-?csO|Xjzn5PiS$@~6v~>A5Z=VTY`~PCQ z_k4bhfBg%%^*JxCJhtQ8yItxp1giwCpP9d~fAHs{jtRG=`p@d0E0fdvjmz@N^b_CY zU6S~C<+Xdh$%gy8@5l*0e!QW-xKy(2bdB}Pxw$8}y#GDnSaScy!=H^mr8jTWio2`a zvBBl9$^+k)E&rysdaV`-zy$}X8lz+_JwA{mmtUTdp*AU<=# z-`W}6FMiy6Y#niP^J8nTOTx<+wlrPYBb)#B%uV4&kIO5JE5h|NOa3w_etB`+_@ipbpDkv zpYaU+Gwrj=c1_W>TcdrzvYz#EwE_E#(z`cyg*Cp^ zN?p24s5Gkf(mjJe-sxXn{ryyUYQO4%2e}J*Eb68-yIiVr=UDdk*Ts+DvK{zWYq`WS z{R-G|`s3ykrS2!X)QkVlKk(4WP-po^u^)ne6rFnJW*oN7F|vjDSBTAX?CjN9fc3{qfkjGm% z|7BTBoA&ScpQ}?h&puF*{3l&A`~AQD+0Rd$J@)L~>E)M|oE$BInzW1=r-*3u~V@hwp65<@j*>*3YZg#n*ooe7!>W4s(v8c#i9l z|DE2`8+Bjm-w2y<*KqQ5Wlj5{sUbavt6%{H@p_wW)1` zMjX!?>oiH7tTRE~Ck+L7uPMD~h_{GoUc0bOY?9uQIbYIuq-3QpJvaA_%wq261y|ln z725L#TJ=`M%;n^dn^4}`C+pJSf2YYn{)Eh%`GOIXXOj6QrvkUOnlkqBs~)x;Fk70yU30(V*y^%HXoQj3Zc^HTH^ zi<2`mlQWBwOLLLCtVuS;KNDp5(x16)jM4I%s&`M}oL8d!9=Gg=zP=luJ>VeeWyb77VrO$_^&O|Qsp{O~X4#T;JlX9@dc{2C8#3e*YoaLs&a^zcya z?#1G*?fbudIr+)_eogRlthK$T==hut z^0@T5rBidI=9`r(j;y)jlh~r+tunFWb1S#Gp3u68Z8<-azHC+snle}Ayl|fQ^N!Zr zEo*)U&r6Wlrr@q~cJfJK?#Uu2HCMJ@y5t&p^N{(ys1L`ti|hURzT?MHPH#S)=zU45 zZEp{hB~@?Od&5qL>vqG@1nG?tIl|xg#2Ryt-8;H1;eF!mjUGMCtqJ;xF+L3wZ(7t1`hECj0bl*OCQH%c zx*4;rz8luoi`B{pY?yVbfbE~hL7!Ky_aY^H^B?LTJKexIMXE@vTf~9MXm*#MteQmc zJZ;ugw%nawt96-N9YR-c$vSJ5oyx7SK>i)mwpF*1OVfgF3mSfHvf3J%th0I&gA@P# z!csLkEGN%E*|%b)eD&E#Z1|IfUWRlTla%wccqvd?B1 ziL5%o|8*T#`?=6GZMG~YvCs{r@r%yRRem5^mNE6)oeN3cM zCp+xgbW;ZD6>GQ6;JA15(7wd8PXkW;zx^V-d1pG`mI*ieQchca4x9YXhd5t~!D|LwgPRO<7B zukbUgkX$;)HkNsL=QF34hD7$UTwlC4A^q{eeQ(a~x%_JL1Em*R_~$BDY}z5ebMkAZ z##d(x^uzwYY?A(8HI>We?|1Ra){+n01yZL}tEU!n#Yiol*6PhE{6YR&d`jB!HR0k% zIk^PN73QBPJZY;|eKoK>;li!zHQWN%Z-pua9rHCx3r%klRZuclRP$ft#U6R$!>`>F z?fd&~MW5&HbFlvxcX8gL+6Idm4>PPPzaCkBcdzI!yZq7(28Ay&H#G1!FZ3@jO=iyE zmpq^C8>*);O|jI1$BQQ^O!mlv6D#sU+>f!i#}!0OxX-J&fX_1T(v>Q6{keN0nTn)D zW2YK)JY+HcJX2xfLI=i8&JzT9I2P#JU3l^$@8XRkVV|xYFIQS>6{_7Uuk3rgl_yJ7Q9-cV#+R8)_cny2xz_GE;y`gZ|Cf@R9tkM=-pJa9gKnf zpF{euYb+OfbvNPC?LD*H<{YRLzvTAo%HoCF52{{&aNxnsX{R4p{ZM)D^~d4Q0lDi| z8<^jO%ZUgU1oHg1d7+ajzTM-sivn{;mut|<2&JI+>_<+W=#DSDxjQP_vw!*ND#i`} zj5k4b7CPZX5BX?kwzx^*{v zK2-HaZIRej(lt$ZkMzv5U3HAY2c8&OoUyJva+RU4z31++c^5aNPkX#;WqQL7$HG02 zR-zsf%S>Wf(b|EsLCW{Xd1HzaS_{owkQxAU2IYgqJTgkPLzcuW0ddy&@L zGctb?zW@6ro1lARvV6V5KZkpIALMRa+Ei`O_#oE8phEM%-wO?k)ZYu*AFRBjcrcf3 zTDnQj9*x3fQgToED?9V2-ZFNOI+4=p{@vqS+?Z|PhN7YgV!^!=Gk3jnEYdgs6>uz%@$k&7GOvq29!~gtb$Y$}!^OYuKb-jBu4&c; z{)HNe72hR`ME`g9m#@3MBlr2Ot+(Ct@9(nuWoc=rba$D>it-c%hsEk8sjoClCMNV> zfAKM$T~%6W>hk7Ef6G@0Y;L|3_UF@4wbzyfGA7fkyJU~LdpC3>T-($<{rar5B%7H6 zn)@}GuZSdkEs*xh{q^MOipCv}@6fjW94)ao-wSkY_}z5TU~?V& zty}6n_v-sDF1Ix0)?YTaCheDYfz`XM-s>06i%7lL?eN1UFjX~xVQFCM{0+Y<&TbCd zYy0zm+SS_!xa#DjGkDRB`y_k{7oohP1%k$Z-`}1CYkUajhcjcRX@8(qr zpJLzcIobIW&l|TZ4|aLHcg<*?QJsE%X{qN-t-xstyR{eFv8FULLQd8ye9oduRlLa%9E&D7TRV%d4EDY>#CV(-hjS0j%d+Ey{?`fgj_Ezj>(KAfN1 zZYN)IWtSDpo|5iU{~tdus#D@TRj~T>jf9u^E3--%%?n<<$yj+UnlJE2)U#N-_1m{> z*b*eQEBsQ$s?`hMy6iPiV*Gkv!_lzH^#9Gm1g8ft7Cc{elXG={^Nig8`zNpY*nZ(~ z`Wv5)H7?OiB8m>-OV?kMHDtLia(()(M!|?xYfZnf&5-+3!F)z#)iX2qimRxF9B zy&({@()dM#oQ99;*`;rCzf5yytg+O%cb*58`37}! zA12sEOc4FY_jq}r)RzS<6HHFtp1o$7@Y$y53(t9YnD#Tin9R0w_JaCzmRnWxbsV;R zIBIgR|K5^G;Zqn(JC;OVduDRIVouY<^KNFlj%=+G=Wxt-&E3W|`IM{M{XgByk0-5X zekZVfjq9qlHfzp{uMzrPf0|X7E%@h>aJJs4<)%}ZG&eTwJ~cT$xYC8$X-Uycmn7|X zx~Vgl)|d93j^+M;%Gl7QZ6#Sp7gwb{{qalXrdj{m{!P~%S+o8x4LDeTZ?$^!-Ny=lJi~m$>gI0W zzEQmALX_Q{gA?>tuG~||b8uHzRBLz2z4oI%BEf4;xL2$`_Q6c9*nH!jRWULZp-126 z=D&Y?Hq+ik>%*2eZ?c!r)rnp$9@%9b8KePtf>!D5b0Z{bz0G+_m=g*NcQ= z)EB(pw0>r!b-DNwRc@Eaof{Pnvn*QoH2MUd_(VXD@UXgoG>;eEfm+(!(qA6W`h&E}M1y{IuuKJ*2vK z@jQ`iKKtO(BF5h_iPgUrRyu@f=>~ay;>%Pz5H=xp^6P+E+N-=66sMg&`o3PZY4iS{ z2kx0V@O`V$Yt(*lYvIDJds*I}zwW=YTK^pL->$|pk5?{VB)y%%X@^19^d+9x(zain z;gj;5v;V1*Lf)K{H|Ni&(3mzS^udzt7KsZkzdR!EQ*ZFN{Z>N*JEO7Tn|j8R`MY{C zhb|03OEjRJUc5sWSl4<$#x0DDKr`wV3YJEe3YLZ@@No+h__&3op$YQ11$crTF>YaK zfn(gl(7*)mjJh%MjJl(~V@hUfQEG8!vAz>{e%%#3!|ti?tq+=I_tW>+4?vn}57Q6V zj|9!PBMoW5XWo+%ld~by9mJy9C z@H~A^YH_hXXtF*pGcT2>8T*2q(qjEW{le1J;*!k#JpIDb{F2m^q#VdZK4R!2wJbFc z+r)lG5w@X_GRR!Neii0`2zVux9ef$DF^CUM8^jNYm{=N{BTe~F4bO}aIezV3;f!}- zf-RoS4d;3uGD|s^;G-_sQQk{#+6sUK&2P>b&LqN^ciQo`o(-7dISu z(zEBuoh?UL9-i#mF7$D6`-kKs*&9BUZ@;hZaV+3q^}Jq(wxpDd4fR_hdo<@~F>UWz zn4qF6*2%T)-Mrmes|~DmRZYVarA0RuT$-X;R{z!F>V^cb7t<+8Q%BKo$&AuS&|}>w)B~X(4TFz+Y>+X3l=P`X-t$i(r|IGPP6UBBGIaYHRE;X=9pGD zwJ%?LeaXq-X*Ybg_LmowXqA-}mM+%F*6P``@X*ScS1hvZOy_kiPz#uN#U+bT)YW&< zqLnAFIAsYwb-o#BcqJxFOVs;k(6=i|r}~Q){LysRYA9O7q?w*2_tc3saNU*PQ=~1>dcp0Mc)Nf|6QkF=nW2dI~D~nXu-et!EcvKfZxf0;z$h){LxNQo%X_L{S9?kqL zl~kvhL4{MMRP{g7FxHy0V~UZ&G11Pp?nAEfT~+NB{E^mA4o;}sxV&QLk9CZ>4>Bg? zZMgTK;h?kUv?mLeOfFm2{8Inr@iWzDc$W3-JQTHA^-W}${g=%P4RU^Ma6Y__zh9!S zONCGwj#8Rm=W73iV#%nX_em($YV7lhyTXH@NivEIoOA zvj35q$L$d@{@EXG`MMM*?Cj{vu?i?K`2X5xO2;HGqp;`c;u_|YTh?g6~79%)A#ly|o+H zACpo$c;sIB1K!Uu_aA9~(w%qbv-Z3jJksaC7i{RyVAq@x7n=E$IpS5#Y1e?|qMx4y zoIa>%=hJl5#P3i;IFpBg^Wj3@w&eFIx8*vDSNFSuU^qOcjb?hT8~zFH?7tyneDxQf6LT6$rc{-dsm&$bkd3V z+Q5-^a_QO!ui2b8+%Z?ORq3o)b$If`dyR_{-aVH#-pDB@XLb2vYMr`7^T(}zyf@!- zEqRu|($zMqZcEsmWVYj(9BiB?6y!f`U_4RKAtS$h>)$Y$dhR`v65k7q_>LZooBYAz zXT3z(25G%X99hrJg?2A_UgBE(MknI{Bkvj6w!U3!U)eI%O#V*wgYN9;8|pqM zulYt_Td1+crgkH{_S(9G5+5Cmo*&<@%d-CJ57S7Gh1aHkNzcku{&)G{hE=cIc<(0J zb(lZ$y3Z1GSh?eTPa3=byh1BZx3p8Ctk=E1Jztp8edESdJMYP-zdb#9?cKkO@5isM z;oi&JpD~L+xnsJU?Y_@ZqV>m~HPl?Y*N{Kucfi)8KaG#idKaoGo-pEo~B6`nMhn`SA1UvhrOC(jOxj=cgYF z-|?8ut;$dE<#Q|jhfi`P&+mAhD|y{|;{I*d<+IjJ_vEgTNV=PlwWmt2EhARVe5#M; zf##N%E&<7Gg*B}yTD`enjoO8>nrZL*{s>|Ve*%)3vWt# z2QX;sZrCmT?e^Vcf2XfIy|2S=Mlt7G$JA*4-1Ltu91kw6>UF)jGgI=keTQpKz^9GJ zPG4=WKJ{c)?EO6Jm}CD=^2@5k?0M+T*EadS-(Lexo`r?V@40y|7#!I3|C+bTo|XfC z5s&+p2rE*nLS^yy+@e%3hV&+5?y0%1%2QJ4JM!O1mi@fIBge6O`4%P5=lDdu`QLERwT9`s_m9&q5^^8aTP5$-eDN_8_}ui6-*k_U z^0mlGm9bx3M1b?__lXO{40}71_9B7279oG~ITbbqB)<*{}>NKGT@#<#$#yb2dHH zsp)uXAi93aSp$aW@f#KSj_telB&}aIr=yIc<-qnQY5MXBEOrYx*F?EWIYq_`?ufnm zrSnsM_O|*qukWX3&1}kjy-h^%nz7!!=yz|#FD#0`bX5Ok8k_2AP1834-SaXRF8BB8 zI;YFW^>X!#JM(S{r~AKK>-;@J!g#Z6|Kx|>OoE@o*fuaPnmO;-(amYD`ekBDlG@P+ zTsX{vnEFZ%YX&jPee65JqwjXBTs!gNL ztXa3?KIv&sHLs`WXvefn3cPjhPCj>8{-0luli!$BwdYn_NGOZ$Du3|f<>cq(_gxdt zOghZq#me;vo4n4b}a_aQa!h;P}I~KkAnZKrVwfD}IZ_o1g+q)l#m{63(`>lfY z=U?7Gzs>hq7RG)%7uqD8z%MTTQz0et->Ke@Czz~zeskxt7gTu12pE{&R@Vyo*Iu!k zKkn(GX?lz99?@mqbHTM__JUh{vbO?a^j7QKn%Nt+q4|}sa82c(IZJoGKYr@L3whSZ z?2h|qW^HY#PX9L3`@W-wfabQ&cdPtLCVBt7q_E}7=^vYXxF@_>9;|IHd*@C#R~u{S z=6+$uWp+RRw(5Bs%)Q%_o3bPQfFl>P(xqzxvgIX@|NUXEOw|swc`Y_S#hWYo8v~=0 z$IcgWoKsF7aenu!OD}4Aror7`hQ5ayAAE~St%@`D=iZ;ew54g%g-^S81Z|jkr8RxB zO2O8-jqaA$UH6@tc6iTGq0IB=EtiM8L@Zje=SYv~sXsePA7+?!-I`rx^?EnIe~zX5 zs&~^4Z2Fp3_73r;_J&~59!O17L=+b{i#RBw%3FUWC>%SG8DNAm9q;Xegi zpYZxVdFs80;kl=X$+=@UCoz_;oIJIAUIu$smF-Wr{mnCz|N3IPIAC z*S{62_w{Fb9L_6>l2~xp{v2DsqW6#4-mlpwC|vS7>mhHur)KlZYn@LVB;Sc!wH%)v z>iN0Fy^iOz&8tT$`&;&Wh=@EkwdEAUllDhp`n+jNzw%2gzF*58eqD67BS+-jwqw(N z$=OVPuekbg<`T;`liUmr<+eXwhfDscp1HPPMkQy*%2%r@Rte30+i1Ku>0feEpx}nM zvooCJPUu@ITHLe0FPg~r@ZtTcxqRz7cT8Oblc@>7Iz1p>v<9c-@|w z-n@mzp7HD(mU!$c^p`sPcFy6Zv~wJpRm*l3ZjMgae$eFi(&!`aFCHzZTB@zTbR+w} z6Y{s+9t(Z5jOG4myFnmEF*jhz;c|Wj_tdDn(pR{bK1tH6Jskbf*)lW!_03bOs;4QG zg-$$_|L9TU(a&1J@ARj7-aq5x#q)i`k+l65jZ7l@zc5{&SW)Y?mS^(^(VrXM*6jJe z=f)4q;-vHUvRB#vcIcDlS(0yJu5@>KyQ|gC*4VeNb``cxDP@=&zw^bSZ}Y-xCQ5W4 zJ)@YTxpFCg<5HF*XKXkBec!a7q)Gd{64KMGoS69)5S{ev-z$DPu5C= z?R%P&bMI13Yh!_Oaz&D_zvX*9p8dkkR-6Kf2c*o4&CIzZpX@iQKbdAWGt;v0-AWlA zPLBR_jylJuKXCD6Up&3hTA$f`q2?EkJt`e7^;>7e)%;m0^{hnZjmh+N53W0#Z&!VK z_~_4-H<*_7S5^Q0Az3p2ujPZjxjw75eEw7(xyk(JwDy=Pw*EP@WgcgA@?PD1aL2xN zIrUT5EP8YMuIbe^A(f|{nUtiY4^DZtTD1HkFL%wVe(v0x8`$S2=*!vHZCq%jT9&!( zQnSD|)^(4Uq#shA{#0+vEa|$nPfu-}e16?Tr4JlOboX!T_>ufUdb$3G$J{SIvNe|* z9Se7~T&~~fax$lFdSdxrp52_j3)|<+DKUJy=$d;{gnLDzk{SOoM-FSnH#O4idk;N( zlX{CaCn?d4DV#;`>w|@i*<}jyC23lLzlB5d{ntzEN-zvRzF>3w+ss{eua!KI*>(C* zz<0Bvb#l*bxo@AIci~0*dH(mS?nKvaS|qsft@tCRN7qzb^(VHwD~C*(y*(>f&nr>f zU~k!+$-er3+anFWPmJge-Wi@yC*34qp;*AzwxBcNoZbS{8_VX2uoN9Trnm9NVOH+6 zNS>N!UfVudnn^?l3-E<3+h6Q_D)+7KZ|=}KTL)R)9d>3viwzWPK4sLhwruN2NbjDZ z5Pt5Vi^rG4ofa4QPb_6Uu~@+Kr|2%%r;DtNo-;{Z`@Vj9%s=COE_dG_xtssLzoTjS zhL`&|Rz2ivH$T(L+bpR4O6{VQrNr8%f{(cZA9HU^nKDgc-~07X7fe{w&K}Z~M zg(O>^xGTPj&wF<4SULI4^gm1&1!BKHI=Z0dg5{*AWtm4dbl*Sqa(h;Z;pdq=I;ZrH zE$+UNu&LjO@k+S)k@X#(|7R6H{%*;Xx*+dg$?lH%jyJw<&hxXMajVA!)=ErEliguAKEne`qBJ(r?Xz1KUZIva{b@m^g0UkNN%JP1B=;2*$tL@B9YBn7%U~&D+e`~+%eaw+t zq`|p>qWqN7IRRv`~6W@KS(fG%ceWCGgP2~r4GXJ}|)Zh@xPz`y`> z!~u#r0~1rwmO~UVLnBkrR%J9XGYs<#jZDpq(CjcYHUjNdM%8O#4mv^nwX)HsRg<}42{e{yZ=$mGcq&5P-kp_=@ugca}zW>3`|VTP0_{74b3dk#0-ti z%rL^G%#{AG%__cH8M9dH8F8EaI`daF>!XaBdj78baq2=Nn%k6 TICB_Tm>L*ysj9mAyKw;kZkIIK literal 0 HcmV?d00001