2017-05-19 20:28:49 +01:00
|
|
|
// /***************************************************************************
|
2020-02-27 12:31:25 +00:00
|
|
|
// Aaru Data Preservation Suite
|
2016-08-26 01:43:15 +01:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
//
|
|
|
|
|
// Filename : Structs.cs
|
|
|
|
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
|
|
|
|
//
|
2016-08-26 01:45:58 +01:00
|
|
|
// Component : CP/M filesystem plugin.
|
2016-08-26 01:43:15 +01:00
|
|
|
//
|
|
|
|
|
// --[ Description ] ----------------------------------------------------------
|
|
|
|
|
//
|
2016-08-26 01:45:58 +01:00
|
|
|
// CP/M filesystem structures.
|
2016-08-26 01:43:15 +01:00
|
|
|
//
|
|
|
|
|
// --[ 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
//
|
|
|
|
|
// ----------------------------------------------------------------------------
|
2022-12-03 16:07:10 +00:00
|
|
|
// Copyright © 2011-2023 Natalia Portillo
|
2016-08-26 01:43:15 +01:00
|
|
|
// ****************************************************************************/
|
2016-08-26 01:45:58 +01:00
|
|
|
|
2020-07-20 07:47:12 +01:00
|
|
|
// ReSharper disable NotAccessedField.Local
|
|
|
|
|
|
2022-03-07 07:36:44 +00:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
2022-11-15 15:58:43 +00:00
|
|
|
namespace Aaru.Filesystems;
|
|
|
|
|
|
2022-03-06 13:29:38 +00:00
|
|
|
public sealed partial class CPM
|
2016-08-26 01:43:15 +01:00
|
|
|
{
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>Most of the times this structure is hard wired or generated by CP/M, not stored on disk</summary>
|
|
|
|
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
2022-11-15 01:35:06 +00:00
|
|
|
sealed class DiscParameterBlock
|
2016-08-26 01:43:15 +01:00
|
|
|
{
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>First byte of allocation bitmap</summary>
|
|
|
|
|
public byte al0;
|
|
|
|
|
/// <summary>Second byte of allocation bitmap</summary>
|
|
|
|
|
public byte al1;
|
|
|
|
|
/// <summary>Block mask</summary>
|
|
|
|
|
public byte blm;
|
|
|
|
|
/// <summary>Block shift</summary>
|
|
|
|
|
public byte bsh;
|
|
|
|
|
/// <summary>Checksum vector size</summary>
|
|
|
|
|
public ushort cks;
|
|
|
|
|
/// <summary>Directory entries - 1</summary>
|
|
|
|
|
public ushort drm;
|
|
|
|
|
/// <summary>Blocks on disk - 1</summary>
|
|
|
|
|
public ushort dsm;
|
|
|
|
|
/// <summary>Extent mask</summary>
|
|
|
|
|
public byte exm;
|
|
|
|
|
/// <summary>Reserved tracks</summary>
|
|
|
|
|
public ushort off;
|
|
|
|
|
/// <summary>Physical sector mask</summary>
|
|
|
|
|
public byte phm;
|
|
|
|
|
/// <summary>Physical sector shift</summary>
|
|
|
|
|
public byte psh;
|
|
|
|
|
/// <summary>Sectors per track</summary>
|
|
|
|
|
public ushort spt;
|
|
|
|
|
}
|
2016-08-26 01:45:58 +01:00
|
|
|
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>Amstrad superblock, for PCW</summary>
|
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
|
|
|
readonly struct AmstradSuperBlock
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Format ID. 0 single-side, 3 double-side. 1 and 2 are for CPC but they don't use the superblock</summary>
|
|
|
|
|
public readonly byte format;
|
|
|
|
|
/// <summary>Gives information about side ordering</summary>
|
|
|
|
|
public readonly byte sidedness;
|
|
|
|
|
/// <summary>Tracks per side, aka, cylinders</summary>
|
|
|
|
|
public readonly byte tps;
|
|
|
|
|
/// <summary>Sectors per track</summary>
|
|
|
|
|
public readonly byte spt;
|
|
|
|
|
/// <summary>Physical sector shift</summary>
|
|
|
|
|
public readonly byte psh;
|
|
|
|
|
/// <summary>Reserved tracks</summary>
|
|
|
|
|
public readonly byte off;
|
|
|
|
|
/// <summary>Block size shift</summary>
|
|
|
|
|
public readonly byte bsh;
|
|
|
|
|
/// <summary>How many blocks does the directory take</summary>
|
|
|
|
|
public readonly byte dirBlocks;
|
|
|
|
|
/// <summary>GAP#3 length (intersector)</summary>
|
|
|
|
|
public readonly byte gapLen;
|
|
|
|
|
/// <summary>GAP#4 length (end-of-track)</summary>
|
|
|
|
|
public readonly byte formatGap;
|
|
|
|
|
/// <summary>Must be 0</summary>
|
|
|
|
|
public readonly byte zero1;
|
|
|
|
|
/// <summary>Must be 0</summary>
|
|
|
|
|
public readonly byte zero2;
|
|
|
|
|
/// <summary>Must be 0</summary>
|
|
|
|
|
public readonly byte zero3;
|
|
|
|
|
/// <summary>Must be 0</summary>
|
|
|
|
|
public readonly byte zero4;
|
|
|
|
|
/// <summary>Must be 0</summary>
|
|
|
|
|
public readonly byte zero5;
|
|
|
|
|
/// <summary>Indicates machine the boot code following the superblock is designed to boot</summary>
|
|
|
|
|
public readonly byte fiddle;
|
|
|
|
|
}
|
2016-08-26 01:45:58 +01:00
|
|
|
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>Superblock found on CP/M-86 hard disk volumes</summary>
|
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
|
|
|
readonly struct HardDiskSuperBlock
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Value so the sum of all the superblock's sector bytes taken as 16-bit values gives 0</summary>
|
|
|
|
|
public readonly ushort checksum;
|
|
|
|
|
/// <summary>Copyright string</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x1F)]
|
|
|
|
|
public readonly byte[] copyright;
|
|
|
|
|
/// <summary>First cylinder of disk where this volume resides</summary>
|
|
|
|
|
public readonly ushort firstCylinder;
|
|
|
|
|
/// <summary>How many cylinders does this volume span</summary>
|
|
|
|
|
public readonly ushort cylinders;
|
|
|
|
|
/// <summary>Heads on hard disk</summary>
|
|
|
|
|
public readonly byte heads;
|
|
|
|
|
/// <summary>Sectors per track</summary>
|
|
|
|
|
public readonly byte sectorsPerTrack;
|
|
|
|
|
/// <summary>Flags, only use by CCP/M where bit 0 equals verify on write</summary>
|
|
|
|
|
public readonly byte flags;
|
|
|
|
|
/// <summary>Sector size / 128</summary>
|
|
|
|
|
public readonly byte recordsPerSector;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.spt" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly ushort spt;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.bsh" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly byte bsh;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.blm" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly byte blm;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.exm" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly byte exm;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.dsm" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly ushort dsm;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.drm" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly ushort drm;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.al0" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly ushort al0;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.al1" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly ushort al1;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.cks" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly ushort cks;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="DiscParameterBlock.off" />
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly ushort off;
|
|
|
|
|
/// <summary>Must be zero</summary>
|
|
|
|
|
public readonly ushort zero1;
|
|
|
|
|
/// <summary>Must be zero</summary>
|
|
|
|
|
public readonly ushort zero2;
|
|
|
|
|
/// <summary>Must be zero</summary>
|
|
|
|
|
public readonly ushort zero3;
|
|
|
|
|
/// <summary>Must be zero</summary>
|
|
|
|
|
public readonly ushort zero4;
|
|
|
|
|
/// <summary>How many 128 bytes are in a block</summary>
|
|
|
|
|
public readonly ushort recordsPerBlock;
|
|
|
|
|
/// <summary>Maximum number of bad blocks in the bad block list</summary>
|
|
|
|
|
public readonly ushort badBlockWordsMax;
|
|
|
|
|
/// <summary>Used number of bad blocks in the bad block list</summary>
|
|
|
|
|
public readonly ushort badBlockWords;
|
|
|
|
|
/// <summary>First block after the blocks reserved for bad block substitution</summary>
|
|
|
|
|
public readonly ushort firstSub;
|
|
|
|
|
}
|
2016-08-26 01:45:58 +01:00
|
|
|
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>Volume label entry</summary>
|
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
|
|
|
readonly struct LabelEntry
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Must be 0x20</summary>
|
|
|
|
|
public readonly byte signature;
|
|
|
|
|
/// <summary>Label in ASCII</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
|
|
|
|
|
public readonly byte[] label;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Label flags. Bit 0 = label exists, bit 4 = creation timestamp, bit 5 = modification timestamp, bit 6 = access
|
|
|
|
|
/// timestamp, bit 7 = password enabled
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly byte flags;
|
|
|
|
|
/// <summary>Password decoder byte</summary>
|
|
|
|
|
public readonly byte passwordDecoder;
|
|
|
|
|
/// <summary>Must be 0</summary>
|
|
|
|
|
public readonly ushort reserved;
|
|
|
|
|
/// <summary>Password XOR'ed with <see cref="passwordDecoder" /></summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
|
|
|
public readonly byte[] password;
|
|
|
|
|
/// <summary>Label creation time</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] ctime;
|
|
|
|
|
/// <summary>Label modification time</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] mtime;
|
|
|
|
|
}
|
2016-08-26 01:45:58 +01:00
|
|
|
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>CP/M 3 timestamp entry</summary>
|
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
|
|
|
readonly struct DateEntry
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Must be 0x21</summary>
|
|
|
|
|
public readonly byte signature;
|
|
|
|
|
/// <summary>File 1 create/access timestamp</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] date1;
|
|
|
|
|
/// <summary>File 1 modification timestamp</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] date2;
|
|
|
|
|
/// <summary>File 1 password mode</summary>
|
|
|
|
|
public readonly byte mode1;
|
|
|
|
|
public readonly byte zero1;
|
|
|
|
|
/// <summary>File 2 create/access timestamp</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] date3;
|
|
|
|
|
/// <summary>File 2 modification timestamp</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] date4;
|
|
|
|
|
/// <summary>File 2 password mode</summary>
|
|
|
|
|
public readonly byte mode2;
|
|
|
|
|
public readonly byte zero2;
|
|
|
|
|
/// <summary>File 3 create/access timestamp</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] date5;
|
|
|
|
|
/// <summary>File 3 modification timestamp</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] date6;
|
|
|
|
|
/// <summary>File 3 password mode</summary>
|
|
|
|
|
public readonly byte mode3;
|
|
|
|
|
public readonly ushort zero3;
|
|
|
|
|
}
|
2016-08-26 01:45:58 +01:00
|
|
|
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>Password entry</summary>
|
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
|
|
|
readonly struct PasswordEntry
|
|
|
|
|
{
|
|
|
|
|
/// <summary>16 + user number</summary>
|
|
|
|
|
public readonly byte userNumber;
|
|
|
|
|
/// <summary>Filename</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
|
|
|
public readonly byte[] filename;
|
|
|
|
|
/// <summary>Extension</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
|
|
|
|
public readonly byte[] extension;
|
|
|
|
|
/// <summary>Password mode. Bit 7 = required for read, bit 6 = required for write, bit 5 = required for delete</summary>
|
|
|
|
|
public readonly byte mode;
|
|
|
|
|
/// <summary>Password decoder byte</summary>
|
|
|
|
|
public readonly byte passwordDecoder;
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
|
|
|
|
public readonly byte[] reserved;
|
|
|
|
|
/// <summary>Password XOR'ed with <see cref="passwordDecoder" /></summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
|
|
|
public readonly byte[] password;
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
|
|
|
public readonly byte[] reserved2;
|
|
|
|
|
}
|
2016-08-26 01:45:58 +01:00
|
|
|
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>Timestamp for Z80DOS or DOS+</summary>
|
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
|
|
|
readonly struct TrdPartyDateEntry
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Must be 0x21</summary>
|
|
|
|
|
public readonly byte signature;
|
|
|
|
|
public readonly byte zero;
|
|
|
|
|
/// <summary>Creation year for file 1</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
|
|
|
|
public readonly byte[] create1;
|
|
|
|
|
/// <summary>Modification time for file 1</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] modify1;
|
|
|
|
|
/// <summary>Access time for file 1</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] access1;
|
|
|
|
|
/// <summary>Creation year for file 2</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
|
|
|
|
public readonly byte[] create2;
|
|
|
|
|
/// <summary>Modification time for file 2</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] modify2;
|
|
|
|
|
/// <summary>Access time for file 2</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] access2;
|
|
|
|
|
/// <summary>Creation year for file 3</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
|
|
|
|
public readonly byte[] create3;
|
|
|
|
|
/// <summary>Modification time for file 3</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] modify3;
|
|
|
|
|
/// <summary>Access time for file 3</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
|
|
|
|
|
public readonly byte[] access3;
|
|
|
|
|
}
|
2016-08-26 01:45:58 +01:00
|
|
|
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>Directory entry for <256 allocation blocks</summary>
|
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
|
|
|
readonly struct DirectoryEntry
|
|
|
|
|
{
|
|
|
|
|
/// <summary>User number. Bit 7 set in CP/M 1 means hidden</summary>
|
|
|
|
|
public readonly byte statusUser;
|
|
|
|
|
/// <summary>Filename and bit 7 as flags</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
|
|
|
public readonly byte[] filename;
|
|
|
|
|
/// <summary>Extension and bit 7 as flags</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
|
|
|
|
public readonly byte[] extension;
|
|
|
|
|
/// <summary>Low byte of extent number</summary>
|
|
|
|
|
public readonly byte extentCounter;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Last record bytes. In some implementations it means how many bytes are used in the last record, in others how
|
|
|
|
|
/// many bytes are free. It always refer to 128 byte records even if blocks are way bigger, so it's mostly useless.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly byte lastRecordBytes;
|
|
|
|
|
/// <summary>High byte of extent number</summary>
|
|
|
|
|
public readonly byte extentCounterHigh;
|
|
|
|
|
/// <summary>How many records are used in this entry. 0x80 if all are used.</summary>
|
|
|
|
|
public readonly byte records;
|
|
|
|
|
/// <summary>Allocation blocks</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
|
|
|
|
|
public readonly byte[] allocations;
|
|
|
|
|
}
|
2016-08-26 01:45:58 +01:00
|
|
|
|
2022-03-06 13:29:38 +00:00
|
|
|
/// <summary>Directory entry for >256 allocation blocks</summary>
|
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
|
|
|
readonly struct DirectoryEntry16
|
|
|
|
|
{
|
|
|
|
|
/// <summary>User number. Bit 7 set in CP/M 1 means hidden</summary>
|
|
|
|
|
public readonly byte statusUser;
|
|
|
|
|
/// <summary>Filename and bit 7 as flags</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
|
|
|
public readonly byte[] filename;
|
|
|
|
|
/// <summary>Extension and bit 7 as flags</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
|
|
|
|
public readonly byte[] extension;
|
|
|
|
|
/// <summary>Low byte of extent number</summary>
|
|
|
|
|
public readonly byte extentCounter;
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Last record bytes. In some implementations it means how many bytes are used in the last record, in others how
|
|
|
|
|
/// many bytes are free. It always refer to 128 byte records even if blocks are way bigger, so it's mostly useless.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public readonly byte lastRecordBytes;
|
|
|
|
|
/// <summary>High byte of extent number</summary>
|
|
|
|
|
public readonly byte extentCounterHigh;
|
|
|
|
|
/// <summary>How many records are used in this entry. 0x80 if all are used.</summary>
|
|
|
|
|
public readonly byte records;
|
|
|
|
|
/// <summary>Allocation blocks</summary>
|
|
|
|
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
|
|
|
|
public readonly ushort[] allocations;
|
2016-08-26 01:43:15 +01:00
|
|
|
}
|
2017-12-19 20:33:03 +00:00
|
|
|
}
|