RAR (Roshall ARchiver) archive files: C# parsing library

RAR is a archive format used by popular proprietary RAR archiver, created by Eugene Roshal. There are two major versions of format (v1.5-4.0 and RAR v5+).

File format essentially consists of a linear sequence of blocks. Each block has fixed header and custom body (that depends on block type), so it's possible to skip block even if one doesn't know how to process a certain block type.

Application

RAR archiver

File extension

rar

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.7

References

This page hosts a formal specification of RAR (Roshall ARchiver) archive files using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C# generated by Kaitai Struct depends on the C# runtime library. You have to install it before you can parse data.

The C# runtime library is available in the NuGet Gallery. Installation instructions can also be found there.

Code

Parse a local file and get structure in memory:

var data = Rar.FromFile("path/to/local/file.rar");

Or parse structure from a byte array:

byte[] someArray = new byte[] { ... };
var data = new Rar(new KaitaiStream(someArray));

After that, one can get various attributes from the structure by accessing properties like:

data.Magic // => File format signature to validate that it is indeed a RAR archive

C# source code to parse RAR (Roshall ARchiver) archive files

Rar.cs

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

using System.Collections.Generic;

namespace Kaitai
{

    /// <summary>
    /// RAR is a archive format used by popular proprietary RAR archiver,
    /// created by Eugene Roshal. There are two major versions of format
    /// (v1.5-4.0 and RAR v5+).
    /// 
    /// File format essentially consists of a linear sequence of
    /// blocks. Each block has fixed header and custom body (that depends on
    /// block type), so it's possible to skip block even if one doesn't know
    /// how to process a certain block type.
    /// </summary>
    /// <remarks>
    /// Reference: <a href="http://acritum.com/winrar/rar-format">Source</a>
    /// </remarks>
    public partial class Rar : KaitaiStruct
    {
        public static Rar FromFile(string fileName)
        {
            return new Rar(new KaitaiStream(fileName));
        }


        public enum BlockTypes
        {
            Marker = 114,
            ArchiveHeader = 115,
            FileHeader = 116,
            OldStyleCommentHeader = 117,
            OldStyleAuthenticityInfo76 = 118,
            OldStyleSubblock = 119,
            OldStyleRecoveryRecord = 120,
            OldStyleAuthenticityInfo79 = 121,
            Subblock = 122,
            Terminator = 123,
        }

        public enum Oses
        {
            MsDos = 0,
            Os2 = 1,
            Windows = 2,
            Unix = 3,
            MacOs = 4,
            Beos = 5,
        }

        public enum Methods
        {
            Store = 48,
            Fastest = 49,
            Fast = 50,
            Normal = 51,
            Good = 52,
            Best = 53,
        }
        public Rar(KaitaiStream p__io, KaitaiStruct p__parent = null, Rar p__root = null) : base(p__io)
        {
            m_parent = p__parent;
            m_root = p__root ?? this;
            _read();
        }
        private void _read()
        {
            _magic = new MagicSignature(m_io, this, m_root);
            _blocks = new List<KaitaiStruct>();
            {
                var i = 0;
                while (!m_io.IsEof) {
                    switch (Magic.Version) {
                    case 0: {
                        _blocks.Add(new Block(m_io, this, m_root));
                        break;
                    }
                    case 1: {
                        _blocks.Add(new BlockV5(m_io, this, m_root));
                        break;
                    }
                    }
                    i++;
                }
            }
        }

        /// <summary>
        /// RAR uses either 7-byte magic for RAR versions 1.5 to 4.0, and
        /// 8-byte magic (and pretty different block format) for v5+. This
        /// type would parse and validate both versions of signature. Note
        /// that actually this signature is a valid RAR &quot;block&quot;: in theory,
        /// one can omit signature reading at all, and read this normally,
        /// as a block, if exact RAR version is known (and thus it's
        /// possible to choose correct block format).
        /// </summary>
        public partial class MagicSignature : KaitaiStruct
        {
            public static MagicSignature FromFile(string fileName)
            {
                return new MagicSignature(new KaitaiStream(fileName));
            }

            public MagicSignature(KaitaiStream p__io, Rar p__parent = null, Rar p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                _read();
            }
            private void _read()
            {
                _magic1 = m_io.ReadBytes(6);
                if (!((KaitaiStream.ByteArrayCompare(Magic1, new byte[] { 82, 97, 114, 33, 26, 7 }) == 0)))
                {
                    throw new ValidationNotEqualError(new byte[] { 82, 97, 114, 33, 26, 7 }, Magic1, M_Io, "/types/magic_signature/seq/0");
                }
                _version = m_io.ReadU1();
                if (Version == 1) {
                    _magic3 = m_io.ReadBytes(1);
                    if (!((KaitaiStream.ByteArrayCompare(Magic3, new byte[] { 0 }) == 0)))
                    {
                        throw new ValidationNotEqualError(new byte[] { 0 }, Magic3, M_Io, "/types/magic_signature/seq/2");
                    }
                }
            }
            private byte[] _magic1;
            private byte _version;
            private byte[] _magic3;
            private Rar m_root;
            private Rar m_parent;

            /// <summary>
            /// Fixed part of file's magic signature that doesn't change with RAR version
            /// </summary>
            public byte[] Magic1 { get { return _magic1; } }

            /// <summary>
            /// Variable part of magic signature: 0 means old (RAR 1.5-4.0)
            /// format, 1 means new (RAR 5+) format
            /// </summary>
            public byte Version { get { return _version; } }

            /// <summary>
            /// New format (RAR 5+) magic contains extra byte
            /// </summary>
            public byte[] Magic3 { get { return _magic3; } }
            public Rar M_Root { get { return m_root; } }
            public Rar M_Parent { get { return m_parent; } }
        }

        /// <summary>
        /// Basic block that RAR files consist of. There are several block
        /// types (see `block_type`), which have different `body` and
        /// `add_body`.
        /// </summary>
        public partial class Block : KaitaiStruct
        {
            public static Block FromFile(string fileName)
            {
                return new Block(new KaitaiStream(fileName));
            }

            public Block(KaitaiStream p__io, Rar p__parent = null, Rar p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                f_hasAdd = false;
                f_headerSize = false;
                f_bodySize = false;
                _read();
            }
            private void _read()
            {
                _crc16 = m_io.ReadU2le();
                _blockType = ((Rar.BlockTypes) m_io.ReadU1());
                _flags = m_io.ReadU2le();
                _blockSize = m_io.ReadU2le();
                if (HasAdd) {
                    _addSize = m_io.ReadU4le();
                }
                switch (BlockType) {
                case Rar.BlockTypes.FileHeader: {
                    __raw_body = m_io.ReadBytes(BodySize);
                    var io___raw_body = new KaitaiStream(__raw_body);
                    _body = new BlockFileHeader(io___raw_body, this, m_root);
                    break;
                }
                default: {
                    _body = m_io.ReadBytes(BodySize);
                    break;
                }
                }
                if (HasAdd) {
                    _addBody = m_io.ReadBytes(AddSize);
                }
            }
            private bool f_hasAdd;
            private bool _hasAdd;

            /// <summary>
            /// True if block has additional content attached to it
            /// </summary>
            public bool HasAdd
            {
                get
                {
                    if (f_hasAdd)
                        return _hasAdd;
                    _hasAdd = (bool) ((Flags & 32768) != 0);
                    f_hasAdd = true;
                    return _hasAdd;
                }
            }
            private bool f_headerSize;
            private sbyte _headerSize;
            public sbyte HeaderSize
            {
                get
                {
                    if (f_headerSize)
                        return _headerSize;
                    _headerSize = (sbyte) ((HasAdd ? 11 : 7));
                    f_headerSize = true;
                    return _headerSize;
                }
            }
            private bool f_bodySize;
            private int _bodySize;
            public int BodySize
            {
                get
                {
                    if (f_bodySize)
                        return _bodySize;
                    _bodySize = (int) ((BlockSize - HeaderSize));
                    f_bodySize = true;
                    return _bodySize;
                }
            }
            private ushort _crc16;
            private BlockTypes _blockType;
            private ushort _flags;
            private ushort _blockSize;
            private uint? _addSize;
            private object _body;
            private byte[] _addBody;
            private Rar m_root;
            private Rar m_parent;
            private byte[] __raw_body;

            /// <summary>
            /// CRC16 of whole block or some part of it (depends on block type)
            /// </summary>
            public ushort Crc16 { get { return _crc16; } }
            public BlockTypes BlockType { get { return _blockType; } }
            public ushort Flags { get { return _flags; } }

            /// <summary>
            /// Size of block (header + body, but without additional content)
            /// </summary>
            public ushort BlockSize { get { return _blockSize; } }

            /// <summary>
            /// Size of additional content in this block
            /// </summary>
            public uint? AddSize { get { return _addSize; } }
            public object Body { get { return _body; } }

            /// <summary>
            /// Additional content in this block
            /// </summary>
            public byte[] AddBody { get { return _addBody; } }
            public Rar M_Root { get { return m_root; } }
            public Rar M_Parent { get { return m_parent; } }
            public byte[] M_RawBody { get { return __raw_body; } }
        }
        public partial class BlockFileHeader : KaitaiStruct
        {
            public static BlockFileHeader FromFile(string fileName)
            {
                return new BlockFileHeader(new KaitaiStream(fileName));
            }

            public BlockFileHeader(KaitaiStream p__io, Rar.Block p__parent = null, Rar p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                _read();
            }
            private void _read()
            {
                _lowUnpSize = m_io.ReadU4le();
                _hostOs = ((Rar.Oses) m_io.ReadU1());
                _fileCrc32 = m_io.ReadU4le();
                __raw_fileTime = m_io.ReadBytes(4);
                var io___raw_fileTime = new KaitaiStream(__raw_fileTime);
                _fileTime = new DosDatetime(io___raw_fileTime);
                _rarVersion = m_io.ReadU1();
                _method = ((Rar.Methods) m_io.ReadU1());
                _nameSize = m_io.ReadU2le();
                _attr = m_io.ReadU4le();
                if ((M_Parent.Flags & 256) != 0) {
                    _highPackSize = m_io.ReadU4le();
                }
                _fileName = m_io.ReadBytes(NameSize);
                if ((M_Parent.Flags & 1024) != 0) {
                    _salt = m_io.ReadU8le();
                }
            }
            private uint _lowUnpSize;
            private Oses _hostOs;
            private uint _fileCrc32;
            private DosDatetime _fileTime;
            private byte _rarVersion;
            private Methods _method;
            private ushort _nameSize;
            private uint _attr;
            private uint? _highPackSize;
            private byte[] _fileName;
            private ulong? _salt;
            private Rar m_root;
            private Rar.Block m_parent;
            private byte[] __raw_fileTime;

            /// <summary>
            /// Uncompressed file size (lower 32 bits, if 64-bit header flag is present)
            /// </summary>
            public uint LowUnpSize { get { return _lowUnpSize; } }

            /// <summary>
            /// Operating system used for archiving
            /// </summary>
            public Oses HostOs { get { return _hostOs; } }
            public uint FileCrc32 { get { return _fileCrc32; } }

            /// <summary>
            /// Date and time in standard MS DOS format
            /// </summary>
            public DosDatetime FileTime { get { return _fileTime; } }

            /// <summary>
            /// RAR version needed to extract file (Version number is encoded as 10 * Major version + minor version.)
            /// </summary>
            public byte RarVersion { get { return _rarVersion; } }

            /// <summary>
            /// Compression method
            /// </summary>
            public Methods Method { get { return _method; } }

            /// <summary>
            /// File name size
            /// </summary>
            public ushort NameSize { get { return _nameSize; } }

            /// <summary>
            /// File attributes
            /// </summary>
            public uint Attr { get { return _attr; } }

            /// <summary>
            /// Compressed file size, high 32 bits, only if 64-bit header flag is present
            /// </summary>
            public uint? HighPackSize { get { return _highPackSize; } }
            public byte[] FileName { get { return _fileName; } }
            public ulong? Salt { get { return _salt; } }
            public Rar M_Root { get { return m_root; } }
            public Rar.Block M_Parent { get { return m_parent; } }
            public byte[] M_RawFileTime { get { return __raw_fileTime; } }
        }
        public partial class BlockV5 : KaitaiStruct
        {
            public static BlockV5 FromFile(string fileName)
            {
                return new BlockV5(new KaitaiStream(fileName));
            }

            public BlockV5(KaitaiStream p__io, Rar p__parent = null, Rar p__root = null) : base(p__io)
            {
                m_parent = p__parent;
                m_root = p__root;
                _read();
            }
            private void _read()
            {
            }
            private Rar m_root;
            private Rar m_parent;
            public Rar M_Root { get { return m_root; } }
            public Rar M_Parent { get { return m_parent; } }
        }
        private MagicSignature _magic;
        private List<KaitaiStruct> _blocks;
        private Rar m_root;
        private KaitaiStruct m_parent;

        /// <summary>
        /// File format signature to validate that it is indeed a RAR archive
        /// </summary>
        public MagicSignature Magic { get { return _magic; } }

        /// <summary>
        /// Sequence of blocks that constitute the RAR file
        /// </summary>
        public List<KaitaiStruct> Blocks { get { return _blocks; } }
        public Rar M_Root { get { return m_root; } }
        public KaitaiStruct M_Parent { get { return m_parent; } }
    }
}