Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/SharpCompress/Common/Arc/ArcEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common.GZip;
using SharpCompress.Common.Tar;

namespace SharpCompress.Common.Arc
{
public class ArcEntry : Entry
{
private readonly ArcFilePart? _filePart;

internal ArcEntry(ArcFilePart? filePart)
{
_filePart = filePart;
}

public override long Crc
{
get
{
if (_filePart == null)
{
return 0;
}
return _filePart.Header.Crc16;
}
}

public override string? Key => _filePart?.Header.Name;

public override string? LinkTarget => null;

public override long CompressedSize => _filePart?.Header.CompressedSize ?? 0;

public override CompressionType CompressionType =>
_filePart?.Header.CompressionMethod ?? CompressionType.Unknown;

public override long Size => throw new NotImplementedException();

public override DateTime? LastModifiedTime => null;

public override DateTime? CreatedTime => null;

public override DateTime? LastAccessedTime => null;

public override DateTime? ArchivedTime => null;

public override bool IsEncrypted => false;

public override bool IsDirectory => false;

public override bool IsSplitAfter => false;

internal override IEnumerable<FilePart> Parts => _filePart.Empty();
}
}
83 changes: 83 additions & 0 deletions src/SharpCompress/Common/Arc/ArcEntryHeader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.IO;
using System.Linq;
using System.Text;

namespace SharpCompress.Common.Arc
{
public class ArcEntryHeader
{
public ArchiveEncoding ArchiveEncoding { get; }
public CompressionType CompressionMethod { get; private set; }
public string? Name { get; private set; }
public long CompressedSize { get; private set; }
public DateTime DateTime { get; private set; }
public int Crc16 { get; private set; }
public long OriginalSize { get; private set; }
public long DataStartPosition { get; private set; }

public ArcEntryHeader(ArchiveEncoding archiveEncoding)
{
this.ArchiveEncoding = archiveEncoding;
}

public ArcEntryHeader? ReadHeader(Stream stream)
{
byte[] headerBytes = new byte[29];
if (stream.Read(headerBytes, 0, headerBytes.Length) != headerBytes.Length)
{
return null;
}
return LoadFrom(headerBytes);
}

public ArcEntryHeader LoadFrom(byte[] headerBytes)
{
CompressionMethod = GetCompressionType(headerBytes[1]);

// Read name
int nameEnd = Array.IndexOf(headerBytes, (byte)0, 1); // Find null terminator
Name = Encoding.UTF8.GetString(headerBytes, 2, nameEnd > 0 ? nameEnd - 2 : 12);

int offset = 15;
CompressedSize = BitConverter.ToUInt32(headerBytes, offset);
offset += 4;
uint rawDateTime = BitConverter.ToUInt32(headerBytes, offset);
DateTime = ConvertToDateTime(rawDateTime);
offset += 4;
Crc16 = BitConverter.ToUInt16(headerBytes, offset);
offset += 2;
OriginalSize = BitConverter.ToUInt32(headerBytes, offset);
return this;
}

private CompressionType GetCompressionType(byte value)
{
return value switch
{
1 or 2 => CompressionType.None,
//3 => CompressionType.RLE90,
//4 => CompressionType.Squeezed,
//5 or 6 or 7 or 8 => CompressionType.Crunched,
//9 => CompressionType.Squashed,
//10 => CompressionType.Crushed,
//11 => CompressionType.Distilled,
_ => CompressionType.Unknown,
};
}

public static DateTime ConvertToDateTime(long rawDateTime)
{
// Extract components using bit manipulation
int year = (int)((rawDateTime >> 25) & 0x7F) + 1980;
int month = (int)((rawDateTime >> 21) & 0xF);
int day = (int)((rawDateTime >> 16) & 0x1F);
int hour = (int)((rawDateTime >> 11) & 0x1F);
int minute = (int)((rawDateTime >> 5) & 0x3F);
int second = (int)((rawDateTime & 0x1F) * 2); // Multiply by 2 since DOS seconds are stored as halves

// Return as a DateTime object
return new DateTime(year, month, day, hour, minute, second);
}
}
}
41 changes: 41 additions & 0 deletions src/SharpCompress/Common/Arc/ArcFilePart.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common.GZip;
using SharpCompress.Common.Tar;
using SharpCompress.Common.Tar.Headers;
using SharpCompress.Common.Zip.Headers;
using SharpCompress.IO;

namespace SharpCompress.Common.Arc
{
public class ArcFilePart : FilePart
{
private readonly Stream? _stream;

internal ArcFilePart(ArcEntryHeader localArcHeader, Stream? seekableStream)
: base(localArcHeader.ArchiveEncoding)
{
_stream = seekableStream;
Header = localArcHeader;
}

internal ArcEntryHeader Header { get; set; }

internal override string? FilePartName => Header.Name;

internal override Stream GetCompressedStream()
{
if (_stream != null)
{
return new ReadOnlySubStream(_stream, Header.CompressedSize);
}
return _stream.NotNull();
}

internal override Stream? GetRawStream() => _stream;
}
}
16 changes: 16 additions & 0 deletions src/SharpCompress/Common/Arc/ArcVolume.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Readers;

namespace SharpCompress.Common.Arc
{
public class ArcVolume : Volume
{
public ArcVolume(Stream stream, ReaderOptions readerOptions, int index = 0)
: base(stream, readerOptions, index) { }
}
}
1 change: 1 addition & 0 deletions src/SharpCompress/Common/ArchiveType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ public enum ArchiveType
Tar,
SevenZip,
GZip,
Arc,
}
42 changes: 42 additions & 0 deletions src/SharpCompress/Factories/ArcFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Readers;
using SharpCompress.Readers.Arc;
using static System.Net.Mime.MediaTypeNames;

namespace SharpCompress.Factories
{
public class ArcFactory : Factory, IReaderFactory
{
public override string Name => "Arc";

public override ArchiveType? KnownArchiveType => ArchiveType.Arc;

public override IEnumerable<string> GetSupportedExtensions()
{
yield return "arc";
}

public override bool IsArchive(Stream stream, string? password = null)
{
//You may have to use some(paranoid) checks to ensure that you actually are
//processing an ARC file, since other archivers also adopted the idea of putting
//a 01Ah byte at offset 0, namely the Hyper archiver. To check if you have a
//Hyper - archive, check the next two bytes for "HP" or "ST"(or look below for
//"HYP").Also the ZOO archiver also does put a 01Ah at the start of the file,
//see the ZOO entry below.
var bytes = new byte[2];
stream.Read(bytes, 0, 2);
return bytes[0] == 0x1A && bytes[1] < 10; //rather thin, but this is all we have
}

public IReader OpenReader(Stream stream, ReaderOptions? options) =>
ArcReader.Open(stream, options);
}
}
1 change: 1 addition & 0 deletions src/SharpCompress/Factories/Factory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ static Factory()
RegisterFactory(new SevenZipFactory());
RegisterFactory(new GZipFactory());
RegisterFactory(new TarFactory());
RegisterFactory(new ArcFactory());
}

private static readonly HashSet<Factory> _factories = new();
Expand Down
41 changes: 41 additions & 0 deletions src/SharpCompress/Readers/Arc/ArcReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.Arc;

namespace SharpCompress.Readers.Arc
{
public class ArcReader : AbstractReader<ArcEntry, ArcVolume>
{
private ArcReader(Stream stream, ReaderOptions options)
: base(options, ArchiveType.Arc) => Volume = new ArcVolume(stream, options, 0);

public override ArcVolume Volume { get; }

/// <summary>
/// Opens an ArcReader for Non-seeking usage with a single volume
/// </summary>
/// <param name="stream"></param>
/// <param name="options"></param>
/// <returns></returns>
public static ArcReader Open(Stream stream, ReaderOptions? options = null)
{
stream.CheckNotNull(nameof(stream));
return new ArcReader(stream, options ?? new ReaderOptions());
}

protected override IEnumerable<ArcEntry> GetEntries(Stream stream)
{
ArcEntryHeader headerReader = new ArcEntryHeader(new ArchiveEncoding());
ArcEntryHeader? header;
while ((header = headerReader.ReadHeader(stream)) != null)
{
yield return new ArcEntry(new ArcFilePart(header, stream));
}
}
}
}
2 changes: 1 addition & 1 deletion src/SharpCompress/Readers/ReaderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static IReader Open(Stream stream, ReaderOptions? options = null)
}

throw new InvalidOperationException(
"Cannot determine compressed stream type. Supported Reader Formats: Zip, GZip, BZip2, Tar, Rar, LZip, XZ"
"Cannot determine compressed stream type. Supported Reader Formats: Arc, Zip, GZip, BZip2, Tar, Rar, LZip, XZ"
);
}
}
22 changes: 22 additions & 0 deletions tests/SharpCompress.Test/Arc/ArcReaderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Common;
using Xunit;

namespace SharpCompress.Test.Arc
{
public class ArcReaderTests : ReaderTests
{
public ArcReaderTests()
{
UseExtensionInsteadOfNameToVerify = true;
UseCaseInsensitiveToVerify = true;
}

[Fact]
public void Arc_Uncompressed_Read() => Read("Arc.uncompressed.arc", CompressionType.None);
}
}
Binary file added tests/TestArchives/Archives/Arc.uncompressed.arc
Binary file not shown.