Classes/BlockClasses.ps1

# ReScenePS Block Classes
# Classes for parsing SRR and RAR block structures

using namespace System.IO
using namespace System.Text

#region Block Type Enumeration

enum BlockType {
    # SRR block types (0x69-0x71)
    SrrHeader = 0x69        # i -> SRR file header
    SrrStoredFile = 0x6A    # j -> Stored files (NFO, SFV, etc.)
    SrrOsoHash = 0x6B       # k -> ISDb/OSO hash
    SrrRarPadding = 0x6C    # l -> Padding after RAR end
    SrrRarFile = 0x71       # q -> RAR volume metadata marker

    # RAR block types (0x72-0x7B)
    RarMarker = 0x72        # r -> RAR marker (always first)
    RarVolumeHeader = 0x73  # s -> Archive header
    RarPackedFile = 0x74    # t -> File header (most important!)
    RarOldComment = 0x75
    RarOldAuthenticity76 = 0x76
    RarOldSubblock = 0x77
    RarOldRecovery = 0x78   # x -> Old-style recovery record
    RarOldAuthenticity79 = 0x79
    RarNewSub = 0x7A        # z -> New-style subblock (RR, CMT, AV)
    RarArchiveEnd = 0x7B    # { -> Archive end (optional)
}

# Block type name mapping for display
$script:BlockTypeNames = @{
    0x69 = "SRR Volume Header"
    0x6A = "SRR Stored File"
    0x6B = "SRR ISDb Hash"
    0x6C = "SRR Padding"
    0x71 = "SRR RAR subblock"
    0x72 = "RAR Marker"
    0x73 = "RAR Archive Header"
    0x74 = "RAR File"
    0x75 = "RAR Old style - Comment"
    0x76 = "RAR Old style - Extra info"
    0x77 = "RAR Old style - Subblock"
    0x78 = "RAR Old style - Recovery record"
    0x79 = "RAR Old style - Archive authenticity"
    0x7A = "RAR New-format subblock"
    0x7B = "RAR Archive end"
}

#endregion

#region SRR Block Classes

class SrrBlock {
    # Common header (7 bytes minimum)
    [uint16]$HeadCrc
    [byte]$HeadType
    [uint16]$HeadFlags
    [uint16]$HeadSize

    # Optional field
    [uint32]$AddSize

    # Position in file
    [long]$BlockPosition

    # Raw header data (after common 7 bytes)
    [byte[]]$RawData

    # Constructor from binary data
    SrrBlock([BinaryReader]$reader, [long]$position) {
        $this.BlockPosition = $position

        # Read 7-byte common header
        $this.HeadCrc = $reader.ReadUInt16()
        $this.HeadType = $reader.ReadByte()
        $this.HeadFlags = $reader.ReadUInt16()
        $this.HeadSize = $reader.ReadUInt16()

        # Sanity check
        if ($this.HeadSize -lt 7) {
            throw "Invalid block header size: $($this.HeadSize)"
        }

        # Read remaining header bytes
        $remainingHeaderBytes = $this.HeadSize - 7
        if ($remainingHeaderBytes -gt 0) {
            $this.RawData = $reader.ReadBytes($remainingHeaderBytes)
        }
        else {
            $this.RawData = [byte[]]::new(0)
        }

        # Check for ADD_SIZE field
        $hasAddSize = ($this.HeadFlags -band 0x8000) -or
                      ($this.HeadType -eq 0x74) -or  # RarPackedFile
                      ($this.HeadType -eq 0x7A)      # RarNewSub

        if ($hasAddSize -and $this.RawData.Length -ge 4) {
            $this.AddSize = [BitConverter]::ToUInt32($this.RawData, 0)
        }
        else {
            $this.AddSize = 0
        }

        # Determine if we should skip ADD_SIZE data
        # For SRR files:
        # - SrrStoredFile (0x6A): Skip the file data
        # - RarPackedFile (0x74): Data is NOT in the file (just metadata)
        # - Other blocks: Data might be included
        $shouldSkip = $false

        if ($this.AddSize -gt 0) {
            if ($this.HeadType -eq 0x6A) {
                # SrrStoredFile: Skip the actual file data
                $shouldSkip = $true
            }
            elseif ($this.HeadType -ne 0x74) {
                # Not a RarPackedFile: might have data to skip
                # (RarPackedFile in SRR has no data following it)
                # For other block types, we generally don't skip unless specific handling
                # Let derived classes handle as needed
                $shouldSkip = $false
            }
            # else: RarPackedFile (0x74) - do nothing, data not present in SRR
        }

        if ($shouldSkip) {
            $reader.BaseStream.Seek($this.AddSize, [SeekOrigin]::Current) | Out-Null
        }
    }

    # Get full block bytes (for writing to output)
    [byte[]] GetBlockBytes() {
        $ms = [MemoryStream]::new()
        $writer = [BinaryWriter]::new($ms)

        try {
            $writer.Write($this.HeadCrc)
            $writer.Write($this.HeadType)
            $writer.Write($this.HeadFlags)
            $writer.Write($this.HeadSize)

            if ($this.RawData.Length -gt 0) {
                $writer.Write($this.RawData)
            }

            return $ms.ToArray()
        }
        finally {
            $writer.Dispose()
            $ms.Dispose()
        }
    }

    # Get friendly block type name
    [string] GetTypeName() {
        if ($script:BlockTypeNames.ContainsKey($this.HeadType)) {
            return $script:BlockTypeNames[$this.HeadType]
        }
        return "Unknown (0x{0:X2})" -f $this.HeadType
    }

    # Total size of block in file
    [int] GetTotalSize() {
        return $this.HeadSize + $this.AddSize
    }
}

class SrrHeaderBlock : SrrBlock {
    [string]$AppName

    SrrHeaderBlock([BinaryReader]$reader, [long]$position) : base($reader, $position) {
        $offset = 0

        # Check if app name is present (flag 0x0001)
        if ($this.HeadFlags -band 0x0001) {
            if ($this.RawData.Length -ge 2) {
                $nameLength = [BitConverter]::ToUInt16($this.RawData, $offset)
                $offset += 2

                if ($nameLength -gt 0 -and $offset + $nameLength -le $this.RawData.Length) {
                    $this.AppName = [Encoding]::UTF8.GetString($this.RawData, $offset, $nameLength)
                }
                else {
                    $this.AppName = ""
                }
            }
        }
        else {
            $this.AppName = ""
        }
    }
}

class SrrStoredFileBlock : SrrBlock {
    [string]$FileName
    [uint32]$FileSize

    SrrStoredFileBlock([BinaryReader]$reader, [long]$position) : base($reader, $position) {
        $offset = 0

        # ADD_SIZE is first 4 bytes
        if ($this.RawData.Length -ge 4) {
            $this.FileSize = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
        }

        # Name length is next 2 bytes
        if ($this.RawData.Length -ge $offset + 2) {
            $nameLength = [BitConverter]::ToUInt16($this.RawData, $offset)
            $offset += 2

            if ($nameLength -gt 0 -and $offset + $nameLength -le $this.RawData.Length) {
                $this.FileName = [Encoding]::UTF8.GetString($this.RawData, $offset, $nameLength)
            }
        }
    }
}

class SrrRarFileBlock : SrrBlock {
    [string]$FileName

    SrrRarFileBlock([BinaryReader]$reader, [long]$position) : base($reader, $position) {
        $offset = 0

        if ($this.RawData.Length -ge 2) {
            $nameLength = [BitConverter]::ToUInt16($this.RawData, $offset)
            $offset += 2

            if ($nameLength -gt 0 -and $offset + $nameLength -le $this.RawData.Length) {
                $this.FileName = [Encoding]::UTF8.GetString($this.RawData, $offset, $nameLength)
            }
        }
    }
}

#endregion

#region RAR Block Classes

class RarMarkerBlock : SrrBlock {
    # Special case: 0x72 is a fixed marker (52 61 72 21 1a 07 00)
    # Usually no parsing needed, just copy as-is

    RarMarkerBlock([BinaryReader]$reader, [long]$position) : base($reader, $position) {
        # Marker block is always fixed 7 bytes, no additional data
    }
}

class RarVolumeHeaderBlock : SrrBlock {
    # 0x73 - Archive header (MAIN_HEAD)
    [uint16]$Reserved1
    [uint32]$Reserved2

    RarVolumeHeaderBlock([BinaryReader]$reader, [long]$position) : base($reader, $position) {
        $offset = 0

        if ($this.RawData.Length -ge 6) {
            $this.Reserved1 = [BitConverter]::ToUInt16($this.RawData, $offset)
            $offset += 2
            $this.Reserved2 = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
        }
    }

    [byte[]] GetBlockBytes() {
        # Return complete block: CRC(2) + HEAD_TYPE(1) + HEAD_FLAGS(2) + HEAD_SIZE(2) + RawData
        $blockBytes = New-Object byte[] ($this.HeadSize)
        $offset = 0

        # CRC16
        [BitConverter]::GetBytes([uint16]$this.HeadCrc).CopyTo($blockBytes, $offset)
        $offset += 2

        # HEAD_TYPE
        $blockBytes[$offset++] = [byte]$this.HeadType

        # HEAD_FLAGS
        [BitConverter]::GetBytes([uint16]$this.HeadFlags).CopyTo($blockBytes, $offset)
        $offset += 2

        # HEAD_SIZE
        [BitConverter]::GetBytes([uint16]$this.HeadSize).CopyTo($blockBytes, $offset)
        $offset += 2

        # RawData (Reserved1 + Reserved2)
        if ($this.RawData.Length -gt 0) {
            [Array]::Copy($this.RawData, 0, $blockBytes, $offset, $this.RawData.Length)
        }

        return $blockBytes
    }
}

class RarPackedFileBlock : SrrBlock {
    # 0x74 - File header (most important for reconstruction)
    [uint32]$PackedSize
    [uint32]$UnpackedSize
    [byte]$HostOs
    [uint32]$FileCrc
    [uint32]$FileDateTime
    [byte]$RarVersion
    [byte]$CompressionMethod
    [uint16]$NameSize
    [uint32]$FileAttributes
    [uint64]$FullPackedSize      # May be 64-bit with LARGE_FILE flag
    [uint64]$FullUnpackedSize    # May be 64-bit with LARGE_FILE flag
    [string]$FileName
    [byte[]]$Salt
    [bool]$HasLargeFile
    [bool]$HasUtf8Name
    [bool]$HasSalt
    [bool]$HasExtTime
    [hashtable]$ExtTime          # Extended time data (mtime, ctime, atime, arctime)

    RarPackedFileBlock([BinaryReader]$reader, [long]$position) : base($reader, $position) {
        $offset = 0

        # Core 25-byte structure
        if ($this.RawData.Length -ge 25) {
            $this.PackedSize = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            $this.UnpackedSize = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            $this.HostOs = $this.RawData[$offset]
            $offset += 1
            $this.FileCrc = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            $this.FileDateTime = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            $this.RarVersion = $this.RawData[$offset]
            $offset += 1
            $this.CompressionMethod = $this.RawData[$offset]
            $offset += 1
            $this.NameSize = [BitConverter]::ToUInt16($this.RawData, $offset)
            $offset += 2
            $this.FileAttributes = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
        }

        # Initialize full sizes with low 32 bits
        $this.FullPackedSize = [uint64]$this.PackedSize
        $this.FullUnpackedSize = [uint64]$this.UnpackedSize

        # Check for LARGE_FILE flag (0x0100)
        $this.HasLargeFile = ($this.HeadFlags -band 0x0100) -eq 0x0100
        if ($this.HasLargeFile -and $this.RawData.Length -ge $offset + 8) {
            $highPackSize = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            $highUnpackSize = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            # Combine high and low 32-bit values into 64-bit
            $this.FullPackedSize = ([uint64]$highPackSize * 0x100000000) + [uint64]$this.PackedSize
            $this.FullUnpackedSize = ([uint64]$highUnpackSize * 0x100000000) + [uint64]$this.UnpackedSize
        }

        # File name (UTF-8 encoded)
        $this.HasUtf8Name = ($this.HeadFlags -band 0x0200) -eq 0x0200
        if ($this.NameSize -gt 0 -and $offset + $this.NameSize -le $this.RawData.Length) {
            $this.FileName = [Encoding]::UTF8.GetString($this.RawData, $offset, $this.NameSize)
            $offset += $this.NameSize
        }

        # Optional SALT field (8 bytes) if flag 0x0400
        $this.HasSalt = ($this.HeadFlags -band 0x0400) -eq 0x0400
        if ($this.HasSalt -and $this.RawData.Length -ge $offset + 8) {
            $this.Salt = $this.RawData[$offset..($offset + 7)]
            $offset += 8
        }

        # EXT_TIME field parsing (flag 0x1000)
        # Contains extended timestamps: mtime, ctime, atime, arctime
        $this.HasExtTime = ($this.HeadFlags -band 0x1000) -eq 0x1000
        if ($this.HasExtTime -and $offset -lt $this.RawData.Length) {
            $this.ExtTime = @{}
            # EXT_TIME format: flags (2 bytes) followed by optional time data
            if ($offset + 2 -le $this.RawData.Length) {
                $extFlags = [BitConverter]::ToUInt16($this.RawData, $offset)
                $offset += 2

                # Parse each time field (mtime, ctime, atime, arctime)
                # Each uses 4 bits: 2 for count, 1 for unix time present, 1 reserved
                $timeNames = @('mtime', 'ctime', 'atime', 'arctime')
                for ($i = 0; $i -lt 4; $i++) {
                    $fieldFlags = ($extFlags -shr (($i) * 4)) -band 0x0F
                    $count = ($fieldFlags -shr 2) -band 0x03
                    $hasUnixTime = ($fieldFlags -band 0x01) -ne 0

                    if ($count -gt 0 -or $hasUnixTime) {
                        $timeData = @{ Flags = $fieldFlags }

                        # Read additional bytes based on count
                        if ($count -gt 0 -and $offset + $count -le $this.RawData.Length) {
                            $timeData['ExtraBytes'] = $this.RawData[$offset..($offset + $count - 1)]
                            $offset += $count
                        }

                        # Read Unix timestamp if present
                        if ($hasUnixTime -and $offset + 4 -le $this.RawData.Length) {
                            $timeData['UnixTime'] = [BitConverter]::ToUInt32($this.RawData, $offset)
                            $offset += 4
                        }

                        $this.ExtTime[$timeNames[$i]] = $timeData
                    }
                }
            }
        }
    }

    [byte[]] GetBlockBytes() {
        # Return complete block header (without file data)
        $blockBytes = New-Object byte[] ($this.HeadSize)
        $offset = 0

        # CRC16
        [BitConverter]::GetBytes([uint16]$this.HeadCrc).CopyTo($blockBytes, $offset)
        $offset += 2

        # HEAD_TYPE
        $blockBytes[$offset++] = [byte]$this.HeadType

        # HEAD_FLAGS
        [BitConverter]::GetBytes([uint16]$this.HeadFlags).CopyTo($blockBytes, $offset)
        $offset += 2

        # HEAD_SIZE
        [BitConverter]::GetBytes([uint16]$this.HeadSize).CopyTo($blockBytes, $offset)
        $offset += 2

        # RawData (all the field data after HEAD_SIZE)
        if ($this.RawData.Length -gt 0) {
            [Array]::Copy($this.RawData, 0, $blockBytes, $offset, $this.RawData.Length)
        }

        return $blockBytes
    }
}

class RarNewSubBlock : SrrBlock {
    # 0x7A - New-format subblock (recovery record, comments, authenticity verification)
    [uint32]$DataSize
    [uint32]$UnpackedSize
    [byte]$HostOs
    [uint32]$DataCrc
    [uint32]$FileDateTime
    [byte]$UnpackVersion
    [byte]$Method
    [uint16]$NameSize
    [uint32]$Attributes
    [string]$SubType        # "RR" = Recovery Record, "CMT" = Comment, "AV" = Authenticity Verification

    RarNewSubBlock([BinaryReader]$reader, [long]$position) : base($reader, $position) {
        $offset = 0

        # Structure similar to RarPackedFileBlock (25 bytes minimum)
        if ($this.RawData.Length -ge 25) {
            $this.DataSize = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            $this.UnpackedSize = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            $this.HostOs = $this.RawData[$offset]
            $offset += 1
            $this.DataCrc = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            $this.FileDateTime = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
            $this.UnpackVersion = $this.RawData[$offset]
            $offset += 1
            $this.Method = $this.RawData[$offset]
            $offset += 1
            $this.NameSize = [BitConverter]::ToUInt16($this.RawData, $offset)
            $offset += 2
            $this.Attributes = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
        }

        # Read sub-type name (e.g., "RR", "CMT", "AV")
        if ($this.NameSize -gt 0 -and $offset + $this.NameSize -le $this.RawData.Length) {
            $this.SubType = [Encoding]::ASCII.GetString($this.RawData, $offset, $this.NameSize)
            $offset += $this.NameSize
        }
    }

    [byte[]] GetBlockBytes() {
        $blockBytes = New-Object byte[] ($this.HeadSize)
        $offset = 0

        [BitConverter]::GetBytes([uint16]$this.HeadCrc).CopyTo($blockBytes, $offset)
        $offset += 2
        $blockBytes[$offset++] = [byte]$this.HeadType
        [BitConverter]::GetBytes([uint16]$this.HeadFlags).CopyTo($blockBytes, $offset)
        $offset += 2
        [BitConverter]::GetBytes([uint16]$this.HeadSize).CopyTo($blockBytes, $offset)
        $offset += 2

        if ($this.RawData.Length -gt 0) {
            [Array]::Copy($this.RawData, 0, $blockBytes, $offset, $this.RawData.Length)
        }

        return $blockBytes
    }
}

class RarOldStyleBlock : SrrBlock {
    # 0x75-0x79 - Old-style RAR blocks (comment, authenticity, subblock, recovery, authenticity2)
    # These are rarely used in modern archives but may appear in legacy files

    [string]$BlockTypeName

    RarOldStyleBlock([BinaryReader]$reader, [long]$position) : base($reader, $position) {
        # Map block type to name for identification
        $this.BlockTypeName = switch ($this.HeadType) {
            0x75 { "OldComment" }
            0x76 { "OldAuthenticity" }
            0x77 { "OldSubblock" }
            0x78 { "OldRecovery" }
            0x79 { "OldAuthenticity2" }
            default { "Unknown" }
        }
    }

    [byte[]] GetBlockBytes() {
        $blockBytes = New-Object byte[] ($this.HeadSize)
        $offset = 0

        [BitConverter]::GetBytes([uint16]$this.HeadCrc).CopyTo($blockBytes, $offset)
        $offset += 2
        $blockBytes[$offset++] = [byte]$this.HeadType
        [BitConverter]::GetBytes([uint16]$this.HeadFlags).CopyTo($blockBytes, $offset)
        $offset += 2
        [BitConverter]::GetBytes([uint16]$this.HeadSize).CopyTo($blockBytes, $offset)
        $offset += 2

        if ($this.RawData.Length -gt 0) {
            [Array]::Copy($this.RawData, 0, $blockBytes, $offset, $this.RawData.Length)
        }

        return $blockBytes
    }
}

class RarEndArchiveBlock : SrrBlock {
    # 0x7B - Archive end block
    [uint32]$ArchiveCrc
    [uint16]$VolumeNumber
    [bool]$HasNextVolume
    [bool]$HasArchiveCrc
    [bool]$HasVolumeNumber

    RarEndArchiveBlock([BinaryReader]$reader, [long]$position) : base($reader, $position) {
        $offset = 0

        # Check flags
        $this.HasNextVolume = ($this.HeadFlags -band 0x0001) -eq 0x0001
        $this.HasArchiveCrc = ($this.HeadFlags -band 0x0002) -eq 0x0002
        $this.HasVolumeNumber = ($this.HeadFlags -band 0x0008) -eq 0x0008

        # Read optional fields based on flags
        if ($this.HasArchiveCrc -and $this.RawData.Length -ge $offset + 4) {
            $this.ArchiveCrc = [BitConverter]::ToUInt32($this.RawData, $offset)
            $offset += 4
        }

        if ($this.HasVolumeNumber -and $this.RawData.Length -ge $offset + 2) {
            $this.VolumeNumber = [BitConverter]::ToUInt16($this.RawData, $offset)
            $offset += 2
        }
    }

    [byte[]] GetBlockBytes() {
        # Return complete block
        $blockBytes = New-Object byte[] ($this.HeadSize)
        $offset = 0

        # CRC16
        [BitConverter]::GetBytes([uint16]$this.HeadCrc).CopyTo($blockBytes, $offset)
        $offset += 2

        # HEAD_TYPE
        $blockBytes[$offset++] = [byte]$this.HeadType

        # HEAD_FLAGS
        [BitConverter]::GetBytes([uint16]$this.HeadFlags).CopyTo($blockBytes, $offset)
        $offset += 2

        # HEAD_SIZE
        [BitConverter]::GetBytes([uint16]$this.HeadSize).CopyTo($blockBytes, $offset)
        $offset += 2

        # RawData (optional fields)
        if ($this.RawData.Length -gt 0) {
            [Array]::Copy($this.RawData, 0, $blockBytes, $offset, $this.RawData.Length)
        }

        return $blockBytes
    }
}

#endregion

#region Block Reader

class BlockReader {
    [FileStream]$Stream
    [BinaryReader]$Reader
    [long]$FileLength
    [string]$FilePath

    BlockReader([string]$srrFilePath) {
        # Resolve to absolute path
        $resolvedPath = (Resolve-Path -Path $srrFilePath -ErrorAction Stop).Path

        if (-not (Test-Path $resolvedPath)) {
            throw "SRR file not found: $resolvedPath"
        }

        $this.FilePath = $resolvedPath
        $this.Stream = [FileStream]::new($resolvedPath, [FileMode]::Open, [FileAccess]::Read)
        $this.Reader = [BinaryReader]::new($this.Stream)
        $this.FileLength = $this.Stream.Length

        # Verify minimum size (20 bytes)
        if ($this.FileLength -lt 20) {
            throw "File too small to be a valid SRR file (minimum 20 bytes)"
        }

        # Verify SRR magic number (69 69 69)
        $magic = $this.Reader.ReadBytes(3)
        if ($magic[0] -ne 0x69 -or $magic[1] -ne 0x69 -or $magic[2] -ne 0x69) {
            throw "Not a valid SRR file (magic number mismatch)"
        }

        # Seek back to start
        $this.Stream.Seek(0, [SeekOrigin]::Begin) | Out-Null
    }

    [SrrBlock] ReadNextBlock() {
        if ($this.Stream.Position -ge $this.FileLength) {
            return $null
        }

        $position = $this.Stream.Position

        # Peek at block type
        $startPos = $this.Stream.Position
        $this.Reader.ReadUInt16() | Out-Null  # HeadCrc
        $blockType = $this.Reader.ReadByte()

        # Seek back to start of block
        $this.Stream.Seek($startPos, [SeekOrigin]::Begin) | Out-Null

        # Instantiate appropriate block class
        $block = switch ($blockType) {
            # SRR-specific blocks
            0x69 { [SrrHeaderBlock]::new($this.Reader, $position) }
            0x6A { [SrrStoredFileBlock]::new($this.Reader, $position) }
            0x6B { [SrrBlock]::new($this.Reader, $position) }  # ISDb hash
            0x6C { [SrrBlock]::new($this.Reader, $position) }  # Padding
            0x71 { [SrrRarFileBlock]::new($this.Reader, $position) }
            # RAR blocks
            0x72 { [RarMarkerBlock]::new($this.Reader, $position) }
            0x73 { [RarVolumeHeaderBlock]::new($this.Reader, $position) }
            0x74 { [RarPackedFileBlock]::new($this.Reader, $position) }
            # Old-style RAR blocks (legacy support)
            0x75 { [RarOldStyleBlock]::new($this.Reader, $position) }  # Old comment
            0x76 { [RarOldStyleBlock]::new($this.Reader, $position) }  # Old authenticity
            0x77 { [RarOldStyleBlock]::new($this.Reader, $position) }  # Old subblock
            0x78 { [RarOldStyleBlock]::new($this.Reader, $position) }  # Old recovery
            0x79 { [RarOldStyleBlock]::new($this.Reader, $position) }  # Old authenticity2
            # New-style subblock (recovery records, comments, AV)
            0x7A { [RarNewSubBlock]::new($this.Reader, $position) }
            0x7B { [RarEndArchiveBlock]::new($this.Reader, $position) }
            default { [SrrBlock]::new($this.Reader, $position) }
        }

        return $block
    }

    [Object[]] ReadAllBlocks() {
        $blocks = [System.Collections.Generic.List[Object]]::new()

        while ($this.Stream.Position -lt $this.FileLength) {
            $block = $this.ReadNextBlock()
            if ($null -eq $block) {
                break
            }
            $blocks.Add($block)
        }

        return $blocks.ToArray()
    }

    [void] Close() {
        if ($null -ne $this.Reader) {
            $this.Reader.Dispose()
        }
        if ($null -ne $this.Stream) {
            $this.Stream.Dispose()
        }
    }
}

#endregion