Reland: [1] Add unrar source code to third_party.

No change from the original CL.
The CL did not need to be reverted since the problem was with
CL https://crrev.com/c/872219 only.

Original CL: https://crrev.com/c/855044
Revert CL: https://crrev.com/c/895399
Reason for revert: Windows build failures (crbug/807391)

TBR=jschuh,nparker

Bug: 750327
Change-Id: I6f596ecfc28f21d8e6a75ed69915b58943d026b5
Reviewed-on: https://chromium-review.googlesource.com/900002
Reviewed-by: Justin Schuh <jschuh@chromium.org>
Reviewed-by: Varun Khaneja <vakh@chromium.org>
Commit-Queue: Varun Khaneja <vakh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#534145}
diff --git a/third_party/unrar/BUILD.gn b/third_party/unrar/BUILD.gn
new file mode 100644
index 0000000..e22bf26
--- /dev/null
+++ b/third_party/unrar/BUILD.gn
@@ -0,0 +1,60 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("unrar") {
+  deps = []
+
+  sources = [
+    "src/archive.cpp",
+    "src/arcread.cpp",
+    "src/blake2s.cpp",
+    "src/cmddata.cpp",
+    "src/consio.cpp",
+    "src/crc.cpp",
+    "src/crypt.cpp",
+    "src/dll.cpp",
+    "src/encname.cpp",
+    "src/errhnd.cpp",
+    "src/extinfo.cpp",
+    "src/extract.cpp",
+    "src/filcreat.cpp",
+    "src/file.cpp",
+    "src/filefn.cpp",
+    "src/filestr.cpp",
+    "src/find.cpp",
+    "src/getbits.cpp",
+    "src/global.cpp",
+    "src/hash.cpp",
+    "src/headers.cpp",
+    "src/isnt.cpp",
+    "src/list.cpp",
+    "src/match.cpp",
+    "src/options.cpp",
+    "src/pathfn.cpp",
+    "src/qopen.cpp",
+    "src/rar.cpp",
+    "src/rarvm.cpp",
+    "src/rawread.cpp",
+    "src/rdwrfn.cpp",
+    "src/resource.cpp",
+    "src/rijndael.cpp",
+    "src/rs16.cpp",
+    "src/scantree.cpp",
+    "src/secpassword.cpp",
+    "src/sha1.cpp",
+    "src/sha256.cpp",
+    "src/smallfn.cpp",
+    "src/strfn.cpp",
+    "src/strlist.cpp",
+    "src/system.cpp",
+    "src/threadpool.cpp",
+    "src/timefn.cpp",
+    "src/ui.cpp",
+    "src/unicode.cpp",
+    "src/unpack.cpp",
+    "src/volume.cpp",
+  ]
+
+  defines = [ "_FILE_OFFSET_BITS=64", "LARGEFILE_SOURCE", "RAR_SMP" ]
+}
diff --git a/third_party/unrar/LICENSE b/third_party/unrar/LICENSE
new file mode 100644
index 0000000..9ddbc43
--- /dev/null
+++ b/third_party/unrar/LICENSE
@@ -0,0 +1,45 @@
+(Copied from src/license.txt)
+-----------------------------
+
+ ******    *****   ******   UnRAR - free utility for RAR archives
+ **   **  **   **  **   **  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ******   *******  ******    License for use and distribution of
+ **   **  **   **  **   **   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ **   **  **   **  **   **         FREE portable version
+                                   ~~~~~~~~~~~~~~~~~~~~~
+
+      The source code of UnRAR utility is freeware. This means:
+
+   1. All copyrights to RAR and the utility UnRAR are exclusively
+      owned by the author - Alexander Roshal.
+
+   2. UnRAR source code may be used in any software to handle
+      RAR archives without limitations free of charge, but cannot be
+      used to develop RAR (WinRAR) compatible archiver and to
+      re-create RAR compression algorithm, which is proprietary.
+      Distribution of modified UnRAR source code in separate form
+      or as a part of other software is permitted, provided that
+      full text of this paragraph, starting from "UnRAR source code"
+      words, is included in license, or in documentation if license
+      is not available, and in source code comments of resulting package.
+
+   3. The UnRAR utility may be freely distributed. It is allowed
+      to distribute UnRAR inside of other software packages.
+
+   4. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS".
+      NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED.  YOU USE AT 
+      YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS, 
+      DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING
+      OR MISUSING THIS SOFTWARE.
+
+   5. Installing and using the UnRAR utility signifies acceptance of
+      these terms and conditions of the license.
+
+   6. If you don't agree with terms of the license you must remove
+      UnRAR files from your storage devices and cease to use the
+      utility.
+
+      Thank you for your interest in RAR and UnRAR.
+
+
+                                            Alexander L. Roshal
diff --git a/third_party/unrar/OWNERS b/third_party/unrar/OWNERS
new file mode 100644
index 0000000..0a76ed53
--- /dev/null
+++ b/third_party/unrar/OWNERS
@@ -0,0 +1,2 @@
+nparker@chromium.org
+vakh@chromium.org
diff --git a/third_party/unrar/README.chromium b/third_party/unrar/README.chromium
new file mode 100644
index 0000000..ca19bb91
--- /dev/null
+++ b/third_party/unrar/README.chromium
@@ -0,0 +1,13 @@
+Name: UnRAR source for decompressing .RAR and other files.
+Short Name: unrar
+URL: https://github.com/aawc/unrar.git
+Revision: 0ff832d31470471803b175cfff4e40c1b08ee779
+Version: 5.6.1.4
+License: Non-standard
+License File: src/license.txt
+Security Critical: yes
+
+Description:
+This library is used to decompress and analyze .RAR and other related files that
+have been downloaded by the user to check their Safe Browsing reputation. It is
+only for Chromium on desktop.
diff --git a/third_party/unrar/src/acknow.txt b/third_party/unrar/src/acknow.txt
new file mode 100644
index 0000000..a68b672
--- /dev/null
+++ b/third_party/unrar/src/acknow.txt
@@ -0,0 +1,92 @@
+                           ACKNOWLEDGMENTS

+

+* We used "Screaming Fast Galois Field Arithmetic Using Intel

+  SIMD Instructions" paper by James S. Plank, Kevin M. Greenan

+  and Ethan L. Miller to improve Reed-Solomon coding performance.

+  Also we are grateful to Artem Drobanov and Bulat Ziganshin

+  for samples and ideas allowed to make Reed-Solomon coding

+  more efficient.

+

+* RAR text compression algorithm is based on Dmitry Shkarin PPMII

+  and Dmitry Subbotin carryless rangecoder public domain source code.

+  You may find it in ftp.elf.stuba.sk/pub/pc/pack.

+

+* RAR encryption includes parts of code from Szymon Stefanek

+  and Brian Gladman AES implementations also as Steve Reid SHA-1 source.

+

+  ---------------------------------------------------------------------------

+  Copyright (c) 2002, Dr Brian Gladman <                 >, Worcester, UK.

+  All rights reserved.

+

+  LICENSE TERMS

+

+  The free distribution and use of this software in both source and binary

+  form is allowed (with or without changes) provided that:

+

+    1. distributions of this source code include the above copyright

+       notice, this list of conditions and the following disclaimer;

+

+    2. distributions in binary form include the above copyright

+       notice, this list of conditions and the following disclaimer

+       in the documentation and/or other associated materials;

+

+    3. the copyright holder's name is not used to endorse products

+       built using this software without specific written permission.

+

+  ALTERNATIVELY, provided that this notice is retained in full, this product

+  may be distributed under the terms of the GNU General Public License (GPL),

+  in which case the provisions of the GPL apply INSTEAD OF those given above.

+

+  DISCLAIMER

+

+  This software is provided 'as is' with no explicit or implied warranties

+  in respect of its properties, including, but not limited to, correctness

+  and/or fitness for purpose.

+  ---------------------------------------------------------------------------

+

+  Source code of this package also as other cryptographic technology

+  and computing project related links are available on Brian Gladman's

+  web site: http://www.gladman.me.uk

+

+* RAR uses CRC32 function based on Intel Slicing-by-8 algorithm.

+  Original Intel Slicing-by-8 code is available here:

+

+    http://sourceforge.net/projects/slicing-by-8/

+

+  Original Intel Slicing-by-8 code is licensed under BSD License

+  available at http://www.opensource.org/licenses/bsd-license.html

+

+    Copyright (c) 2004-2006 Intel Corporation. 

+    All Rights Reserved

+

+    Redistribution and use in source and binary forms, with or without

+    modification, are permitted provided that the following conditions

+    are met:

+

+    Redistributions of source code must retain the above copyright notice,

+    this list of conditions and the following disclaimer.

+    

+    Redistributions in binary form must reproduce the above copyright

+    notice, this list of conditions and the following disclaimer

+    in the documentation and/or other materials provided with

+    the distribution.

+

+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT

+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS

+    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT

+    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,

+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT

+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,

+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND

+    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,

+    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT

+    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF

+    SUCH DAMAGE.

+

+* RAR archives may optionally include BLAKE2sp hash ( https://blake2.net ),

+  designed by Jean-Philippe Aumasson, Samuel Neves, Zooko Wilcox-O'Hearn

+  and Christian Winnerlein.

+

+* Useful hints provided by Alexander Khoroshev and Bulat Ziganshin allowed

+  to significantly improve RAR compression and speed.

diff --git a/third_party/unrar/src/arccmt.cpp b/third_party/unrar/src/arccmt.cpp
new file mode 100644
index 0000000..33f2204
--- /dev/null
+++ b/third_party/unrar/src/arccmt.cpp
@@ -0,0 +1,173 @@
+static bool IsAnsiEscComment(const wchar *Data,size_t Size);
+
+bool Archive::GetComment(Array<wchar> *CmtData)
+{
+  if (!MainComment)
+    return false;
+  SaveFilePos SavePos(*this);
+
+#ifndef SFX_MODULE
+  uint CmtLength;
+  if (Format==RARFMT14)
+  {
+    Seek(SFXSize+SIZEOF_MAINHEAD14,SEEK_SET);
+    CmtLength=GetByte();
+    CmtLength+=(GetByte()<<8);
+  }
+  else
+#endif
+  {
+    if (MainHead.CommentInHeader)
+    {
+      // Old style (RAR 2.9) archive comment embedded into the main 
+      // archive header.
+      Seek(SFXSize+SIZEOF_MARKHEAD3+SIZEOF_MAINHEAD3,SEEK_SET);
+      if (!ReadHeader())
+        return false;
+    }
+    else
+    {
+      // Current (RAR 3.0+) version of archive comment.
+      Seek(GetStartPos(),SEEK_SET);
+      return SearchSubBlock(SUBHEAD_TYPE_CMT)!=0 && ReadCommentData(CmtData);
+    }
+#ifndef SFX_MODULE
+    // Old style (RAR 2.9) comment header embedded into the main 
+    // archive header.
+    if (BrokenHeader)
+    {
+      uiMsg(UIERROR_CMTBROKEN,FileName);
+      return false;
+    }
+    CmtLength=CommHead.HeadSize-SIZEOF_COMMHEAD;
+#endif
+  }
+#ifndef SFX_MODULE
+  if (Format==RARFMT14 && MainHead.PackComment || Format!=RARFMT14 && CommHead.Method!=0x30)
+  {
+    if (Format!=RARFMT14 && (CommHead.UnpVer < 15 || CommHead.UnpVer > VER_UNPACK || CommHead.Method > 0x35))
+      return false;
+    ComprDataIO DataIO;
+    DataIO.SetTestMode(true);
+    uint UnpCmtLength;
+    if (Format==RARFMT14)
+    {
+#ifdef RAR_NOCRYPT
+      return false;
+#else
+      UnpCmtLength=GetByte();
+      UnpCmtLength+=(GetByte()<<8);
+      CmtLength-=2;
+      DataIO.SetCmt13Encryption();
+      CommHead.UnpVer=15;
+#endif
+    }
+    else
+      UnpCmtLength=CommHead.UnpSize;
+    DataIO.SetFiles(this,NULL);
+    DataIO.EnableShowProgress(false);
+    DataIO.SetPackedSizeToRead(CmtLength);
+    DataIO.UnpHash.Init(HASH_CRC32,1);
+    DataIO.SetNoFileHeader(true); // this->FileHead is not filled yet.
+
+    Unpack CmtUnpack(&DataIO);
+    CmtUnpack.Init(0x10000,false);
+    CmtUnpack.SetDestSize(UnpCmtLength);
+    CmtUnpack.DoUnpack(CommHead.UnpVer,false);
+
+    if (Format!=RARFMT14 && (DataIO.UnpHash.GetCRC32()&0xffff)!=CommHead.CommCRC)
+    {
+      uiMsg(UIERROR_CMTBROKEN,FileName);
+      return false;
+    }
+    else
+    {
+      byte *UnpData;
+      size_t UnpDataSize;
+      DataIO.GetUnpackedData(&UnpData,&UnpDataSize);
+#ifdef _WIN_ALL
+      // If we ever decide to extend it to Android, we'll need to alloc
+      // 4x memory for OEM to UTF-8 output here.
+      OemToCharBuffA((char *)UnpData,(char *)UnpData,(DWORD)UnpDataSize);
+#endif
+      CmtData->Alloc(UnpDataSize+1);
+      memset(CmtData->Addr(0),0,CmtData->Size()*sizeof(wchar));
+      CharToWide((char *)UnpData,CmtData->Addr(0),CmtData->Size());
+      CmtData->Alloc(wcslen(CmtData->Addr(0)));
+    }
+  }
+  else
+  {
+    if (CmtLength==0)
+      return false;
+    Array<byte> CmtRaw(CmtLength);
+    int ReadSize=Read(&CmtRaw[0],CmtLength);
+    if (ReadSize>=0 && (uint)ReadSize<CmtLength) // Comment is shorter than declared.
+    {
+      CmtLength=ReadSize;
+      CmtRaw.Alloc(CmtLength);
+    }
+
+    if (Format!=RARFMT14 && CommHead.CommCRC!=(~CRC32(0xffffffff,&CmtRaw[0],CmtLength)&0xffff))
+    {
+      uiMsg(UIERROR_CMTBROKEN,FileName);
+      return false;
+    }
+    CmtData->Alloc(CmtLength+1);
+    CmtRaw.Push(0);
+#ifdef _WIN_ALL
+    // If we ever decide to extend it to Android, we'll need to alloc
+    // 4x memory for OEM to UTF-8 output here.
+    OemToCharA((char *)&CmtRaw[0],(char *)&CmtRaw[0]);
+#endif
+    CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size());
+    CmtData->Alloc(wcslen(CmtData->Addr(0)));
+  }
+#endif
+  return CmtData->Size() > 0;
+}
+
+
+bool Archive::ReadCommentData(Array<wchar> *CmtData)
+{
+  Array<byte> CmtRaw;
+  if (!ReadSubData(&CmtRaw,NULL))
+    return false;
+  size_t CmtSize=CmtRaw.Size();
+  CmtRaw.Push(0);
+  CmtData->Alloc(CmtSize+1);
+  if (Format==RARFMT50)
+    UtfToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size());
+  else
+    if ((SubHead.SubFlags & SUBHEAD_FLAGS_CMT_UNICODE)!=0)
+    {
+      RawToWide(&CmtRaw[0],CmtData->Addr(0),CmtSize/2);
+      (*CmtData)[CmtSize/2]=0;
+
+    }
+    else
+    {
+      CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size());
+    }
+  CmtData->Alloc(wcslen(CmtData->Addr(0))); // Set buffer size to actual comment length.
+  return true;
+}
+
+
+void Archive::ViewComment()
+{
+  if (Cmd->DisableComment)
+    return;
+  Array<wchar> CmtBuf;
+  if (GetComment(&CmtBuf)) // In GUI too, so "Test" command detects broken comments.
+  {
+    size_t CmtSize=CmtBuf.Size();
+    wchar *ChPtr=wcschr(&CmtBuf[0],0x1A);
+    if (ChPtr!=NULL)
+      CmtSize=ChPtr-&CmtBuf[0];
+    mprintf(L"\n");
+    OutComment(&CmtBuf[0],CmtSize);
+  }
+}
+
+
diff --git a/third_party/unrar/src/archive.cpp b/third_party/unrar/src/archive.cpp
new file mode 100644
index 0000000..401fc34c
--- /dev/null
+++ b/third_party/unrar/src/archive.cpp
@@ -0,0 +1,393 @@
+#include "rar.hpp"
+
+#include "arccmt.cpp"
+
+
+#ifdef USE_ARCMEM
+#include "arcmem.cpp"
+#endif
+
+Archive::Archive(RAROptions *InitCmd)
+{
+  Cmd=NULL; // Just in case we'll have an exception in 'new' below.
+
+  DummyCmd=(InitCmd==NULL);
+  Cmd=DummyCmd ? (new RAROptions):InitCmd;
+
+  OpenShared=Cmd->OpenShared;
+  Format=RARFMT15;
+  Solid=false;
+  Volume=false;
+  MainComment=false;
+  Locked=false;
+  Signed=false;
+  FirstVolume=false;
+  NewNumbering=false;
+  SFXSize=0;
+  LatestTime.Reset();
+  Protected=false;
+  Encrypted=false;
+  FailedHeaderDecryption=false;
+  BrokenHeader=false;
+  LastReadBlock=0;
+
+  CurBlockPos=0;
+  NextBlockPos=0;
+
+  RecoverySize=-1;
+  RecoveryPercent=-1;
+
+  memset(&MainHead,0,sizeof(MainHead));
+  memset(&CryptHead,0,sizeof(CryptHead));
+  memset(&EndArcHead,0,sizeof(EndArcHead));
+
+  VolNumber=0;
+  VolWrite=0;
+  AddingFilesSize=0;
+  AddingHeadersSize=0;
+  *FirstVolumeName=0;
+
+  Splitting=false;
+  NewArchive=false;
+
+  SilentOpen=false;
+
+#ifdef USE_QOPEN
+  ProhibitQOpen=false;
+#endif
+
+}
+
+
+Archive::~Archive()
+{
+  if (DummyCmd)
+    delete Cmd;
+}
+
+
+void Archive::CheckArc(bool EnableBroken)
+{
+  if (!IsArchive(EnableBroken))
+  {
+    // If FailedHeaderDecryption is set, we already reported that archive
+    // password is incorrect.
+    if (!FailedHeaderDecryption)
+      uiMsg(UIERROR_BADARCHIVE,FileName);
+    ErrHandler.Exit(RARX_FATAL);
+  }
+}
+
+
+#if !defined(SFX_MODULE)
+void Archive::CheckOpen(const wchar *Name)
+{
+  TOpen(Name);
+  CheckArc(false);
+}
+#endif
+
+
+bool Archive::WCheckOpen(const wchar *Name)
+{
+  if (!WOpen(Name))
+    return false;
+  if (!IsArchive(false))
+  {
+    uiMsg(UIERROR_BADARCHIVE,FileName);
+    Close();
+    return false;
+  }
+  return true;
+}
+
+
+RARFORMAT Archive::IsSignature(const byte *D,size_t Size)
+{
+  RARFORMAT Type=RARFMT_NONE;
+  if (Size>=1 && D[0]==0x52)
+#ifndef SFX_MODULE
+    if (Size>=4 && D[1]==0x45 && D[2]==0x7e && D[3]==0x5e)
+      Type=RARFMT14;
+    else
+#endif
+      if (Size>=7 && D[1]==0x61 && D[2]==0x72 && D[3]==0x21 && D[4]==0x1a && D[5]==0x07)
+      {
+        // We check the last signature byte, so we can return a sensible
+        // warning in case we'll want to change the archive format
+        // sometimes in the future.
+        if (D[6]==0)
+          Type=RARFMT15;
+        else
+          if (D[6]==1)
+            Type=RARFMT50;
+          else
+            if (D[6]>1 && D[6]<5)
+              Type=RARFMT_FUTURE;
+      }
+  return Type;
+}
+
+
+bool Archive::IsArchive(bool EnableBroken)
+{
+  Encrypted=false;
+  BrokenHeader=false; // Might be left from previous volume.
+  
+#ifndef SFX_MODULE
+  if (IsDevice())
+  {
+    uiMsg(UIERROR_INVALIDNAME,FileName,FileName);
+    return false;
+  }
+#endif
+  if (Read(MarkHead.Mark,SIZEOF_MARKHEAD3)!=SIZEOF_MARKHEAD3)
+    return false;
+  SFXSize=0;
+  
+  RARFORMAT Type;
+  if ((Type=IsSignature(MarkHead.Mark,SIZEOF_MARKHEAD3))!=RARFMT_NONE)
+  {
+    Format=Type;
+    if (Format==RARFMT14)
+      Seek(Tell()-SIZEOF_MARKHEAD3,SEEK_SET);
+  }
+  else
+  {
+    Array<char> Buffer(MAXSFXSIZE);
+    long CurPos=(long)Tell();
+    int ReadSize=Read(&Buffer[0],Buffer.Size()-16);
+    for (int I=0;I<ReadSize;I++)
+      if (Buffer[I]==0x52 && (Type=IsSignature((byte *)&Buffer[I],ReadSize-I))!=RARFMT_NONE)
+      {
+        Format=Type;
+        if (Format==RARFMT14 && I>0 && CurPos<28 && ReadSize>31)
+        {
+          char *D=&Buffer[28-CurPos];
+          if (D[0]!=0x52 || D[1]!=0x53 || D[2]!=0x46 || D[3]!=0x58)
+            continue;
+        }
+        SFXSize=CurPos+I;
+        Seek(SFXSize,SEEK_SET);
+        if (Format==RARFMT15 || Format==RARFMT50)
+          Read(MarkHead.Mark,SIZEOF_MARKHEAD3);
+        break;
+      }
+    if (SFXSize==0)
+      return false;
+  }
+  if (Format==RARFMT_FUTURE)
+  {
+    uiMsg(UIERROR_NEWRARFORMAT,FileName);
+    return false;
+  }
+  if (Format==RARFMT50) // RAR 5.0 signature is by one byte longer.
+  {
+    if (Read(MarkHead.Mark+SIZEOF_MARKHEAD3,1)!=1 || MarkHead.Mark[SIZEOF_MARKHEAD3]!=0)
+      return false;
+    MarkHead.HeadSize=SIZEOF_MARKHEAD5;
+  }
+  else
+    MarkHead.HeadSize=SIZEOF_MARKHEAD3;
+
+#ifdef RARDLL
+  // If callback function is not set, we cannot get the password,
+  // so we skip the initial header processing for encrypted header archive.
+  // It leads to skipped archive comment, but the rest of archive data
+  // is processed correctly.
+  if (Cmd->Callback==NULL)
+    SilentOpen=true;
+#endif
+
+  bool HeadersLeft; // Any headers left to read.
+  // Skip the archive encryption header if any and read the main header.
+  while ((HeadersLeft=(ReadHeader()!=0))==true) // Additional parentheses to silence Clang.
+  {
+    SeekToNext();
+
+    HEADER_TYPE Type=GetHeaderType();
+    // In RAR 5.0 we need to quit after reading HEAD_CRYPT if we wish to
+    // avoid the password prompt.
+    if (Type==HEAD_MAIN || SilentOpen && Type==HEAD_CRYPT)
+      break;
+  }
+
+  // This check allows to make RS based recovery even if password is incorrect.
+  // But we should not do it for EnableBroken or we'll get 'not RAR archive'
+  // messages when extracting encrypted archives with wrong password.
+  if (FailedHeaderDecryption && !EnableBroken)
+    return false;
+
+  if (BrokenHeader) // Main archive header is corrupt.
+  {
+    uiMsg(UIERROR_MHEADERBROKEN,FileName);
+    if (!EnableBroken)
+      return false;
+  }
+
+  MainComment=MainHead.CommentInHeader;
+
+  // If we process non-encrypted archive or can request a password,
+  // we set 'first volume' flag based on file attributes below.
+  // It is necessary for RAR 2.x archives, which did not have 'first volume'
+  // flag in main header. Also for all RAR formats we need to scan until
+  // first file header to set "comment" flag when reading service header.
+  // Unless we are in silent mode, we need to know about presence of comment
+  // immediately after IsArchive call.
+  if (HeadersLeft && (!SilentOpen || !Encrypted))
+  {
+    SaveFilePos SavePos(*this);
+    int64 SaveCurBlockPos=CurBlockPos,SaveNextBlockPos=NextBlockPos;
+    HEADER_TYPE SaveCurHeaderType=CurHeaderType;
+
+    while (ReadHeader()!=0)
+    {
+      HEADER_TYPE HeaderType=GetHeaderType();
+      if (HeaderType==HEAD_SERVICE)
+      {
+        // If we have a split service headers, it surely indicates non-first
+        // volume. But not split service header does not guarantee the first
+        // volume, because we can have split file after non-split archive
+        // comment. So we do not quit from loop here.
+        FirstVolume=Volume && !SubHead.SplitBefore;
+      }
+      else
+        if (HeaderType==HEAD_FILE)
+        {
+          FirstVolume=Volume && !FileHead.SplitBefore;
+          break;
+        }
+        else
+          if (HeaderType==HEAD_ENDARC) // Might happen if archive contains only a split service header.
+            break;
+      SeekToNext();
+    }
+    CurBlockPos=SaveCurBlockPos;
+    NextBlockPos=SaveNextBlockPos;
+    CurHeaderType=SaveCurHeaderType;
+  }
+  if (!Volume || FirstVolume)
+    wcsncpyz(FirstVolumeName,FileName,ASIZE(FirstVolumeName));
+
+  return true;
+}
+
+
+
+
+void Archive::SeekToNext()
+{
+  Seek(NextBlockPos,SEEK_SET);
+}
+
+
+
+
+
+
+// Calculate the block size including encryption fields and padding if any.
+uint Archive::FullHeaderSize(size_t Size)
+{
+  if (Encrypted)
+  {
+    Size = ALIGN_VALUE(Size, CRYPT_BLOCK_SIZE); // Align to encryption block size.
+    if (Format == RARFMT50)
+      Size += SIZE_INITV;
+    else
+      Size += SIZE_SALT30;
+  }
+  return uint(Size);
+}
+
+
+
+
+bool Archive::Open(const wchar *Name,uint Mode)
+{
+#ifdef USE_QOPEN
+  // Important if we reuse Archive object and it has virtual QOpen
+  // file position not matching real. For example, for 'l -v volname'.
+  QOpen.Unload();
+#endif
+
+#ifdef USE_ARCMEM
+  if (Cmd->ArcInMem)
+  {
+    wcsncpyz(FileName,Name,ASIZE(FileName));
+    ArcMem.Load(Cmd->ArcMemData,Cmd->ArcMemSize);
+    Cmd->SetArcInMem(NULL,0); // Return in memory data for first volume only, not for next volumes.
+    return true;
+  }
+#endif
+
+  return File::Open(Name,Mode);
+}
+
+
+
+bool Archive::Close()
+{
+#ifdef USE_ARCMEM
+  if (ArcMem.Unload())
+    return true;
+#endif
+  return File::Close();
+}
+
+
+
+int Archive::Read(void *Data,size_t Size)
+{
+#ifdef USE_QOPEN
+  size_t QResult;
+  if (QOpen.Read(Data,Size,QResult))
+    return (int)QResult;
+#endif
+#ifdef USE_ARCMEM
+  size_t AResult;
+  if (ArcMem.Read(Data,Size,AResult))
+    return (int)AResult;
+#endif
+  return File::Read(Data,Size);
+}
+
+
+void Archive::Seek(int64 Offset,int Method)
+{
+#ifdef USE_QOPEN
+  if (QOpen.Seek(Offset,Method))
+    return;
+#endif
+#ifdef USE_ARCMEM
+  if (ArcMem.Seek(Offset,Method))
+    return;
+#endif
+  File::Seek(Offset,Method);
+}
+
+
+int64 Archive::Tell()
+{
+#ifdef USE_QOPEN
+  int64 QPos;
+  if (QOpen.Tell(&QPos))
+    return QPos;
+#endif
+#ifdef USE_ARCMEM
+  int64 APos;
+  if (ArcMem.Tell(&APos))
+    return APos;
+#endif
+  return File::Tell();
+}
+
+
+
+bool Archive::IsOpened()
+{
+#ifdef USE_ARCMEM
+  if (ArcMem.IsLoaded())
+    return true;
+#endif
+  return File::IsOpened();
+};
diff --git a/third_party/unrar/src/archive.hpp b/third_party/unrar/src/archive.hpp
new file mode 100644
index 0000000..473912fb
--- /dev/null
+++ b/third_party/unrar/src/archive.hpp
@@ -0,0 +1,158 @@
+#ifndef _RAR_ARCHIVE_
+#define _RAR_ARCHIVE_
+
+class PPack;
+class RawRead;
+class RawWrite;
+
+enum NOMODIFY_FLAGS 
+{
+  NMDF_ALLOWLOCK=1,NMDF_ALLOWANYVOLUME=2,NMDF_ALLOWFIRSTVOLUME=4
+};
+
+enum RARFORMAT {RARFMT_NONE,RARFMT14,RARFMT15,RARFMT50,RARFMT_FUTURE};
+
+enum ADDSUBDATA_FLAGS
+{
+  ASDF_SPLIT          = 1, // Allow to split archive just before header if necessary.
+  ASDF_COMPRESS       = 2, // Allow to compress data following subheader.
+  ASDF_CRYPT          = 4, // Encrypt data after subheader if password is set.
+  ASDF_CRYPTIFHEADERS = 8  // Encrypt data after subheader only in -hp mode.
+};
+
+class Archive:public File
+{
+  private:
+    void UpdateLatestTime(FileHeader *CurBlock);
+    void ConvertNameCase(wchar *Name);
+    void ConvertFileHeader(FileHeader *hd);
+    void WriteBlock50(HEADER_TYPE HeaderType,BaseBlock *wb,bool OnlySetSize,bool NonFinalWrite);
+    size_t ReadHeader14();
+    size_t ReadHeader15();
+    size_t ReadHeader50();
+    void ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb);
+    void RequestArcPassword();
+    void UnexpEndArcMsg();
+    void BrokenHeaderMsg();
+    void UnkEncVerMsg(const wchar *Name);
+    void UnkEncVerMsg();
+    bool ReadCommentData(Array<wchar> *CmtData);
+
+#if !defined(RAR_NOCRYPT)
+    CryptData HeadersCrypt;
+#endif
+    ComprDataIO SubDataIO;
+    bool DummyCmd;
+    RAROptions *Cmd;
+
+    int64 RecoverySize;
+    int RecoveryPercent;
+
+    RarTime LatestTime;
+    int LastReadBlock;
+    HEADER_TYPE CurHeaderType;
+
+    bool SilentOpen;
+#ifdef USE_QOPEN
+    QuickOpen QOpen;
+    bool ProhibitQOpen;
+#endif
+#ifdef USE_ARCMEM
+    ArcMemory ArcMem;
+#endif
+  public:
+    Archive(RAROptions *InitCmd=NULL);
+    ~Archive();
+    static RARFORMAT IsSignature(const byte *D,size_t Size);
+    bool IsArchive(bool EnableBroken);
+    size_t SearchBlock(HEADER_TYPE HeaderType);
+    size_t SearchSubBlock(const wchar *Type);
+    size_t SearchRR();
+    void WriteBlock(HEADER_TYPE HeaderType,BaseBlock *wb=NULL,bool OnlySetSize=false,bool NonFinalWrite=false);
+    void SetBlockSize(HEADER_TYPE HeaderType,BaseBlock *wb=NULL) {WriteBlock(HeaderType,wb,true);}
+    size_t ReadHeader();
+    void CheckArc(bool EnableBroken);
+    void CheckOpen(const wchar *Name);
+    bool WCheckOpen(const wchar *Name);
+    bool GetComment(Array<wchar> *CmtData);
+    void ViewComment();
+    void SetLatestTime(RarTime *NewTime);
+    void SeekToNext();
+    bool CheckAccess();
+    bool IsArcDir();
+    void ConvertAttributes();
+    void VolSubtractHeaderSize(size_t SubSize);
+    uint FullHeaderSize(size_t Size);
+    int64 GetStartPos();
+    void AddSubData(byte *SrcData,uint64 DataSize,File *SrcFile,
+         const wchar *Name,uint Flags);
+    bool ReadSubData(Array<byte> *UnpData,File *DestFile);
+    HEADER_TYPE GetHeaderType() {return CurHeaderType;};
+    RAROptions* GetRAROptions() {return Cmd;}
+    void SetSilentOpen(bool Mode) {SilentOpen=Mode;}
+#if 0
+    void GetRecoveryInfo(bool Required,int64 *Size,int *Percent);
+#endif
+    bool Open(const wchar *Name,uint Mode=FMF_READ);
+    bool Close();
+    int Read(void *Data,size_t Size);
+    void Seek(int64 Offset,int Method);
+    int64 Tell();
+    bool IsOpened();
+#ifdef USE_QOPEN
+    void QOpenUnload() {QOpen.Unload();}
+    void SetProhibitQOpen(bool Mode) {ProhibitQOpen=Mode;}
+#endif
+
+    BaseBlock ShortBlock;
+    MarkHeader MarkHead;
+    MainHeader MainHead;
+    CryptHeader CryptHead;
+    FileHeader FileHead;
+    EndArcHeader EndArcHead;
+    SubBlockHeader SubBlockHead;
+    FileHeader SubHead;
+    CommentHeader CommHead;
+    ProtectHeader ProtectHead;
+    AVHeader AVHead;
+    SignHeader SignHead;
+    UnixOwnersHeader UOHead;
+    MacFInfoHeader MACHead;
+    EAHeader EAHead;
+    StreamHeader StreamHead;
+
+    int64 CurBlockPos;
+    int64 NextBlockPos;
+
+    RARFORMAT Format;
+    bool Solid;
+    bool Volume;
+    bool MainComment;
+    bool Locked;
+    bool Signed;
+    bool FirstVolume;
+    bool NewNumbering;
+    bool Protected;
+    bool Encrypted;
+    size_t SFXSize;
+    bool BrokenHeader;
+    bool FailedHeaderDecryption;
+
+#if !defined(RAR_NOCRYPT)
+    byte ArcSalt[SIZE_SALT50];
+#endif
+
+    bool Splitting;
+
+    uint VolNumber;
+    int64 VolWrite;
+    uint64 AddingFilesSize;
+    uint64 AddingHeadersSize;
+
+    bool NewArchive;
+
+    wchar FirstVolumeName[NM];
+};
+
+
+#endif
diff --git a/third_party/unrar/src/arcmem.cpp b/third_party/unrar/src/arcmem.cpp
new file mode 100644
index 0000000..a8497856
--- /dev/null
+++ b/third_party/unrar/src/arcmem.cpp
@@ -0,0 +1,62 @@
+ArcMemory::ArcMemory()
+{
+  Loaded=false;
+  SeekPos=0;
+}
+
+
+void ArcMemory::Load(const byte *Data,size_t Size)
+{
+  ArcData.Alloc(Size);
+  memcpy(&ArcData[0],Data,Size);
+  Loaded=true;
+  SeekPos=0;
+}
+
+
+bool ArcMemory::Unload()
+{
+  if (!Loaded)
+    return false;
+  Loaded=false;
+  return true;
+}
+
+
+bool ArcMemory::Read(void *Data,size_t Size,size_t &Result)
+{
+  if (!Loaded)
+    return false;
+  Result=(size_t)Min(Size,ArcData.Size()-SeekPos);
+  memcpy(Data,&ArcData[(size_t)SeekPos],Result);
+  SeekPos+=Result;
+  return true;
+}
+
+
+bool ArcMemory::Seek(int64 Offset,int Method)
+{
+  if (!Loaded)
+    return false;
+  if (Method==SEEK_SET)
+    SeekPos=Min(Offset,ArcData.Size());
+  else
+    if (Method==SEEK_CUR || Method==SEEK_END)
+    {
+      if (Method==SEEK_END)
+        SeekPos=ArcData.Size();
+      SeekPos+=(uint64)Offset;
+      if (SeekPos>ArcData.Size())
+        SeekPos=Offset<0 ? 0 : ArcData.Size();
+    }
+  return true;
+}
+
+
+bool ArcMemory::Tell(int64 *Pos)
+{
+  if (!Loaded)
+    return false;
+  *Pos=SeekPos;
+  return true;
+}
diff --git a/third_party/unrar/src/arcmem.hpp b/third_party/unrar/src/arcmem.hpp
new file mode 100644
index 0000000..6fbe7c38
--- /dev/null
+++ b/third_party/unrar/src/arcmem.hpp
@@ -0,0 +1,22 @@
+#ifndef _RAR_ARCMEM_
+#define _RAR_ARCMEM_
+
+// Memory interface for software fuzzers.
+
+class ArcMemory
+{
+  private:
+    bool Loaded;
+    Array<byte> ArcData;
+    uint64 SeekPos;
+  public:
+    ArcMemory();
+    void Load(const byte *Data,size_t Size);
+    bool Unload();
+    bool IsLoaded() {return Loaded;}
+    bool Read(void *Data,size_t Size,size_t &Result);
+    bool Seek(int64 Offset,int Method);
+    bool Tell(int64 *Pos);
+};
+
+#endif
diff --git a/third_party/unrar/src/arcread.cpp b/third_party/unrar/src/arcread.cpp
new file mode 100644
index 0000000..6b3de71
--- /dev/null
+++ b/third_party/unrar/src/arcread.cpp
@@ -0,0 +1,1464 @@
+#include "rar.hpp"
+
+size_t Archive::ReadHeader()
+{
+  // Once we failed to decrypt an encrypted block, there is no reason to
+  // attempt to do it further. We'll never be successful and only generate
+  // endless errors.
+  if (FailedHeaderDecryption)
+    return 0;
+
+  CurBlockPos=Tell();
+
+  size_t ReadSize;
+  switch(Format)
+  {
+#ifndef SFX_MODULE
+    case RARFMT14:
+      ReadSize=ReadHeader14();
+      break;
+#endif
+    case RARFMT15:
+      ReadSize=ReadHeader15();
+      break;
+    case RARFMT50:
+      ReadSize=ReadHeader50();
+      break;
+  }
+
+  // It is important to check ReadSize>0 here, because it is normal
+  // for RAR2 and RAR3 archives without end of archive block to have
+  // NextBlockPos==CurBlockPos after the end of archive has reached.
+  if (ReadSize>0 && NextBlockPos<=CurBlockPos)
+  {
+    BrokenHeaderMsg();
+    ReadSize=0;
+  }
+
+  if (ReadSize==0)
+    CurHeaderType=HEAD_UNKNOWN;
+
+  return ReadSize;
+}
+
+
+size_t Archive::SearchBlock(HEADER_TYPE HeaderType)
+{
+  size_t Size,Count=0;
+  while ((Size=ReadHeader())!=0 &&
+         (HeaderType==HEAD_ENDARC || GetHeaderType()!=HEAD_ENDARC))
+  {
+    if ((++Count & 127)==0)
+      Wait();
+    if (GetHeaderType()==HeaderType)
+      return Size;
+    SeekToNext();
+  }
+  return 0;
+}
+
+
+size_t Archive::SearchSubBlock(const wchar *Type)
+{
+  size_t Size,Count=0;
+  while ((Size=ReadHeader())!=0 && GetHeaderType()!=HEAD_ENDARC)
+  {
+    if ((++Count & 127)==0)
+      Wait();
+    if (GetHeaderType()==HEAD_SERVICE && SubHead.CmpName(Type))
+      return Size;
+    SeekToNext();
+  }
+  return 0;
+}
+
+
+size_t Archive::SearchRR()
+{
+  // If locator extra field is available for recovery record, let's utilize it.
+  if (MainHead.Locator && MainHead.RROffset!=0)
+  {
+    uint64 CurPos=Tell();
+    Seek(MainHead.RROffset,SEEK_SET);
+    size_t Size=ReadHeader();
+    if (Size!=0 && !BrokenHeader && GetHeaderType()==HEAD_SERVICE && SubHead.CmpName(SUBHEAD_TYPE_RR))
+      return Size;
+    Seek(CurPos,SEEK_SET);
+  }
+  // Otherwise scan the entire archive to find the recovery record.
+  return SearchSubBlock(SUBHEAD_TYPE_RR);
+}
+
+
+void Archive::UnexpEndArcMsg()
+{
+  int64 ArcSize=FileLength();
+
+  // If block positions are equal to file size, this is not an error.
+  // It can happen when we reached the end of older RAR 1.5 archive,
+  // which did not have the end of archive block.
+  if (CurBlockPos!=ArcSize || NextBlockPos!=ArcSize)
+  {
+    uiMsg(UIERROR_UNEXPEOF,FileName);
+    ErrHandler.SetErrorCode(RARX_WARNING);
+  }
+}
+
+
+void Archive::BrokenHeaderMsg()
+{
+  uiMsg(UIERROR_HEADERBROKEN,FileName);
+  BrokenHeader=true;
+  ErrHandler.SetErrorCode(RARX_CRC);
+}
+
+
+void Archive::UnkEncVerMsg(const wchar *Name)
+{
+  uiMsg(UIERROR_UNKNOWNENCMETHOD,FileName,Name);
+  ErrHandler.SetErrorCode(RARX_WARNING);
+}
+
+
+// Return f in case of signed integer overflow or negative parameters
+// or v1+v2 otherwise. We use it for file offsets, which are signed
+// for compatibility with off_t in POSIX file functions and third party code.
+// Signed integer overflow is the undefined behavior according to
+// C++ standard and it causes fuzzers to complain.
+inline int64 SafeAdd(int64 v1,int64 v2,int64 f)
+{
+  return v1>=0 && v2>=0 && v1<=MAX_INT64-v2 ? v1+v2 : f;
+}
+
+
+size_t Archive::ReadHeader15()
+{
+  RawRead Raw(this);
+
+  bool Decrypt=Encrypted && CurBlockPos>(int64)SFXSize+SIZEOF_MARKHEAD3;
+
+  if (Decrypt)
+  {
+#ifdef RAR_NOCRYPT // For rarext.dll and unrar_nocrypt.dll.
+    return 0;
+#else
+    RequestArcPassword();
+
+    byte Salt[SIZE_SALT30];
+    if (Read(Salt,SIZE_SALT30)!=SIZE_SALT30)
+    {
+      UnexpEndArcMsg();
+      return 0;
+    }
+    HeadersCrypt.SetCryptKeys(false,CRYPT_RAR30,&Cmd->Password,Salt,NULL,0,NULL,NULL);
+    Raw.SetCrypt(&HeadersCrypt);
+#endif
+  }
+
+  Raw.Read(SIZEOF_SHORTBLOCKHEAD);
+  if (Raw.Size()==0)
+  {
+    UnexpEndArcMsg();
+    return 0;
+  }
+
+  ShortBlock.HeadCRC=Raw.Get2();
+
+  ShortBlock.Reset();
+
+  uint HeaderType=Raw.Get1();
+  ShortBlock.Flags=Raw.Get2();
+  ShortBlock.SkipIfUnknown=(ShortBlock.Flags & SKIP_IF_UNKNOWN)!=0;
+  ShortBlock.HeadSize=Raw.Get2();
+
+  ShortBlock.HeaderType=(HEADER_TYPE)HeaderType;
+  if (ShortBlock.HeadSize<SIZEOF_SHORTBLOCKHEAD)
+  {
+    BrokenHeaderMsg();
+    return 0;
+  }
+
+  // For simpler further processing we map header types common
+  // for RAR 1.5 and 5.0 formats to RAR 5.0 values. It does not include
+  // header types specific for RAR 1.5 - 4.x only.
+  switch(ShortBlock.HeaderType)
+  {
+    case HEAD3_MAIN:    ShortBlock.HeaderType=HEAD_MAIN;     break;
+    case HEAD3_FILE:    ShortBlock.HeaderType=HEAD_FILE;     break;
+    case HEAD3_SERVICE: ShortBlock.HeaderType=HEAD_SERVICE;  break;
+    case HEAD3_ENDARC:  ShortBlock.HeaderType=HEAD_ENDARC;   break;
+  }
+  CurHeaderType=ShortBlock.HeaderType;
+
+  if (ShortBlock.HeaderType==HEAD3_CMT)
+  {
+    // Old style (up to RAR 2.9) comment header embedded into main
+    // or file header. We must not read the entire ShortBlock.HeadSize here
+    // to not break the comment processing logic later.
+    Raw.Read(SIZEOF_COMMHEAD-SIZEOF_SHORTBLOCKHEAD);
+  }
+  else
+    if (ShortBlock.HeaderType==HEAD_MAIN && (ShortBlock.Flags & MHD_COMMENT)!=0)
+    {
+      // Old style (up to RAR 2.9) main archive comment embedded into
+      // the main archive header found. While we can read the entire 
+      // ShortBlock.HeadSize here and remove this part of "if", it would be
+      // waste of memory, because we'll read and process this comment data
+      // in other function anyway and we do not need them here now.
+      Raw.Read(SIZEOF_MAINHEAD3-SIZEOF_SHORTBLOCKHEAD);
+    }
+    else
+      Raw.Read(ShortBlock.HeadSize-SIZEOF_SHORTBLOCKHEAD);
+
+  NextBlockPos=CurBlockPos+FullHeaderSize(ShortBlock.HeadSize);
+
+  switch(ShortBlock.HeaderType)
+  {
+    case HEAD_MAIN:
+      MainHead.Reset();
+      *(BaseBlock *)&MainHead=ShortBlock;
+      MainHead.HighPosAV=Raw.Get2();
+      MainHead.PosAV=Raw.Get4();
+
+      Volume=(MainHead.Flags & MHD_VOLUME)!=0;
+      Solid=(MainHead.Flags & MHD_SOLID)!=0;
+      Locked=(MainHead.Flags & MHD_LOCK)!=0;
+      Protected=(MainHead.Flags & MHD_PROTECT)!=0;
+      Encrypted=(MainHead.Flags & MHD_PASSWORD)!=0;
+      Signed=MainHead.PosAV!=0 || MainHead.HighPosAV!=0;
+      MainHead.CommentInHeader=(MainHead.Flags & MHD_COMMENT)!=0;
+    
+      // Only for encrypted 3.0+ archives. 2.x archives did not have this
+      // flag, so for non-encrypted archives, we'll set it later based on
+      // file attributes.
+      FirstVolume=(MainHead.Flags & MHD_FIRSTVOLUME)!=0;
+
+      NewNumbering=(MainHead.Flags & MHD_NEWNUMBERING)!=0;
+      break;
+    case HEAD_FILE:
+    case HEAD_SERVICE:
+      {
+        bool FileBlock=ShortBlock.HeaderType==HEAD_FILE;
+        FileHeader *hd=FileBlock ? &FileHead:&SubHead;
+        hd->Reset();
+
+        *(BaseBlock *)hd=ShortBlock;
+
+        hd->SplitBefore=(hd->Flags & LHD_SPLIT_BEFORE)!=0;
+        hd->SplitAfter=(hd->Flags & LHD_SPLIT_AFTER)!=0;
+        hd->Encrypted=(hd->Flags & LHD_PASSWORD)!=0;
+        hd->SaltSet=(hd->Flags & LHD_SALT)!=0;
+        hd->Solid=FileBlock && (hd->Flags & LHD_SOLID)!=0;
+        hd->SubBlock=!FileBlock && (hd->Flags & LHD_SOLID)!=0;
+        hd->Dir=(hd->Flags & LHD_WINDOWMASK)==LHD_DIRECTORY;
+        hd->WinSize=hd->Dir ? 0:0x10000<<((hd->Flags & LHD_WINDOWMASK)>>5);
+        hd->CommentInHeader=(hd->Flags & LHD_COMMENT)!=0;
+        hd->Version=(hd->Flags & LHD_VERSION)!=0;
+        
+        hd->DataSize=Raw.Get4();
+        uint LowUnpSize=Raw.Get4();
+        hd->HostOS=Raw.Get1();
+
+        hd->FileHash.Type=HASH_CRC32;
+        hd->FileHash.CRC32=Raw.Get4();
+
+        uint FileTime=Raw.Get4();
+        hd->UnpVer=Raw.Get1();
+        hd->Method=Raw.Get1()-0x30;
+        size_t NameSize=Raw.Get2();
+        hd->FileAttr=Raw.Get4();
+
+        hd->CryptMethod=CRYPT_NONE;
+        if (hd->Encrypted)
+          switch(hd->UnpVer)
+          {
+            case 13: hd->CryptMethod=CRYPT_RAR13; break;
+            case 15: hd->CryptMethod=CRYPT_RAR15; break;
+            case 20: 
+            case 26: hd->CryptMethod=CRYPT_RAR20; break;
+            default: hd->CryptMethod=CRYPT_RAR30; break;
+          }
+
+        hd->HSType=HSYS_UNKNOWN;
+        if (hd->HostOS==HOST_UNIX || hd->HostOS==HOST_BEOS)
+          hd->HSType=HSYS_UNIX;
+        else
+          if (hd->HostOS<HOST_MAX)
+            hd->HSType=HSYS_WINDOWS;
+
+        hd->RedirType=FSREDIR_NONE;
+
+        // RAR 4.x Unix symlink.
+        if (hd->HostOS==HOST_UNIX && (hd->FileAttr & 0xF000)==0xA000)
+        {
+          hd->RedirType=FSREDIR_UNIXSYMLINK;
+          *hd->RedirName=0;
+        }
+
+        hd->Inherited=!FileBlock && (hd->SubFlags & SUBHEAD_FLAGS_INHERITED)!=0;
+        
+        hd->LargeFile=(hd->Flags & LHD_LARGE)!=0;
+
+        uint HighPackSize,HighUnpSize;
+        if (hd->LargeFile)
+        {
+          HighPackSize=Raw.Get4();
+          HighUnpSize=Raw.Get4();
+          hd->UnknownUnpSize=(LowUnpSize==0xffffffff && HighUnpSize==0xffffffff);
+        }
+        else 
+        {
+          HighPackSize=HighUnpSize=0;
+          // UnpSize equal to 0xffffffff without LHD_LARGE flag indicates
+          // that we do not know the unpacked file size and must unpack it
+          // until we find the end of file marker in compressed data.
+          hd->UnknownUnpSize=(LowUnpSize==0xffffffff);
+        }
+        hd->PackSize=INT32TO64(HighPackSize,hd->DataSize);
+        hd->UnpSize=INT32TO64(HighUnpSize,LowUnpSize);
+        if (hd->UnknownUnpSize)
+          hd->UnpSize=INT64NDF;
+
+        char FileName[NM*4];
+        size_t ReadNameSize=Min(NameSize,ASIZE(FileName)-1);
+        Raw.GetB((byte *)FileName,ReadNameSize);
+        FileName[ReadNameSize]=0;
+
+        if (FileBlock)
+        {
+          *hd->FileName=0;
+          if ((hd->Flags & LHD_UNICODE)!=0)
+          {
+            EncodeFileName NameCoder;
+            size_t Length=strlen(FileName);
+            Length++;
+            if (ReadNameSize>Length)
+              NameCoder.Decode(FileName,(byte *)FileName+Length,
+                               ReadNameSize-Length,hd->FileName,
+                               ASIZE(hd->FileName));
+          }
+
+          if (*hd->FileName==0)
+            ArcCharToWide(FileName,hd->FileName,ASIZE(hd->FileName),ACTW_OEM);
+
+#ifndef SFX_MODULE
+          ConvertNameCase(hd->FileName);
+#endif
+          ConvertFileHeader(hd);
+        }
+        else
+        {
+          CharToWide(FileName,hd->FileName,ASIZE(hd->FileName));
+
+          // Calculate the size of optional data.
+          int DataSize=int(hd->HeadSize-NameSize-SIZEOF_FILEHEAD3);
+          if ((hd->Flags & LHD_SALT)!=0)
+            DataSize-=SIZE_SALT30;
+
+          if (DataSize>0)
+          {
+            // Here we read optional additional fields for subheaders.
+            // They are stored after the file name and before salt.
+            hd->SubData.Alloc(DataSize);
+            Raw.GetB(&hd->SubData[0],DataSize);
+            if (hd->CmpName(SUBHEAD_TYPE_RR))
+            {
+              byte *D=&hd->SubData[8];
+              RecoverySize=D[0]+((uint)D[1]<<8)+((uint)D[2]<<16)+((uint)D[3]<<24);
+              RecoverySize*=512; // Sectors to size.
+              int64 CurPos=Tell();
+              RecoveryPercent=ToPercent(RecoverySize,CurPos);
+              // Round fractional percent exceeding .5 to upper value.
+              if (ToPercent(RecoverySize+CurPos/200,CurPos)>RecoveryPercent)
+                RecoveryPercent++;
+            }
+          }
+
+          if (hd->CmpName(SUBHEAD_TYPE_CMT))
+            MainComment=true;
+        }
+        if ((hd->Flags & LHD_SALT)!=0)
+          Raw.GetB(hd->Salt,SIZE_SALT30);
+        hd->mtime.SetDos(FileTime);
+        if ((hd->Flags & LHD_EXTTIME)!=0)
+        {
+          ushort Flags=Raw.Get2();
+          RarTime *tbl[4];
+          tbl[0]=&FileHead.mtime;
+          tbl[1]=&FileHead.ctime;
+          tbl[2]=&FileHead.atime;
+          tbl[3]=NULL; // Archive time is not used now.
+          for (int I=0;I<4;I++)
+          {
+            RarTime *CurTime=tbl[I];
+            uint rmode=Flags>>(3-I)*4;
+            if ((rmode & 8)==0 || CurTime==NULL)
+              continue;
+            if (I!=0)
+            {
+              uint DosTime=Raw.Get4();
+              CurTime->SetDos(DosTime);
+            }
+            RarLocalTime rlt;
+            CurTime->GetLocal(&rlt);
+            if (rmode & 4)
+              rlt.Second++;
+            rlt.Reminder=0;
+            int count=rmode&3;
+            for (int J=0;J<count;J++)
+            {
+              byte CurByte=Raw.Get1();
+              rlt.Reminder|=(((uint)CurByte)<<((J+3-count)*8));
+            }
+            // Convert from 100ns RAR precision to REMINDER_PRECISION.
+            rlt.Reminder*=RarTime::REMINDER_PRECISION/10000000;
+            CurTime->SetLocal(&rlt);
+          }
+        }
+        // Set to 0 in case of overflow, so end of ReadHeader cares about it.
+        NextBlockPos=SafeAdd(NextBlockPos,hd->PackSize,0);
+
+        bool CRCProcessedOnly=hd->CommentInHeader;
+        ushort HeaderCRC=Raw.GetCRC15(CRCProcessedOnly);
+        if (hd->HeadCRC!=HeaderCRC)
+        {
+          BrokenHeader=true;
+          ErrHandler.SetErrorCode(RARX_WARNING);
+
+          // If we have a broken encrypted header, we do not need to display
+          // the error message here, because it will be displayed for such
+          // headers later in this function. Also such headers are unlikely
+          // to have anything sensible in file name field, so it is useless
+          // to display the file name.
+          if (!Decrypt)
+            uiMsg(UIERROR_FHEADERBROKEN,Archive::FileName,hd->FileName);
+        }
+      }
+      break;
+    case HEAD_ENDARC:
+      *(BaseBlock *)&EndArcHead=ShortBlock;
+      EndArcHead.NextVolume=(EndArcHead.Flags & EARC_NEXT_VOLUME)!=0;
+      EndArcHead.DataCRC=(EndArcHead.Flags & EARC_DATACRC)!=0;
+      EndArcHead.RevSpace=(EndArcHead.Flags & EARC_REVSPACE)!=0;
+      EndArcHead.StoreVolNumber=(EndArcHead.Flags & EARC_VOLNUMBER)!=0;
+      if (EndArcHead.DataCRC)
+        EndArcHead.ArcDataCRC=Raw.Get4();
+      if (EndArcHead.StoreVolNumber)
+        VolNumber=EndArcHead.VolNumber=Raw.Get2();
+      break;
+#ifndef SFX_MODULE
+    case HEAD3_CMT:
+      *(BaseBlock *)&CommHead=ShortBlock;
+      CommHead.UnpSize=Raw.Get2();
+      CommHead.UnpVer=Raw.Get1();
+      CommHead.Method=Raw.Get1();
+      CommHead.CommCRC=Raw.Get2();
+      break;
+    case HEAD3_SIGN:
+      *(BaseBlock *)&SignHead=ShortBlock;
+      SignHead.CreationTime=Raw.Get4();
+      SignHead.ArcNameSize=Raw.Get2();
+      SignHead.UserNameSize=Raw.Get2();
+      break;
+    case HEAD3_AV:
+      *(BaseBlock *)&AVHead=ShortBlock;
+      AVHead.UnpVer=Raw.Get1();
+      AVHead.Method=Raw.Get1();
+      AVHead.AVVer=Raw.Get1();
+      AVHead.AVInfoCRC=Raw.Get4();
+      break;
+    case HEAD3_PROTECT:
+      *(BaseBlock *)&ProtectHead=ShortBlock;
+      ProtectHead.DataSize=Raw.Get4();
+      ProtectHead.Version=Raw.Get1();
+      ProtectHead.RecSectors=Raw.Get2();
+      ProtectHead.TotalBlocks=Raw.Get4();
+      Raw.GetB(ProtectHead.Mark,8);
+      NextBlockPos+=ProtectHead.DataSize;
+      RecoverySize=ProtectHead.RecSectors*512;
+      break;
+    case HEAD3_OLDSERVICE:
+      *(BaseBlock *)&SubBlockHead=ShortBlock;
+      SubBlockHead.DataSize=Raw.Get4();
+      NextBlockPos+=SubBlockHead.DataSize;
+      SubBlockHead.SubType=Raw.Get2();
+      SubBlockHead.Level=Raw.Get1();
+      switch(SubBlockHead.SubType)
+      {
+        case UO_HEAD:
+          *(SubBlockHeader *)&UOHead=SubBlockHead;
+          UOHead.OwnerNameSize=Raw.Get2();
+          UOHead.GroupNameSize=Raw.Get2();
+          if (UOHead.OwnerNameSize>=ASIZE(UOHead.OwnerName))
+            UOHead.OwnerNameSize=ASIZE(UOHead.OwnerName)-1;
+          if (UOHead.GroupNameSize>=ASIZE(UOHead.GroupName))
+            UOHead.GroupNameSize=ASIZE(UOHead.GroupName)-1;
+          Raw.GetB(UOHead.OwnerName,UOHead.OwnerNameSize);
+          Raw.GetB(UOHead.GroupName,UOHead.GroupNameSize);
+          UOHead.OwnerName[UOHead.OwnerNameSize]=0;
+          UOHead.GroupName[UOHead.GroupNameSize]=0;
+          break;
+        case MAC_HEAD:
+          *(SubBlockHeader *)&MACHead=SubBlockHead;
+          MACHead.fileType=Raw.Get4();
+          MACHead.fileCreator=Raw.Get4();
+          break;
+        case EA_HEAD:
+        case BEEA_HEAD:
+        case NTACL_HEAD:
+          *(SubBlockHeader *)&EAHead=SubBlockHead;
+          EAHead.UnpSize=Raw.Get4();
+          EAHead.UnpVer=Raw.Get1();
+          EAHead.Method=Raw.Get1();
+          EAHead.EACRC=Raw.Get4();
+          break;
+        case STREAM_HEAD:
+          *(SubBlockHeader *)&StreamHead=SubBlockHead;
+          StreamHead.UnpSize=Raw.Get4();
+          StreamHead.UnpVer=Raw.Get1();
+          StreamHead.Method=Raw.Get1();
+          StreamHead.StreamCRC=Raw.Get4();
+          StreamHead.StreamNameSize=Raw.Get2();
+          if (StreamHead.StreamNameSize>=ASIZE(StreamHead.StreamName))
+            StreamHead.StreamNameSize=ASIZE(StreamHead.StreamName)-1;
+          Raw.GetB(StreamHead.StreamName,StreamHead.StreamNameSize);
+          StreamHead.StreamName[StreamHead.StreamNameSize]=0;
+          break;
+      }
+      break;
+#endif
+    default:
+      if (ShortBlock.Flags & LONG_BLOCK)
+        NextBlockPos+=Raw.Get4();
+      break;
+  }
+  
+  ushort HeaderCRC=Raw.GetCRC15(false);
+
+  // Old AV header does not have header CRC properly set.
+  if (ShortBlock.HeadCRC!=HeaderCRC && ShortBlock.HeaderType!=HEAD3_SIGN &&
+      ShortBlock.HeaderType!=HEAD3_AV)
+  {
+    bool Recovered=false;
+    if (ShortBlock.HeaderType==HEAD_ENDARC && EndArcHead.RevSpace)
+    {
+      // Last 7 bytes of recovered volume can contain zeroes, because
+      // REV files store its own information (volume number, etc.) here.
+      SaveFilePos SavePos(*this);
+      int64 Length=Tell();
+      Seek(Length-7,SEEK_SET);
+      Recovered=true;
+      for (int J=0;J<7;J++)
+        if (GetByte()!=0)
+          Recovered=false;
+    }
+    if (!Recovered)
+    {
+      BrokenHeader=true;
+      ErrHandler.SetErrorCode(RARX_CRC);
+
+      if (Decrypt)
+      {
+        uiMsg(UIERROR_CHECKSUMENC,FileName,FileName);
+        FailedHeaderDecryption=true;
+        return 0;
+      }
+    }
+  }
+
+  return Raw.Size();
+}
+
+
+size_t Archive::ReadHeader50()
+{
+  RawRead Raw(this);
+
+  bool Decrypt=Encrypted && CurBlockPos>(int64)SFXSize+SIZEOF_MARKHEAD5;
+
+  if (Decrypt)
+  {
+#if defined(RAR_NOCRYPT)
+    return 0;
+#else
+
+    byte HeadersInitV[SIZE_INITV];
+    if (Read(HeadersInitV,SIZE_INITV)!=SIZE_INITV)
+    {
+      UnexpEndArcMsg();
+      return 0;
+    }
+
+    while (true) // Repeat the password prompt for wrong passwords.
+    {
+      RequestArcPassword();
+
+      byte PswCheck[SIZE_PSWCHECK];
+      HeadersCrypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,CryptHead.Salt,HeadersInitV,CryptHead.Lg2Count,NULL,PswCheck);
+      // Verify password validity.
+      if (CryptHead.UsePswCheck && memcmp(PswCheck,CryptHead.PswCheck,SIZE_PSWCHECK)!=0)
+      {
+        // This message is used by Android GUI and Windows GUI and SFX to
+        // reset cached passwords. Update appropriate code if changed.
+        uiMsg(UIWAIT_BADPSW,FileName);
+
+        Cmd->Password.Clean();
+        continue;
+      }
+      break;
+    }
+
+    Raw.SetCrypt(&HeadersCrypt);
+#endif
+  }
+
+  // Header size must not occupy more than 3 variable length integer bytes
+  // resulting in 2 MB maximum header size, so here we read 4 byte CRC32
+  // followed by 3 bytes or less of header size.
+  const size_t FirstReadSize=7; // Smallest possible block size.
+  if (Raw.Read(FirstReadSize)<FirstReadSize)
+  {
+    UnexpEndArcMsg();
+    return 0;
+  }
+
+  ShortBlock.Reset();
+  ShortBlock.HeadCRC=Raw.Get4();
+  uint SizeBytes=Raw.GetVSize(4);
+  uint64 BlockSize=Raw.GetV();
+
+  if (BlockSize==0 || SizeBytes==0)
+  {
+    BrokenHeaderMsg();
+    return 0;
+  }
+
+  int SizeToRead=int(BlockSize);
+  SizeToRead-=FirstReadSize-SizeBytes-4; // Adjust overread size bytes if any.
+  uint HeaderSize=4+SizeBytes+(uint)BlockSize;
+
+  if (SizeToRead<0 || HeaderSize<SIZEOF_SHORTBLOCKHEAD5)
+  {
+    BrokenHeaderMsg();
+    return 0;
+  }
+  
+  Raw.Read(SizeToRead);
+
+  if (Raw.Size()<HeaderSize)
+  {
+    UnexpEndArcMsg();
+    return 0;
+  }
+
+  uint HeaderCRC=Raw.GetCRC50();
+
+  ShortBlock.HeaderType=(HEADER_TYPE)Raw.GetV();
+  ShortBlock.Flags=(uint)Raw.GetV();
+  ShortBlock.SkipIfUnknown=(ShortBlock.Flags & HFL_SKIPIFUNKNOWN)!=0;
+  ShortBlock.HeadSize=HeaderSize;
+
+  CurHeaderType=ShortBlock.HeaderType;
+
+  bool BadCRC=(ShortBlock.HeadCRC!=HeaderCRC);
+  if (BadCRC)
+  {
+    BrokenHeaderMsg(); // Report, but attempt to process.
+
+    BrokenHeader=true;
+    ErrHandler.SetErrorCode(RARX_CRC);
+
+    if (Decrypt)
+    {
+      uiMsg(UIERROR_CHECKSUMENC,FileName,FileName);
+      FailedHeaderDecryption=true;
+      return 0;
+    }
+  }
+  
+  uint64 ExtraSize=0;
+  if ((ShortBlock.Flags & HFL_EXTRA)!=0)
+  {
+    ExtraSize=Raw.GetV();
+    if (ExtraSize>=ShortBlock.HeadSize)
+    {
+      BrokenHeaderMsg();
+      return 0;
+    }
+  }
+
+  uint64 DataSize=0;
+  if ((ShortBlock.Flags & HFL_DATA)!=0)
+    DataSize=Raw.GetV();
+
+  NextBlockPos=CurBlockPos+FullHeaderSize(ShortBlock.HeadSize);
+  // Set to 0 in case of overflow, so end of ReadHeader cares about it.
+  NextBlockPos=SafeAdd(NextBlockPos,DataSize,0);
+
+  switch(ShortBlock.HeaderType)
+  {
+    case HEAD_CRYPT:
+      {
+        *(BaseBlock *)&CryptHead=ShortBlock;
+        uint CryptVersion=(uint)Raw.GetV();
+        if (CryptVersion>CRYPT_VERSION)
+        {
+          UnkEncVerMsg(FileName);
+          return 0;
+        }
+        uint EncFlags=(uint)Raw.GetV();
+        CryptHead.UsePswCheck=(EncFlags & CHFL_CRYPT_PSWCHECK)!=0;
+        CryptHead.Lg2Count=Raw.Get1();
+        if (CryptHead.Lg2Count>CRYPT5_KDF_LG2_COUNT_MAX)
+        {
+          UnkEncVerMsg(FileName);
+          return 0;
+        }
+        Raw.GetB(CryptHead.Salt,SIZE_SALT50);
+        if (CryptHead.UsePswCheck)
+        {
+          Raw.GetB(CryptHead.PswCheck,SIZE_PSWCHECK);
+
+          byte csum[SIZE_PSWCHECK_CSUM];
+          Raw.GetB(csum,SIZE_PSWCHECK_CSUM);
+
+          sha256_context ctx;
+          sha256_init(&ctx);
+          sha256_process(&ctx, CryptHead.PswCheck, SIZE_PSWCHECK);
+
+          byte Digest[SHA256_DIGEST_SIZE];
+          sha256_done(&ctx, Digest);
+
+          CryptHead.UsePswCheck=memcmp(csum,Digest,SIZE_PSWCHECK_CSUM)==0;
+        }
+        Encrypted=true;
+      }
+      break;
+    case HEAD_MAIN:
+      {
+        MainHead.Reset();
+        *(BaseBlock *)&MainHead=ShortBlock;
+        uint ArcFlags=(uint)Raw.GetV();
+
+        Volume=(ArcFlags & MHFL_VOLUME)!=0;
+        Solid=(ArcFlags & MHFL_SOLID)!=0;
+        Locked=(ArcFlags & MHFL_LOCK)!=0;
+        Protected=(ArcFlags & MHFL_PROTECT)!=0;
+        Signed=false;
+        NewNumbering=true;
+
+        if ((ArcFlags & MHFL_VOLNUMBER)!=0)
+          VolNumber=(uint)Raw.GetV();
+        else
+          VolNumber=0;
+        FirstVolume=Volume && VolNumber==0;
+
+        if (ExtraSize!=0)
+          ProcessExtra50(&Raw,(size_t)ExtraSize,&MainHead);
+
+#ifdef USE_QOPEN
+        if (!ProhibitQOpen && MainHead.Locator && MainHead.QOpenOffset>0 && Cmd->QOpenMode!=QOPEN_NONE)
+        {
+          // We seek to QO block in the end of archive when processing
+          // QOpen.Load, so we need to preserve current block positions
+          // to not break normal archive processing by calling function.
+          int64 SaveCurBlockPos=CurBlockPos,SaveNextBlockPos=NextBlockPos;
+          HEADER_TYPE SaveCurHeaderType=CurHeaderType;
+          
+          QOpen.Init(this,false);
+          QOpen.Load(MainHead.QOpenOffset);
+
+          CurBlockPos=SaveCurBlockPos;
+          NextBlockPos=SaveNextBlockPos;
+          CurHeaderType=SaveCurHeaderType;
+        }
+#endif
+      }
+      break;
+    case HEAD_FILE:
+    case HEAD_SERVICE:
+      {
+        FileHeader *hd=ShortBlock.HeaderType==HEAD_FILE ? &FileHead:&SubHead;
+        hd->Reset();
+        *(BaseBlock *)hd=ShortBlock;
+
+        bool FileBlock=ShortBlock.HeaderType==HEAD_FILE;
+
+        hd->LargeFile=true;
+
+        hd->PackSize=DataSize;
+        hd->FileFlags=(uint)Raw.GetV();
+        hd->UnpSize=Raw.GetV();
+        
+        hd->UnknownUnpSize=(hd->FileFlags & FHFL_UNPUNKNOWN)!=0;
+        if (hd->UnknownUnpSize)
+          hd->UnpSize=INT64NDF;
+
+        hd->MaxSize=Max(hd->PackSize,hd->UnpSize);
+        hd->FileAttr=(uint)Raw.GetV();
+        if ((hd->FileFlags & FHFL_UTIME)!=0)
+          hd->mtime.SetUnix((time_t)Raw.Get4());
+
+        hd->FileHash.Type=HASH_NONE;
+        if ((hd->FileFlags & FHFL_CRC32)!=0)
+        {
+          hd->FileHash.Type=HASH_CRC32;
+          hd->FileHash.CRC32=Raw.Get4();
+        }
+
+        hd->RedirType=FSREDIR_NONE;
+
+        uint CompInfo=(uint)Raw.GetV();
+        hd->Method=(CompInfo>>7) & 7;
+
+        // "+ 50" to not mix with old RAR format algorithms. For example,
+        // we may need to use the compression algorithm 15 in the future,
+        // but it was already used in RAR 1.5 and Unpack needs to distinguish
+        // them.
+        hd->UnpVer=(CompInfo & 0x3f) + 50;
+
+        hd->HostOS=(byte)Raw.GetV();
+        size_t NameSize=(size_t)Raw.GetV();
+        hd->Inherited=(ShortBlock.Flags & HFL_INHERITED)!=0;
+
+        hd->HSType=HSYS_UNKNOWN;
+        if (hd->HostOS==HOST5_UNIX)
+          hd->HSType=HSYS_UNIX;
+        else
+          if (hd->HostOS==HOST5_WINDOWS)
+            hd->HSType=HSYS_WINDOWS;
+
+        hd->SplitBefore=(hd->Flags & HFL_SPLITBEFORE)!=0;
+        hd->SplitAfter=(hd->Flags & HFL_SPLITAFTER)!=0;
+        hd->SubBlock=(hd->Flags & HFL_CHILD)!=0;
+        hd->Solid=FileBlock && (CompInfo & FCI_SOLID)!=0;
+        hd->Dir=(hd->FileFlags & FHFL_DIRECTORY)!=0;
+        hd->WinSize=hd->Dir ? 0:size_t(0x20000)<<((CompInfo>>10)&0xf);
+
+        hd->CryptMethod=hd->Encrypted ? CRYPT_RAR50:CRYPT_NONE;
+
+        char FileName[NM*4];
+        size_t ReadNameSize=Min(NameSize,ASIZE(FileName)-1);
+        Raw.GetB((byte *)FileName,ReadNameSize);
+        FileName[ReadNameSize]=0;
+
+        UtfToWide(FileName,hd->FileName,ASIZE(hd->FileName));
+
+        // Should do it before converting names, because extra fields can
+        // affect name processing, like in case of NTFS streams.
+        if (ExtraSize!=0)
+          ProcessExtra50(&Raw,(size_t)ExtraSize,hd);
+
+        if (FileBlock)
+        {
+#ifndef SFX_MODULE
+          ConvertNameCase(hd->FileName);
+#endif
+          ConvertFileHeader(hd);
+        }
+
+        if (!FileBlock && hd->CmpName(SUBHEAD_TYPE_CMT))
+          MainComment=true;
+
+#if 0
+        // For RAR5 format we read the user specified recovery percent here.
+        // It would be useful to do it for shell extension too, so we display
+        // the correct recovery record size in archive properties. But then
+        // we would need to include the entire recovery record processing
+        // code to shell extension, which is not done now.
+        if (!FileBlock && hd->CmpName(SUBHEAD_TYPE_RR) && hd->SubData.Size()>0)
+        {
+          RecoveryPercent=hd->SubData[0];
+          RSBlockHeader Header;
+          GetRRInfo(this,&Header);
+          RecoverySize=Header.RecSectionSize*Header.RecCount;
+        }
+#endif
+          
+        if (BadCRC) // Add the file name to broken header message displayed above.
+          uiMsg(UIERROR_FHEADERBROKEN,Archive::FileName,hd->FileName);
+      }
+      break;
+    case HEAD_ENDARC:
+      {
+        *(BaseBlock *)&EndArcHead=ShortBlock;
+        uint ArcFlags=(uint)Raw.GetV();
+        EndArcHead.NextVolume=(ArcFlags & EHFL_NEXTVOLUME)!=0;
+        EndArcHead.StoreVolNumber=false;
+        EndArcHead.DataCRC=false;
+        EndArcHead.RevSpace=false;
+      }
+      break;
+  }
+
+  return Raw.Size();
+}
+
+
+#if !defined(RAR_NOCRYPT)
+void Archive::RequestArcPassword()
+{
+  if (!Cmd->Password.IsSet())
+  {
+#ifdef RARDLL
+    if (Cmd->Callback!=NULL)
+    {
+      wchar PasswordW[MAXPASSWORD];
+      *PasswordW=0;
+      if (Cmd->Callback(UCM_NEEDPASSWORDW,Cmd->UserData,(LPARAM)PasswordW,ASIZE(PasswordW))==-1)
+        *PasswordW=0;
+      if (*PasswordW==0)
+      {
+        char PasswordA[MAXPASSWORD];
+        *PasswordA=0;
+        if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1)
+          *PasswordA=0;
+        GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW));
+        cleandata(PasswordA,sizeof(PasswordA));
+      }
+      Cmd->Password.Set(PasswordW);
+      cleandata(PasswordW,sizeof(PasswordW));
+    }
+    if (!Cmd->Password.IsSet())
+    {
+      Close();
+      Cmd->DllError=ERAR_MISSING_PASSWORD;
+      ErrHandler.Exit(RARX_USERBREAK);
+    }
+#else
+    if (!uiGetPassword(UIPASSWORD_ARCHIVE,FileName,&Cmd->Password) ||
+        !Cmd->Password.IsSet())
+    {
+      Close();
+      uiMsg(UIERROR_INCERRCOUNT);
+      ErrHandler.Exit(RARX_USERBREAK);
+    }
+#endif
+    Cmd->ManualPassword=true;
+  }
+}
+#endif
+
+
+void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb)
+{
+  // Read extra data from the end of block skipping any fields before it.
+  size_t ExtraStart=Raw->Size()-ExtraSize;
+  if (ExtraStart<Raw->GetPos())
+    return;
+  Raw->SetPos(ExtraStart);
+  while (Raw->DataLeft()>=2)
+  {
+    int64 FieldSize=Raw->GetV(); // Needs to be signed for check below and can be negative.
+    if (FieldSize<=0 || Raw->DataLeft()==0 || FieldSize>(int64)Raw->DataLeft())
+      break;
+    size_t NextPos=size_t(Raw->GetPos()+FieldSize);
+    uint64 FieldType=Raw->GetV();
+
+    FieldSize=int64(NextPos-Raw->GetPos()); // Field size without size and type fields.
+
+    if (FieldSize<0) // FieldType is longer than expected extra field size.
+      break;
+
+    if (bb->HeaderType==HEAD_MAIN)
+    {
+      MainHeader *hd=(MainHeader *)bb;
+      if (FieldType==MHEXTRA_LOCATOR)
+      {
+        hd->Locator=true;
+        uint Flags=(uint)Raw->GetV();
+        if ((Flags & MHEXTRA_LOCATOR_QLIST)!=0)
+        {
+          uint64 Offset=Raw->GetV();
+          if (Offset!=0) // 0 means that reserved space was not enough to write the offset.
+            hd->QOpenOffset=Offset+CurBlockPos;
+        }
+        if ((Flags & MHEXTRA_LOCATOR_RR)!=0)
+        {
+          uint64 Offset=Raw->GetV();
+          if (Offset!=0) // 0 means that reserved space was not enough to write the offset.
+            hd->RROffset=Offset+CurBlockPos;
+        }
+      }
+    }
+
+    if (bb->HeaderType==HEAD_FILE || bb->HeaderType==HEAD_SERVICE)
+    {
+      FileHeader *hd=(FileHeader *)bb;
+      switch(FieldType)
+      {
+        case FHEXTRA_CRYPT:
+          {
+            FileHeader *hd=(FileHeader *)bb;
+            uint EncVersion=(uint)Raw->GetV();
+            if (EncVersion > CRYPT_VERSION)
+              UnkEncVerMsg(hd->FileName);
+            else
+            {
+              uint Flags=(uint)Raw->GetV();
+              hd->UsePswCheck=(Flags & FHEXTRA_CRYPT_PSWCHECK)!=0;
+              hd->UseHashKey=(Flags & FHEXTRA_CRYPT_HASHMAC)!=0;
+              hd->Lg2Count=Raw->Get1();
+              if (hd->Lg2Count>CRYPT5_KDF_LG2_COUNT_MAX)
+                UnkEncVerMsg(hd->FileName);
+              Raw->GetB(hd->Salt,SIZE_SALT50);
+              Raw->GetB(hd->InitV,SIZE_INITV);
+              if (hd->UsePswCheck)
+              {
+                Raw->GetB(hd->PswCheck,SIZE_PSWCHECK);
+
+                // It is important to know if password check data is valid.
+                // If it is damaged and header CRC32 fails to detect it,
+                // archiver would refuse to decompress a possibly valid file.
+                // Since we want to be sure distinguishing a wrong password
+                // or corrupt file data, we use 64-bit password check data
+                // and to control its validity we use 32 bits of password
+                // check data SHA-256 additionally to 32-bit header CRC32.
+                byte csum[SIZE_PSWCHECK_CSUM];
+                Raw->GetB(csum,SIZE_PSWCHECK_CSUM);
+
+                sha256_context ctx;
+                sha256_init(&ctx);
+                sha256_process(&ctx, hd->PswCheck, SIZE_PSWCHECK);
+
+                byte Digest[SHA256_DIGEST_SIZE];
+                sha256_done(&ctx, Digest);
+
+                hd->UsePswCheck=memcmp(csum,Digest,SIZE_PSWCHECK_CSUM)==0;
+
+                // RAR 5.21 and earlier set PswCheck field in service records to 0
+                // even if UsePswCheck was present.
+                if (bb->HeaderType==HEAD_SERVICE && memcmp(hd->PswCheck,"\0\0\0\0\0\0\0\0",SIZE_PSWCHECK)==0)
+                  hd->UsePswCheck=0;
+              }
+              hd->SaltSet=true;
+              hd->CryptMethod=CRYPT_RAR50;
+              hd->Encrypted=true;
+            }
+          }
+          break;
+        case FHEXTRA_HASH:
+          {
+            FileHeader *hd=(FileHeader *)bb;
+            uint Type=(uint)Raw->GetV();
+            if (Type==FHEXTRA_HASH_BLAKE2)
+            {
+              hd->FileHash.Type=HASH_BLAKE2;
+              Raw->GetB(hd->FileHash.Digest,BLAKE2_DIGEST_SIZE);
+            }
+          }
+          break;
+        case FHEXTRA_HTIME:
+          if (FieldSize>=5)
+          {
+            byte Flags=(byte)Raw->GetV();
+            bool UnixTime=(Flags & FHEXTRA_HTIME_UNIXTIME)!=0;
+            if ((Flags & FHEXTRA_HTIME_MTIME)!=0)
+              if (UnixTime)
+                hd->mtime.SetUnix(Raw->Get4());
+              else
+                hd->mtime.SetWin(Raw->Get8());
+            if ((Flags & FHEXTRA_HTIME_CTIME)!=0)
+              if (UnixTime)
+                hd->ctime.SetUnix(Raw->Get4());
+              else
+                hd->ctime.SetWin(Raw->Get8());
+            if ((Flags & FHEXTRA_HTIME_ATIME)!=0)
+              if (UnixTime)
+                hd->atime.SetUnix((time_t)Raw->Get4());
+              else
+                hd->atime.SetWin(Raw->Get8());
+            if (UnixTime && (Flags & FHEXTRA_HTIME_UNIX_NS)!=0) // Add nanoseconds.
+            {
+              uint ns;
+              if ((Flags & FHEXTRA_HTIME_MTIME)!=0 && (ns=(Raw->Get4() & 0x3fffffff))<1000000000)
+                hd->mtime.Adjust(ns);
+              if ((Flags & FHEXTRA_HTIME_CTIME)!=0 && (ns=(Raw->Get4() & 0x3fffffff))<1000000000)
+                hd->ctime.Adjust(ns);
+              if ((Flags & FHEXTRA_HTIME_ATIME)!=0 && (ns=(Raw->Get4() & 0x3fffffff))<1000000000)
+                hd->atime.Adjust(ns);
+            }
+          }
+          break;
+        case FHEXTRA_VERSION:
+          if (FieldSize>=1)
+          {
+            Raw->GetV(); // Skip flags field.
+            uint Version=(uint)Raw->GetV();
+            if (Version!=0)
+            {
+              hd->Version=true;
+
+              wchar VerText[20];
+              swprintf(VerText,ASIZE(VerText),L";%u",Version);
+              wcsncatz(hd->FileName,VerText,ASIZE(hd->FileName));
+            }
+          }
+          break;
+        case FHEXTRA_REDIR:
+          {
+            hd->RedirType=(FILE_SYSTEM_REDIRECT)Raw->GetV();
+            uint Flags=(uint)Raw->GetV();
+            hd->DirTarget=(Flags & FHEXTRA_REDIR_DIR)!=0;
+            size_t NameSize=(size_t)Raw->GetV();
+
+            char UtfName[NM*4];
+            *UtfName=0;
+            if (NameSize<ASIZE(UtfName)-1)
+            {
+              Raw->GetB(UtfName,NameSize);
+              UtfName[NameSize]=0;
+            }
+#ifdef _WIN_ALL
+            UnixSlashToDos(UtfName,UtfName,ASIZE(UtfName));
+#endif
+            UtfToWide(UtfName,hd->RedirName,ASIZE(hd->RedirName));
+          }
+          break;
+        case FHEXTRA_UOWNER:
+          {
+            uint Flags=(uint)Raw->GetV();
+            hd->UnixOwnerNumeric=(Flags & FHEXTRA_UOWNER_NUMUID)!=0;
+            hd->UnixGroupNumeric=(Flags & FHEXTRA_UOWNER_NUMGID)!=0;
+            *hd->UnixOwnerName=*hd->UnixGroupName=0;
+            if ((Flags & FHEXTRA_UOWNER_UNAME)!=0)
+            {
+              size_t Length=(size_t)Raw->GetV();
+              Length=Min(Length,ASIZE(hd->UnixOwnerName)-1);
+              Raw->GetB(hd->UnixOwnerName,Length);
+              hd->UnixOwnerName[Length]=0;
+            }
+            if ((Flags & FHEXTRA_UOWNER_GNAME)!=0)
+            {
+              size_t Length=(size_t)Raw->GetV();
+              Length=Min(Length,ASIZE(hd->UnixGroupName)-1);
+              Raw->GetB(hd->UnixGroupName,Length);
+              hd->UnixGroupName[Length]=0;
+            }
+#ifdef _UNIX
+            if (hd->UnixOwnerNumeric)
+              hd->UnixOwnerID=(uid_t)Raw->GetV();
+            if (hd->UnixGroupNumeric)
+              hd->UnixGroupID=(gid_t)Raw->GetV();
+#else
+            // Need these fields in Windows too for 'list' command,
+            // but uid_t and gid_t are not defined.
+            if (hd->UnixOwnerNumeric)
+              hd->UnixOwnerID=(uint)Raw->GetV();
+            if (hd->UnixGroupNumeric)
+              hd->UnixGroupID=(uint)Raw->GetV();
+#endif
+            hd->UnixOwnerSet=true;
+          }
+          break;
+        case FHEXTRA_SUBDATA:
+          {
+            // RAR 5.21 and earlier set FHEXTRA_SUBDATA size to 1 less than
+            // required. It did not hurt extraction, because UnRAR 5.21
+            // and earlier ignored this field and set FieldSize as data left
+            // in entire extra area. But now we set the correct field size
+            // and set FieldSize based on the actual extra record size,
+            // so we need to adjust it for those older archives here.
+            // FHEXTRA_SUBDATA in those archives always belongs to HEAD_SERVICE
+            // and always is last in extra area. So since its size is by 1
+            // less than needed, we always have 1 byte left in extra area,
+            // which fact we use here to detect such archives.
+            if (bb->HeaderType==HEAD_SERVICE && Raw->Size()-NextPos==1)
+              FieldSize++;
+
+            // We cannot allocate too much memory here, because above
+            // we check FieldSize againt Raw size and we control that Raw size
+            // is sensible when reading headers.
+            hd->SubData.Alloc((size_t)FieldSize);
+            Raw->GetB(hd->SubData.Addr(0),(size_t)FieldSize);
+          }
+          break;
+      }
+    }
+
+    Raw->SetPos(NextPos);
+  }
+}
+
+
+#ifndef SFX_MODULE
+size_t Archive::ReadHeader14()
+{
+  RawRead Raw(this);
+  if (CurBlockPos<=(int64)SFXSize)
+  {
+    Raw.Read(SIZEOF_MAINHEAD14);
+    MainHead.Reset();
+    byte Mark[4];
+    Raw.GetB(Mark,4);
+    uint HeadSize=Raw.Get2();
+    byte Flags=Raw.Get1();
+    NextBlockPos=CurBlockPos+HeadSize;
+    CurHeaderType=HEAD_MAIN;
+
+    Volume=(Flags & MHD_VOLUME)!=0;
+    Solid=(Flags & MHD_SOLID)!=0;
+    Locked=(Flags & MHD_LOCK)!=0;
+    MainHead.CommentInHeader=(Flags & MHD_COMMENT)!=0;
+    MainHead.PackComment=(Flags & MHD_PACK_COMMENT)!=0;
+  }
+  else
+  {
+    Raw.Read(SIZEOF_FILEHEAD14);
+    FileHead.Reset();
+
+    FileHead.HeaderType=HEAD_FILE;
+    FileHead.DataSize=Raw.Get4();
+    FileHead.UnpSize=Raw.Get4();
+    FileHead.FileHash.Type=HASH_RAR14;
+    FileHead.FileHash.CRC32=Raw.Get2();
+    FileHead.HeadSize=Raw.Get2();
+    uint FileTime=Raw.Get4();
+    FileHead.FileAttr=Raw.Get1();
+    FileHead.Flags=Raw.Get1()|LONG_BLOCK;
+    FileHead.UnpVer=(Raw.Get1()==2) ? 13 : 10;
+    size_t NameSize=Raw.Get1();
+    FileHead.Method=Raw.Get1();
+
+    FileHead.SplitBefore=(FileHead.Flags & LHD_SPLIT_BEFORE)!=0;
+    FileHead.SplitAfter=(FileHead.Flags & LHD_SPLIT_AFTER)!=0;
+    FileHead.Encrypted=(FileHead.Flags & LHD_PASSWORD)!=0;
+    FileHead.CryptMethod=FileHead.Encrypted ? CRYPT_RAR13:CRYPT_NONE;
+
+    FileHead.PackSize=FileHead.DataSize;
+    FileHead.WinSize=0x10000;
+
+    FileHead.HostOS=HOST_MSDOS;
+    FileHead.HSType=HSYS_WINDOWS;
+
+    FileHead.mtime.SetDos(FileTime);
+
+    Raw.Read(NameSize);
+
+    char FileName[NM];
+    Raw.GetB((byte *)FileName,Min(NameSize,ASIZE(FileName)));
+    FileName[NameSize]=0;
+    IntToExt(FileName,FileName,ASIZE(FileName));
+    CharToWide(FileName,FileHead.FileName,ASIZE(FileHead.FileName));
+    ConvertNameCase(FileHead.FileName);
+
+    if (Raw.Size()!=0)
+      NextBlockPos=CurBlockPos+FileHead.HeadSize+FileHead.PackSize;
+    CurHeaderType=HEAD_FILE;
+  }
+  return NextBlockPos>CurBlockPos ? Raw.Size() : 0;
+}
+#endif
+
+
+#ifndef SFX_MODULE
+void Archive::ConvertNameCase(wchar *Name)
+{
+  if (Cmd->ConvertNames==NAMES_UPPERCASE)
+    wcsupper(Name);
+  if (Cmd->ConvertNames==NAMES_LOWERCASE)
+    wcslower(Name);
+}
+#endif
+
+
+bool Archive::IsArcDir()
+{
+  return FileHead.Dir;
+}
+
+
+void Archive::ConvertAttributes()
+{
+#if defined(_WIN_ALL) || defined(_EMX)
+  if (FileHead.HSType!=HSYS_WINDOWS)
+    FileHead.FileAttr=FileHead.Dir ? 0x10 : 0x20;
+#endif
+#ifdef _UNIX
+  // umask defines which permission bits must not be set by default
+  // when creating a file or directory. The typical default value
+  // for the process umask is S_IWGRP | S_IWOTH (octal 022),
+  // resulting in 0644 mode for new files.
+  // Normally umask is applied automatically when creating a file,
+  // but we set attributes with chmod later, so we need to calculate
+  // resulting attributes here. We do it only for non-Unix archives.
+  // We restore native Unix attributes as is, because it can be backup.
+  static mode_t mask = (mode_t) -1;
+
+  if (mask == (mode_t) -1)
+  {
+    // umask call returns the current umask value. Argument (022) is not 
+    // really important here.
+    mask = umask(022);
+
+    // Restore the original umask value, which was changed to 022 above.
+    umask(mask);
+  }
+
+  switch(FileHead.HSType)
+  {
+    case HSYS_WINDOWS:
+      {
+        // Mapping MSDOS, OS/2 and Windows file attributes to Unix.
+
+        if (FileHead.FileAttr & 0x10) // FILE_ATTRIBUTE_DIRECTORY
+        {
+          // For directories we use 0777 mask.
+          FileHead.FileAttr=0777 & ~mask;
+        }
+        else
+          if (FileHead.FileAttr & 1)  // FILE_ATTRIBUTE_READONLY
+          {
+            // For read only files we use 0444 mask with 'w' bits turned off.
+            FileHead.FileAttr=0444 & ~mask;
+          }
+          else
+          {
+            // umask does not set +x for regular files, so we use 0666
+            // instead of 0777 as for directories.
+            FileHead.FileAttr=0666 & ~mask;
+          }
+      }
+      break;
+    case HSYS_UNIX:
+      break;
+    default:
+      if (FileHead.Dir)
+        FileHead.FileAttr=0x41ff & ~mask;
+      else
+        FileHead.FileAttr=0x81b6 & ~mask;
+      break;
+  }
+#endif
+}
+
+
+void Archive::ConvertFileHeader(FileHeader *hd)
+{
+  if (Format==RARFMT15 && hd->UnpVer<20 && (hd->FileAttr & 0x10))
+    hd->Dir=true;
+  if (hd->HSType==HSYS_UNKNOWN)
+    if (hd->Dir)
+      hd->FileAttr=0x10;
+    else
+      hd->FileAttr=0x20;
+
+#ifdef _WIN_ALL
+  if (hd->HSType==HSYS_UNIX) // Convert Unix, OS X and Android decomposed chracters to Windows precomposed.
+    ConvertToPrecomposed(hd->FileName,ASIZE(hd->FileName));
+#endif
+
+  for (wchar *s=hd->FileName;*s!=0;s++)
+  {
+#ifdef _UNIX
+    // Backslash is the invalid character for Windows file headers,
+    // but it can present in Unix file names extracted in Unix.
+    if (*s=='\\' && Format==RARFMT50 && hd->HSType==HSYS_WINDOWS)
+      *s='_';
+#endif
+
+#if defined(_WIN_ALL) || defined(_EMX)
+    // RAR 5.0 archives do not use '\' as path separator, so if we see it,
+    // it means that it is a part of Unix file name, which we cannot
+    // extract in Windows.
+    if (*s=='\\' && Format==RARFMT50)
+      *s='_';
+
+    // ':' in file names is allowed in Unix, but not in Windows.
+    // Even worse, file data will be written to NTFS stream on NTFS,
+    // so automatic name correction on file create error in extraction 
+    // routine does not work. In Windows and DOS versions we better 
+    // replace ':' now.
+    if (*s==':')
+      *s='_';
+#endif
+
+    // This code must be performed only after other path separator checks,
+    // because it produces backslashes illegal for some of checks above.
+    // Backslash is allowed in file names in Unix, but not in Windows.
+    // Still, RAR 4.x uses backslashes as path separator even in Unix.
+    // Forward slash is not allowed in both systems. In RAR 5.0 we use
+    // the forward slash as universal path separator.
+    if (*s=='/' || *s=='\\' && Format!=RARFMT50)
+      *s=CPATHDIVIDER;
+  }
+}
+
+
+int64 Archive::GetStartPos()
+{
+  int64 StartPos=SFXSize+MarkHead.HeadSize;
+  if (Format==RARFMT15)
+    StartPos+=MainHead.HeadSize;
+  else // RAR 5.0.
+    StartPos+=CryptHead.HeadSize+FullHeaderSize(MainHead.HeadSize);
+  return StartPos;
+}
+
+
+bool Archive::ReadSubData(Array<byte> *UnpData,File *DestFile)
+{
+  if (BrokenHeader)
+  {
+    uiMsg(UIERROR_SUBHEADERBROKEN,FileName);
+    ErrHandler.SetErrorCode(RARX_CRC);
+    return false;
+  }
+  if (SubHead.Method>5 || SubHead.UnpVer>(Format==RARFMT50 ? VER_UNPACK5:VER_UNPACK))
+  {
+    uiMsg(UIERROR_SUBHEADERUNKNOWN,FileName);
+    return false;
+  }
+
+  if (SubHead.PackSize==0 && !SubHead.SplitAfter)
+    return true;
+
+  SubDataIO.Init();
+  Unpack Unpack(&SubDataIO);
+  Unpack.Init(SubHead.WinSize,false);
+
+  if (DestFile==NULL)
+  {
+    if (SubHead.UnpSize>0x1000000)
+    {
+      // So huge allocation must never happen in valid archives.
+      uiMsg(UIERROR_SUBHEADERUNKNOWN,FileName);
+      return false;
+    }
+    if (UnpData==NULL)
+      SubDataIO.SetTestMode(true);
+    else
+    {
+      UnpData->Alloc((size_t)SubHead.UnpSize);
+      SubDataIO.SetUnpackToMemory(&(*UnpData)[0],(uint)SubHead.UnpSize);
+    }
+  }
+  if (SubHead.Encrypted)
+    if (Cmd->Password.IsSet())
+      SubDataIO.SetEncryption(false,SubHead.CryptMethod,&Cmd->Password,
+                SubHead.SaltSet ? SubHead.Salt:NULL,SubHead.InitV,
+                SubHead.Lg2Count,SubHead.HashKey,SubHead.PswCheck);
+    else
+      return false;
+  SubDataIO.UnpHash.Init(SubHead.FileHash.Type,1);
+  SubDataIO.SetPackedSizeToRead(SubHead.PackSize);
+  SubDataIO.EnableShowProgress(false);
+  SubDataIO.SetFiles(this,DestFile);
+  SubDataIO.UnpVolume=SubHead.SplitAfter;
+  SubDataIO.SetSubHeader(&SubHead,NULL);
+  Unpack.SetDestSize(SubHead.UnpSize);
+  if (SubHead.Method==0)
+    CmdExtract::UnstoreFile(SubDataIO,SubHead.UnpSize);
+  else
+    Unpack.DoUnpack(SubHead.UnpVer,false);
+
+  if (!SubDataIO.UnpHash.Cmp(&SubHead.FileHash,SubHead.UseHashKey ? SubHead.HashKey:NULL))
+  {
+    uiMsg(UIERROR_SUBHEADERDATABROKEN,FileName,SubHead.FileName);
+    ErrHandler.SetErrorCode(RARX_CRC);
+    if (UnpData!=NULL)
+      UnpData->Reset();
+    return false;
+  }
+  return true;
+}
diff --git a/third_party/unrar/src/array.hpp b/third_party/unrar/src/array.hpp
new file mode 100644
index 0000000..20d258d
--- /dev/null
+++ b/third_party/unrar/src/array.hpp
@@ -0,0 +1,191 @@
+#ifndef _RAR_ARRAY_
+#define _RAR_ARRAY_
+
+extern ErrorHandler ErrHandler;
+
+template <class T> class Array
+{
+  private:
+    T *Buffer;
+    size_t BufSize;
+    size_t AllocSize;
+    size_t MaxSize;
+    bool Secure; // Clean memory if true.
+  public:
+    Array();
+    Array(size_t Size);
+    Array(const Array &Src); // Copy constructor.
+    ~Array();
+    inline void CleanData();
+    inline T& operator [](size_t Item) const;
+    inline T* operator + (size_t Pos);
+    inline size_t Size(); // Returns the size in items, not in bytes.
+    void Add(size_t Items);
+    void Alloc(size_t Items);
+    void Reset();
+    void SoftReset();
+    void operator = (Array<T> &Src);
+    void Push(T Item);
+    void Append(T *Item,size_t Count);
+    T* Addr(size_t Item) {return Buffer+Item;}
+    void SetMaxSize(size_t Size) {MaxSize=Size;}
+    T* Begin() {return Buffer;}
+    T* End() {return Buffer==NULL ? NULL:Buffer+BufSize;}
+    void SetSecure() {Secure=true;}
+};
+
+
+template <class T> void Array<T>::CleanData()
+{
+  Buffer=NULL;
+  BufSize=0;
+  AllocSize=0;
+  MaxSize=0;
+  Secure=false;
+}
+
+
+template <class T> Array<T>::Array()
+{
+  CleanData();
+}
+
+
+template <class T> Array<T>::Array(size_t Size)
+{
+  CleanData();
+  Add(Size);
+}
+
+
+// Copy constructor in case we need to pass an object as value.
+template <class T> Array<T>::Array(const Array &Src)
+{
+  CleanData();
+  Alloc(Src.BufSize);
+  if (Src.BufSize!=0)
+    memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T));
+}
+
+
+template <class T> Array<T>::~Array()
+{
+  if (Buffer!=NULL)
+  {
+    if (Secure)
+      cleandata(Buffer,AllocSize*sizeof(T));
+    free(Buffer);
+  }
+}
+
+
+template <class T> inline T& Array<T>::operator [](size_t Item) const
+{
+  return Buffer[Item];
+}
+
+
+template <class T> inline T* Array<T>::operator +(size_t Pos)
+{
+  return Buffer+Pos;
+}
+
+
+template <class T> inline size_t Array<T>::Size()
+{
+  return BufSize;
+}
+
+
+template <class T> void Array<T>::Add(size_t Items)
+{
+  BufSize+=Items;
+  if (BufSize>AllocSize)
+  {
+    if (MaxSize!=0 && BufSize>MaxSize)
+    {
+      ErrHandler.GeneralErrMsg(L"Maximum allowed array size (%u) is exceeded",MaxSize);
+      ErrHandler.MemoryError();
+    }
+
+    size_t Suggested=AllocSize+AllocSize/4+32;
+    size_t NewSize=Max(BufSize,Suggested);
+
+    T *NewBuffer;
+    if (Secure)
+    {
+      NewBuffer=(T *)malloc(NewSize*sizeof(T));
+      if (NewBuffer==NULL)
+        ErrHandler.MemoryError();
+      if (Buffer!=NULL)
+      {
+        memcpy(NewBuffer,Buffer,AllocSize*sizeof(T));
+        cleandata(Buffer,AllocSize*sizeof(T));
+        free(Buffer);
+      }
+    }
+    else
+    {
+      NewBuffer=(T *)realloc(Buffer,NewSize*sizeof(T));
+      if (NewBuffer==NULL)
+        ErrHandler.MemoryError();
+    }
+    Buffer=NewBuffer;
+    AllocSize=NewSize;
+  }
+}
+
+
+template <class T> void Array<T>::Alloc(size_t Items)
+{
+  if (Items>AllocSize)
+    Add(Items-BufSize);
+  else
+    BufSize=Items;
+}
+
+
+template <class T> void Array<T>::Reset()
+{
+  if (Buffer!=NULL)
+  {
+    free(Buffer);
+    Buffer=NULL;
+  }
+  BufSize=0;
+  AllocSize=0;
+}
+
+
+// Reset buffer size, but preserve already allocated memory if any,
+// so we can reuse it without wasting time to allocation.
+template <class T> void Array<T>::SoftReset()
+{
+  BufSize=0;
+}
+
+
+template <class T> void Array<T>::operator =(Array<T> &Src)
+{
+  Reset();
+  Alloc(Src.BufSize);
+  if (Src.BufSize!=0)
+    memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T));
+}
+
+
+template <class T> void Array<T>::Push(T Item)
+{
+  Add(1);
+  (*this)[Size()-1]=Item;
+}
+
+
+template <class T> void Array<T>::Append(T *Items,size_t Count)
+{
+  size_t CurSize=Size();
+  Add(Count);
+  memcpy(Buffer+CurSize,Items,Count*sizeof(T));
+}
+
+#endif
diff --git a/third_party/unrar/src/blake2s.cpp b/third_party/unrar/src/blake2s.cpp
new file mode 100644
index 0000000..317603d
--- /dev/null
+++ b/third_party/unrar/src/blake2s.cpp
@@ -0,0 +1,183 @@
+// Based on public domain code written in 2012 by Samuel Neves
+
+#include "rar.hpp"
+
+#ifdef USE_SSE
+#include "blake2s_sse.cpp"
+#endif
+
+static void blake2s_init_param( blake2s_state *S, uint32 node_offset, uint32 node_depth);
+static void blake2s_update( blake2s_state *S, const byte *in, size_t inlen );
+static void blake2s_final( blake2s_state *S, byte *digest );
+
+#include "blake2sp.cpp"
+
+static const uint32 blake2s_IV[8] =
+{
+  0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
+  0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
+};
+
+static const byte blake2s_sigma[10][16] =
+{
+  {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15 } ,
+  { 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3 } ,
+  { 11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4 } ,
+  {  7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8 } ,
+  {  9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13 } ,
+  {  2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9 } ,
+  { 12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11 } ,
+  { 13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10 } ,
+  {  6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5 } ,
+  { 10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0 } ,
+};
+
+static inline void blake2s_set_lastnode( blake2s_state *S )
+{
+  S->f[1] = ~0U;
+}
+
+
+/* Some helper functions, not necessarily useful */
+static inline void blake2s_set_lastblock( blake2s_state *S )
+{
+  if( S->last_node ) blake2s_set_lastnode( S );
+
+  S->f[0] = ~0U;
+}
+
+
+static inline void blake2s_increment_counter( blake2s_state *S, const uint32 inc )
+{
+  S->t[0] += inc;
+  S->t[1] += ( S->t[0] < inc );
+}
+
+
+/* init2 xors IV with input parameter block */
+void blake2s_init_param( blake2s_state *S, uint32 node_offset, uint32 node_depth)
+{
+#ifdef USE_SSE
+  if (_SSE_Version>=SSE_SSE2)
+    blake2s_init_sse();
+#endif
+
+  S->init(); // Clean data.
+  for( int i = 0; i < 8; ++i )
+    S->h[i] = blake2s_IV[i];
+
+  S->h[0] ^= 0x02080020; // We use BLAKE2sp parameters block.
+  S->h[2] ^= node_offset;
+  S->h[3] ^= (node_depth<<16)|0x20000000;
+}
+
+
+#define G(r,i,m,a,b,c,d) \
+  a = a + b + m[blake2s_sigma[r][2*i+0]]; \
+  d = rotr32(d ^ a, 16); \
+  c = c + d; \
+  b = rotr32(b ^ c, 12); \
+  a = a + b + m[blake2s_sigma[r][2*i+1]]; \
+  d = rotr32(d ^ a, 8); \
+  c = c + d; \
+  b = rotr32(b ^ c, 7);
+
+
+static void blake2s_compress( blake2s_state *S, const byte block[BLAKE2S_BLOCKBYTES] )
+{
+  uint32 m[16];
+  uint32 v[16];
+
+  for( size_t i = 0; i < 16; ++i )
+    m[i] = RawGet4( block + i * 4 );
+
+  for( size_t i = 0; i < 8; ++i )
+    v[i] = S->h[i];
+
+  v[ 8] = blake2s_IV[0];
+  v[ 9] = blake2s_IV[1];
+  v[10] = blake2s_IV[2];
+  v[11] = blake2s_IV[3];
+  v[12] = S->t[0] ^ blake2s_IV[4];
+  v[13] = S->t[1] ^ blake2s_IV[5];
+  v[14] = S->f[0] ^ blake2s_IV[6];
+  v[15] = S->f[1] ^ blake2s_IV[7];
+
+  for ( uint r = 0; r <= 9; ++r ) // No gain on i7 if unrolled, but exe size grows.
+  {
+    G(r,0,m,v[ 0],v[ 4],v[ 8],v[12]);
+    G(r,1,m,v[ 1],v[ 5],v[ 9],v[13]);
+    G(r,2,m,v[ 2],v[ 6],v[10],v[14]);
+    G(r,3,m,v[ 3],v[ 7],v[11],v[15]);
+    G(r,4,m,v[ 0],v[ 5],v[10],v[15]);
+    G(r,5,m,v[ 1],v[ 6],v[11],v[12]);
+    G(r,6,m,v[ 2],v[ 7],v[ 8],v[13]);
+    G(r,7,m,v[ 3],v[ 4],v[ 9],v[14]);
+  }
+
+  for( size_t i = 0; i < 8; ++i )
+    S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
+}
+
+
+void blake2s_update( blake2s_state *S, const byte *in, size_t inlen )
+{
+  while( inlen > 0 )
+  {
+    size_t left = S->buflen;
+    size_t fill = 2 * BLAKE2S_BLOCKBYTES - left;
+
+    if( inlen > fill )
+    {
+      memcpy( S->buf + left, in, fill ); // Fill buffer
+      S->buflen += fill;
+      blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES );
+
+#ifdef USE_SSE
+#ifdef _WIN_32 // We use SSSE3 _mm_shuffle_epi8 only in x64 mode.
+      if (_SSE_Version>=SSE_SSE2)
+#else
+      if (_SSE_Version>=SSE_SSSE3)
+#endif
+        blake2s_compress_sse( S, S->buf );
+      else
+        blake2s_compress( S, S->buf ); // Compress
+#else
+      blake2s_compress( S, S->buf ); // Compress
+#endif
+      
+      memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES ); // Shift buffer left
+      S->buflen -= BLAKE2S_BLOCKBYTES;
+      in += fill;
+      inlen -= fill;
+    }
+    else // inlen <= fill
+    {
+      memcpy( S->buf + left, in, (size_t)inlen );
+      S->buflen += (size_t)inlen; // Be lazy, do not compress
+      in += inlen;
+      inlen = 0;
+    }
+  }
+}
+
+
+void blake2s_final( blake2s_state *S, byte *digest )
+{
+  if( S->buflen > BLAKE2S_BLOCKBYTES )
+  {
+    blake2s_increment_counter( S, BLAKE2S_BLOCKBYTES );
+    blake2s_compress( S, S->buf );
+    S->buflen -= BLAKE2S_BLOCKBYTES;
+    memcpy( S->buf, S->buf + BLAKE2S_BLOCKBYTES, S->buflen );
+  }
+
+  blake2s_increment_counter( S, ( uint32 )S->buflen );
+  blake2s_set_lastblock( S );
+  memset( S->buf + S->buflen, 0, 2 * BLAKE2S_BLOCKBYTES - S->buflen ); /* Padding */
+  blake2s_compress( S, S->buf );
+
+  for( int i = 0; i < 8; ++i ) /* Output full hash  */
+    RawPut4( S->h[i], digest + 4 * i );
+}
+
diff --git a/third_party/unrar/src/blake2s.hpp b/third_party/unrar/src/blake2s.hpp
new file mode 100644
index 0000000..7dd715713
--- /dev/null
+++ b/third_party/unrar/src/blake2s.hpp
@@ -0,0 +1,101 @@
+// Based on public domain code written in 2012 by Samuel Neves
+#ifndef _RAR_BLAKE2_
+#define _RAR_BLAKE2_
+
+#define BLAKE2_DIGEST_SIZE 32
+
+enum blake2s_constant
+{
+  BLAKE2S_BLOCKBYTES = 64,
+  BLAKE2S_OUTBYTES   = 32
+};
+
+
+// Alignment to 64 improves performance of both SSE and non-SSE versions.
+// Alignment to n*16 is required for SSE version, so we selected 64.
+// We use the custom alignment scheme instead of __declspec(align(x)),
+// because it is less compiler dependent. Also the compiler directive
+// does not help if structure is a member of class allocated through
+// 'new' operator.
+struct blake2s_state
+{
+  enum { BLAKE_ALIGNMENT = 64 };
+
+  // buffer and uint32 h[8], t[2], f[2];
+  enum { BLAKE_DATA_SIZE = 48 + 2 * BLAKE2S_BLOCKBYTES };
+
+  byte ubuf[BLAKE_DATA_SIZE + BLAKE_ALIGNMENT];
+
+  byte   *buf;       // byte   buf[2 * BLAKE2S_BLOCKBYTES].
+  uint32 *h, *t, *f; // uint32 h[8], t[2], f[2].
+
+  size_t   buflen;
+  byte  last_node;
+
+  blake2s_state()
+  {
+    set_pointers();
+  }
+
+  // Required when we declare and assign in the same command.
+  blake2s_state(blake2s_state &st)
+  {
+    set_pointers();
+    *this=st;
+  }
+
+  void set_pointers()
+  {
+    // Set aligned pointers. Must be done in constructor, not in Init(),
+    // so assignments like 'blake2sp_state res=blake2ctx' work correctly
+    // even if blake2sp_init is not called for 'res'.
+    buf = (byte *) ALIGN_VALUE(ubuf, BLAKE_ALIGNMENT);
+    h   = (uint32 *) (buf + 2 * BLAKE2S_BLOCKBYTES);
+    t   = h + 8;
+    f   = t + 2;
+  }
+
+  void init()
+  {
+    memset( ubuf, 0, sizeof( ubuf ) );
+    buflen = 0;
+    last_node = 0;
+  }
+
+  // Since we use pointers, the default = would work incorrectly.
+  blake2s_state& operator = (blake2s_state &st)
+  {
+    if (this != &st)
+    {
+      memcpy(buf, st.buf, BLAKE_DATA_SIZE);
+      buflen = st.buflen;
+      last_node = st.last_node;
+    }
+    return *this;
+  }
+};
+
+
+#ifdef RAR_SMP
+class ThreadPool;
+#endif
+
+struct blake2sp_state
+{
+  blake2s_state S[8];
+  blake2s_state R;
+  byte buf[8 * BLAKE2S_BLOCKBYTES];
+  size_t buflen;
+
+#ifdef RAR_SMP
+  ThreadPool *ThPool;
+  uint MaxThreads;
+#endif
+};
+
+void blake2sp_init( blake2sp_state *S );
+void blake2sp_update( blake2sp_state *S, const byte *in, size_t inlen );
+void blake2sp_final( blake2sp_state *S, byte *digest );
+
+#endif
+
diff --git a/third_party/unrar/src/blake2s_sse.cpp b/third_party/unrar/src/blake2s_sse.cpp
new file mode 100644
index 0000000..1a02f21
--- /dev/null
+++ b/third_party/unrar/src/blake2s_sse.cpp
@@ -0,0 +1,129 @@
+// Based on public domain code written in 2012 by Samuel Neves
+
+extern const byte blake2s_sigma[10][16];
+
+// Initialization vector.
+static __m128i blake2s_IV_0_3, blake2s_IV_4_7;
+
+#ifdef _WIN_64
+// Constants for cyclic rotation. Used in 64-bit mode in mm_rotr_epi32 macro.
+static __m128i crotr8, crotr16;
+#endif
+
+static void blake2s_init_sse()
+{
+  // We cannot initialize these 128 bit variables in place when declaring
+  // them globally, because global scope initialization is performed before
+  // our SSE check and it would make code incompatible with older non-SSE2
+  // CPUs. Also we cannot initialize them as static inside of function
+  // using these variables, because SSE static initialization is not thread
+  // safe: first thread starts initialization and sets "init done" flag even
+  // if it is not done yet, second thread can attempt to access half-init
+  // SSE data. So we moved init code here.
+
+  blake2s_IV_0_3 = _mm_setr_epi32( 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A );
+  blake2s_IV_4_7 = _mm_setr_epi32( 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 );
+
+#ifdef _WIN_64
+  crotr8 = _mm_set_epi8( 12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1 );
+  crotr16 = _mm_set_epi8( 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2 );
+#endif
+}
+
+
+#define LOAD(p)  _mm_load_si128( (__m128i *)(p) )
+#define STORE(p,r) _mm_store_si128((__m128i *)(p), r)
+
+#ifdef _WIN_32
+// 32-bit mode has less SSE2 registers and in MSVC2008 it is more efficient
+// to not use _mm_shuffle_epi8 here.
+#define mm_rotr_epi32(r, c) ( \
+              _mm_xor_si128(_mm_srli_epi32( (r), c ),_mm_slli_epi32( (r), 32-c )) )
+#else
+#define mm_rotr_epi32(r, c) ( \
+                c==8 ? _mm_shuffle_epi8(r,crotr8) \
+              : c==16 ? _mm_shuffle_epi8(r,crotr16) \
+              : _mm_xor_si128(_mm_srli_epi32( (r), c ),_mm_slli_epi32( (r), 32-c )) )
+#endif
+
+
+#define G1(row1,row2,row3,row4,buf) \
+  row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \
+  row4 = _mm_xor_si128( row4, row1 ); \
+  row4 =  mm_rotr_epi32(row4, 16); \
+  row3 = _mm_add_epi32( row3, row4 );   \
+  row2 = _mm_xor_si128( row2, row3 ); \
+  row2 =  mm_rotr_epi32(row2, 12);
+
+#define G2(row1,row2,row3,row4,buf) \
+  row1 = _mm_add_epi32( _mm_add_epi32( row1, buf), row2 ); \
+  row4 = _mm_xor_si128( row4, row1 ); \
+  row4 =  mm_rotr_epi32(row4, 8); \
+  row3 = _mm_add_epi32( row3, row4 );   \
+  row2 = _mm_xor_si128( row2, row3 ); \
+  row2 =  mm_rotr_epi32(row2, 7);
+
+#define DIAGONALIZE(row1,row2,row3,row4) \
+  row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(2,1,0,3) ); \
+  row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \
+  row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(0,3,2,1) );
+
+#define UNDIAGONALIZE(row1,row2,row3,row4) \
+  row4 = _mm_shuffle_epi32( row4, _MM_SHUFFLE(0,3,2,1) ); \
+  row3 = _mm_shuffle_epi32( row3, _MM_SHUFFLE(1,0,3,2) ); \
+  row2 = _mm_shuffle_epi32( row2, _MM_SHUFFLE(2,1,0,3) );
+
+#ifdef _WIN_64
+  // MSVC 2008 in x64 mode expands _mm_set_epi32 to store to stack and load
+  // from stack operations, which are slower than this code.
+  #define _mm_set_epi32(i3,i2,i1,i0) \
+    _mm_unpacklo_epi32(_mm_unpacklo_epi32(_mm_cvtsi32_si128(i0),_mm_cvtsi32_si128(i2)), \
+                       _mm_unpacklo_epi32(_mm_cvtsi32_si128(i1),_mm_cvtsi32_si128(i3)))
+#endif
+
+// Original BLAKE2 SSE4.1 message loading code was a little slower in x86 mode
+// and about the same in x64 mode in our test. Perhaps depends on compiler.
+// We also tried _mm_i32gather_epi32 and _mm256_i32gather_epi32 AVX2 gather
+// instructions here, but they did not show any speed gain on i7-6700K.
+#define SSE_ROUND(m,row,r) \
+{ \
+  __m128i buf; \
+  buf=_mm_set_epi32(m[blake2s_sigma[r][6]],m[blake2s_sigma[r][4]],m[blake2s_sigma[r][2]],m[blake2s_sigma[r][0]]); \
+  G1(row[0],row[1],row[2],row[3],buf); \
+  buf=_mm_set_epi32(m[blake2s_sigma[r][7]],m[blake2s_sigma[r][5]],m[blake2s_sigma[r][3]],m[blake2s_sigma[r][1]]); \
+  G2(row[0],row[1],row[2],row[3],buf); \
+  DIAGONALIZE(row[0],row[1],row[2],row[3]); \
+  buf=_mm_set_epi32(m[blake2s_sigma[r][14]],m[blake2s_sigma[r][12]],m[blake2s_sigma[r][10]],m[blake2s_sigma[r][8]]); \
+  G1(row[0],row[1],row[2],row[3],buf); \
+  buf=_mm_set_epi32(m[blake2s_sigma[r][15]],m[blake2s_sigma[r][13]],m[blake2s_sigma[r][11]],m[blake2s_sigma[r][9]]); \
+  G2(row[0],row[1],row[2],row[3],buf); \
+  UNDIAGONALIZE(row[0],row[1],row[2],row[3]); \
+}
+
+
+static int blake2s_compress_sse( blake2s_state *S, const byte block[BLAKE2S_BLOCKBYTES] )
+{
+  __m128i row[4];
+  __m128i ff0, ff1;
+  
+  const uint32  *m = ( uint32 * )block;
+
+  row[0] = ff0 = LOAD( &S->h[0] );
+  row[1] = ff1 = LOAD( &S->h[4] );
+
+  row[2] = blake2s_IV_0_3;
+  row[3] = _mm_xor_si128( blake2s_IV_4_7, LOAD( &S->t[0] ) );
+  SSE_ROUND( m, row, 0 );
+  SSE_ROUND( m, row, 1 );
+  SSE_ROUND( m, row, 2 );
+  SSE_ROUND( m, row, 3 );
+  SSE_ROUND( m, row, 4 );
+  SSE_ROUND( m, row, 5 );
+  SSE_ROUND( m, row, 6 );
+  SSE_ROUND( m, row, 7 );
+  SSE_ROUND( m, row, 8 );
+  SSE_ROUND( m, row, 9 );
+  STORE( &S->h[0], _mm_xor_si128( ff0, _mm_xor_si128( row[0], row[2] ) ) );
+  STORE( &S->h[4], _mm_xor_si128( ff1, _mm_xor_si128( row[1], row[3] ) ) );
+  return 0;
+}
diff --git a/third_party/unrar/src/blake2sp.cpp b/third_party/unrar/src/blake2sp.cpp
new file mode 100644
index 0000000..da645883
--- /dev/null
+++ b/third_party/unrar/src/blake2sp.cpp
@@ -0,0 +1,153 @@
+/*
+   BLAKE2 reference source code package - reference C implementations
+
+   Written in 2012 by Samuel Neves <sneves@dei.uc.pt>
+
+   To the extent possible under law, the author(s) have dedicated all copyright
+   and related and neighboring rights to this software to the public domain
+   worldwide. This software is distributed without any warranty.
+
+   You should have received a copy of the CC0 Public Domain Dedication along with
+   this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+*/
+
+#define PARALLELISM_DEGREE 8
+
+void blake2sp_init( blake2sp_state *S )
+{
+  memset( S->buf, 0, sizeof( S->buf ) );
+  S->buflen = 0;
+
+  blake2s_init_param( &S->R, 0, 1 ); // Init root.
+
+  for( uint i = 0; i < PARALLELISM_DEGREE; ++i )
+    blake2s_init_param( &S->S[i], i, 0 ); // Init leaf.
+
+  S->R.last_node = 1;
+  S->S[PARALLELISM_DEGREE - 1].last_node = 1;
+}
+
+
+struct Blake2ThreadData
+{
+  void Update();
+  blake2s_state *S;
+  const byte *in;
+  size_t inlen;
+};
+
+
+void Blake2ThreadData::Update()
+{
+  size_t inlen__ = inlen;
+  const byte *in__ = ( const byte * )in;
+
+  while( inlen__ >= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES )
+  {
+#ifdef USE_SSE
+    // We gain 5% in i7 SSE mode by prefetching next data block.
+    if (_SSE_Version>=SSE_SSE && inlen__ >= 2 * PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES)
+      _mm_prefetch((char*)(in__ +  PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES), _MM_HINT_T0);
+#endif
+    blake2s_update( S, in__, BLAKE2S_BLOCKBYTES );
+    in__ += PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
+    inlen__ -= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
+  }
+}
+
+#ifdef RAR_SMP
+THREAD_PROC(Blake2Thread)
+{
+  Blake2ThreadData *td=(Blake2ThreadData *)Data;
+  td->Update();
+}
+#endif
+
+
+void blake2sp_update( blake2sp_state *S, const byte *in, size_t inlen )
+{
+  size_t left = S->buflen;
+  size_t fill = sizeof( S->buf ) - left;
+
+  if( left && inlen >= fill )
+  {
+    memcpy( S->buf + left, in, fill );
+
+    for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+      blake2s_update( &S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, BLAKE2S_BLOCKBYTES );
+
+    in += fill;
+    inlen -= fill;
+    left = 0;
+  }
+
+  Blake2ThreadData btd_array[PARALLELISM_DEGREE];
+
+#ifdef RAR_SMP
+  uint ThreadNumber = inlen < 0x1000 ? 1 : S->MaxThreads;
+  
+  if (ThreadNumber==6 || ThreadNumber==7) // 6 and 7 threads work slower than 4 here.
+    ThreadNumber=4;
+#else
+  uint ThreadNumber=1;
+#endif
+
+  for (size_t id__=0;id__<PARALLELISM_DEGREE;)
+  {
+    for (uint Thread=0;Thread<ThreadNumber && id__<PARALLELISM_DEGREE;Thread++)
+    {
+      Blake2ThreadData *btd=btd_array+Thread;
+
+      btd->inlen = inlen;
+      btd->in = in + id__ * BLAKE2S_BLOCKBYTES;
+      btd->S = &S->S[id__];
+  
+#ifdef RAR_SMP
+      if (ThreadNumber>1)
+        S->ThPool->AddTask(Blake2Thread,(void*)btd);
+      else
+        btd->Update();
+#else
+      btd->Update();
+#endif
+      id__++;
+    }
+#ifdef RAR_SMP
+    if (S->ThPool!=NULL) // Can be NULL in -mt1 mode.
+      S->ThPool->WaitDone();
+#endif // RAR_SMP
+  }
+
+  in += inlen - inlen % ( PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES );
+  inlen %= PARALLELISM_DEGREE * BLAKE2S_BLOCKBYTES;
+
+  if( inlen > 0 )
+    memcpy( S->buf + left, in, (size_t)inlen );
+
+  S->buflen = left + (size_t)inlen;
+}
+
+
+void blake2sp_final( blake2sp_state *S, byte *digest )
+{
+  byte hash[PARALLELISM_DEGREE][BLAKE2S_OUTBYTES];
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+  {
+    if( S->buflen > i * BLAKE2S_BLOCKBYTES )
+    {
+      size_t left = S->buflen - i * BLAKE2S_BLOCKBYTES;
+
+      if( left > BLAKE2S_BLOCKBYTES ) left = BLAKE2S_BLOCKBYTES;
+
+      blake2s_update( &S->S[i], S->buf + i * BLAKE2S_BLOCKBYTES, left );
+    }
+
+    blake2s_final( &S->S[i], hash[i] );
+  }
+
+  for( size_t i = 0; i < PARALLELISM_DEGREE; ++i )
+    blake2s_update( &S->R, hash[i], BLAKE2S_OUTBYTES );
+
+  blake2s_final( &S->R, digest );
+}
diff --git a/third_party/unrar/src/cmddata.cpp b/third_party/unrar/src/cmddata.cpp
new file mode 100644
index 0000000..baa3eb6
--- /dev/null
+++ b/third_party/unrar/src/cmddata.cpp
@@ -0,0 +1,1404 @@
+#include "rar.hpp"
+
+CommandData::CommandData()
+{
+  Init();
+}
+
+
+void CommandData::Init()
+{
+  RAROptions::Init();
+
+  *Command=0;
+  *ArcName=0;
+  FileLists=false;
+  NoMoreSwitches=false;
+
+  ListMode=RCLM_AUTO;
+
+  BareOutput=false;
+
+
+  FileArgs.Reset();
+  ExclArgs.Reset();
+  InclArgs.Reset();
+  StoreArgs.Reset();
+  ArcNames.Reset();
+  NextVolSizes.Reset();
+}
+
+
+// Return the pointer to next position in the string and store dynamically
+// allocated command line parameter in Par.
+static const wchar *AllocCmdParam(const wchar *CmdLine,wchar **Par)
+{
+  const wchar *NextCmd=GetCmdParam(CmdLine,NULL,0);
+  if (NextCmd==NULL)
+    return NULL;
+  size_t ParSize=NextCmd-CmdLine+2; // Parameter size including the trailing zero.
+  *Par=(wchar *)malloc(ParSize*sizeof(wchar));
+  if (*Par==NULL)
+    return NULL;
+  return GetCmdParam(CmdLine,*Par,ParSize);
+}
+
+
+#if !defined(SFX_MODULE)
+void CommandData::ParseCommandLine(bool Preprocess,int argc, char *argv[])
+{
+  *Command=0;
+  NoMoreSwitches=false;
+#ifdef CUSTOM_CMDLINE_PARSER
+  // In Windows we may prefer to implement our own command line parser
+  // to avoid replacing \" by " in standard parser. Such replacing corrupts
+  // destination paths like "dest path\" in extraction commands.
+  // Also our own parser is Unicode compatible.
+  const wchar *CmdLine=GetCommandLine();
+
+  wchar *Par;
+  for (bool FirstParam=true;;FirstParam=false)
+  {
+    if ((CmdLine=AllocCmdParam(CmdLine,&Par))==NULL)
+      break;
+    if (!FirstParam) // First parameter is the executable name.
+      if (Preprocess)
+        PreprocessArg(Par);
+      else
+        ParseArg(Par);
+    free(Par);
+  }
+#else
+  Array<wchar> Arg;
+  for (int I=1;I<argc;I++)
+  {
+    Arg.Alloc(strlen(argv[I])+1);
+    CharToWide(argv[I],&Arg[0],Arg.Size());
+    if (Preprocess)
+      PreprocessArg(&Arg[0]);
+    else
+      ParseArg(&Arg[0]);
+  }
+#endif
+  if (!Preprocess)
+    ParseDone();
+}
+#endif
+
+
+#if !defined(SFX_MODULE)
+void CommandData::ParseArg(wchar *Arg)
+{
+  if (IsSwitch(*Arg) && !NoMoreSwitches)
+    if (Arg[1]=='-' && Arg[2]==0)
+      NoMoreSwitches=true;
+    else
+      ProcessSwitch(Arg+1);
+  else
+    if (*Command==0)
+    {
+      wcsncpyz(Command,Arg,ASIZE(Command));
+
+
+      *Command=toupperw(*Command);
+      // 'I' and 'S' commands can contain case sensitive strings after
+      // the first character, so we must not modify their case.
+      // 'S' can contain SFX name, which case is important in Unix.
+      if (*Command!='I' && *Command!='S')
+        wcsupper(Command);
+    }
+    else
+      if (*ArcName==0)
+        wcsncpyz(ArcName,Arg,ASIZE(ArcName));
+      else
+      {
+        // Check if last character is the path separator.
+        size_t Length=wcslen(Arg);
+        wchar EndChar=Length==0 ? 0:Arg[Length-1];
+        bool EndSeparator=IsDriveDiv(EndChar) || IsPathDiv(EndChar);
+
+        wchar CmdChar=toupperw(*Command);
+        bool Add=wcschr(L"AFUM",CmdChar)!=NULL;
+        bool Extract=CmdChar=='X' || CmdChar=='E';
+        if (EndSeparator && !Add)
+          wcsncpyz(ExtrPath,Arg,ASIZE(ExtrPath));
+        else
+          if ((Add || CmdChar=='T') && (*Arg!='@' || ListMode==RCLM_REJECT_LISTS))
+            FileArgs.AddString(Arg);
+          else
+          {
+            FindData FileData;
+            bool Found=FindFile::FastFind(Arg,&FileData);
+            if ((!Found || ListMode==RCLM_ACCEPT_LISTS) && 
+                ListMode!=RCLM_REJECT_LISTS && *Arg=='@' && !IsWildcard(Arg))
+            {
+              FileLists=true;
+
+              ReadTextFile(Arg+1,&FileArgs,false,true,FilelistCharset,true,true,true);
+
+            }
+            else
+              if (Found && FileData.IsDir && Extract && *ExtrPath==0)
+              {
+                wcsncpyz(ExtrPath,Arg,ASIZE(ExtrPath));
+                AddEndSlash(ExtrPath,ASIZE(ExtrPath));
+              }
+              else
+                FileArgs.AddString(Arg);
+          }
+      }
+}
+#endif
+
+
+void CommandData::ParseDone()
+{
+  if (FileArgs.ItemsCount()==0 && !FileLists)
+    FileArgs.AddString(MASKALL);
+  wchar CmdChar=toupperw(Command[0]);
+  bool Extract=CmdChar=='X' || CmdChar=='E' || CmdChar=='P';
+  if (Test && Extract)
+    Test=false;        // Switch '-t' is senseless for 'X', 'E', 'P' commands.
+
+  // Suppress the copyright message and final end of line for 'lb' and 'vb'.
+  if ((CmdChar=='L' || CmdChar=='V') && Command[1]=='B')
+    BareOutput=true;
+}
+
+
+#if !defined(SFX_MODULE)
+void CommandData::ParseEnvVar()
+{
+  char *EnvStr=getenv("RAR");
+  if (EnvStr!=NULL)
+  {
+    Array<wchar> EnvStrW(strlen(EnvStr)+1);
+    CharToWide(EnvStr,&EnvStrW[0],EnvStrW.Size());
+    ProcessSwitchesString(&EnvStrW[0]);
+  }
+}
+#endif
+
+
+
+#if !defined(SFX_MODULE)
+// Preprocess those parameters, which must be processed before the rest of
+// command line. Return 'false' to stop further processing.
+void CommandData::PreprocessArg(const wchar *Arg)
+{
+  if (IsSwitch(Arg[0]) && !NoMoreSwitches)
+  {
+    Arg++;
+    if (Arg[0]=='-' && Arg[1]==0) // Switch "--".
+      NoMoreSwitches=true;
+    if (wcsicomp(Arg,L"cfg-")==0)
+      ConfigDisabled=true;
+    if (wcsnicomp(Arg,L"ilog",4)==0)
+    {
+      // Ensure that correct log file name is already set
+      // if we need to report an error when processing the command line.
+      ProcessSwitch(Arg);
+      InitLogOptions(LogName,ErrlogCharset);
+    }
+    if (wcsnicomp(Arg,L"sc",2)==0)
+    {
+      // Process -sc before reading any file lists.
+      ProcessSwitch(Arg);
+      if (*LogName!=0)
+        InitLogOptions(LogName,ErrlogCharset);
+    }
+  }
+  else
+    if (*Command==0)
+      wcsncpy(Command,Arg,ASIZE(Command)); // Need for rar.ini.
+}
+#endif
+
+
+#if !defined(SFX_MODULE)
+void CommandData::ReadConfig()
+{
+  StringList List;
+  if (ReadTextFile(DefConfigName,&List,true))
+  {
+    wchar *Str;
+    while ((Str=List.GetString())!=NULL)
+    {
+      while (IsSpace(*Str))
+        Str++;
+      if (wcsnicomp(Str,L"switches=",9)==0)
+        ProcessSwitchesString(Str+9);
+      if (*Command!=0)
+      {
+        wchar Cmd[16];
+        wcsncpyz(Cmd,Command,ASIZE(Cmd));
+        wchar C0=toupperw(Cmd[0]);
+        wchar C1=toupperw(Cmd[1]);
+        if (C0=='I' || C0=='L' || C0=='M' || C0=='S' || C0=='V')
+          Cmd[1]=0;
+        if (C0=='R' && (C1=='R' || C1=='V'))
+          Cmd[2]=0;
+        wchar SwName[16+ASIZE(Cmd)];
+        swprintf(SwName,ASIZE(SwName),L"switches_%s=",Cmd);
+        size_t Length=wcslen(SwName);
+        if (wcsnicomp(Str,SwName,Length)==0)
+          ProcessSwitchesString(Str+Length);
+      }
+    }
+  }
+}
+#endif
+
+
+#if !defined(SFX_MODULE)
+void CommandData::ProcessSwitchesString(const wchar *Str)
+{
+  wchar *Par;
+  while ((Str=AllocCmdParam(Str,&Par))!=NULL)
+  {
+    if (IsSwitch(*Par))
+      ProcessSwitch(Par+1);
+    free(Par);
+  }
+}
+#endif
+
+
+#if !defined(SFX_MODULE)
+void CommandData::ProcessSwitch(const wchar *Switch)
+{
+
+  switch(toupperw(Switch[0]))
+  {
+    case '@':
+      ListMode=Switch[1]=='+' ? RCLM_ACCEPT_LISTS:RCLM_REJECT_LISTS;
+      break;
+    case 'A':
+      switch(toupperw(Switch[1]))
+      {
+        case 'C':
+          ClearArc=true;
+          break;
+        case 'D':
+          AppendArcNameToPath=true;
+          break;
+#ifndef SFX_MODULE
+        case 'G':
+          if (Switch[2]=='-' && Switch[3]==0)
+            GenerateArcName=0;
+          else
+          {
+            GenerateArcName=true;
+            wcsncpyz(GenerateMask,Switch+2,ASIZE(GenerateMask));
+          }
+          break;
+#endif
+        case 'I':
+          IgnoreGeneralAttr=true;
+          break;
+        case 'N': // Reserved for archive name.
+          break;
+        case 'O':
+          AddArcOnly=true;
+          break;
+        case 'P':
+          wcscpy(ArcPath,Switch+2);
+          break;
+        case 'S':
+          SyncFiles=true;
+          break;
+        default:
+          BadSwitch(Switch);
+          break;
+      }
+      break;
+    case 'C':
+      if (Switch[2]==0)
+        switch(toupperw(Switch[1]))
+        {
+          case '-':
+            DisableComment=true;
+            break;
+          case 'U':
+            ConvertNames=NAMES_UPPERCASE;
+            break;
+          case 'L':
+            ConvertNames=NAMES_LOWERCASE;
+            break;
+        }
+      break;
+    case 'D':
+      if (Switch[2]==0)
+        switch(toupperw(Switch[1]))
+        {
+          case 'S':
+            DisableSortSolid=true;
+            break;
+          case 'H':
+            OpenShared=true;
+            break;
+          case 'F':
+            DeleteFiles=true;
+            break;
+        }
+      break;
+    case 'E':
+      switch(toupperw(Switch[1]))
+      {
+        case 'P':
+          switch(Switch[2])
+          {
+            case 0:
+              ExclPath=EXCL_SKIPWHOLEPATH;
+              break;
+            case '1':
+              ExclPath=EXCL_BASEPATH;
+              break;
+            case '2':
+              ExclPath=EXCL_SAVEFULLPATH;
+              break;
+            case '3':
+              ExclPath=EXCL_ABSPATH;
+              break;
+          }
+          break;
+        default:
+          if (Switch[1]=='+')
+          {
+            InclFileAttr|=GetExclAttr(Switch+2);
+            InclAttrSet=true;
+          }
+          else
+            ExclFileAttr|=GetExclAttr(Switch+1);
+          break;
+      }
+      break;
+    case 'F':
+      if (Switch[1]==0)
+        FreshFiles=true;
+      else
+        BadSwitch(Switch);
+      break;
+    case 'H':
+      switch (toupperw(Switch[1]))
+      {
+        case 'P':
+          EncryptHeaders=true;
+          if (Switch[2]!=0)
+          {
+            Password.Set(Switch+2);
+            cleandata((void *)Switch,wcslen(Switch)*sizeof(Switch[0]));
+          }
+          else
+            if (!Password.IsSet())
+            {
+              uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password);
+              eprintf(L"\n");
+            }
+          break;
+        default :
+          BadSwitch(Switch);
+          break;
+      }
+      break;
+    case 'I':
+      if (wcsnicomp(Switch+1,L"LOG",3)==0)
+      {
+        wcsncpyz(LogName,Switch[4]!=0 ? Switch+4:DefLogName,ASIZE(LogName));
+        break;
+      }
+      if (wcsicomp(Switch+1,L"SND")==0)
+      {
+        Sound=true;
+        break;
+      }
+      if (wcsicomp(Switch+1,L"ERR")==0)
+      {
+        MsgStream=MSG_STDERR;
+        // Set it immediately when parsing the command line, so it also
+        // affects messages issued while parsing the command line.
+        SetConsoleMsgStream(MSG_STDERR);
+        break;
+      }
+      if (wcsnicomp(Switch+1,L"EML",3)==0)
+      {
+        wcsncpyz(EmailTo,Switch[4]!=0 ? Switch+4:L"@",ASIZE(EmailTo));
+        break;
+      }
+      if (wcsicomp(Switch+1,L"M")==0)
+      {
+        MoreInfo=true;
+        break;
+      }
+      if (wcsicomp(Switch+1,L"NUL")==0)
+      {
+        MsgStream=MSG_NULL;
+        SetConsoleMsgStream(MSG_NULL);
+        break;
+      }
+      if (toupperw(Switch[1])=='D')
+      {
+        for (uint I=2;Switch[I]!=0;I++)
+          switch(toupperw(Switch[I]))
+          {
+            case 'Q':
+              MsgStream=MSG_ERRONLY;
+              SetConsoleMsgStream(MSG_ERRONLY);
+              break;
+            case 'C':
+              DisableCopyright=true;
+              break;
+            case 'D':
+              DisableDone=true;
+              break;
+            case 'P':
+              DisablePercentage=true;
+              break;
+          }
+        break;
+      }
+      if (wcsnicomp(Switch+1,L"OFF",3)==0)
+      {
+        switch(Switch[4])
+        {
+          case 0:
+          case '1':
+            Shutdown=POWERMODE_OFF;
+            break;
+          case '2':
+            Shutdown=POWERMODE_HIBERNATE;
+            break;
+          case '3':
+            Shutdown=POWERMODE_SLEEP;
+            break;
+        }
+        break;
+      }
+      if (wcsicomp(Switch+1,L"VER")==0)
+      {
+        PrintVersion=true;
+        break;
+      }
+      break;
+    case 'K':
+      switch(toupperw(Switch[1]))
+      {
+        case 'B':
+          KeepBroken=true;
+          break;
+        case 0:
+          Lock=true;
+          break;
+      }
+      break;
+    case 'M':
+      switch(toupperw(Switch[1]))
+      {
+        case 'C':
+          {
+            const wchar *Str=Switch+2;
+            if (*Str=='-')
+              for (uint I=0;I<ASIZE(FilterModes);I++)
+                FilterModes[I].State=FILTER_DISABLE;
+            else
+              while (*Str!=0)
+              {
+                int Param1=0,Param2=0;
+                FilterState State=FILTER_AUTO;
+                FilterType Type=FILTER_NONE;
+                if (IsDigit(*Str))
+                {
+                  Param1=atoiw(Str);
+                  while (IsDigit(*Str))
+                    Str++;
+                }
+                if (*Str==':' && IsDigit(Str[1]))
+                {
+                  Param2=atoiw(++Str);
+                  while (IsDigit(*Str))
+                    Str++;
+                }
+                switch(toupperw(*(Str++)))
+                {
+                  case 'T': Type=FILTER_PPM;         break;
+                  case 'E': Type=FILTER_E8;          break;
+                  case 'D': Type=FILTER_DELTA;       break;
+                  case 'A': Type=FILTER_AUDIO;       break;
+                  case 'C': Type=FILTER_RGB;         break;
+                  case 'I': Type=FILTER_ITANIUM;     break;
+                  case 'R': Type=FILTER_ARM;         break;
+                }
+                if (*Str=='+' || *Str=='-')
+                  State=*(Str++)=='+' ? FILTER_FORCE:FILTER_DISABLE;
+                FilterModes[Type].State=State;
+                FilterModes[Type].Param1=Param1;
+                FilterModes[Type].Param2=Param2;
+              }
+            }
+          break;
+        case 'M':
+          break;
+        case 'D':
+          break;
+        case 'S':
+          {
+            wchar StoreNames[1024];
+            wcsncpyz(StoreNames,(Switch[2]==0 ? DefaultStoreList:Switch+2),ASIZE(StoreNames));
+            wchar *Names=StoreNames;
+            while (*Names!=0)
+            {
+              wchar *End=wcschr(Names,';');
+              if (End!=NULL)
+                *End=0;
+              if (*Names=='.')
+                Names++;
+              wchar Mask[NM];
+              if (wcspbrk(Names,L"*?.")==NULL)
+                swprintf(Mask,ASIZE(Mask),L"*.%ls",Names);
+              else
+                wcsncpyz(Mask,Names,ASIZE(Mask));
+              StoreArgs.AddString(Mask);
+              if (End==NULL)
+                break;
+              Names=End+1;
+            }
+          }
+          break;
+#ifdef RAR_SMP
+        case 'T':
+          Threads=atoiw(Switch+2);
+          if (Threads>MaxPoolThreads || Threads<1)
+            BadSwitch(Switch);
+          else
+          {
+          }
+          break;
+#endif
+        default:
+          Method=Switch[1]-'0';
+          if (Method>5 || Method<0)
+            BadSwitch(Switch);
+          break;
+      }
+      break;
+    case 'N':
+    case 'X':
+      if (Switch[1]!=0)
+      {
+        StringList *Args=toupperw(Switch[0])=='N' ? &InclArgs:&ExclArgs;
+        if (Switch[1]=='@' && !IsWildcard(Switch))
+          ReadTextFile(Switch+2,Args,false,true,FilelistCharset,true,true,true);
+        else
+          Args->AddString(Switch+1);
+      }
+      break;
+    case 'O':
+      switch(toupperw(Switch[1]))
+      {
+        case '+':
+          Overwrite=OVERWRITE_ALL;
+          break;
+        case '-':
+          Overwrite=OVERWRITE_NONE;
+          break;
+        case 0:
+          Overwrite=OVERWRITE_FORCE_ASK;
+          break;
+#ifdef _WIN_ALL
+        case 'C':
+          SetCompressedAttr=true;
+          break;
+#endif
+        case 'H':
+          SaveHardLinks=true;
+          break;
+
+
+#ifdef SAVE_LINKS
+        case 'L':
+          SaveSymLinks=true;
+          if (toupperw(Switch[2])=='A')
+            AbsoluteLinks=true;
+          break;
+#endif
+#ifdef _WIN_ALL
+        case 'N':
+          if (toupperw(Switch[2])=='I')
+            AllowIncompatNames=true;
+          break;
+#endif
+        case 'R':
+          Overwrite=OVERWRITE_AUTORENAME;
+          break;
+#ifdef _WIN_ALL
+        case 'S':
+          SaveStreams=true;
+          break;
+#endif
+        case 'W':
+          ProcessOwners=true;
+          break;
+        default :
+          BadSwitch(Switch);
+          break;
+      }
+      break;
+    case 'P':
+      if (Switch[1]==0)
+      {
+        uiGetPassword(UIPASSWORD_GLOBAL,NULL,&Password);
+        eprintf(L"\n");
+      }
+      else
+      {
+        Password.Set(Switch+1);
+        cleandata((void *)Switch,wcslen(Switch)*sizeof(Switch[0]));
+      }
+      break;
+#ifndef SFX_MODULE
+    case 'Q':
+      if (toupperw(Switch[1])=='O')
+        switch(toupperw(Switch[2]))
+        {
+          case 0:
+            QOpenMode=QOPEN_AUTO;
+            break;
+          case '-':
+            QOpenMode=QOPEN_NONE;
+            break;
+          case '+':
+            QOpenMode=QOPEN_ALWAYS;
+            break;
+          default:
+            BadSwitch(Switch);
+            break;
+        }
+      else
+        BadSwitch(Switch);
+      break;
+#endif
+    case 'R':
+      switch(toupperw(Switch[1]))
+      {
+        case 0:
+          Recurse=RECURSE_ALWAYS;
+          break;
+        case '-':
+          Recurse=RECURSE_DISABLE;
+          break;
+        case '0':
+          Recurse=RECURSE_WILDCARDS;
+          break;
+        case 'I':
+          {
+            Priority=atoiw(Switch+2);
+            if (Priority<0 || Priority>15)
+              BadSwitch(Switch);
+            const wchar *ChPtr=wcschr(Switch+2,':');
+            if (ChPtr!=NULL)
+            {
+              SleepTime=atoiw(ChPtr+1);
+              if (SleepTime>1000)
+                BadSwitch(Switch);
+              InitSystemOptions(SleepTime);
+            }
+            SetPriority(Priority);
+          }
+          break;
+      }
+      break;
+    case 'S':
+      if (IsDigit(Switch[1]))
+      {
+        Solid|=SOLID_COUNT;
+        SolidCount=atoiw(&Switch[1]);
+      }
+      else
+        switch(toupperw(Switch[1]))
+        {
+          case 0:
+            Solid|=SOLID_NORMAL;
+            break;
+          case '-':
+            Solid=SOLID_NONE;
+            break;
+          case 'E':
+            Solid|=SOLID_FILEEXT;
+            break;
+          case 'V':
+            Solid|=Switch[2]=='-' ? SOLID_VOLUME_DEPENDENT:SOLID_VOLUME_INDEPENDENT;
+            break;
+          case 'D':
+            Solid|=SOLID_VOLUME_DEPENDENT;
+            break;
+          case 'L':
+            if (IsDigit(Switch[2]))
+              FileSizeLess=atoilw(Switch+2);
+            break;
+          case 'M':
+            if (IsDigit(Switch[2]))
+              FileSizeMore=atoilw(Switch+2);
+            break;
+          case 'C':
+            {
+              bool AlreadyBad=false; // Avoid reporting "bad switch" several times.
+
+              RAR_CHARSET rch=RCH_DEFAULT;
+              switch(toupperw(Switch[2]))
+              {
+                case 'A':
+                  rch=RCH_ANSI;
+                  break;
+                case 'O':
+                  rch=RCH_OEM;
+                  break;
+                case 'U':
+                  rch=RCH_UNICODE;
+                  break;
+                case 'F':
+                  rch=RCH_UTF8;
+                  break;
+                default :
+                  BadSwitch(Switch);
+                  AlreadyBad=true;
+                  break;
+              };
+              if (!AlreadyBad)
+                if (Switch[3]==0)
+                  CommentCharset=FilelistCharset=ErrlogCharset=RedirectCharset=rch;
+                else
+                  for (uint I=3;Switch[I]!=0 && !AlreadyBad;I++)
+                    switch(toupperw(Switch[I]))
+                    {
+                      case 'C':
+                        CommentCharset=rch;
+                        break;
+                      case 'L':
+                        FilelistCharset=rch;
+                        break;
+                      case 'R':
+                        RedirectCharset=rch;
+                        break;
+                      default:
+                        BadSwitch(Switch);
+                        AlreadyBad=true;
+                        break;
+                    }
+              // Set it immediately when parsing the command line, so it also
+              // affects messages issued while parsing the command line.
+              SetConsoleRedirectCharset(RedirectCharset);
+            }
+            break;
+
+        }
+      break;
+    case 'T':
+      switch(toupperw(Switch[1]))
+      {
+        case 'K':
+          ArcTime=ARCTIME_KEEP;
+          break;
+        case 'L':
+          ArcTime=ARCTIME_LATEST;
+          break;
+        case 'O':
+          FileTimeBefore.SetAgeText(Switch+2);
+          break;
+        case 'N':
+          FileTimeAfter.SetAgeText(Switch+2);
+          break;
+        case 'B':
+          FileTimeBefore.SetIsoText(Switch+2);
+          break;
+        case 'A':
+          FileTimeAfter.SetIsoText(Switch+2);
+          break;
+        case 'S':
+          {
+            EXTTIME_MODE Mode=EXTTIME_HIGH3;
+            bool CommonMode=Switch[2]>='0' && Switch[2]<='4';
+            if (CommonMode)
+              Mode=(EXTTIME_MODE)(Switch[2]-'0');
+            if (Mode==EXTTIME_HIGH1 || Mode==EXTTIME_HIGH2) // '2' and '3' not supported anymore.
+              Mode=EXTTIME_HIGH3;
+            if (Switch[2]=='-')
+              Mode=EXTTIME_NONE;
+            if (CommonMode || Switch[2]=='-' || Switch[2]=='+' || Switch[2]==0)
+              xmtime=xctime=xatime=Mode;
+            else
+            {
+              if (Switch[3]>='0' && Switch[3]<='4')
+                Mode=(EXTTIME_MODE)(Switch[3]-'0');
+              if (Mode==EXTTIME_HIGH1 || Mode==EXTTIME_HIGH2) // '2' and '3' not supported anymore.
+                Mode=EXTTIME_HIGH3;
+              if (Switch[3]=='-')
+                Mode=EXTTIME_NONE;
+              switch(toupperw(Switch[2]))
+              {
+                case 'M':
+                  xmtime=Mode;
+                  break;
+                case 'C':
+                  xctime=Mode;
+                  break;
+                case 'A':
+                  xatime=Mode;
+                  break;
+              }
+            }
+          }
+          break;
+        case '-':
+          Test=false;
+          break;
+        case 0:
+          Test=true;
+          break;
+        default:
+          BadSwitch(Switch);
+          break;
+      }
+      break;
+    case 'U':
+      if (Switch[1]==0)
+        UpdateFiles=true;
+      else
+        BadSwitch(Switch);
+      break;
+    case 'V':
+      switch(toupperw(Switch[1]))
+      {
+        case 'P':
+          VolumePause=true;
+          break;
+        case 'E':
+          if (toupperw(Switch[2])=='R')
+            VersionControl=atoiw(Switch+3)+1;
+          break;
+        case '-':
+          VolSize=0;
+          break;
+        default:
+          VolSize=VOLSIZE_AUTO; // UnRAR -v switch for list command.
+          break;
+      }
+      break;
+    case 'W':
+      wcsncpyz(TempPath,Switch+1,ASIZE(TempPath));
+      AddEndSlash(TempPath,ASIZE(TempPath));
+      break;
+    case 'Y':
+      AllYes=true;
+      break;
+    case 'Z':
+      if (Switch[1]==0)
+      {
+        // If comment file is not specified, we read data from stdin.
+        wcscpy(CommentFile,L"stdin");
+      }
+      else
+        wcsncpyz(CommentFile,Switch+1,ASIZE(CommentFile));
+      break;
+    case '?' :
+      OutHelp(RARX_SUCCESS);
+      break;
+    default :
+      BadSwitch(Switch);
+      break;
+  }
+}
+#endif
+
+
+#if !defined(SFX_MODULE)
+void CommandData::BadSwitch(const wchar *Switch)
+{
+  mprintf(St(MUnknownOption),Switch);
+  ErrHandler.Exit(RARX_USERERROR);
+}
+#endif
+
+
+void CommandData::OutTitle()
+{
+  if (BareOutput || DisableCopyright)
+    return;
+#if defined(__GNUC__) && defined(SFX_MODULE)
+  mprintf(St(MCopyrightS));
+#else
+#ifndef SILENT
+  static bool TitleShown=false;
+  if (TitleShown)
+    return;
+  TitleShown=true;
+
+  wchar Version[80];
+  if (RARVER_BETA!=0)
+    swprintf(Version,ASIZE(Version),L"%d.%02d %ls %d",RARVER_MAJOR,RARVER_MINOR,St(MBeta),RARVER_BETA);
+  else
+    swprintf(Version,ASIZE(Version),L"%d.%02d",RARVER_MAJOR,RARVER_MINOR);
+#if defined(_WIN_32) || defined(_WIN_64)
+  wcsncatz(Version,L" ",ASIZE(Version));
+#endif
+#ifdef _WIN_32
+  wcsncatz(Version,St(Mx86),ASIZE(Version));
+#endif
+#ifdef _WIN_64
+  wcsncatz(Version,St(Mx64),ASIZE(Version));
+#endif
+  if (PrintVersion)
+  {
+    mprintf(L"%s",Version);
+    exit(0);
+  }
+  mprintf(St(MUCopyright),Version,RARVER_YEAR);
+#endif
+#endif
+}
+
+
+inline bool CmpMSGID(MSGID i1,MSGID i2)
+{
+#ifdef MSGID_INT
+  return i1==i2;
+#else
+  // If MSGID is const char*, we cannot compare pointers only.
+  // Pointers to different instances of same string can differ,
+  // so we need to compare complete strings.
+  return wcscmp(i1,i2)==0;
+#endif
+}
+
+void CommandData::OutHelp(RAR_EXIT ExitCode)
+{
+#if !defined(SILENT)
+  OutTitle();
+  static MSGID Help[]={
+#ifdef SFX_MODULE
+    // Console SFX switches definition.
+    MCHelpCmd,MSHelpCmdE,MSHelpCmdT,MSHelpCmdV
+#else
+    // UnRAR switches definition.
+    MUNRARTitle1,MRARTitle2,MCHelpCmd,MCHelpCmdE,MCHelpCmdL,
+    MCHelpCmdP,MCHelpCmdT,MCHelpCmdV,MCHelpCmdX,MCHelpSw,MCHelpSwm,
+    MCHelpSwAT,MCHelpSwAC,MCHelpSwAD,MCHelpSwAG,MCHelpSwAI,MCHelpSwAP,
+    MCHelpSwCm,MCHelpSwCFGm,MCHelpSwCL,MCHelpSwCU,
+    MCHelpSwDH,MCHelpSwEP,MCHelpSwEP3,MCHelpSwF,MCHelpSwIDP,MCHelpSwIERR,
+    MCHelpSwINUL,MCHelpSwIOFF,MCHelpSwKB,MCHelpSwN,MCHelpSwNa,MCHelpSwNal,
+    MCHelpSwO,MCHelpSwOC,MCHelpSwOL,MCHelpSwOR,MCHelpSwOW,MCHelpSwP,
+    MCHelpSwPm,MCHelpSwR,MCHelpSwRI,MCHelpSwSC,MCHelpSwSL,MCHelpSwSM,
+    MCHelpSwTA,MCHelpSwTB,MCHelpSwTN,MCHelpSwTO,MCHelpSwTS,MCHelpSwU,
+    MCHelpSwVUnr,MCHelpSwVER,MCHelpSwVP,MCHelpSwX,MCHelpSwXa,MCHelpSwXal,
+    MCHelpSwY
+#endif
+  };
+
+  for (uint I=0;I<ASIZE(Help);I++)
+  {
+#ifndef SFX_MODULE
+    if (CmpMSGID(Help[I],MCHelpSwV))
+      continue;
+#ifndef _WIN_ALL
+    static MSGID Win32Only[]={
+      MCHelpSwIEML,MCHelpSwVD,MCHelpSwAO,MCHelpSwOS,MCHelpSwIOFF,
+      MCHelpSwEP2,MCHelpSwOC,MCHelpSwONI,MCHelpSwDR,MCHelpSwRI
+    };
+    bool Found=false;
+    for (int J=0;J<sizeof(Win32Only)/sizeof(Win32Only[0]);J++)
+      if (CmpMSGID(Help[I],Win32Only[J]))
+      {
+        Found=true;
+        break;
+      }
+    if (Found)
+      continue;
+#endif
+#if !defined(_UNIX) && !defined(_WIN_ALL)
+    if (CmpMSGID(Help[I],MCHelpSwOW))
+      continue;
+#endif
+#if !defined(_WIN_ALL) && !defined(_EMX)
+    if (CmpMSGID(Help[I],MCHelpSwAC))
+      continue;
+#endif
+#ifndef SAVE_LINKS
+    if (CmpMSGID(Help[I],MCHelpSwOL))
+      continue;
+#endif
+#ifndef RAR_SMP
+    if (CmpMSGID(Help[I],MCHelpSwMT))
+      continue;
+#endif
+#endif
+    mprintf(St(Help[I]));
+  }
+  mprintf(L"\n");
+  ErrHandler.Exit(ExitCode);
+#endif
+}
+
+
+// Return 'true' if we need to exclude the file from processing as result
+// of -x switch. If CheckInclList is true, we also check the file against
+// the include list created with -n switch.
+bool CommandData::ExclCheck(const wchar *CheckName,bool Dir,bool CheckFullPath,bool CheckInclList)
+{
+  if (CheckArgs(&ExclArgs,Dir,CheckName,CheckFullPath,MATCH_WILDSUBPATH))
+    return true;
+  if (!CheckInclList || InclArgs.ItemsCount()==0)
+    return false;
+  if (CheckArgs(&InclArgs,Dir,CheckName,CheckFullPath,MATCH_WILDSUBPATH))
+    return false;
+  return true;
+}
+
+
+bool CommandData::CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,bool CheckFullPath,int MatchMode)
+{
+  wchar *Name=ConvertPath(CheckName,NULL);
+  wchar FullName[NM];
+  wchar CurMask[NM];
+  *FullName=0;
+  Args->Rewind();
+  while (Args->GetString(CurMask,ASIZE(CurMask)))
+  {
+    wchar *LastMaskChar=PointToLastChar(CurMask);
+    bool DirMask=IsPathDiv(*LastMaskChar); // Mask for directories only.
+
+    if (Dir)
+    {
+      // CheckName is a directory.
+      if (DirMask)
+      {
+        // We process the directory and have the directory exclusion mask.
+        // So let's convert "mask\" to "mask" and process it normally.
+        
+        *LastMaskChar=0;
+      }
+      else
+      {
+        // REMOVED, we want -npath\* to match empty folders too.
+        // If mask has wildcards in name part and does not have the trailing
+        // '\' character, we cannot use it for directories.
+      
+        // if (IsWildcard(PointToName(CurMask)))
+        //  continue;
+      }
+    }
+    else
+    {
+      // If we process a file inside of directory excluded by "dirmask\".
+      // we want to exclude such file too. So we convert "dirmask\" to
+      // "dirmask\*". It is important for operations other than archiving
+      // with -x. When archiving with -x, directory matched by "dirmask\"
+      // is excluded from further scanning.
+
+      if (DirMask)
+        wcsncatz(CurMask,L"*",ASIZE(CurMask));
+    }
+
+#ifndef SFX_MODULE
+    if (CheckFullPath && IsFullPath(CurMask))
+    {
+      // We do not need to do the special "*\" processing here, because
+      // unlike the "else" part of this "if", now we convert names to full
+      // format, so they all include the path, which is matched by "*\"
+      // correctly. Moreover, removing "*\" from mask would break
+      // the comparison, because now all names have the path.
+
+      if (*FullName==0)
+        ConvertNameToFull(CheckName,FullName,ASIZE(FullName));
+      if (CmpName(CurMask,FullName,MatchMode))
+        return true;
+    }
+    else
+#endif
+    {
+      wchar NewName[NM+2],*CurName=Name;
+
+      // Important to convert before "*\" check below, so masks like
+      // d:*\something are processed properly.
+      wchar *CmpMask=ConvertPath(CurMask,NULL);
+
+      if (CmpMask[0]=='*' && IsPathDiv(CmpMask[1]))
+      {
+        // We want "*\name" to match 'name' not only in subdirectories,
+        // but also in the current directory. We convert the name
+        // from 'name' to '.\name' to be matched by "*\" part even if it is
+        // in current directory.
+        NewName[0]='.';
+        NewName[1]=CPATHDIVIDER;
+        wcsncpyz(NewName+2,Name,ASIZE(NewName)-2);
+        CurName=NewName;
+      }
+
+      if (CmpName(CmpMask,CurName,MatchMode))
+        return true;
+    }
+  }
+  return false;
+}
+
+
+#ifndef SFX_MODULE
+// Now this function performs only one task and only in Windows version:
+// it skips symlinks to directories if -e1024 switch is specified.
+// Symlinks are skipped in ScanTree class, so their entire contents
+// is skipped too. Without this function we would check the attribute
+// only directly before archiving, so we would skip the symlink record,
+// but not the contents of symlinked directory.
+bool CommandData::ExclDirByAttr(uint FileAttr)
+{
+#ifdef _WIN_ALL
+  if ((FileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0 &&
+      (ExclFileAttr & FILE_ATTRIBUTE_REPARSE_POINT)!=0)
+    return true;
+#endif
+  return false;
+}
+#endif
+
+
+
+
+#ifndef SFX_MODULE
+// Return 'true' if we need to exclude the file from processing.
+bool CommandData::TimeCheck(RarTime &ft)
+{
+  if (FileTimeBefore.IsSet() && ft>=FileTimeBefore)
+    return true;
+  if (FileTimeAfter.IsSet() && ft<=FileTimeAfter)
+    return true;
+  return false;
+}
+#endif
+
+
+#ifndef SFX_MODULE
+// Return 'true' if we need to exclude the file from processing.
+bool CommandData::SizeCheck(int64 Size)
+{
+  if (FileSizeLess!=INT64NDF && Size>=FileSizeLess)
+    return(true);
+  if (FileSizeMore!=INT64NDF && Size<=FileSizeMore)
+    return(true);
+  return(false);
+}
+#endif
+
+
+
+
+int CommandData::IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchType,
+                               wchar *MatchedArg,uint MatchedArgSize)
+{
+  if (MatchedArg!=NULL && MatchedArgSize>0)
+    *MatchedArg=0;
+//  if (wcslen(FileHead.FileName)>=NM)
+//    return 0;
+  bool Dir=FileHead.Dir;
+  if (ExclCheck(FileHead.FileName,Dir,false,true))
+    return 0;
+#ifndef SFX_MODULE
+  if (TimeCheck(FileHead.mtime))
+    return 0;
+  if ((FileHead.FileAttr & ExclFileAttr)!=0 || InclAttrSet && (FileHead.FileAttr & InclFileAttr)==0)
+    return 0;
+  if (!Dir && SizeCheck(FileHead.UnpSize))
+    return 0;
+#endif
+  wchar *ArgName;
+  FileArgs.Rewind();
+  for (int StringCount=1;(ArgName=FileArgs.GetString())!=NULL;StringCount++)
+    if (CmpName(ArgName,FileHead.FileName,MatchType))
+    {
+      if (ExactMatch!=NULL)
+        *ExactMatch=wcsicompc(ArgName,FileHead.FileName)==0;
+      if (MatchedArg!=NULL)
+        wcsncpyz(MatchedArg,ArgName,MatchedArgSize);
+      return StringCount;
+    }
+  return 0;
+}
+
+
+void CommandData::ProcessCommand()
+{
+#ifndef SFX_MODULE
+
+  const wchar *SingleCharCommands=L"FUADPXETK";
+  if (Command[0]!=0 && Command[1]!=0 && wcschr(SingleCharCommands,Command[0])!=NULL || *ArcName==0)
+    OutHelp(*Command==0 ? RARX_SUCCESS:RARX_USERERROR); // Return 'success' for 'rar' without parameters.
+
+  const wchar *ArcExt=GetExt(ArcName);
+#ifdef _UNIX
+  if (ArcExt==NULL && (!FileExist(ArcName) || IsDir(GetFileAttr(ArcName))))
+    wcsncatz(ArcName,L".rar",ASIZE(ArcName));
+#else
+  if (ArcExt==NULL)
+    wcsncatz(ArcName,L".rar",ASIZE(ArcName));
+#endif
+  // Treat arcname.part1 as arcname.part1.rar.
+  if (ArcExt!=NULL && wcsnicomp(ArcExt,L".part",5)==0 && IsDigit(ArcExt[5]) &&
+      !FileExist(ArcName))
+  {
+    wchar Name[NM];
+    wcsncpyz(Name,ArcName,ASIZE(Name));
+    wcsncatz(Name,L".rar",ASIZE(Name));
+    if (FileExist(Name))
+      wcsncpyz(ArcName,Name,ASIZE(ArcName));
+  }
+
+  if (wcschr(L"AFUMD",*Command)==NULL && !ArcInMem)
+  {
+    if (GenerateArcName)
+      GenerateArchiveName(ArcName,ASIZE(ArcName),GenerateMask,false);
+
+    StringList ArcMasks;
+    ArcMasks.AddString(ArcName);
+    ScanTree Scan(&ArcMasks,Recurse,SaveSymLinks,SCAN_SKIPDIRS);
+    FindData FindData;
+    while (Scan.GetNext(&FindData)==SCAN_SUCCESS)
+      AddArcName(FindData.Name);
+  }
+  else
+    AddArcName(ArcName);
+#endif
+
+  switch(Command[0])
+  {
+    case 'P':
+    case 'X':
+    case 'E':
+    case 'T':
+    case 'I':
+      {
+        CmdExtract Extract(this);
+        Extract.DoExtract();
+      }
+      break;
+#ifndef SILENT
+    case 'V':
+    case 'L':
+      ListArchive(this);
+      break;
+    default:
+      OutHelp(RARX_USERERROR);
+#endif
+  }
+  if (!BareOutput)
+    mprintf(L"\n");
+}
+
+
+void CommandData::AddArcName(const wchar *Name)
+{
+  ArcNames.AddString(Name);
+}
+
+
+bool CommandData::GetArcName(wchar *Name,int MaxSize)
+{
+  return ArcNames.GetString(Name,MaxSize);
+}
+
+
+bool CommandData::IsSwitch(int Ch)
+{
+#if defined(_WIN_ALL) || defined(_EMX)
+  return Ch=='-' || Ch=='/';
+#else
+  return Ch=='-';
+#endif
+}
+
+
+#ifndef SFX_MODULE
+uint CommandData::GetExclAttr(const wchar *Str)
+{
+  if (IsDigit(*Str))
+    return wcstol(Str,NULL,0);
+
+  uint Attr=0;
+  while (*Str!=0)
+  {
+    switch(toupperw(*Str))
+    {
+#ifdef _UNIX
+      case 'D':
+        Attr|=S_IFDIR;
+        break;
+      case 'V':
+        Attr|=S_IFCHR;
+        break;
+#elif defined(_WIN_ALL) || defined(_EMX)
+      case 'R':
+        Attr|=0x1;
+        break;
+      case 'H':
+        Attr|=0x2;
+        break;
+      case 'S':
+        Attr|=0x4;
+        break;
+      case 'D':
+        Attr|=0x10;
+        break;
+      case 'A':
+        Attr|=0x20;
+        break;
+#endif
+    }
+    Str++;
+  }
+  return Attr;
+}
+#endif
+
+
+
+
+#ifndef SFX_MODULE
+bool CommandData::CheckWinSize()
+{
+  // Define 0x100000000 as macro to avoid troubles with older compilers.
+  const uint64 MaxDictSize=INT32TO64(1,0);
+  // Limit the dictionary size to 4 GB.
+  for (uint64 I=0x10000;I<=MaxDictSize;I*=2)
+    if (WinSize==I)
+      return true;
+  WinSize=0x400000;
+  return false;
+}
+#endif
+
+
+#ifndef SFX_MODULE
+void CommandData::ReportWrongSwitches(RARFORMAT Format)
+{
+  if (Format==RARFMT15)
+  {
+    if (HashType!=HASH_CRC32)
+      uiMsg(UIERROR_INCOMPATSWITCH,L"-ht",4);
+#ifdef _WIN_ALL
+    if (SaveSymLinks)
+      uiMsg(UIERROR_INCOMPATSWITCH,L"-ol",4);
+#endif
+    if (SaveHardLinks)
+      uiMsg(UIERROR_INCOMPATSWITCH,L"-oh",4);
+
+#ifdef _WIN_ALL
+    // Do not report a wrong dictionary size here, because we are not sure
+    // yet about archive format. We can switch to RAR5 mode later
+    // if we update RAR5 archive.
+
+
+#endif
+    if (QOpenMode!=QOPEN_AUTO)
+      uiMsg(UIERROR_INCOMPATSWITCH,L"-qo",4);
+  }
+  if (Format==RARFMT50)
+  {
+  }
+}
+#endif
diff --git a/third_party/unrar/src/cmddata.hpp b/third_party/unrar/src/cmddata.hpp
new file mode 100644
index 0000000..e12d4fbc
--- /dev/null
+++ b/third_party/unrar/src/cmddata.hpp
@@ -0,0 +1,64 @@
+#ifndef _RAR_CMDDATA_
+#define _RAR_CMDDATA_
+
+
+#define DefaultStoreList L"7z;ace;arj;bz2;cab;gz;jpeg;jpg;lha;lz;lzh;mp3;rar;taz;tgz;xz;z;zip;zipx"
+
+enum RAR_CMD_LIST_MODE {RCLM_AUTO,RCLM_REJECT_LISTS,RCLM_ACCEPT_LISTS};
+
+class CommandData:public RAROptions
+{
+  private:
+    void ProcessSwitchesString(const wchar *Str);
+    void ProcessSwitch(const wchar *Switch);
+    void BadSwitch(const wchar *Switch);
+    uint GetExclAttr(const wchar *Str);
+
+    bool FileLists;
+    bool NoMoreSwitches;
+    RAR_CMD_LIST_MODE ListMode;
+    bool BareOutput;
+  public:
+    CommandData();
+    void Init();
+
+    void ParseCommandLine(bool Preprocess,int argc, char *argv[]);
+    void ParseArg(wchar *ArgW);
+    void ParseDone();
+    void ParseEnvVar();
+    void ReadConfig();
+    void PreprocessArg(const wchar *Arg);
+    void OutTitle();
+    void OutHelp(RAR_EXIT ExitCode);
+    bool IsSwitch(int Ch);
+    bool ExclCheck(const wchar *CheckName,bool Dir,bool CheckFullPath,bool CheckInclList);
+    static bool CheckArgs(StringList *Args,bool Dir,const wchar *CheckName,bool CheckFullPath,int MatchMode);
+    bool ExclDirByAttr(uint FileAttr);
+    bool TimeCheck(RarTime &ft);
+    bool SizeCheck(int64 Size);
+    bool AnyFiltersActive();
+    int IsProcessFile(FileHeader &FileHead,bool *ExactMatch=NULL,int MatchType=MATCH_WILDSUBPATH,
+                      wchar *MatchedArg=NULL,uint MatchedArgSize=0);
+    void ProcessCommand();
+    void AddArcName(const wchar *Name);
+    bool GetArcName(wchar *Name,int MaxSize);
+    bool CheckWinSize();
+
+    int GetRecoverySize(const wchar *Str,int DefSize);
+
+#ifndef SFX_MODULE
+    void ReportWrongSwitches(RARFORMAT Format);
+#endif
+
+    wchar Command[NM+16];
+
+    wchar ArcName[NM];
+
+    StringList FileArgs;
+    StringList ExclArgs;
+    StringList InclArgs;
+    StringList ArcNames;
+    StringList StoreArgs;
+};
+
+#endif
diff --git a/third_party/unrar/src/coder.cpp b/third_party/unrar/src/coder.cpp
new file mode 100644
index 0000000..9d971a8
--- /dev/null
+++ b/third_party/unrar/src/coder.cpp
@@ -0,0 +1,48 @@
+
+
+inline unsigned int RangeCoder::GetChar()
+{
+  return(UnpackRead->GetChar());
+}
+
+
+void RangeCoder::InitDecoder(Unpack *UnpackRead)
+{
+  RangeCoder::UnpackRead=UnpackRead;
+
+  low=code=0;
+  range=uint(-1);
+  for (int i=0;i < 4;i++)
+    code=(code << 8) | GetChar();
+}
+
+
+// (int) cast before "low" added only to suppress compiler warnings.
+#define ARI_DEC_NORMALIZE(code,low,range,read)                           \
+{                                                                        \
+  while ((low^(low+range))<TOP || range<BOT && ((range=-(int)low&(BOT-1)),1)) \
+  {                                                                      \
+    code=(code << 8) | read->GetChar();                                  \
+    range <<= 8;                                                         \
+    low <<= 8;                                                           \
+  }                                                                      \
+}
+
+
+inline int RangeCoder::GetCurrentCount() 
+{
+  return (code-low)/(range /= SubRange.scale);
+}
+
+
+inline uint RangeCoder::GetCurrentShiftCount(uint SHIFT) 
+{
+  return (code-low)/(range >>= SHIFT);
+}
+
+
+inline void RangeCoder::Decode()
+{
+  low += range*SubRange.LowCount;
+  range *= SubRange.HighCount-SubRange.LowCount;
+}
diff --git a/third_party/unrar/src/coder.hpp b/third_party/unrar/src/coder.hpp
new file mode 100644
index 0000000..7b36ff21
--- /dev/null
+++ b/third_party/unrar/src/coder.hpp
@@ -0,0 +1,23 @@
+/****************************************************************************
+ *  Contents: 'Carryless rangecoder' by Dmitry Subbotin                     *
+ ****************************************************************************/
+
+
+class RangeCoder
+{
+  public:
+    void InitDecoder(Unpack *UnpackRead);
+    inline int GetCurrentCount();
+    inline uint GetCurrentShiftCount(uint SHIFT);
+    inline void Decode();
+    inline void PutChar(unsigned int c);
+    inline unsigned int GetChar();
+
+    uint low, code, range;
+    struct SUBRANGE 
+    {
+      uint LowCount, HighCount, scale;
+    } SubRange;
+
+    Unpack *UnpackRead;
+};
diff --git a/third_party/unrar/src/compress.hpp b/third_party/unrar/src/compress.hpp
new file mode 100644
index 0000000..73f7ee4
--- /dev/null
+++ b/third_party/unrar/src/compress.hpp
@@ -0,0 +1,59 @@
+#ifndef _RAR_COMPRESS_
+#define _RAR_COMPRESS_
+
+// Combine pack and unpack constants to class to avoid polluting global
+// namespace with numerous short names.
+class PackDef
+{
+  public:
+    // Maximum LZ match length we can encode even for short distances.
+    static const uint MAX_LZ_MATCH = 0x1001;
+
+    // We increment LZ match length for longer distances, because shortest
+    // matches are not allowed for them. Maximum length increment is 3
+    // for distances larger than 256KB (0x40000). Here we define the maximum
+    // incremented LZ match. Normally packer does not use it, but we must be
+    // ready to process it in corrupt archives.
+    static const uint MAX_INC_LZ_MATCH = MAX_LZ_MATCH + 3;
+
+    static const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3.
+    static const uint LOW_DIST_REP_COUNT = 16;
+
+    static const uint NC    = 306; /* alphabet = {0, 1, 2, ..., NC - 1} */
+    static const uint DC    = 64;
+    static const uint LDC   = 16;
+    static const uint RC    = 44;
+    static const uint HUFF_TABLE_SIZE = NC + DC + RC + LDC;
+    static const uint BC    = 20;
+
+    static const uint NC30  = 299; /* alphabet = {0, 1, 2, ..., NC - 1} */
+    static const uint DC30  = 60;
+    static const uint LDC30 = 17;
+    static const uint RC30  = 28;
+    static const uint BC30  = 20;
+    static const uint HUFF_TABLE_SIZE30 = NC30 + DC30 + RC30 + LDC30;
+
+    static const uint NC20  = 298; /* alphabet = {0, 1, 2, ..., NC - 1} */
+    static const uint DC20  = 48;
+    static const uint RC20  = 28;
+    static const uint BC20  = 19;
+    static const uint MC20  = 257;
+
+    // Largest alphabet size among all values listed above.
+    static const uint LARGEST_TABLE_SIZE = 306;
+
+    enum {
+      CODE_HUFFMAN, CODE_LZ, CODE_REPEATLZ, CODE_CACHELZ, CODE_STARTFILE,
+      CODE_ENDFILE, CODE_FILTER, CODE_FILTERDATA
+    };
+};
+
+
+enum FilterType {
+  // These values must not be changed, because we use them directly
+  // in RAR5 compression and decompression code.
+  FILTER_DELTA=0, FILTER_E8, FILTER_E8E9, FILTER_ARM, 
+  FILTER_AUDIO, FILTER_RGB, FILTER_ITANIUM, FILTER_PPM, FILTER_NONE
+};
+
+#endif
diff --git a/third_party/unrar/src/consio.cpp b/third_party/unrar/src/consio.cpp
new file mode 100644
index 0000000..196066e
--- /dev/null
+++ b/third_party/unrar/src/consio.cpp
@@ -0,0 +1,357 @@
+#include "rar.hpp"
+#include "log.cpp"
+
+static MESSAGE_TYPE MsgStream=MSG_STDOUT;
+static RAR_CHARSET RedirectCharset=RCH_DEFAULT;
+
+const int MaxMsgSize=2*NM+2048;
+
+static bool StdoutRedirected=false,StderrRedirected=false,StdinRedirected=false;
+
+#ifdef _WIN_ALL
+static bool IsRedirected(DWORD nStdHandle)
+{
+  HANDLE hStd=GetStdHandle(nStdHandle);
+  DWORD Mode;
+  return GetFileType(hStd)!=FILE_TYPE_CHAR || GetConsoleMode(hStd,&Mode)==0;
+}
+#endif
+
+
+void InitConsole()
+{
+#ifdef _WIN_ALL
+  // We want messages like file names or progress percent to be printed
+  // immediately. Use only in Windows, in Unix they can cause wprintf %ls
+  // to fail with non-English strings.
+  setbuf(stdout,NULL);
+  setbuf(stderr,NULL);
+
+  // Detect if output is redirected and set output mode properly.
+  // We do not want to send Unicode output to files and especially to pipes
+  // like '|more', which cannot handle them correctly in Windows.
+  // In Unix console output is UTF-8 and it is handled correctly
+  // when redirecting, so no need to perform any adjustments.
+  StdoutRedirected=IsRedirected(STD_OUTPUT_HANDLE);
+  StderrRedirected=IsRedirected(STD_ERROR_HANDLE);
+  StdinRedirected=IsRedirected(STD_INPUT_HANDLE);
+#ifdef _MSC_VER
+  if (!StdoutRedirected)
+    _setmode(_fileno(stdout), _O_U16TEXT);
+  if (!StderrRedirected)
+    _setmode(_fileno(stderr), _O_U16TEXT);
+#endif
+#elif defined(_UNIX)
+  StdoutRedirected=!isatty(fileno(stdout));
+  StderrRedirected=!isatty(fileno(stderr));
+  StdinRedirected=!isatty(fileno(stdin));
+#endif
+}
+
+
+void SetConsoleMsgStream(MESSAGE_TYPE MsgStream)
+{
+  ::MsgStream=MsgStream;
+}
+
+
+void SetConsoleRedirectCharset(RAR_CHARSET RedirectCharset)
+{
+  ::RedirectCharset=RedirectCharset;
+}
+
+
+#ifndef SILENT
+static void cvt_wprintf(FILE *dest,const wchar *fmt,va_list arglist)
+{
+  // This buffer is for format string only, not for entire output,
+  // so it can be short enough.
+  wchar fmtw[1024];
+  PrintfPrepareFmt(fmt,fmtw,ASIZE(fmtw));
+#ifdef _WIN_ALL
+  safebuf wchar Msg[MaxMsgSize];
+  if (dest==stdout && StdoutRedirected || dest==stderr && StderrRedirected)
+  {
+    HANDLE hOut=GetStdHandle(dest==stdout ? STD_OUTPUT_HANDLE:STD_ERROR_HANDLE);
+    vswprintf(Msg,ASIZE(Msg),fmtw,arglist);
+    DWORD Written;
+    if (RedirectCharset==RCH_UNICODE)
+      WriteFile(hOut,Msg,(DWORD)wcslen(Msg)*sizeof(*Msg),&Written,NULL);
+    else
+    {
+      // Avoid Unicode for redirect in Windows, it does not work with pipes.
+      safebuf char MsgA[MaxMsgSize];
+      if (RedirectCharset==RCH_UTF8)
+        WideToUtf(Msg,MsgA,ASIZE(MsgA));
+      else
+        WideToChar(Msg,MsgA,ASIZE(MsgA));
+      if (RedirectCharset==RCH_DEFAULT || RedirectCharset==RCH_OEM)
+        CharToOemA(MsgA,MsgA); // Console tools like 'more' expect OEM encoding.
+
+      // We already converted \n to \r\n above, so we use WriteFile instead
+      // of C library to avoid unnecessary additional conversion.
+      WriteFile(hOut,MsgA,(DWORD)strlen(MsgA),&Written,NULL);
+    }
+    return;
+  }
+  // MSVC2008 vfwprintf writes every character to console separately
+  // and it is too slow. We use direct WriteConsole call instead.
+  vswprintf(Msg,ASIZE(Msg),fmtw,arglist);
+  HANDLE hOut=GetStdHandle(dest==stderr ? STD_ERROR_HANDLE:STD_OUTPUT_HANDLE);
+  DWORD Written;
+  WriteConsole(hOut,Msg,(DWORD)wcslen(Msg),&Written,NULL);
+#else
+  vfwprintf(dest,fmtw,arglist);
+  // We do not use setbuf(NULL) in Unix (see comments in InitConsole).
+  fflush(dest);
+#endif
+}
+
+
+void mprintf(const wchar *fmt,...)
+{
+  if (MsgStream==MSG_NULL || MsgStream==MSG_ERRONLY)
+    return;
+
+  fflush(stderr); // Ensure proper message order.
+
+  va_list arglist;
+  va_start(arglist,fmt);
+  FILE *dest=MsgStream==MSG_STDERR ? stderr:stdout;
+  cvt_wprintf(dest,fmt,arglist);
+  va_end(arglist);
+}
+#endif
+
+
+#ifndef SILENT
+void eprintf(const wchar *fmt,...)
+{
+  if (MsgStream==MSG_NULL)
+    return;
+
+  fflush(stdout); // Ensure proper message order.
+
+  va_list arglist;
+  va_start(arglist,fmt);
+  cvt_wprintf(stderr,fmt,arglist);
+  va_end(arglist);
+}
+#endif
+
+
+#ifndef SILENT
+static void GetPasswordText(wchar *Str,uint MaxLength)
+{
+  if (MaxLength==0)
+    return;
+  if (StdinRedirected)
+    getwstr(Str,MaxLength); // Read from pipe or redirected file.
+  else
+  {
+#ifdef _WIN_ALL
+    HANDLE hConIn=GetStdHandle(STD_INPUT_HANDLE);
+    HANDLE hConOut=GetStdHandle(STD_OUTPUT_HANDLE);
+    DWORD ConInMode,ConOutMode;
+    DWORD Read=0;
+    GetConsoleMode(hConIn,&ConInMode);
+    GetConsoleMode(hConOut,&ConOutMode);
+    SetConsoleMode(hConIn,ENABLE_LINE_INPUT);
+    SetConsoleMode(hConOut,ENABLE_PROCESSED_OUTPUT|ENABLE_WRAP_AT_EOL_OUTPUT);
+
+    ReadConsole(hConIn,Str,MaxLength-1,&Read,NULL);
+    Str[Read]=0;
+    SetConsoleMode(hConIn,ConInMode);
+    SetConsoleMode(hConOut,ConOutMode);
+#else
+    char StrA[MAXPASSWORD];
+#if defined(_EMX) || defined (__VMS)
+    fgets(StrA,ASIZE(StrA)-1,stdin);
+#elif defined(__sun)
+    strncpyz(StrA,getpassphrase(""),ASIZE(StrA));
+#else
+    strncpyz(StrA,getpass(""),ASIZE(StrA));
+#endif
+    CharToWide(StrA,Str,MaxLength);
+    cleandata(StrA,sizeof(StrA));
+#endif
+  }
+  Str[MaxLength-1]=0;
+  RemoveLF(Str);
+}
+#endif
+
+
+#ifndef SILENT
+bool GetConsolePassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password)
+{
+  if (!StdinRedirected)
+    uiAlarm(UIALARM_QUESTION);
+  
+  while (true)
+  {
+    if (!StdinRedirected)
+      if (Type==UIPASSWORD_GLOBAL)
+        eprintf(L"\n%s: ",St(MAskPsw));
+      else
+        eprintf(St(MAskPswFor),FileName);
+
+    wchar PlainPsw[MAXPASSWORD];
+    GetPasswordText(PlainPsw,ASIZE(PlainPsw));
+    if (*PlainPsw==0 && Type==UIPASSWORD_GLOBAL)
+      return false;
+    if (!StdinRedirected && Type==UIPASSWORD_GLOBAL)
+    {
+      eprintf(St(MReAskPsw));
+      wchar CmpStr[MAXPASSWORD];
+      GetPasswordText(CmpStr,ASIZE(CmpStr));
+      if (*CmpStr==0 || wcscmp(PlainPsw,CmpStr)!=0)
+      {
+        eprintf(St(MNotMatchPsw));
+        cleandata(PlainPsw,sizeof(PlainPsw));
+        cleandata(CmpStr,sizeof(CmpStr));
+        continue;
+      }
+      cleandata(CmpStr,sizeof(CmpStr));
+    }
+    Password->Set(PlainPsw);
+    cleandata(PlainPsw,sizeof(PlainPsw));
+    break;
+  }
+  return true;
+}
+#endif
+
+
+#ifndef SILENT
+bool getwstr(wchar *str,size_t n)
+{
+  // Print buffered prompt title function before waiting for input.
+  fflush(stderr);
+
+  *str=0;
+#if defined(_WIN_ALL)
+  // fgetws does not work well with non-English text in Windows,
+  // so we do not use it.
+  if (StdinRedirected) // ReadConsole does not work if redirected.
+  {
+    // fgets does not work well with pipes in Windows in our test.
+    // Let's use files.
+    Array<char> StrA(n*4); // Up to 4 UTF-8 characters per wchar_t.
+    File SrcFile;
+    SrcFile.SetHandleType(FILE_HANDLESTD);
+    int ReadSize=SrcFile.Read(&StrA[0],StrA.Size()-1);
+    if (ReadSize<=0)
+    {
+      // Looks like stdin is a null device. We can enter to infinite loop
+      // calling Ask(), so let's better exit.
+      ErrHandler.Exit(RARX_USERBREAK);
+    }
+    StrA[ReadSize]=0;
+    CharToWide(&StrA[0],str,n);
+    cleandata(&StrA[0],StrA.Size()); // We can use this function to enter passwords.
+  }
+  else
+  {
+    DWORD ReadSize=0;
+    if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE),str,DWORD(n-1),&ReadSize,NULL)==0)
+      return false;
+    str[ReadSize]=0;
+  }
+#else
+  if (fgetws(str,n,stdin)==NULL)
+    ErrHandler.Exit(RARX_USERBREAK); // Avoid infinite Ask() loop.
+#endif
+  RemoveLF(str);
+  return true;
+}
+#endif
+
+
+#ifndef SILENT
+// We allow this function to return 0 in case of invalid input,
+// because it might be convenient to press Enter to some not dangerous
+// prompts like "insert disk with next volume". We should call this function
+// again in case of 0 in dangerous prompt such as overwriting file.
+int Ask(const wchar *AskStr)
+{
+  uiAlarm(UIALARM_QUESTION);
+
+  const int MaxItems=10;
+  wchar Item[MaxItems][40];
+  int ItemKeyPos[MaxItems],NumItems=0;
+
+  for (const wchar *NextItem=AskStr;NextItem!=NULL;NextItem=wcschr(NextItem+1,'_'))
+  {
+    wchar *CurItem=Item[NumItems];
+    wcsncpyz(CurItem,NextItem+1,ASIZE(Item[0]));
+    wchar *EndItem=wcschr(CurItem,'_');
+    if (EndItem!=NULL)
+      *EndItem=0;
+    int KeyPos=0,CurKey;
+    while ((CurKey=CurItem[KeyPos])!=0)
+    {
+      bool Found=false;
+      for (int I=0;I<NumItems && !Found;I++)
+        if (toupperw(Item[I][ItemKeyPos[I]])==toupperw(CurKey))
+          Found=true;
+      if (!Found && CurKey!=' ')
+        break;
+      KeyPos++;
+    }
+    ItemKeyPos[NumItems]=KeyPos;
+    NumItems++;
+  }
+
+  for (int I=0;I<NumItems;I++)
+  {
+    eprintf(I==0 ? (NumItems>4 ? L"\n":L" "):L", ");
+    int KeyPos=ItemKeyPos[I];
+    for (int J=0;J<KeyPos;J++)
+      eprintf(L"%c",Item[I][J]);
+    eprintf(L"[%c]%ls",Item[I][KeyPos],&Item[I][KeyPos+1]);
+  }
+  eprintf(L" ");
+  wchar Str[50];
+  getwstr(Str,ASIZE(Str));
+  wchar Ch=toupperw(Str[0]);
+  for (int I=0;I<NumItems;I++)
+    if (Ch==Item[I][ItemKeyPos[I]])
+      return I+1;
+  return 0;
+}
+#endif
+
+
+static bool IsCommentUnsafe(const wchar *Data,size_t Size)
+{
+  for (size_t I=0;I<Size;I++)
+    if (Data[I]==27 && Data[I+1]=='[')
+      for (size_t J=I+2;J<Size;J++)
+      {
+        // Return true for <ESC>[{key};"{string}"p used to redefine
+        // a keyboard key on some terminals.
+        if (Data[J]=='\"')
+          return true;
+        if (!IsDigit(Data[J]) && Data[J]!=';')
+          break;
+      }
+  return false;
+}
+
+
+void OutComment(const wchar *Comment,size_t Size)
+{
+  if (IsCommentUnsafe(Comment,Size))
+    return;
+  const size_t MaxOutSize=0x400;
+  for (size_t I=0;I<Size;I+=MaxOutSize)
+  {
+    wchar Msg[MaxOutSize+1];
+    size_t CopySize=Min(MaxOutSize,Size-I);
+    wcsncpy(Msg,Comment+I,CopySize);
+    Msg[CopySize]=0;
+    mprintf(L"%s",Msg);
+  }
+  mprintf(L"\n");
+}
diff --git a/third_party/unrar/src/consio.hpp b/third_party/unrar/src/consio.hpp
new file mode 100644
index 0000000..903dc21a
--- /dev/null
+++ b/third_party/unrar/src/consio.hpp
@@ -0,0 +1,27 @@
+#ifndef _RAR_CONSIO_
+#define _RAR_CONSIO_
+
+void InitConsole();
+void SetConsoleMsgStream(MESSAGE_TYPE MsgStream);
+void SetConsoleRedirectCharset(RAR_CHARSET RedirectCharset);
+void OutComment(const wchar *Comment,size_t Size);
+
+#ifndef SILENT
+bool GetConsolePassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password);
+#endif
+
+#ifdef SILENT
+  inline void mprintf(const wchar *fmt,...) {}
+  inline void eprintf(const wchar *fmt,...) {}
+  inline void Alarm() {}
+  inline int Ask(const wchar *AskStr) {return 0;}
+  inline bool getwstr(wchar *str,size_t n) {return false;}
+#else
+  void mprintf(const wchar *fmt,...);
+  void eprintf(const wchar *fmt,...);
+  void Alarm();
+  int Ask(const wchar *AskStr);
+  bool getwstr(wchar *str,size_t n);
+#endif
+
+#endif
diff --git a/third_party/unrar/src/crc.cpp b/third_party/unrar/src/crc.cpp
new file mode 100644
index 0000000..1097f4cd
--- /dev/null
+++ b/third_party/unrar/src/crc.cpp
@@ -0,0 +1,102 @@
+// This CRC function is based on Intel Slicing-by-8 algorithm.
+//
+// Original Intel Slicing-by-8 code is available here:
+//
+//    http://sourceforge.net/projects/slicing-by-8/
+//
+// Original Intel Slicing-by-8 code is licensed as:
+//    
+//    Copyright (c) 2004-2006 Intel Corporation - All Rights Reserved
+//    
+//    This software program is licensed subject to the BSD License, 
+//    available at http://www.opensource.org/licenses/bsd-license.html
+
+
+#include "rar.hpp"
+
+static uint crc_tables[8][256]; // Tables for Slicing-by-8.
+
+
+// Build the classic CRC32 lookup table.
+// We also provide this function to legacy RAR and ZIP decryption code.
+void InitCRC32(uint *CRCTab)
+{
+  if (CRCTab[1]!=0)
+    return;
+  for (uint I=0;I<256;I++)
+  {
+    uint C=I;
+    for (uint J=0;J<8;J++)
+      C=(C & 1) ? (C>>1)^0xEDB88320 : (C>>1);
+    CRCTab[I]=C;
+  }
+}
+
+
+static void InitTables()
+{
+  InitCRC32(crc_tables[0]);
+
+  for (uint I=0;I<256;I++) // Build additional lookup tables.
+  {
+    uint C=crc_tables[0][I];
+    for (uint J=1;J<8;J++)
+    {
+      C=crc_tables[0][(byte)C]^(C>>8);
+      crc_tables[J][I]=C;
+    }
+  }
+}
+
+
+struct CallInitCRC {CallInitCRC() {InitTables();}} static CallInit32;
+
+uint CRC32(uint StartCRC,const void *Addr,size_t Size)
+{
+  byte *Data=(byte *)Addr;
+
+  // Align Data to 8 for better performance.
+  for (;Size>0 && ((size_t)Data & 7);Size--,Data++)
+    StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8);
+
+  for (;Size>=8;Size-=8,Data+=8)
+  {
+#ifdef BIG_ENDIAN
+    StartCRC ^= Data[0]|(Data[1] << 8)|(Data[2] << 16)|(Data[3] << 24);
+    uint NextData = Data[4]|(Data[5] << 8)|(Data[6] << 16)|(Data[7] << 24);
+#else
+    StartCRC ^= *(uint32 *) Data;
+    uint NextData = *(uint32 *) (Data+4);
+#endif
+    StartCRC = crc_tables[7][(byte) StartCRC       ] ^
+               crc_tables[6][(byte)(StartCRC >> 8) ] ^
+               crc_tables[5][(byte)(StartCRC >> 16)] ^
+               crc_tables[4][(byte)(StartCRC >> 24)] ^
+               crc_tables[3][(byte) NextData       ] ^
+               crc_tables[2][(byte)(NextData >>8 ) ] ^
+               crc_tables[1][(byte)(NextData >> 16)] ^
+               crc_tables[0][(byte)(NextData >> 24)];
+  }
+
+  for (;Size>0;Size--,Data++) // Process left data.
+    StartCRC=crc_tables[0][(byte)(StartCRC^Data[0])]^(StartCRC>>8);
+
+  return StartCRC;
+}
+
+
+#ifndef SFX_MODULE
+// For RAR 1.4 archives in case somebody still has them.
+ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size)
+{
+  byte *Data=(byte *)Addr;
+  for (size_t I=0;I<Size;I++)
+  {
+    StartCRC=(StartCRC+Data[I])&0xffff;
+    StartCRC=((StartCRC<<1)|(StartCRC>>15))&0xffff;
+  }
+  return StartCRC;
+}
+#endif
+
+
diff --git a/third_party/unrar/src/crc.hpp b/third_party/unrar/src/crc.hpp
new file mode 100644
index 0000000..d8fea28
--- /dev/null
+++ b/third_party/unrar/src/crc.hpp
@@ -0,0 +1,15 @@
+#ifndef _RAR_CRC_
+#define _RAR_CRC_
+
+// This function is only to intialize external CRC tables. We do not need to
+// call it before calculating CRC32.
+void InitCRC32(uint *CRCTab);
+
+uint CRC32(uint StartCRC,const void *Addr,size_t Size);
+
+#ifndef SFX_MODULE
+ushort Checksum14(ushort StartCRC,const void *Addr,size_t Size);
+#endif
+
+
+#endif
diff --git a/third_party/unrar/src/crypt.cpp b/third_party/unrar/src/crypt.cpp
new file mode 100644
index 0000000..fc2126d4
--- /dev/null
+++ b/third_party/unrar/src/crypt.cpp
@@ -0,0 +1,134 @@
+#include "rar.hpp"
+
+#ifndef SFX_MODULE
+#include "crypt1.cpp"
+#include "crypt2.cpp"
+#endif
+#include "crypt3.cpp"
+#include "crypt5.cpp"
+
+
+CryptData::CryptData()
+{
+  Method=CRYPT_NONE;
+  memset(KDF3Cache,0,sizeof(KDF3Cache));
+  memset(KDF5Cache,0,sizeof(KDF5Cache));
+  KDF3CachePos=0;
+  KDF5CachePos=0;
+  memset(CRCTab,0,sizeof(CRCTab));
+}
+
+
+CryptData::~CryptData()
+{
+  cleandata(KDF3Cache,sizeof(KDF3Cache));
+  cleandata(KDF5Cache,sizeof(KDF5Cache));
+}
+
+
+
+
+void CryptData::DecryptBlock(byte *Buf,size_t Size)
+{
+  switch(Method)
+  {
+#ifndef SFX_MODULE
+    case CRYPT_RAR13:
+      Decrypt13(Buf,Size);
+      break;
+    case CRYPT_RAR15:
+      Crypt15(Buf,Size);
+      break;
+    case CRYPT_RAR20:
+      for (size_t I=0;I<Size;I+=CRYPT_BLOCK_SIZE)
+        DecryptBlock20(Buf+I);
+      break;
+#endif
+    case CRYPT_RAR30:
+    case CRYPT_RAR50:
+      rin.blockDecrypt(Buf,Size,Buf);
+      break;
+  }
+}
+
+
+bool CryptData::SetCryptKeys(bool Encrypt,CRYPT_METHOD Method,
+     SecPassword *Password,const byte *Salt,
+     const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck)
+{
+  if (!Password->IsSet() || Method==CRYPT_NONE)
+    return false;
+
+  CryptData::Method=Method;
+
+  wchar PwdW[MAXPASSWORD];
+  Password->Get(PwdW,ASIZE(PwdW));
+  char PwdA[MAXPASSWORD];
+  WideToChar(PwdW,PwdA,ASIZE(PwdA));
+
+  switch(Method)
+  {
+#ifndef SFX_MODULE
+    case CRYPT_RAR13:
+      SetKey13(PwdA);
+      break;
+    case CRYPT_RAR15:
+      SetKey15(PwdA);
+      break;
+    case CRYPT_RAR20:
+      SetKey20(PwdA);
+      break;
+#endif
+    case CRYPT_RAR30:
+      SetKey30(Encrypt,Password,PwdW,Salt);
+      break;
+    case CRYPT_RAR50:
+      SetKey50(Encrypt,Password,PwdW,Salt,InitV,Lg2Cnt,HashKey,PswCheck);
+      break;
+  }
+  cleandata(PwdA,sizeof(PwdA));
+  cleandata(PwdW,sizeof(PwdW));
+  return true;
+}
+
+
+// Use the current system time to additionally randomize data.
+static void TimeRandomize(byte *RndBuf,size_t BufSize)
+{
+  static uint Count=0;
+  RarTime CurTime;
+  CurTime.SetCurrentTime();
+  uint64 Random=CurTime.GetWin()+clock();
+  for (size_t I=0;I<BufSize;I++)
+  {
+    byte RndByte = byte (Random >> ( (I & 7) * 8 ));
+    RndBuf[I]=byte( (RndByte ^ I) + Count++);
+  }
+}
+
+
+
+
+// Fill buffer with random data.
+void GetRnd(byte *RndBuf,size_t BufSize)
+{
+  bool Success=false;
+#if defined(_WIN_ALL)
+  HCRYPTPROV hProvider = 0;
+  if (CryptAcquireContext(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
+  {
+    Success=CryptGenRandom(hProvider, (DWORD)BufSize, RndBuf) == TRUE;
+    CryptReleaseContext(hProvider, 0);
+  }
+#elif defined(_UNIX)
+  FILE *rndf = fopen("/dev/urandom", "r");
+  if (rndf!=NULL)
+  {
+    Success=fread(RndBuf, BufSize, 1, rndf) == BufSize;
+    fclose(rndf);
+  }
+#endif
+  // We use this code only as the last resort if code above failed.
+  if (!Success)
+    TimeRandomize(RndBuf,BufSize);
+}
diff --git a/third_party/unrar/src/crypt.hpp b/third_party/unrar/src/crypt.hpp
new file mode 100644
index 0000000..f6382ef
--- /dev/null
+++ b/third_party/unrar/src/crypt.hpp
@@ -0,0 +1,101 @@
+#ifndef _RAR_CRYPT_
+#define _RAR_CRYPT_
+
+
+enum CRYPT_METHOD {
+  CRYPT_NONE,CRYPT_RAR13,CRYPT_RAR15,CRYPT_RAR20,CRYPT_RAR30,CRYPT_RAR50
+};
+
+#define SIZE_SALT50              16
+#define SIZE_SALT30               8
+#define SIZE_INITV               16
+#define SIZE_PSWCHECK             8
+#define SIZE_PSWCHECK_CSUM        4
+
+#define CRYPT_BLOCK_SIZE         16
+#define CRYPT_BLOCK_MASK         (CRYPT_BLOCK_SIZE-1) // 0xf
+
+#define CRYPT5_KDF_LG2_COUNT     15 // LOG2 of PDKDF2 iteration count.
+#define CRYPT5_KDF_LG2_COUNT_MAX 24 // LOG2 of maximum accepted iteration count.
+#define CRYPT_VERSION             0 // Supported encryption version.
+
+
+class CryptData
+{
+  struct KDF5CacheItem
+  {
+    SecPassword Pwd;
+    byte Salt[SIZE_SALT50];
+    byte Key[32];
+    uint Lg2Count; // Log2 of PBKDF2 repetition count.
+    byte PswCheckValue[SHA256_DIGEST_SIZE];
+    byte HashKeyValue[SHA256_DIGEST_SIZE];
+  };
+
+  struct KDF3CacheItem
+  {
+    SecPassword Pwd;
+    byte Salt[SIZE_SALT30];
+    byte Key[16];
+    byte Init[16];
+    bool SaltPresent;
+  };
+
+
+  private:
+    void SetKey13(const char *Password);
+    void Decrypt13(byte *Data,size_t Count);
+
+    void SetKey15(const char *Password);
+    void Crypt15(byte *Data,size_t Count);
+
+    void SetKey20(const char *Password);
+    void Swap20(byte *Ch1,byte *Ch2);
+    void UpdKeys20(byte *Buf);
+    void EncryptBlock20(byte *Buf);
+    void DecryptBlock20(byte *Buf);
+
+    void SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt);
+    void SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck);
+
+    KDF3CacheItem KDF3Cache[4];
+    uint KDF3CachePos;
+    
+    KDF5CacheItem KDF5Cache[4];
+    uint KDF5CachePos;
+
+    CRYPT_METHOD Method;
+
+    Rijndael rin;
+
+    uint CRCTab[256]; // For RAR 1.5 and RAR 2.0 encryption.
+    
+    byte SubstTable20[256];
+    uint Key20[4];
+
+    byte Key13[3];
+    ushort Key15[4];
+  public:
+    CryptData();
+    ~CryptData();
+    bool SetCryptKeys(bool Encrypt,CRYPT_METHOD Method,SecPassword *Password,
+         const byte *Salt,const byte *InitV,uint Lg2Cnt,
+         byte *HashKey,byte *PswCheck);
+    void SetAV15Encryption();
+    void SetCmt13Encryption();
+    void EncryptBlock(byte *Buf,size_t Size);
+    void DecryptBlock(byte *Buf,size_t Size);
+    static void SetSalt(byte *Salt,size_t SaltSize);
+};
+
+void GetRnd(byte *RndBuf,size_t BufSize);
+
+void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data,
+                 size_t DataLength,byte *ResDigest);
+void pbkdf2(const byte *pass, size_t pass_len, const byte *salt,
+            size_t salt_len,byte *key, byte *Value1, byte *Value2,
+            uint rounds);
+
+void ConvertHashToMAC(HashValue *Value,byte *Key);
+
+#endif
diff --git a/third_party/unrar/src/crypt1.cpp b/third_party/unrar/src/crypt1.cpp
new file mode 100644
index 0000000..1426393
--- /dev/null
+++ b/third_party/unrar/src/crypt1.cpp
@@ -0,0 +1,79 @@
+extern uint CRCTab[256];
+
+void CryptData::SetKey13(const char *Password)
+{
+  Key13[0]=Key13[1]=Key13[2]=0;
+  for (size_t I=0;Password[I]!=0;I++)
+  {
+    byte P=Password[I];
+    Key13[0]+=P;
+    Key13[1]^=P;
+    Key13[2]+=P;
+    Key13[2]=(byte)rotls(Key13[2],1,8);
+  }
+}
+
+
+void CryptData::SetKey15(const char *Password)
+{
+  InitCRC32(CRCTab);
+  uint PswCRC=CRC32(0xffffffff,Password,strlen(Password));
+  Key15[0]=PswCRC&0xffff;
+  Key15[1]=(PswCRC>>16)&0xffff;
+  Key15[2]=Key15[3]=0;
+  for (size_t I=0;Password[I]!=0;I++)
+  {
+    byte P=Password[I];
+    Key15[2]^=P^CRCTab[P];
+    Key15[3]+=P+(CRCTab[P]>>16);
+  }
+}
+
+
+void CryptData::SetAV15Encryption()
+{
+  InitCRC32(CRCTab);
+  Method=CRYPT_RAR15;
+  Key15[0]=0x4765;
+  Key15[1]=0x9021;
+  Key15[2]=0x7382;
+  Key15[3]=0x5215;
+}
+
+
+void CryptData::SetCmt13Encryption()
+{
+  Method=CRYPT_RAR13;
+  Key13[0]=0;
+  Key13[1]=7;
+  Key13[2]=77;
+}
+
+
+void CryptData::Decrypt13(byte *Data,size_t Count)
+{
+  while (Count--)
+  {
+    Key13[1]+=Key13[2];
+    Key13[0]+=Key13[1];
+    *Data-=Key13[0];
+    Data++;
+  }
+}
+
+
+void CryptData::Crypt15(byte *Data,size_t Count)
+{
+  while (Count--)
+  {
+    Key15[0]+=0x1234;
+    Key15[1]^=CRCTab[(Key15[0] & 0x1fe)>>1];
+    Key15[2]-=CRCTab[(Key15[0] & 0x1fe)>>1]>>16;
+    Key15[0]^=Key15[2];
+    Key15[3]=rotrs(Key15[3]&0xffff,1,16)^Key15[1];
+    Key15[3]=rotrs(Key15[3]&0xffff,1,16);
+    Key15[0]^=Key15[3];
+    *Data^=(byte)(Key15[0]>>8);
+    Data++;
+  }
+}
diff --git a/third_party/unrar/src/crypt2.cpp b/third_party/unrar/src/crypt2.cpp
new file mode 100644
index 0000000..5fa4a97
--- /dev/null
+++ b/third_party/unrar/src/crypt2.cpp
@@ -0,0 +1,133 @@
+#define NROUNDS 32
+
+#define substLong(t) ( (uint)SubstTable20[(uint)t&255] | \
+           ((uint)SubstTable20[(int)(t>> 8)&255]<< 8) | \
+           ((uint)SubstTable20[(int)(t>>16)&255]<<16) | \
+           ((uint)SubstTable20[(int)(t>>24)&255]<<24) )
+
+
+static byte InitSubstTable20[256]={
+  215, 19,149, 35, 73,197,192,205,249, 28, 16,119, 48,221,  2, 42,
+  232,  1,177,233, 14, 88,219, 25,223,195,244, 90, 87,239,153,137,
+  255,199,147, 70, 92, 66,246, 13,216, 40, 62, 29,217,230, 86,  6,
+   71, 24,171,196,101,113,218,123, 93, 91,163,178,202, 67, 44,235,
+  107,250, 75,234, 49,167,125,211, 83,114,157,144, 32,193,143, 36,
+  158,124,247,187, 89,214,141, 47,121,228, 61,130,213,194,174,251,
+   97,110, 54,229,115, 57,152, 94,105,243,212, 55,209,245, 63, 11,
+  164,200, 31,156, 81,176,227, 21, 76, 99,139,188,127, 17,248, 51,
+  207,120,189,210,  8,226, 41, 72,183,203,135,165,166, 60, 98,  7,
+  122, 38,155,170, 69,172,252,238, 39,134, 59,128,236, 27,240, 80,
+  131,  3, 85,206,145, 79,154,142,159,220,201,133, 74, 64, 20,129,
+  224,185,138,103,173,182, 43, 34,254, 82,198,151,231,180, 58, 10,
+  118, 26,102, 12, 50,132, 22,191,136,111,162,179, 45,  4,148,108,
+  161, 56, 78,126,242,222, 15,175,146, 23, 33,241,181,190, 77,225,
+    0, 46,169,186, 68, 95,237, 65, 53,208,253,168,  9, 18,100, 52,
+  116,184,160, 96,109, 37, 30,106,140,104,150,  5,204,117,112, 84
+};
+
+
+void CryptData::SetKey20(const char *Password)
+{
+  InitCRC32(CRCTab);
+
+  char Psw[MAXPASSWORD];
+  strncpyz(Psw,Password,ASIZE(Psw)); // We'll need to modify it below.
+  size_t PswLength=strlen(Psw);
+
+  Key20[0]=0xD3A3B879L;
+  Key20[1]=0x3F6D12F7L;
+  Key20[2]=0x7515A235L;
+  Key20[3]=0xA4E7F123L;
+
+  memcpy(SubstTable20,InitSubstTable20,sizeof(SubstTable20));
+  for (uint J=0;J<256;J++)
+    for (size_t I=0;I<PswLength;I+=2)
+    {
+      uint N1=(byte)CRCTab [ (byte(Password[I])   - J) &0xff];
+      uint N2=(byte)CRCTab [ (byte(Password[I+1]) + J) &0xff];
+      for (int K=1;N1!=N2;N1=(N1+1)&0xff,K++)
+        Swap20(&SubstTable20[N1],&SubstTable20[(N1+I+K)&0xff]);
+    }
+  
+  // Incomplete last block of password must be zero padded.
+  if ((PswLength & CRYPT_BLOCK_MASK)!=0)
+    for (size_t I=PswLength;I<=(PswLength|CRYPT_BLOCK_MASK);I++)
+       Psw[I]=0;
+
+  for (size_t I=0;I<PswLength;I+=CRYPT_BLOCK_SIZE)
+    EncryptBlock20((byte *)Psw+I);
+}
+
+
+void CryptData::EncryptBlock20(byte *Buf)
+{
+  uint A,B,C,D,T,TA,TB;
+  A=RawGet4(Buf+0)^Key20[0];
+  B=RawGet4(Buf+4)^Key20[1];
+  C=RawGet4(Buf+8)^Key20[2];
+  D=RawGet4(Buf+12)^Key20[3];
+  for(int I=0;I<NROUNDS;I++)
+  {
+    T=((C+rotls(D,11,32))^Key20[I&3]);
+    TA=A^substLong(T);
+    T=((D^rotls(C,17,32))+Key20[I&3]);
+    TB=B^substLong(T);
+    A=C;
+    B=D;
+    C=TA;
+    D=TB;
+  }
+  RawPut4(C^Key20[0],Buf+0);
+  RawPut4(D^Key20[1],Buf+4);
+  RawPut4(A^Key20[2],Buf+8);
+  RawPut4(B^Key20[3],Buf+12);
+  UpdKeys20(Buf);
+}
+
+
+void CryptData::DecryptBlock20(byte *Buf)
+{
+  byte InBuf[16];
+  uint A,B,C,D,T,TA,TB;
+  A=RawGet4(Buf+0)^Key20[0];
+  B=RawGet4(Buf+4)^Key20[1];
+  C=RawGet4(Buf+8)^Key20[2];
+  D=RawGet4(Buf+12)^Key20[3];
+  memcpy(InBuf,Buf,sizeof(InBuf));
+  for(int I=NROUNDS-1;I>=0;I--)
+  {
+    T=((C+rotls(D,11,32))^Key20[I&3]);
+    TA=A^substLong(T);
+    T=((D^rotls(C,17,32))+Key20[I&3]);
+    TB=B^substLong(T);
+    A=C;
+    B=D;
+    C=TA;
+    D=TB;
+  }
+  RawPut4(C^Key20[0],Buf+0);
+  RawPut4(D^Key20[1],Buf+4);
+  RawPut4(A^Key20[2],Buf+8);
+  RawPut4(B^Key20[3],Buf+12);
+  UpdKeys20(InBuf);
+}
+
+
+void CryptData::UpdKeys20(byte *Buf)
+{
+  for (int I=0;I<16;I+=4)
+  {
+    Key20[0]^=CRCTab[Buf[I]];
+    Key20[1]^=CRCTab[Buf[I+1]];
+    Key20[2]^=CRCTab[Buf[I+2]];
+    Key20[3]^=CRCTab[Buf[I+3]];
+  }
+}
+
+
+void CryptData::Swap20(byte *Ch1,byte *Ch2)
+{
+  byte Ch=*Ch1;
+  *Ch1=*Ch2;
+  *Ch2=Ch;
+}
diff --git a/third_party/unrar/src/crypt3.cpp b/third_party/unrar/src/crypt3.cpp
new file mode 100644
index 0000000..4840648
--- /dev/null
+++ b/third_party/unrar/src/crypt3.cpp
@@ -0,0 +1,68 @@
+void CryptData::SetKey30(bool Encrypt,SecPassword *Password,const wchar *PwdW,const byte *Salt)
+{
+  byte AESKey[16],AESInit[16];
+
+  bool Cached=false;
+  for (uint I=0;I<ASIZE(KDF3Cache);I++)
+    if (KDF3Cache[I].Pwd==*Password &&
+        (Salt==NULL && !KDF3Cache[I].SaltPresent || Salt!=NULL &&
+        KDF3Cache[I].SaltPresent && memcmp(KDF3Cache[I].Salt,Salt,SIZE_SALT30)==0))
+    {
+      memcpy(AESKey,KDF3Cache[I].Key,sizeof(AESKey));
+      SecHideData(AESKey,sizeof(AESKey),false,false);
+      memcpy(AESInit,KDF3Cache[I].Init,sizeof(AESInit));
+      Cached=true;
+      break;
+    }
+
+  if (!Cached)
+  {
+    byte RawPsw[2*MAXPASSWORD+SIZE_SALT30];
+    WideToRaw(PwdW,RawPsw,ASIZE(RawPsw));
+    size_t RawLength=2*wcslen(PwdW);
+    if (Salt!=NULL)
+    {
+      memcpy(RawPsw+RawLength,Salt,SIZE_SALT30);
+      RawLength+=SIZE_SALT30;
+    }
+    sha1_context c;
+    sha1_init(&c);
+
+    const int HashRounds=0x40000;
+    for (int I=0;I<HashRounds;I++)
+    {
+      sha1_process_rar29( &c, RawPsw, RawLength );
+      byte PswNum[3];
+      PswNum[0]=(byte)I;
+      PswNum[1]=(byte)(I>>8);
+      PswNum[2]=(byte)(I>>16);
+      sha1_process(&c, PswNum, 3);
+      if (I%(HashRounds/16)==0)
+      {
+        sha1_context tempc=c;
+        uint32 digest[5];
+        sha1_done( &tempc, digest );
+        AESInit[I/(HashRounds/16)]=(byte)digest[4];
+      }
+    }
+    uint32 digest[5];
+    sha1_done( &c, digest );
+    for (int I=0;I<4;I++)
+      for (int J=0;J<4;J++)
+        AESKey[I*4+J]=(byte)(digest[I]>>(J*8));
+
+    KDF3Cache[KDF3CachePos].Pwd=*Password;
+    if ((KDF3Cache[KDF3CachePos].SaltPresent=(Salt!=NULL))==true)
+      memcpy(KDF3Cache[KDF3CachePos].Salt,Salt,SIZE_SALT30);
+    memcpy(KDF3Cache[KDF3CachePos].Key,AESKey,sizeof(AESKey));
+    SecHideData(KDF3Cache[KDF3CachePos].Key,sizeof(KDF3Cache[KDF3CachePos].Key),true,false);
+    memcpy(KDF3Cache[KDF3CachePos].Init,AESInit,sizeof(AESInit));
+    KDF3CachePos=(KDF3CachePos+1)%ASIZE(KDF3Cache);
+
+    cleandata(RawPsw,sizeof(RawPsw));
+  }
+  rin.Init(Encrypt, AESKey, 128, AESInit);
+  cleandata(AESKey,sizeof(AESKey));
+  cleandata(AESInit,sizeof(AESInit));
+}
+
diff --git a/third_party/unrar/src/crypt5.cpp b/third_party/unrar/src/crypt5.cpp
new file mode 100644
index 0000000..7562469
--- /dev/null
+++ b/third_party/unrar/src/crypt5.cpp
@@ -0,0 +1,233 @@
+static void hmac_sha256(const byte *Key,size_t KeyLength,const byte *Data,
+                        size_t DataLength,byte *ResDigest,
+                        sha256_context *ICtxOpt,bool *SetIOpt,
+                        sha256_context *RCtxOpt,bool *SetROpt)
+{
+  const size_t Sha256BlockSize=64; // As defined in RFC 4868.
+
+  byte KeyHash[SHA256_DIGEST_SIZE];
+  if (KeyLength > Sha256BlockSize) // Convert longer keys to key hash.
+  {
+    sha256_context KCtx;
+    sha256_init(&KCtx);
+    sha256_process(&KCtx, Key, KeyLength);
+    sha256_done(&KCtx, KeyHash);
+
+    Key = KeyHash;
+    KeyLength = SHA256_DIGEST_SIZE;
+  }
+
+  byte KeyBuf[Sha256BlockSize]; // Store the padded key here.
+  sha256_context ICtx;
+
+  if (ICtxOpt!=NULL && *SetIOpt)
+    ICtx=*ICtxOpt; // Use already calculated first block context.
+  else
+  {
+    // This calculation is the same for all iterations with same password.
+    // So for PBKDF2 we can calculate it only for first block and then reuse
+    // to improve performance. 
+
+    for (size_t I = 0; I < KeyLength; I++) // Use 0x36 padding for inner digest.
+      KeyBuf[I] = Key[I] ^ 0x36;
+    for (size_t I = KeyLength; I < Sha256BlockSize; I++)
+      KeyBuf[I] = 0x36;
+
+    sha256_init(&ICtx);
+    sha256_process(&ICtx, KeyBuf, Sha256BlockSize); // Hash padded key.
+  }
+
+  if (ICtxOpt!=NULL && !*SetIOpt) // Store constant context for further reuse.
+  {
+    *ICtxOpt=ICtx;
+    *SetIOpt=true;
+  }
+
+  sha256_process(&ICtx, Data, DataLength); // Hash data.
+
+  byte IDig[SHA256_DIGEST_SIZE]; // Internal digest for padded key and data.
+  sha256_done(&ICtx, IDig);
+
+  sha256_context RCtx;
+
+  if (RCtxOpt!=NULL && *SetROpt)
+    RCtx=*RCtxOpt; // Use already calculated first block context.
+  else
+  {
+    // This calculation is the same for all iterations with same password.
+    // So for PBKDF2 we can calculate it only for first block and then reuse
+    // to improve performance. 
+
+    for (size_t I = 0; I < KeyLength; I++) // Use 0x5c for outer key padding.
+      KeyBuf[I] = Key[I] ^ 0x5c;
+    for (size_t I = KeyLength; I < Sha256BlockSize; I++)
+      KeyBuf[I] = 0x5c;
+
+    sha256_init(&RCtx);
+    sha256_process(&RCtx, KeyBuf, Sha256BlockSize); // Hash padded key.
+  }
+
+  if (RCtxOpt!=NULL && !*SetROpt) // Store constant context for further reuse.
+  {
+    *RCtxOpt=RCtx;
+    *SetROpt=true;
+  }
+
+  sha256_process(&RCtx, IDig, SHA256_DIGEST_SIZE); // Hash internal digest.
+
+  sha256_done(&RCtx, ResDigest);
+}
+
+
+// PBKDF2 for 32 byte key length. We generate the key for specified number
+// of iteration count also as two supplementary values (key for checksums
+// and password verification) for iterations+16 and iterations+32.
+void pbkdf2(const byte *Pwd, size_t PwdLength, 
+            const byte *Salt, size_t SaltLength,
+            byte *Key, byte *V1, byte *V2, uint Count)
+{
+  const size_t MaxSalt=64;
+  byte SaltData[MaxSalt+4];
+  memcpy(SaltData, Salt, Min(SaltLength,MaxSalt));
+
+  SaltData[SaltLength + 0] = 0; // Salt concatenated to 1.
+  SaltData[SaltLength + 1] = 0;
+  SaltData[SaltLength + 2] = 0;
+  SaltData[SaltLength + 3] = 1;
+
+  // First iteration: HMAC of password, salt and block index (1).
+  byte U1[SHA256_DIGEST_SIZE];
+  hmac_sha256(Pwd, PwdLength, SaltData, SaltLength + 4, U1, NULL, NULL, NULL, NULL);
+  byte Fn[SHA256_DIGEST_SIZE]; // Current function value.
+  memcpy(Fn, U1, sizeof(Fn)); // Function at first iteration.
+
+  uint  CurCount[] = { Count-1, 16, 16 };
+  byte *CurValue[] = { Key    , V1, V2 };
+  
+  sha256_context ICtxOpt,RCtxOpt;
+  bool SetIOpt=false,SetROpt=false;
+  
+  byte U2[SHA256_DIGEST_SIZE];
+  for (uint I = 0; I < 3; I++) // For output key and 2 supplementary values.
+  {
+    for (uint J = 0; J < CurCount[I]; J++) 
+    {
+      // U2 = PRF (P, U1).
+      hmac_sha256(Pwd, PwdLength, U1, sizeof(U1), U2, &ICtxOpt, &SetIOpt, &RCtxOpt, &SetROpt);
+      memcpy(U1, U2, sizeof(U1));
+      for (uint K = 0; K < sizeof(Fn); K++) // Function ^= U.
+        Fn[K] ^= U1[K];
+    }
+    memcpy(CurValue[I], Fn, SHA256_DIGEST_SIZE);
+  }
+
+  cleandata(SaltData, sizeof(SaltData));
+  cleandata(Fn, sizeof(Fn));
+  cleandata(U1, sizeof(U1));
+  cleandata(U2, sizeof(U2));
+}
+
+
+void CryptData::SetKey50(bool Encrypt,SecPassword *Password,const wchar *PwdW,
+     const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,
+     byte *PswCheck)
+{
+  if (Lg2Cnt>CRYPT5_KDF_LG2_COUNT_MAX)
+    return;
+
+  byte Key[32],PswCheckValue[SHA256_DIGEST_SIZE],HashKeyValue[SHA256_DIGEST_SIZE];
+  bool Found=false;
+  for (uint I=0;I<ASIZE(KDF5Cache);I++)
+  {
+    KDF5CacheItem *Item=KDF5Cache+I;
+    if (Item->Lg2Count==Lg2Cnt && Item->Pwd==*Password &&
+        memcmp(Item->Salt,Salt,SIZE_SALT50)==0)
+    {
+      memcpy(Key,Item->Key,sizeof(Key));
+      SecHideData(Key,sizeof(Key),false,false);
+
+      memcpy(PswCheckValue,Item->PswCheckValue,sizeof(PswCheckValue));
+      memcpy(HashKeyValue,Item->HashKeyValue,sizeof(HashKeyValue));
+      Found=true;
+      break;
+    }
+  }
+
+  if (!Found)
+  {
+    char PwdUtf[MAXPASSWORD*4];
+    WideToUtf(PwdW,PwdUtf,ASIZE(PwdUtf));
+    
+    pbkdf2((byte *)PwdUtf,strlen(PwdUtf),Salt,SIZE_SALT50,Key,HashKeyValue,PswCheckValue,(1<<Lg2Cnt));
+    cleandata(PwdUtf,sizeof(PwdUtf));
+
+    KDF5CacheItem *Item=KDF5Cache+(KDF5CachePos++ % ASIZE(KDF5Cache));
+    Item->Lg2Count=Lg2Cnt;
+    Item->Pwd=*Password;
+    memcpy(Item->Salt,Salt,SIZE_SALT50);
+    memcpy(Item->Key,Key,sizeof(Item->Key));
+    memcpy(Item->PswCheckValue,PswCheckValue,sizeof(PswCheckValue));
+    memcpy(Item->HashKeyValue,HashKeyValue,sizeof(HashKeyValue));
+    SecHideData(Item->Key,sizeof(Item->Key),true,false);
+  }
+  if (HashKey!=NULL)
+    memcpy(HashKey,HashKeyValue,SHA256_DIGEST_SIZE);
+  if (PswCheck!=NULL)
+  {
+    memset(PswCheck,0,SIZE_PSWCHECK);
+    for (uint I=0;I<SHA256_DIGEST_SIZE;I++)
+      PswCheck[I%SIZE_PSWCHECK]^=PswCheckValue[I];
+    cleandata(PswCheckValue,sizeof(PswCheckValue));
+  }
+
+  // NULL initialization vector is possible if we only need the password
+  // check value for archive encryption header.
+  if (InitV!=NULL)
+    rin.Init(Encrypt, Key, 256, InitV);
+
+  cleandata(Key,sizeof(Key));
+}
+
+
+void ConvertHashToMAC(HashValue *Value,byte *Key)
+{
+  if (Value->Type==HASH_CRC32)
+  {
+    byte RawCRC[4];
+    RawPut4(Value->CRC32,RawCRC);
+    byte Digest[SHA256_DIGEST_SIZE];
+    hmac_sha256(Key,SHA256_DIGEST_SIZE,RawCRC,sizeof(RawCRC),Digest,NULL,NULL,NULL,NULL);
+    Value->CRC32=0;
+    for (uint I=0;I<ASIZE(Digest);I++)
+      Value->CRC32^=Digest[I] << ((I & 3) * 8);
+  }
+  if (Value->Type==HASH_BLAKE2)
+  {
+    byte Digest[BLAKE2_DIGEST_SIZE];
+    hmac_sha256(Key,BLAKE2_DIGEST_SIZE,Value->Digest,sizeof(Value->Digest),Digest,NULL,NULL,NULL,NULL);
+    memcpy(Value->Digest,Digest,sizeof(Value->Digest));
+  }
+}
+
+
+#if 0
+static void TestPBKDF2();
+struct TestKDF {TestKDF() {TestPBKDF2();exit(0);}} GlobalTestKDF;
+
+void TestPBKDF2() // Test PBKDF2 HMAC-SHA256
+{
+  byte Key[32],V1[32],V2[32];
+
+  pbkdf2((byte *)"password", 8, (byte *)"salt", 4, Key, V1, V2, 1);
+  byte Res1[32]={0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c, 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37, 0xa8, 0x65, 0x48, 0xc9, 0x2c, 0xcc, 0x35, 0x48, 0x08, 0x05, 0x98, 0x7c, 0xb7, 0x0b, 0xe1, 0x7b };
+  mprintf(L"\nPBKDF2 test1: %s", memcmp(Key,Res1,32)==0 ? L"OK":L"Failed");
+
+  pbkdf2((byte *)"password", 8, (byte *)"salt", 4, Key, V1, V2, 4096);
+  byte Res2[32]={0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41, 0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d, 0x96, 0x28, 0x93, 0xa0, 0x01, 0xce, 0x4e, 0x11, 0xa4, 0x96, 0x38, 0x73, 0xaa, 0x98, 0x13, 0x4a };
+  mprintf(L"\nPBKDF2 test2: %s", memcmp(Key,Res2,32)==0 ? L"OK":L"Failed");
+
+  pbkdf2((byte *)"just some long string pretending to be a password", 49, (byte *)"salt, salt, salt, a lot of salt", 31, Key, V1, V2, 65536);
+  byte Res3[32]={0x08, 0x0f, 0xa3, 0x1d, 0x42, 0x2d, 0xb0, 0x47, 0x83, 0x9b, 0xce, 0x3a, 0x3b, 0xce, 0x49, 0x51, 0xe2, 0x62, 0xb9, 0xff, 0x76, 0x2f, 0x57, 0xe9, 0xc4, 0x71, 0x96, 0xce, 0x4b, 0x6b, 0x6e, 0xbf};
+  mprintf(L"\nPBKDF2 test3: %s", memcmp(Key,Res3,32)==0 ? L"OK":L"Failed");
+}
+#endif
diff --git a/third_party/unrar/src/dll.cpp b/third_party/unrar/src/dll.cpp
new file mode 100644
index 0000000..c88337df
--- /dev/null
+++ b/third_party/unrar/src/dll.cpp
@@ -0,0 +1,481 @@
+#include "rar.hpp"
+
+static int RarErrorToDll(RAR_EXIT ErrCode);
+
+struct DataSet
+{
+  CommandData Cmd;
+  Archive Arc;
+  CmdExtract Extract;
+  int OpenMode;
+  int HeaderSize;
+
+  DataSet():Arc(&Cmd),Extract(&Cmd) {};
+};
+
+
+HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *r)
+{
+  RAROpenArchiveDataEx rx;
+  memset(&rx,0,sizeof(rx));
+  rx.ArcName=r->ArcName;
+  rx.OpenMode=r->OpenMode;
+  rx.CmtBuf=r->CmtBuf;
+  rx.CmtBufSize=r->CmtBufSize;
+  HANDLE hArc=RAROpenArchiveEx(&rx);
+  r->OpenResult=rx.OpenResult;
+  r->CmtSize=rx.CmtSize;
+  r->CmtState=rx.CmtState;
+  return hArc;
+}
+
+
+HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *r)
+{
+  DataSet *Data=NULL;
+  try
+  {
+    r->OpenResult=0;
+    Data=new DataSet;
+    Data->Cmd.DllError=0;
+    Data->OpenMode=r->OpenMode;
+    Data->Cmd.FileArgs.AddString(L"*");
+
+    char AnsiArcName[NM];
+    *AnsiArcName=0;
+    if (r->ArcName!=NULL)
+    {
+      strncpyz(AnsiArcName,r->ArcName,ASIZE(AnsiArcName));
+#ifdef _WIN_ALL
+      if (!AreFileApisANSI())
+      {
+        OemToCharBuffA(r->ArcName,AnsiArcName,ASIZE(AnsiArcName));
+        AnsiArcName[ASIZE(AnsiArcName)-1]=0;
+      }
+#endif
+    }
+
+    wchar ArcName[NM];
+    GetWideName(AnsiArcName,r->ArcNameW,ArcName,ASIZE(ArcName));
+
+    Data->Cmd.AddArcName(ArcName);
+    Data->Cmd.Overwrite=OVERWRITE_ALL;
+    Data->Cmd.VersionControl=1;
+
+    Data->Cmd.Callback=r->Callback;
+    Data->Cmd.UserData=r->UserData;
+
+    // Open shared mode is added by request of dll users, who need to
+    // browse and unpack archives while downloading.
+    Data->Cmd.OpenShared = true;
+    if (!Data->Arc.Open(ArcName,FMF_OPENSHARED))
+    {
+      r->OpenResult=ERAR_EOPEN;
+      delete Data;
+      return NULL;
+    }
+    if (!Data->Arc.IsArchive(true))
+    {
+      if (Data->Cmd.DllError!=0)
+        r->OpenResult=Data->Cmd.DllError;
+      else
+      {
+        RAR_EXIT ErrCode=ErrHandler.GetErrorCode();
+        if (ErrCode!=RARX_SUCCESS && ErrCode!=RARX_WARNING)
+          r->OpenResult=RarErrorToDll(ErrCode);
+        else
+          r->OpenResult=ERAR_BAD_ARCHIVE;
+      }
+      delete Data;
+      return NULL;
+    }
+    r->Flags=0;
+    
+    if (Data->Arc.Volume)
+      r->Flags|=0x01;
+    if (Data->Arc.Locked)
+      r->Flags|=0x04;
+    if (Data->Arc.Solid)
+      r->Flags|=0x08;
+    if (Data->Arc.NewNumbering)
+      r->Flags|=0x10;
+    if (Data->Arc.Signed)
+      r->Flags|=0x20;
+    if (Data->Arc.Protected)
+      r->Flags|=0x40;
+    if (Data->Arc.Encrypted)
+      r->Flags|=0x80;
+    if (Data->Arc.FirstVolume)
+      r->Flags|=0x100;
+
+    Array<wchar> CmtDataW;
+    if (r->CmtBufSize!=0 && Data->Arc.GetComment(&CmtDataW))
+    {
+      Array<char> CmtData(CmtDataW.Size()*4+1);
+      memset(&CmtData[0],0,CmtData.Size());
+      WideToChar(&CmtDataW[0],&CmtData[0],CmtData.Size()-1);
+      size_t Size=strlen(&CmtData[0])+1;
+
+      r->Flags|=2;
+      r->CmtState=Size>r->CmtBufSize ? ERAR_SMALL_BUF:1;
+      r->CmtSize=(uint)Min(Size,r->CmtBufSize);
+      memcpy(r->CmtBuf,&CmtData[0],r->CmtSize-1);
+      if (Size<=r->CmtBufSize)
+        r->CmtBuf[r->CmtSize-1]=0;
+    }
+    else
+      r->CmtState=r->CmtSize=0;
+    Data->Extract.ExtractArchiveInit(Data->Arc);
+    return (HANDLE)Data;
+  }
+  catch (RAR_EXIT ErrCode)
+  {
+    if (Data!=NULL && Data->Cmd.DllError!=0)
+      r->OpenResult=Data->Cmd.DllError;
+    else
+      r->OpenResult=RarErrorToDll(ErrCode);
+    if (Data != NULL)
+      delete Data;
+    return NULL;
+  }
+  catch (std::bad_alloc&) // Catch 'new' exception.
+  {
+    r->OpenResult=ERAR_NO_MEMORY;
+    if (Data != NULL)
+      delete Data;
+  }
+  return NULL; // To make compilers happy.
+}
+
+
+int PASCAL RARCloseArchive(HANDLE hArcData)
+{
+  DataSet *Data=(DataSet *)hArcData;
+  try
+  {
+    bool Success=Data==NULL ? false:Data->Arc.Close();
+    delete Data;
+    return Success ? ERAR_SUCCESS : ERAR_ECLOSE;
+  }
+  catch (RAR_EXIT ErrCode)
+  {
+    return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode);
+  }
+}
+
+
+int PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *D)
+{
+  struct RARHeaderDataEx X;
+  memset(&X,0,sizeof(X));
+
+  int Code=RARReadHeaderEx(hArcData,&X);
+
+  strncpyz(D->ArcName,X.ArcName,ASIZE(D->ArcName));
+  strncpyz(D->FileName,X.FileName,ASIZE(D->FileName));
+  D->Flags=X.Flags;
+  D->PackSize=X.PackSize;
+  D->UnpSize=X.UnpSize;
+  D->HostOS=X.HostOS;
+  D->FileCRC=X.FileCRC;
+  D->FileTime=X.FileTime;
+  D->UnpVer=X.UnpVer;
+  D->Method=X.Method;
+  D->FileAttr=X.FileAttr;
+  D->CmtSize=0;
+  D->CmtState=0;
+
+  return Code;
+}
+
+
+int PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *D)
+{
+  DataSet *Data=(DataSet *)hArcData;
+  try
+  {
+    if ((Data->HeaderSize=(int)Data->Arc.SearchBlock(HEAD_FILE))<=0)
+    {
+      if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_ENDARC &&
+          Data->Arc.EndArcHead.NextVolume)
+        if (MergeArchive(Data->Arc,NULL,false,'L'))
+        {
+          Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET);
+          return RARReadHeaderEx(hArcData,D);
+        }
+        else
+          return ERAR_EOPEN;
+
+      if (Data->Arc.BrokenHeader)
+        return ERAR_BAD_DATA;
+
+      // Might be necessary if RARSetPassword is still called instead of
+      // open callback for RAR5 archives and if password is invalid.
+      if (Data->Arc.FailedHeaderDecryption)
+        return ERAR_BAD_PASSWORD;
+      
+      return ERAR_END_ARCHIVE;
+    }
+    FileHeader *hd=&Data->Arc.FileHead;
+    if (Data->OpenMode==RAR_OM_LIST && hd->SplitBefore)
+    {
+      int Code=RARProcessFile(hArcData,RAR_SKIP,NULL,NULL);
+      if (Code==0)
+        return RARReadHeaderEx(hArcData,D);
+      else
+        return Code;
+    }
+    wcsncpy(D->ArcNameW,Data->Arc.FileName,ASIZE(D->ArcNameW));
+    WideToChar(D->ArcNameW,D->ArcName,ASIZE(D->ArcName));
+
+    wcsncpy(D->FileNameW,hd->FileName,ASIZE(D->FileNameW));
+    WideToChar(D->FileNameW,D->FileName,ASIZE(D->FileName));
+#ifdef _WIN_ALL
+    CharToOemA(D->FileName,D->FileName);
+#endif
+
+    D->Flags=0;
+    if (hd->SplitBefore)
+      D->Flags|=RHDF_SPLITBEFORE;
+    if (hd->SplitAfter)
+      D->Flags|=RHDF_SPLITAFTER;
+    if (hd->Encrypted)
+      D->Flags|=RHDF_ENCRYPTED;
+    if (hd->Solid)
+      D->Flags|=RHDF_SOLID;
+    if (hd->Dir)
+      D->Flags|=RHDF_DIRECTORY;
+
+    D->PackSize=uint(hd->PackSize & 0xffffffff);
+    D->PackSizeHigh=uint(hd->PackSize>>32);
+    D->UnpSize=uint(hd->UnpSize & 0xffffffff);
+    D->UnpSizeHigh=uint(hd->UnpSize>>32);
+    D->HostOS=hd->HSType==HSYS_WINDOWS ? HOST_WIN32:HOST_UNIX;
+    if (Data->Arc.Format==RARFMT50)
+      D->UnpVer=Data->Arc.FileHead.UnpVer==0 ? 50 : 200; // If it is not 0, just set it to something big.
+    else
+      D->UnpVer=Data->Arc.FileHead.UnpVer;
+    D->FileCRC=hd->FileHash.CRC32;
+    D->FileTime=hd->mtime.GetDos();
+    
+    uint64 MRaw=hd->mtime.GetWin();
+    D->MtimeLow=(uint)MRaw;
+    D->MtimeHigh=(uint)(MRaw>>32);
+    uint64 CRaw=hd->ctime.GetWin();
+    D->CtimeLow=(uint)CRaw;
+    D->CtimeHigh=(uint)(CRaw>>32);
+    uint64 ARaw=hd->atime.GetWin();
+    D->AtimeLow=(uint)ARaw;
+    D->AtimeHigh=(uint)(ARaw>>32);
+
+    D->Method=hd->Method+0x30;
+    D->FileAttr=hd->FileAttr;
+    D->CmtSize=0;
+    D->CmtState=0;
+
+    D->DictSize=uint(hd->WinSize/1024);
+
+    switch (hd->FileHash.Type)
+    {
+      case HASH_RAR14:
+      case HASH_CRC32:
+        D->HashType=RAR_HASH_CRC32;
+        break;
+      case HASH_BLAKE2:
+        D->HashType=RAR_HASH_BLAKE2;
+        memcpy(D->Hash,hd->FileHash.Digest,BLAKE2_DIGEST_SIZE);
+        break;
+      default:
+        D->HashType=RAR_HASH_NONE;
+        break;
+    }
+
+    D->RedirType=hd->RedirType;
+    // RedirNameSize sanity check is useful in case some developer
+    // did not initialize Reserved area with 0 as required in docs.
+    // We have taken 'Redir*' fields from Reserved area. We may remove
+    // this RedirNameSize check sometimes later.
+    if (hd->RedirType!=FSREDIR_NONE && D->RedirName!=NULL &&
+        D->RedirNameSize>0 && D->RedirNameSize<100000)
+      wcsncpyz(D->RedirName,hd->RedirName,D->RedirNameSize);
+    D->DirTarget=hd->DirTarget;
+  }
+  catch (RAR_EXIT ErrCode)
+  {
+    return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode);
+  }
+  return ERAR_SUCCESS;
+}
+
+
+int PASCAL ProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName,wchar *DestPathW,wchar *DestNameW)
+{
+  DataSet *Data=(DataSet *)hArcData;
+  try
+  {
+    Data->Cmd.DllError=0;
+    if (Data->OpenMode==RAR_OM_LIST || Data->OpenMode==RAR_OM_LIST_INCSPLIT ||
+        Operation==RAR_SKIP && !Data->Arc.Solid)
+    {
+      if (Data->Arc.Volume && Data->Arc.GetHeaderType()==HEAD_FILE &&
+          Data->Arc.FileHead.SplitAfter)
+        if (MergeArchive(Data->Arc,NULL,false,'L'))
+        {
+          Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET);
+          return ERAR_SUCCESS;
+        }
+        else
+          return ERAR_EOPEN;
+      Data->Arc.SeekToNext();
+    }
+    else
+    {
+      Data->Cmd.DllOpMode=Operation;
+
+      *Data->Cmd.ExtrPath=0;
+      *Data->Cmd.DllDestName=0;
+
+      if (DestPath!=NULL)
+      {
+        char ExtrPathA[NM];
+        strncpyz(ExtrPathA,DestPath,ASIZE(ExtrPathA)-2);
+#ifdef _WIN_ALL
+        // We must not apply OemToCharBuffA directly to DestPath,
+        // because we do not know DestPath length and OemToCharBuffA
+        // does not stop at 0.
+        OemToCharA(ExtrPathA,ExtrPathA);
+#endif
+        CharToWide(ExtrPathA,Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath));
+        AddEndSlash(Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath));
+      }
+      if (DestName!=NULL)
+      {
+        char DestNameA[NM];
+        strncpyz(DestNameA,DestName,ASIZE(DestNameA)-2);
+#ifdef _WIN_ALL
+        // We must not apply OemToCharBuffA directly to DestName,
+        // because we do not know DestName length and OemToCharBuffA
+        // does not stop at 0.
+        OemToCharA(DestNameA,DestNameA);
+#endif
+        CharToWide(DestNameA,Data->Cmd.DllDestName,ASIZE(Data->Cmd.DllDestName));
+      }
+
+      if (DestPathW!=NULL)
+      {
+        wcsncpy(Data->Cmd.ExtrPath,DestPathW,ASIZE(Data->Cmd.ExtrPath));
+        AddEndSlash(Data->Cmd.ExtrPath,ASIZE(Data->Cmd.ExtrPath));
+      }
+
+      if (DestNameW!=NULL)
+        wcsncpyz(Data->Cmd.DllDestName,DestNameW,ASIZE(Data->Cmd.DllDestName));
+
+      wcscpy(Data->Cmd.Command,Operation==RAR_EXTRACT ? L"X":L"T");
+      Data->Cmd.Test=Operation!=RAR_EXTRACT;
+      bool Repeat=false;
+      Data->Extract.ExtractCurrentFile(Data->Arc,Data->HeaderSize,Repeat);
+
+      // Now we process extra file information if any.
+      //
+      // Archive can be closed if we process volumes, next volume is missing
+      // and current one is already removed or deleted. So we need to check
+      // if archive is still open to avoid calling file operations on
+      // the invalid file handle. Some of our file operations like Seek()
+      // process such invalid handle correctly, some not.
+      while (Data->Arc.IsOpened() && Data->Arc.ReadHeader()!=0 && 
+             Data->Arc.GetHeaderType()==HEAD_SERVICE)
+      {
+        Data->Extract.ExtractCurrentFile(Data->Arc,Data->HeaderSize,Repeat);
+        Data->Arc.SeekToNext();
+      }
+      Data->Arc.Seek(Data->Arc.CurBlockPos,SEEK_SET);
+    }
+  }
+  catch (std::bad_alloc&)
+  {
+    return ERAR_NO_MEMORY;
+  }
+  catch (RAR_EXIT ErrCode)
+  {
+    return Data->Cmd.DllError!=0 ? Data->Cmd.DllError : RarErrorToDll(ErrCode);
+  }
+  return Data->Cmd.DllError;
+}
+
+
+int PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName)
+{
+  return ProcessFile(hArcData,Operation,DestPath,DestName,NULL,NULL);
+}
+
+
+int PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar *DestPath,wchar *DestName)
+{
+  return ProcessFile(hArcData,Operation,NULL,NULL,DestPath,DestName);
+}
+
+
+void PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc)
+{
+  DataSet *Data=(DataSet *)hArcData;
+  Data->Cmd.ChangeVolProc=ChangeVolProc;
+}
+
+
+void PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData)
+{
+  DataSet *Data=(DataSet *)hArcData;
+  Data->Cmd.Callback=Callback;
+  Data->Cmd.UserData=UserData;
+}
+
+
+void PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc)
+{
+  DataSet *Data=(DataSet *)hArcData;
+  Data->Cmd.ProcessDataProc=ProcessDataProc;
+}
+
+
+#ifndef RAR_NOCRYPT
+void PASCAL RARSetPassword(HANDLE hArcData,char *Password)
+{
+  DataSet *Data=(DataSet *)hArcData;
+  wchar PasswordW[MAXPASSWORD];
+  GetWideName(Password,NULL,PasswordW,ASIZE(PasswordW));
+  Data->Cmd.Password.Set(PasswordW);
+  cleandata(PasswordW,sizeof(PasswordW));
+}
+#endif
+
+
+int PASCAL RARGetDllVersion()
+{
+  return RAR_DLL_VERSION;
+}
+
+
+static int RarErrorToDll(RAR_EXIT ErrCode)
+{
+  switch(ErrCode)
+  {
+    case RARX_FATAL:
+      return ERAR_EREAD;
+    case RARX_CRC:
+      return ERAR_BAD_DATA;
+    case RARX_WRITE:
+      return ERAR_EWRITE;
+    case RARX_OPEN:
+      return ERAR_EOPEN;
+    case RARX_CREATE:
+      return ERAR_ECREATE;
+    case RARX_MEMORY:
+      return ERAR_NO_MEMORY;
+    case RARX_BADPWD:
+      return ERAR_BAD_PASSWORD;
+    case RARX_SUCCESS:
+      return ERAR_SUCCESS; // 0.
+    default:
+      return ERAR_UNKNOWN;
+  }
+}
diff --git a/third_party/unrar/src/dll.def b/third_party/unrar/src/dll.def
new file mode 100644
index 0000000..660f69bf
--- /dev/null
+++ b/third_party/unrar/src/dll.def
@@ -0,0 +1,12 @@
+EXPORTS

+  RAROpenArchive

+  RAROpenArchiveEx

+  RARCloseArchive

+  RARReadHeader

+  RARReadHeaderEx

+  RARProcessFile

+  RARSetCallback

+  RARSetChangeVolProc

+  RARSetProcessDataProc

+  RARSetPassword

+  RARGetDllVersion

diff --git a/third_party/unrar/src/dll.hpp b/third_party/unrar/src/dll.hpp
new file mode 100644
index 0000000..7f82906
--- /dev/null
+++ b/third_party/unrar/src/dll.hpp
@@ -0,0 +1,185 @@
+#ifndef _UNRAR_DLL_
+#define _UNRAR_DLL_
+
+#pragma pack(1)
+
+#define ERAR_SUCCESS             0
+#define ERAR_END_ARCHIVE        10
+#define ERAR_NO_MEMORY          11
+#define ERAR_BAD_DATA           12
+#define ERAR_BAD_ARCHIVE        13
+#define ERAR_UNKNOWN_FORMAT     14
+#define ERAR_EOPEN              15
+#define ERAR_ECREATE            16
+#define ERAR_ECLOSE             17
+#define ERAR_EREAD              18
+#define ERAR_EWRITE             19
+#define ERAR_SMALL_BUF          20
+#define ERAR_UNKNOWN            21
+#define ERAR_MISSING_PASSWORD   22
+#define ERAR_EREFERENCE         23
+#define ERAR_BAD_PASSWORD       24
+
+#define RAR_OM_LIST              0
+#define RAR_OM_EXTRACT           1
+#define RAR_OM_LIST_INCSPLIT     2
+
+#define RAR_SKIP              0
+#define RAR_TEST              1
+#define RAR_EXTRACT           2
+
+#define RAR_VOL_ASK           0
+#define RAR_VOL_NOTIFY        1
+
+#define RAR_DLL_VERSION       8
+
+#define RAR_HASH_NONE         0
+#define RAR_HASH_CRC32        1
+#define RAR_HASH_BLAKE2       2
+
+
+#ifdef _UNIX
+#define CALLBACK
+#define PASCAL
+#define LONG long
+#define HANDLE void *
+#define LPARAM long
+#define UINT unsigned int
+#endif
+
+#define RHDF_SPLITBEFORE 0x01
+#define RHDF_SPLITAFTER  0x02
+#define RHDF_ENCRYPTED   0x04
+#define RHDF_SOLID       0x10
+#define RHDF_DIRECTORY   0x20
+
+
+struct RARHeaderData
+{
+  char         ArcName[260];
+  char         FileName[260];
+  unsigned int Flags;
+  unsigned int PackSize;
+  unsigned int UnpSize;
+  unsigned int HostOS;
+  unsigned int FileCRC;
+  unsigned int FileTime;
+  unsigned int UnpVer;
+  unsigned int Method;
+  unsigned int FileAttr;
+  char         *CmtBuf;
+  unsigned int CmtBufSize;
+  unsigned int CmtSize;
+  unsigned int CmtState;
+};
+
+
+struct RARHeaderDataEx
+{
+  char         ArcName[1024];
+  wchar_t      ArcNameW[1024];
+  char         FileName[1024];
+  wchar_t      FileNameW[1024];
+  unsigned int Flags;
+  unsigned int PackSize;
+  unsigned int PackSizeHigh;
+  unsigned int UnpSize;
+  unsigned int UnpSizeHigh;
+  unsigned int HostOS;
+  unsigned int FileCRC;
+  unsigned int FileTime;
+  unsigned int UnpVer;
+  unsigned int Method;
+  unsigned int FileAttr;
+  char         *CmtBuf;
+  unsigned int CmtBufSize;
+  unsigned int CmtSize;
+  unsigned int CmtState;
+  unsigned int DictSize;
+  unsigned int HashType;
+  char         Hash[32];
+  unsigned int RedirType;
+  wchar_t      *RedirName;
+  unsigned int RedirNameSize;
+  unsigned int DirTarget;
+  unsigned int MtimeLow;
+  unsigned int MtimeHigh;
+  unsigned int CtimeLow;
+  unsigned int CtimeHigh;
+  unsigned int AtimeLow;
+  unsigned int AtimeHigh;
+  unsigned int Reserved[988];
+};
+
+
+struct RAROpenArchiveData
+{
+  char         *ArcName;
+  unsigned int OpenMode;
+  unsigned int OpenResult;
+  char         *CmtBuf;
+  unsigned int CmtBufSize;
+  unsigned int CmtSize;
+  unsigned int CmtState;
+};
+
+typedef int (CALLBACK *UNRARCALLBACK)(UINT msg,LPARAM UserData,LPARAM P1,LPARAM P2);
+
+#define ROADF_VOLUME       0x0001
+#define ROADF_COMMENT      0x0002
+#define ROADF_LOCK         0x0004
+#define ROADF_SOLID        0x0008
+#define ROADF_NEWNUMBERING 0x0010
+#define ROADF_SIGNED       0x0020
+#define ROADF_RECOVERY     0x0040
+#define ROADF_ENCHEADERS   0x0080
+#define ROADF_FIRSTVOLUME  0x0100
+
+struct RAROpenArchiveDataEx
+{
+  char         *ArcName;
+  wchar_t      *ArcNameW;
+  unsigned int  OpenMode;
+  unsigned int  OpenResult;
+  char         *CmtBuf;
+  unsigned int  CmtBufSize;
+  unsigned int  CmtSize;
+  unsigned int  CmtState;
+  unsigned int  Flags;
+  UNRARCALLBACK Callback;
+  LPARAM        UserData;
+  unsigned int  Reserved[28];
+};
+
+enum UNRARCALLBACK_MESSAGES {
+  UCM_CHANGEVOLUME,UCM_PROCESSDATA,UCM_NEEDPASSWORD,UCM_CHANGEVOLUMEW,
+  UCM_NEEDPASSWORDW
+};
+
+typedef int (PASCAL *CHANGEVOLPROC)(char *ArcName,int Mode);
+typedef int (PASCAL *PROCESSDATAPROC)(unsigned char *Addr,int Size);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+HANDLE PASCAL RAROpenArchive(struct RAROpenArchiveData *ArchiveData);
+HANDLE PASCAL RAROpenArchiveEx(struct RAROpenArchiveDataEx *ArchiveData);
+int    PASCAL RARCloseArchive(HANDLE hArcData);
+int    PASCAL RARReadHeader(HANDLE hArcData,struct RARHeaderData *HeaderData);
+int    PASCAL RARReadHeaderEx(HANDLE hArcData,struct RARHeaderDataEx *HeaderData);
+int    PASCAL RARProcessFile(HANDLE hArcData,int Operation,char *DestPath,char *DestName);
+int    PASCAL RARProcessFileW(HANDLE hArcData,int Operation,wchar_t *DestPath,wchar_t *DestName);
+void   PASCAL RARSetCallback(HANDLE hArcData,UNRARCALLBACK Callback,LPARAM UserData);
+void   PASCAL RARSetChangeVolProc(HANDLE hArcData,CHANGEVOLPROC ChangeVolProc);
+void   PASCAL RARSetProcessDataProc(HANDLE hArcData,PROCESSDATAPROC ProcessDataProc);
+void   PASCAL RARSetPassword(HANDLE hArcData,char *Password);
+int    PASCAL RARGetDllVersion();
+
+#ifdef __cplusplus
+}
+#endif
+
+#pragma pack()
+
+#endif
diff --git a/third_party/unrar/src/dll.rc b/third_party/unrar/src/dll.rc
new file mode 100644
index 0000000..430e9cc
--- /dev/null
+++ b/third_party/unrar/src/dll.rc
@@ -0,0 +1,28 @@
+#include <windows.h>

+#include <commctrl.h>

+

+VS_VERSION_INFO VERSIONINFO

+FILEVERSION 5, 60, 1, 2512
+PRODUCTVERSION 5, 60, 1, 2512
+FILEOS VOS__WINDOWS32

+FILETYPE VFT_APP

+{

+  BLOCK "StringFileInfo"

+  {

+    BLOCK "040904E4"

+    {

+      VALUE "CompanyName", "Alexander Roshal\0"

+      VALUE "ProductName", "RAR decompression library\0"

+      VALUE "FileDescription", "RAR decompression library\0"

+      VALUE "FileVersion", "5.60.1\0"
+      VALUE "ProductVersion", "5.60.1\0"
+      VALUE "LegalCopyright", "Copyright © Alexander Roshal 1993-2017\0"
+      VALUE "OriginalFilename", "Unrar.dll\0"

+    }

+  }

+  BLOCK "VarFileInfo"

+  {

+    VALUE "Translation", 0x0409, 0x04E4

+  }

+}

+

diff --git a/third_party/unrar/src/encname.cpp b/third_party/unrar/src/encname.cpp
new file mode 100644
index 0000000..5556af3e
--- /dev/null
+++ b/third_party/unrar/src/encname.cpp
@@ -0,0 +1,69 @@
+#include "rar.hpp"
+
+EncodeFileName::EncodeFileName()
+{
+  Flags=0;
+  FlagBits=0;
+  FlagsPos=0;
+  DestSize=0;
+}
+
+
+
+
+void EncodeFileName::Decode(char *Name,byte *EncName,size_t EncSize,wchar *NameW,
+                            size_t MaxDecSize)
+{
+  size_t EncPos=0,DecPos=0;
+  byte HighByte=EncPos<EncSize ? EncName[EncPos++] : 0;
+  while (EncPos<EncSize && DecPos<MaxDecSize)
+  {
+    if (FlagBits==0)
+    {
+      if (EncPos>=EncSize)
+        break;
+      Flags=EncName[EncPos++];
+      FlagBits=8;
+    }
+    switch(Flags>>6)
+    {
+      case 0:
+        if (EncPos>=EncSize)
+          break;
+        NameW[DecPos++]=EncName[EncPos++];
+        break;
+      case 1:
+        if (EncPos>=EncSize)
+          break;
+        NameW[DecPos++]=EncName[EncPos++]+(HighByte<<8);
+        break;
+      case 2:
+        if (EncPos+1>=EncSize)
+          break;
+        NameW[DecPos++]=EncName[EncPos]+(EncName[EncPos+1]<<8);
+        EncPos+=2;
+        break;
+      case 3:
+        {
+          if (EncPos>=EncSize)
+            break;
+          int Length=EncName[EncPos++];
+          if ((Length & 0x80)!=0)
+          {
+            if (EncPos>=EncSize)
+              break;
+            byte Correction=EncName[EncPos++];
+            for (Length=(Length&0x7f)+2;Length>0 && DecPos<MaxDecSize;Length--,DecPos++)
+              NameW[DecPos]=((Name[DecPos]+Correction)&0xff)+(HighByte<<8);
+          }
+          else
+            for (Length+=2;Length>0 && DecPos<MaxDecSize;Length--,DecPos++)
+              NameW[DecPos]=Name[DecPos];
+        }
+        break;
+    }
+    Flags<<=2;
+    FlagBits-=2;
+  }
+  NameW[DecPos<MaxDecSize ? DecPos:MaxDecSize-1]=0;
+}
diff --git a/third_party/unrar/src/encname.hpp b/third_party/unrar/src/encname.hpp
new file mode 100644
index 0000000..3e7786f
--- /dev/null
+++ b/third_party/unrar/src/encname.hpp
@@ -0,0 +1,20 @@
+#ifndef _RAR_ENCNAME_
+#define _RAR_ENCNAME_
+
+class EncodeFileName
+{
+  private:
+    void AddFlags(int Value);
+
+    byte *EncName;
+    byte Flags;
+    uint FlagBits;
+    size_t FlagsPos;
+    size_t DestSize;
+  public:
+    EncodeFileName();
+    size_t Encode(char *Name,wchar *NameW,byte *EncName);
+    void Decode(char *Name,byte *EncName,size_t EncSize,wchar *NameW,size_t MaxDecSize);
+};
+
+#endif
diff --git a/third_party/unrar/src/errhnd.cpp b/third_party/unrar/src/errhnd.cpp
new file mode 100644
index 0000000..e862bb3a
--- /dev/null
+++ b/third_party/unrar/src/errhnd.cpp
@@ -0,0 +1,393 @@
+#include "rar.hpp"
+
+ErrorHandler::ErrorHandler()
+{
+  Clean();
+}
+
+
+void ErrorHandler::Clean()
+{
+  ExitCode=RARX_SUCCESS;
+  ErrCount=0;
+  EnableBreak=true;
+  Silent=false;
+  UserBreak=false;
+  MainExit=false;
+  DisableShutdown=false;
+}
+
+
+void ErrorHandler::MemoryError()
+{
+  MemoryErrorMsg();
+  Exit(RARX_MEMORY);
+}
+
+
+void ErrorHandler::OpenError(const wchar *FileName)
+{
+#ifndef SILENT
+  OpenErrorMsg(FileName);
+  Exit(RARX_OPEN);
+#endif
+}
+
+
+void ErrorHandler::CloseError(const wchar *FileName)
+{
+  if (!UserBreak)
+  {
+    uiMsg(UIERROR_FILECLOSE,FileName);
+    SysErrMsg();
+  }
+  // We must not call Exit and throw an exception here, because this function
+  // is called from File object destructor and can be invoked when stack
+  // unwinding while handling another exception. Throwing a new exception
+  // when stack unwinding is prohibited and terminates a program.
+  SetErrorCode(RARX_FATAL);
+}
+
+
+void ErrorHandler::ReadError(const wchar *FileName)
+{
+#ifndef SILENT
+  ReadErrorMsg(FileName);
+#endif
+#if !defined(SILENT) || defined(RARDLL)
+  Exit(RARX_FATAL);
+#endif
+}
+
+
+bool ErrorHandler::AskRepeatRead(const wchar *FileName)
+{
+#if !defined(SILENT) && !defined(SFX_MODULE)
+  if (!Silent)
+  {
+    SysErrMsg();
+    bool Repeat=uiAskRepeatRead(FileName);
+    if (!Repeat) // Disable shutdown if user pressed Cancel in error dialog.
+      DisableShutdown=true;
+    return Repeat;
+  }
+#endif
+  return false;
+}
+
+
+void ErrorHandler::WriteError(const wchar *ArcName,const wchar *FileName)
+{
+#ifndef SILENT
+  WriteErrorMsg(ArcName,FileName);
+#endif
+#if !defined(SILENT) || defined(RARDLL)
+  Exit(RARX_WRITE);
+#endif
+}
+
+
+#ifdef _WIN_ALL
+void ErrorHandler::WriteErrorFAT(const wchar *FileName)
+{
+  SysErrMsg();
+  uiMsg(UIERROR_NTFSREQUIRED,FileName);
+#if !defined(SILENT) && !defined(SFX_MODULE) || defined(RARDLL)
+  Exit(RARX_WRITE);
+#endif
+}
+#endif
+
+
+bool ErrorHandler::AskRepeatWrite(const wchar *FileName,bool DiskFull)
+{
+#ifndef SILENT
+  if (!Silent)
+  {
+    // We do not display "repeat write" prompt in Android, so we do not
+    // need the matching system error message.
+    SysErrMsg();
+    bool Repeat=uiAskRepeatWrite(FileName,DiskFull);
+    if (!Repeat) // Disable shutdown if user pressed Cancel in error dialog.
+      DisableShutdown=true;
+    return Repeat;
+  }
+#endif
+  return false;
+}
+
+
+void ErrorHandler::SeekError(const wchar *FileName)
+{
+  if (!UserBreak)
+  {
+    uiMsg(UIERROR_FILESEEK,FileName);
+    SysErrMsg();
+  }
+#if !defined(SILENT) || defined(RARDLL)
+  Exit(RARX_FATAL);
+#endif
+}
+
+
+void ErrorHandler::GeneralErrMsg(const wchar *fmt,...)
+{
+  va_list arglist;
+  va_start(arglist,fmt);
+  wchar Msg[1024];
+  vswprintf(Msg,ASIZE(Msg),fmt,arglist);
+  uiMsg(UIERROR_GENERALERRMSG,Msg);
+  SysErrMsg();
+  va_end(arglist);
+}
+
+
+void ErrorHandler::MemoryErrorMsg()
+{
+  uiMsg(UIERROR_MEMORY);
+  SetErrorCode(RARX_MEMORY);
+}
+
+
+void ErrorHandler::OpenErrorMsg(const wchar *FileName)
+{
+  OpenErrorMsg(NULL,FileName);
+}
+
+
+void ErrorHandler::OpenErrorMsg(const wchar *ArcName,const wchar *FileName)
+{
+  uiMsg(UIERROR_FILEOPEN,ArcName,FileName);
+  SysErrMsg();
+  SetErrorCode(RARX_OPEN);
+}
+
+
+void ErrorHandler::CreateErrorMsg(const wchar *FileName)
+{
+  CreateErrorMsg(NULL,FileName);
+}
+
+
+void ErrorHandler::CreateErrorMsg(const wchar *ArcName,const wchar *FileName)
+{
+  uiMsg(UIERROR_FILECREATE,ArcName,FileName);
+  SysErrMsg();
+  SetErrorCode(RARX_CREATE);
+}
+
+
+void ErrorHandler::ReadErrorMsg(const wchar *FileName)
+{
+  ReadErrorMsg(NULL,FileName);
+}
+
+
+void ErrorHandler::ReadErrorMsg(const wchar *ArcName,const wchar *FileName)
+{
+  uiMsg(UIERROR_FILEREAD,ArcName,FileName);
+  SysErrMsg();
+  SetErrorCode(RARX_FATAL);
+}
+
+
+void ErrorHandler::WriteErrorMsg(const wchar *ArcName,const wchar *FileName)
+{
+  uiMsg(UIERROR_FILEWRITE,ArcName,FileName);
+  SysErrMsg();
+  SetErrorCode(RARX_WRITE);
+}
+
+
+void ErrorHandler::ArcBrokenMsg(const wchar *ArcName)
+{
+  uiMsg(UIERROR_ARCBROKEN,ArcName);
+  SetErrorCode(RARX_CRC);
+}
+
+
+void ErrorHandler::ChecksumFailedMsg(const wchar *ArcName,const wchar *FileName)
+{
+  uiMsg(UIERROR_CHECKSUM,ArcName,FileName);
+  SetErrorCode(RARX_CRC);
+}
+
+
+void ErrorHandler::UnknownMethodMsg(const wchar *ArcName,const wchar *FileName)
+{
+  uiMsg(UIERROR_UNKNOWNMETHOD,ArcName,FileName);
+  ErrHandler.SetErrorCode(RARX_FATAL);
+}
+
+
+void ErrorHandler::Exit(RAR_EXIT ExitCode)
+{
+  uiAlarm(UIALARM_ERROR);
+  Throw(ExitCode);
+}
+
+
+void ErrorHandler::SetErrorCode(RAR_EXIT Code)
+{
+  switch(Code)
+  {
+    case RARX_WARNING:
+    case RARX_USERBREAK:
+      if (ExitCode==RARX_SUCCESS)
+        ExitCode=Code;
+      break;
+    case RARX_CRC:
+      if (ExitCode!=RARX_BADPWD)
+        ExitCode=Code;
+      break;
+    case RARX_FATAL:
+      if (ExitCode==RARX_SUCCESS || ExitCode==RARX_WARNING)
+        ExitCode=RARX_FATAL;
+      break;
+    default:
+      ExitCode=Code;
+      break;
+  }
+  ErrCount++;
+}
+
+
+#ifdef _WIN_ALL
+BOOL __stdcall ProcessSignal(DWORD SigType)
+#else
+#if defined(__sun)
+extern "C"
+#endif
+void _stdfunction ProcessSignal(int SigType)
+#endif
+{
+#ifdef _WIN_ALL
+  // When a console application is run as a service, this allows the service
+  // to continue running after the user logs off. 
+  if (SigType==CTRL_LOGOFF_EVENT)
+    return TRUE;
+#endif
+
+  ErrHandler.UserBreak=true;
+  mprintf(St(MBreak));
+
+#ifdef _WIN_ALL
+  // Let the main thread to handle 'throw' and destroy file objects.
+  for (uint I=0;!ErrHandler.MainExit && I<50;I++)
+    Sleep(100);
+#if defined(USE_RC) && !defined(SFX_MODULE) && !defined(RARDLL)
+  ExtRes.UnloadDLL();
+#endif
+  exit(RARX_USERBREAK);
+#endif
+
+#ifdef _UNIX
+  static uint BreakCount=0;
+  // User continues to press Ctrl+C, exit immediately without cleanup.
+  if (++BreakCount>1)
+    exit(RARX_USERBREAK);
+  // Otherwise return from signal handler and let Wait() function to close
+  // files and quit. We cannot use the same approach as in Windows,
+  // because Unix signal handler can block execution of our main code.
+#endif
+
+#if defined(_WIN_ALL) && !defined(_MSC_VER)
+  // never reached, just to avoid a compiler warning
+  return TRUE;
+#endif
+}
+
+
+void ErrorHandler::SetSignalHandlers(bool Enable)
+{
+  EnableBreak=Enable;
+#ifdef _WIN_ALL
+  SetConsoleCtrlHandler(Enable ? ProcessSignal:NULL,TRUE);
+#else
+  signal(SIGINT,Enable ? ProcessSignal:SIG_IGN);
+  signal(SIGTERM,Enable ? ProcessSignal:SIG_IGN);
+#endif
+}
+
+
+void ErrorHandler::Throw(RAR_EXIT Code)
+{
+  if (Code==RARX_USERBREAK && !EnableBreak)
+    return;
+#if !defined(SILENT)
+  // Do not write "aborted" when just displaying online help.
+  if (Code!=RARX_SUCCESS && Code!=RARX_USERERROR)
+    mprintf(L"\n%s\n",St(MProgAborted));
+#endif
+  SetErrorCode(Code);
+  throw Code;
+}
+
+
+void ErrorHandler::SysErrMsg()
+{
+#if !defined(SFX_MODULE) && !defined(SILENT)
+#ifdef _WIN_ALL
+  wchar *lpMsgBuf=NULL;
+  int ErrType=GetLastError();
+  if (ErrType!=0 && FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
+              NULL,ErrType,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+              (LPTSTR)&lpMsgBuf,0,NULL))
+  {
+    wchar *CurMsg=lpMsgBuf;
+    while (CurMsg!=NULL)
+    {
+      while (*CurMsg=='\r' || *CurMsg=='\n')
+        CurMsg++;
+      if (*CurMsg==0)
+        break;
+      wchar *EndMsg=wcschr(CurMsg,'\r');
+      if (EndMsg==NULL)
+        EndMsg=wcschr(CurMsg,'\n');
+      if (EndMsg!=NULL)
+      {
+        *EndMsg=0;
+        EndMsg++;
+      }
+      uiMsg(UIERROR_SYSERRMSG,CurMsg);
+      CurMsg=EndMsg;
+    }
+  }
+  LocalFree( lpMsgBuf );
+#endif
+
+#if defined(_UNIX) || defined(_EMX)
+  if (errno!=0)
+  {
+    char *err=strerror(errno);
+    if (err!=NULL)
+    {
+      wchar Msg[1024];
+      CharToWide(err,Msg,ASIZE(Msg));
+      uiMsg(UIERROR_SYSERRMSG,Msg);
+    }
+  }
+#endif
+
+#endif
+}
+
+
+int ErrorHandler::GetSystemErrorCode()
+{
+#ifdef _WIN_ALL
+  return GetLastError();
+#else
+  return errno;
+#endif
+}
+
+
+void ErrorHandler::SetSystemErrorCode(int Code)
+{
+#ifdef _WIN_ALL
+  SetLastError(Code);
+#else
+  errno=Code;
+#endif
+}
diff --git a/third_party/unrar/src/errhnd.hpp b/third_party/unrar/src/errhnd.hpp
new file mode 100644
index 0000000..26df96d
--- /dev/null
+++ b/third_party/unrar/src/errhnd.hpp
@@ -0,0 +1,70 @@
+#ifndef _RAR_ERRHANDLER_
+#define _RAR_ERRHANDLER_
+
+enum RAR_EXIT // RAR exit code.
+{ 
+  RARX_SUCCESS   =   0,
+  RARX_WARNING   =   1,
+  RARX_FATAL     =   2,
+  RARX_CRC       =   3,
+  RARX_LOCK      =   4,
+  RARX_WRITE     =   5,
+  RARX_OPEN      =   6,
+  RARX_USERERROR =   7,
+  RARX_MEMORY    =   8,
+  RARX_CREATE    =   9,
+  RARX_NOFILES   =  10,
+  RARX_BADPWD    =  11,
+  RARX_USERBREAK = 255
+};
+
+class ErrorHandler
+{
+  private:
+    RAR_EXIT ExitCode;
+    uint ErrCount;
+    bool EnableBreak;
+    bool Silent;
+    bool DisableShutdown; // Shutdown is not suitable after last error.
+  public:
+    ErrorHandler();
+    void Clean();
+    void MemoryError();
+    void OpenError(const wchar *FileName);
+    void CloseError(const wchar *FileName);
+    void ReadError(const wchar *FileName);
+    bool AskRepeatRead(const wchar *FileName);
+    void WriteError(const wchar *ArcName,const wchar *FileName);
+    void WriteErrorFAT(const wchar *FileName);
+    bool AskRepeatWrite(const wchar *FileName,bool DiskFull);
+    void SeekError(const wchar *FileName);
+    void GeneralErrMsg(const wchar *fmt,...);
+    void MemoryErrorMsg();
+    void OpenErrorMsg(const wchar *FileName);
+    void OpenErrorMsg(const wchar *ArcName,const wchar *FileName);
+    void CreateErrorMsg(const wchar *FileName);
+    void CreateErrorMsg(const wchar *ArcName,const wchar *FileName);
+    void ReadErrorMsg(const wchar *FileName);
+    void ReadErrorMsg(const wchar *ArcName,const wchar *FileName);
+    void WriteErrorMsg(const wchar *ArcName,const wchar *FileName);
+    void ArcBrokenMsg(const wchar *ArcName);
+    void ChecksumFailedMsg(const wchar *ArcName,const wchar *FileName);
+    void UnknownMethodMsg(const wchar *ArcName,const wchar *FileName);
+    void Exit(RAR_EXIT ExitCode);
+    void SetErrorCode(RAR_EXIT Code);
+    RAR_EXIT GetErrorCode() {return ExitCode;}
+    uint GetErrorCount() {return ErrCount;}
+    void SetSignalHandlers(bool Enable);
+    void Throw(RAR_EXIT Code);
+    void SetSilent(bool Mode) {Silent=Mode;};
+    void SysErrMsg();
+    int GetSystemErrorCode();
+    void SetSystemErrorCode(int Code);
+    bool IsShutdownEnabled() {return !DisableShutdown;}
+
+    bool UserBreak; // Ctrl+Break is pressed.
+    bool MainExit; // main() is completed.
+};
+
+
+#endif
diff --git a/third_party/unrar/src/extinfo.cpp b/third_party/unrar/src/extinfo.cpp
new file mode 100644
index 0000000..5cb90a40
--- /dev/null
+++ b/third_party/unrar/src/extinfo.cpp
@@ -0,0 +1,178 @@
+#include "rar.hpp"
+
+#include "hardlinks.cpp"
+#include "win32stm.cpp"
+
+#ifdef _WIN_ALL
+#include "win32acl.cpp"
+#include "win32lnk.cpp"
+#endif
+
+#ifdef _UNIX
+#include "uowners.cpp"
+#ifdef SAVE_LINKS
+#include "ulinks.cpp"
+#endif
+#endif
+
+
+
+// RAR2 service header extra records.
+#ifndef SFX_MODULE
+void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name)
+{
+  if (Cmd->Test)
+    return;
+  switch(Arc.SubBlockHead.SubType)
+  {
+#ifdef _UNIX
+    case UO_HEAD:
+      if (Cmd->ProcessOwners)
+        ExtractUnixOwner20(Arc,Name);
+      break;
+#endif
+#ifdef _WIN_ALL
+    case NTACL_HEAD:
+      if (Cmd->ProcessOwners)
+        ExtractACL20(Arc,Name);
+      break;
+    case STREAM_HEAD:
+      ExtractStreams20(Arc,Name);
+      break;
+#endif
+  }
+}
+#endif
+
+
+// RAR3 and RAR5 service header extra records.
+void SetExtraInfo(CommandData *Cmd,Archive &Arc,wchar *Name)
+{
+#ifdef _UNIX
+  if (!Cmd->Test && Cmd->ProcessOwners && Arc.Format==RARFMT15 &&
+      Arc.SubHead.CmpName(SUBHEAD_TYPE_UOWNER))
+    ExtractUnixOwner30(Arc,Name);
+#endif
+#ifdef _WIN_ALL
+  if (!Cmd->Test && Cmd->ProcessOwners && Arc.SubHead.CmpName(SUBHEAD_TYPE_ACL))
+    ExtractACL(Arc,Name);
+  if (Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM))
+    ExtractStreams(Arc,Name,Cmd->Test);
+#endif
+}
+
+
+// Extra data stored directly in file header.
+void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,wchar *Name)
+{
+#ifdef _UNIX
+   if (Cmd->ProcessOwners && Arc.Format==RARFMT50 && Arc.FileHead.UnixOwnerSet)
+     SetUnixOwner(Arc,Name);
+#endif
+}
+
+
+
+
+// Calculate a number of path components except \. and \..
+static int CalcAllowedDepth(const wchar *Name)
+{
+  int AllowedDepth=0;
+  while (*Name!=0)
+  {
+    if (IsPathDiv(Name[0]) && Name[1]!=0 && !IsPathDiv(Name[1]))
+    {
+      bool Dot=Name[1]=='.' && (IsPathDiv(Name[2]) || Name[2]==0);
+      bool Dot2=Name[1]=='.' && Name[2]=='.' && (IsPathDiv(Name[3]) || Name[3]==0);
+      if (!Dot && !Dot2)
+        AllowedDepth++;
+    }
+    Name++;
+  }
+  return AllowedDepth;
+}
+
+
+// Check if all existing path components are directories and not links.
+static bool LinkInPath(const wchar *Name)
+{
+  wchar Path[NM];
+  if (wcslen(Name)>=ASIZE(Path))
+    return true;  // It should not be that long, skip.
+  wcsncpyz(Path,Name,ASIZE(Path));
+  for (wchar *s=Path+wcslen(Path)-1;s>Path;s--)
+    if (IsPathDiv(*s))
+    {
+      *s=0;
+      FindData FD;
+      if (FindFile::FastFind(Path,&FD,true) && (FD.IsLink || !FD.IsDir))
+        return true;
+    }
+  return false;
+}
+
+
+bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName)
+{
+  // Catch root dir based /path/file paths also as stuff like \\?\.
+  // Do not check PrepSrcName here, it can be root based if destination path
+  // is a root based.
+  if (IsFullRootPath(SrcName) || IsFullRootPath(TargetName))
+    return false;
+
+  // Number of ".." in link target.
+  int UpLevels=0;
+  for (int Pos=0;*TargetName!=0;Pos++)
+  {
+    bool Dot2=TargetName[0]=='.' && TargetName[1]=='.' && 
+              (IsPathDiv(TargetName[2]) || TargetName[2]==0) &&
+              (Pos==0 || IsPathDiv(*(TargetName-1)));
+    if (Dot2)
+      UpLevels++;
+    TargetName++;
+  }
+  // If link target includes "..", it must not have another links
+  // in the path, because they can bypass our safety check. For example,
+  // suppose we extracted "lnk1" -> "." first and "lnk1/lnk2" -> ".." next
+  // or "dir/lnk1" -> ".." first and "dir/lnk1/lnk2" -> ".." next.
+  if (UpLevels>0 && LinkInPath(PrepSrcName))
+    return false;
+    
+  // We could check just prepared src name, but for extra safety
+  // we check both original (as from archive header) and prepared
+  // (after applying the destination path and -ep switches) names.
+
+  int AllowedDepth=CalcAllowedDepth(SrcName); // Original name depth.
+
+  // Remove the destination path from prepared name if any. We should not
+  // count the destination path depth, because the link target must point
+  // inside of this path, not outside of it.
+  size_t ExtrPathLength=wcslen(Cmd->ExtrPath);
+  if (ExtrPathLength>0 && wcsncmp(PrepSrcName,Cmd->ExtrPath,ExtrPathLength)==0)
+  {
+    PrepSrcName+=ExtrPathLength;
+    while (IsPathDiv(*PrepSrcName))
+      PrepSrcName++;
+  }
+  int PrepAllowedDepth=CalcAllowedDepth(PrepSrcName);
+
+  return AllowedDepth>=UpLevels && PrepAllowedDepth>=UpLevels;
+}
+
+
+bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName)
+{
+#if defined(SAVE_LINKS) && defined(_UNIX)
+  // For RAR 3.x archives we process links even in test mode to skip link data.
+  if (Arc.Format==RARFMT15)
+    return ExtractUnixLink30(Cmd,DataIO,Arc,LinkName);
+  if (Arc.Format==RARFMT50)
+    return ExtractUnixLink50(Cmd,LinkName,&Arc.FileHead);
+#elif defined _WIN_ALL
+  // RAR 5.0 archives store link information in file header, so there is
+  // no need to additionally test it if we do not create a file.
+  if (Arc.Format==RARFMT50)
+    return CreateReparsePoint(Cmd,LinkName,&Arc.FileHead);
+#endif
+  return false;
+}
diff --git a/third_party/unrar/src/extinfo.hpp b/third_party/unrar/src/extinfo.hpp
new file mode 100644
index 0000000..2b0005d
--- /dev/null
+++ b/third_party/unrar/src/extinfo.hpp
@@ -0,0 +1,23 @@
+#ifndef _RAR_EXTINFO_
+#define _RAR_EXTINFO_
+
+bool IsRelativeSymlinkSafe(CommandData *Cmd,const wchar *SrcName,const wchar *PrepSrcName,const wchar *TargetName);
+bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName);
+#ifdef _UNIX
+void SetUnixOwner(Archive &Arc,const wchar *FileName);
+#endif
+
+bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize);
+
+void GetStreamNameNTFS(Archive &Arc,wchar *StreamName,size_t MaxSize);
+
+#ifdef _WIN_ALL
+bool SetPrivilege(LPCTSTR PrivName);
+#endif
+
+void SetExtraInfo20(CommandData *Cmd,Archive &Arc,wchar *Name);
+void SetExtraInfo(CommandData *Cmd,Archive &Arc,wchar *Name);
+void SetFileHeaderExtra(CommandData *Cmd,Archive &Arc,wchar *Name);
+
+
+#endif
diff --git a/third_party/unrar/src/extract.cpp b/third_party/unrar/src/extract.cpp
new file mode 100644
index 0000000..abdd928
--- /dev/null
+++ b/third_party/unrar/src/extract.cpp
@@ -0,0 +1,1170 @@
+#include "rar.hpp"
+
+CmdExtract::CmdExtract(CommandData *Cmd)
+{
+  CmdExtract::Cmd=Cmd;
+
+  *ArcName=0;
+
+  *DestFileName=0;
+
+  TotalFileCount=0;
+  Unp=new Unpack(&DataIO);
+#ifdef RAR_SMP
+  Unp->SetThreads(Cmd->Threads);
+#endif
+}
+
+
+CmdExtract::~CmdExtract()
+{
+  delete Unp;
+}
+
+
+void CmdExtract::DoExtract()
+{
+#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
+  Fat32=NotFat32=false;
+#endif
+  PasswordCancelled=false;
+  DataIO.SetCurrentCommand(Cmd->Command[0]);
+
+  FindData FD;
+  while (Cmd->GetArcName(ArcName,ASIZE(ArcName)))
+    if (FindFile::FastFind(ArcName,&FD))
+      DataIO.TotalArcSize+=FD.Size;
+
+  Cmd->ArcNames.Rewind();
+  while (Cmd->GetArcName(ArcName,ASIZE(ArcName)))
+  {
+    if (Cmd->ManualPassword)
+      Cmd->Password.Clean(); // Clean user entered password before processing next archive.
+    while (true)
+    {
+      EXTRACT_ARC_CODE Code=ExtractArchive();
+      if (Code!=EXTRACT_ARC_REPEAT)
+        break;
+    }
+    if (FindFile::FastFind(ArcName,&FD))
+      DataIO.ProcessedArcSize+=FD.Size;
+  }
+
+  // Clean user entered password. Not really required, just for extra safety.
+  if (Cmd->ManualPassword)
+    Cmd->Password.Clean();
+
+  if (TotalFileCount==0 && Cmd->Command[0]!='I' && 
+      ErrHandler.GetErrorCode()!=RARX_BADPWD) // Not in case of wrong archive password.
+  {
+    if (!PasswordCancelled)
+      uiMsg(UIERROR_NOFILESTOEXTRACT,ArcName);
+    ErrHandler.SetErrorCode(RARX_NOFILES);
+  }
+  else
+    if (!Cmd->DisableDone)
+      if (Cmd->Command[0]=='I')
+        mprintf(St(MDone));
+      else
+        if (ErrHandler.GetErrorCount()==0)
+          mprintf(St(MExtrAllOk));
+        else
+          mprintf(St(MExtrTotalErr),ErrHandler.GetErrorCount());
+}
+
+
+void CmdExtract::ExtractArchiveInit(Archive &Arc)
+{
+  DataIO.UnpArcSize=Arc.FileLength();
+
+  FileCount=0;
+  MatchedArgs=0;
+#ifndef SFX_MODULE
+  FirstFile=true;
+#endif
+
+  PasswordAll=(Cmd->Password.IsSet());
+
+  DataIO.UnpVolume=false;
+
+  PrevProcessed=false;
+  AllMatchesExact=true;
+  ReconstructDone=false;
+  AnySolidDataUnpackedWell=false;
+
+  StartTime.SetCurrentTime();
+}
+
+
+EXTRACT_ARC_CODE CmdExtract::ExtractArchive()
+{
+  Archive Arc(Cmd);
+  if (!Arc.WOpen(ArcName))
+    return EXTRACT_ARC_NEXT;
+
+  if (!Arc.IsArchive(true))
+  {
+#if !defined(SFX_MODULE) && !defined(RARDLL)
+    if (CmpExt(ArcName,L"rev"))
+    {
+      wchar FirstVolName[NM];
+      VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),true);
+
+      // If several volume names from same volume set are specified
+      // and current volume is not first in set and first volume is present
+      // and specified too, let's skip the current volume.
+      if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) &&
+          Cmd->ArcNames.Search(FirstVolName,false))
+        return EXTRACT_ARC_NEXT;
+      RecVolumesTest(Cmd,NULL,ArcName);
+      TotalFileCount++; // Suppress "No files to extract" message.
+      return EXTRACT_ARC_NEXT;
+    }
+#endif
+
+    mprintf(St(MNotRAR),ArcName);
+
+#ifndef SFX_MODULE
+    if (CmpExt(ArcName,L"rar"))
+#endif
+      ErrHandler.SetErrorCode(RARX_WARNING);
+    return EXTRACT_ARC_NEXT;
+  }
+
+  if (Arc.FailedHeaderDecryption) // Bad archive password.
+    return EXTRACT_ARC_NEXT;
+
+#ifndef SFX_MODULE
+  if (Arc.Volume && !Arc.FirstVolume)
+  {
+    wchar FirstVolName[NM];
+    VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),Arc.NewNumbering);
+
+    // If several volume names from same volume set are specified
+    // and current volume is not first in set and first volume is present
+    // and specified too, let's skip the current volume.
+    if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) &&
+        Cmd->ArcNames.Search(FirstVolName,false))
+      return EXTRACT_ARC_NEXT;
+  }
+#endif
+
+  int64 VolumeSetSize=0; // Total size of volumes after the current volume.
+
+  if (Arc.Volume)
+  {
+    // Calculate the total size of all accessible volumes.
+    // This size is necessary to display the correct total progress indicator.
+
+    wchar NextName[NM];
+    wcscpy(NextName,Arc.FileName);
+
+    while (true)
+    {
+      // First volume is already added to DataIO.TotalArcSize 
+      // in initial TotalArcSize calculation in DoExtract.
+      // So we skip it and start from second volume.
+      NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering);
+      FindData FD;
+      if (FindFile::FastFind(NextName,&FD))
+        VolumeSetSize+=FD.Size;
+      else
+        break;
+    }
+    DataIO.TotalArcSize+=VolumeSetSize;
+  }
+
+  ExtractArchiveInit(Arc);
+
+  if (*Cmd->Command=='T' || *Cmd->Command=='I')
+    Cmd->Test=true;
+
+
+  if (*Cmd->Command=='I')
+  {
+    Cmd->DisablePercentage=true;
+  }
+  else
+    uiStartArchiveExtract(!Cmd->Test,ArcName);
+
+  Arc.ViewComment();
+
+
+  while (1)
+  {
+    size_t Size=Arc.ReadHeader();
+
+
+    bool Repeat=false;
+    if (!ExtractCurrentFile(Arc,Size,Repeat))
+      if (Repeat)
+      {
+        // If we started extraction from not first volume and need to
+        // restart it from first, we must correct DataIO.TotalArcSize
+        // for correct total progress display. We subtract the size
+        // of current volume and all volumes after it and add the size
+        // of new (first) volume.
+        FindData OldArc,NewArc;
+        if (FindFile::FastFind(Arc.FileName,&OldArc) &&
+            FindFile::FastFind(ArcName,&NewArc))
+          DataIO.TotalArcSize-=VolumeSetSize+OldArc.Size-NewArc.Size;
+        return EXTRACT_ARC_REPEAT;
+      }
+      else
+        break;
+  }
+
+
+#if !defined(SFX_MODULE) && !defined(RARDLL)
+  if (Cmd->Test && Arc.Volume)
+    RecVolumesTest(Cmd,&Arc,ArcName);
+#endif
+
+  return EXTRACT_ARC_NEXT;
+}
+
+
+bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat)
+{
+  wchar Command=Cmd->Command[0];
+  if (HeaderSize==0)
+    if (DataIO.UnpVolume)
+    {
+#ifdef NOVOLUME
+      return false;
+#else
+      // Supposing we unpack an old RAR volume without the end of archive
+      // record and last file is not split between volumes.
+      if (!MergeArchive(Arc,&DataIO,false,Command))
+      {
+        ErrHandler.SetErrorCode(RARX_WARNING);
+        return false;
+      }
+#endif
+    }
+    else
+      return false;
+
+  HEADER_TYPE HeaderType=Arc.GetHeaderType();
+  if (HeaderType!=HEAD_FILE)
+  {
+#ifndef SFX_MODULE
+    if (HeaderType==HEAD3_OLDSERVICE && PrevProcessed)
+      SetExtraInfo20(Cmd,Arc,DestFileName);
+#endif
+    if (HeaderType==HEAD_SERVICE && PrevProcessed)
+      SetExtraInfo(Cmd,Arc,DestFileName);
+    if (HeaderType==HEAD_ENDARC)
+      if (Arc.EndArcHead.NextVolume)
+      {
+#ifndef NOVOLUME
+        if (!MergeArchive(Arc,&DataIO,false,Command))
+        {
+          ErrHandler.SetErrorCode(RARX_WARNING);
+          return false;
+        }
+#endif
+        Arc.Seek(Arc.CurBlockPos,SEEK_SET);
+        return true;
+      }
+      else
+        return false;
+    Arc.SeekToNext();
+    return true;
+  }
+  PrevProcessed=false;
+
+  // We can get negative sizes in corrupt archive and it is unacceptable
+  // for size comparisons in ComprDataIO::UnpRead, where we cast sizes
+  // to size_t and can exceed another read or available size. We could fix it
+  // when reading an archive. But we prefer to do it here, because this
+  // function is called directly in unrar.dll, so we fix bad parameters
+  // passed to dll. Also we want to see real negative sizes in the listing
+  // of corrupt archive. To prevent uninitialized data access perform
+  // these checks after rejecting zero length and non-file headers above.
+  if (Arc.FileHead.PackSize<0)
+    Arc.FileHead.PackSize=0;
+  if (Arc.FileHead.UnpSize<0)
+    Arc.FileHead.UnpSize=0;
+
+  if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact)
+    return false;
+
+  int MatchType=MATCH_WILDSUBPATH;
+
+  bool EqualNames=false;
+  wchar MatchedArg[NM];
+  int MatchNumber=Cmd->IsProcessFile(Arc.FileHead,&EqualNames,MatchType,MatchedArg,ASIZE(MatchedArg));
+  bool MatchFound=MatchNumber!=0;
+#ifndef SFX_MODULE
+  if (Cmd->ExclPath==EXCL_BASEPATH)
+  {
+    wcsncpyz(Cmd->ArcPath,MatchedArg,ASIZE(Cmd->ArcPath));
+    *PointToName(Cmd->ArcPath)=0;
+    if (IsWildcard(Cmd->ArcPath)) // Cannot correctly process path*\* masks here.
+      *Cmd->ArcPath=0;
+  }
+#endif
+  if (MatchFound && !EqualNames)
+    AllMatchesExact=false;
+
+  Arc.ConvertAttributes();
+
+#if !defined(SFX_MODULE) && !defined(RARDLL)
+  if (Arc.FileHead.SplitBefore && FirstFile)
+  {
+    wchar CurVolName[NM];
+    wcsncpyz(CurVolName,ArcName,ASIZE(CurVolName));
+    VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),Arc.NewNumbering);
+
+    if (wcsicomp(ArcName,CurVolName)!=0 && FileExist(ArcName))
+    {
+      // If first volume name does not match the current name and if such
+      // volume name really exists, let's unpack from this first volume.
+      Repeat=true;
+      return false;
+    }
+#ifndef RARDLL
+    if (!ReconstructDone)
+    {
+      ReconstructDone=true;
+      if (RecVolumesRestore(Cmd,Arc.FileName,true))
+      {
+        Repeat=true;
+        return false;
+      }
+    }
+#endif
+    wcsncpyz(ArcName,CurVolName,ASIZE(ArcName));
+  }
+#endif
+
+  wchar ArcFileName[NM];
+  ConvertPath(Arc.FileHead.FileName,ArcFileName);
+
+  if (Arc.FileHead.Version)
+  {
+    if (Cmd->VersionControl!=1 && !EqualNames)
+    {
+      if (Cmd->VersionControl==0)
+        MatchFound=false;
+      int Version=ParseVersionFileName(ArcFileName,false);
+      if (Cmd->VersionControl-1==Version)
+        ParseVersionFileName(ArcFileName,true);
+      else
+        MatchFound=false;
+    }
+  }
+  else
+    if (!Arc.IsArcDir() && Cmd->VersionControl>1)
+      MatchFound=false;
+
+  DataIO.UnpVolume=Arc.FileHead.SplitAfter;
+  DataIO.NextVolumeMissing=false;
+
+  Arc.Seek(Arc.NextBlockPos-Arc.FileHead.PackSize,SEEK_SET);
+
+  bool ExtrFile=false;
+  bool SkipSolid=false;
+
+#ifndef SFX_MODULE
+  if (FirstFile && (MatchFound || Arc.Solid) && Arc.FileHead.SplitBefore)
+  {
+    if (MatchFound)
+    {
+      uiMsg(UIERROR_NEEDPREVVOL,Arc.FileName,ArcFileName);
+#ifdef RARDLL
+      Cmd->DllError=ERAR_BAD_DATA;
+#endif
+      ErrHandler.SetErrorCode(RARX_OPEN);
+    }
+    MatchFound=false;
+  }
+
+  FirstFile=false;
+#endif
+
+  if (MatchFound || (SkipSolid=Arc.Solid)!=0)
+  {
+    // First common call of uiStartFileExtract. It is done before overwrite
+    // prompts, so if SkipSolid state is changed below, we'll need to make
+    // additional uiStartFileExtract calls with updated parameters.
+    if (!uiStartFileExtract(ArcFileName,!Cmd->Test,Cmd->Test && Command!='I',SkipSolid))
+      return false;
+
+    ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName));
+
+    // DestFileName can be set empty in case of excessive -ap switch.
+    ExtrFile=!SkipSolid && *DestFileName!=0 && !Arc.FileHead.SplitBefore;
+
+    if ((Cmd->FreshFiles || Cmd->UpdateFiles) && (Command=='E' || Command=='X'))
+    {
+      FindData FD;
+      if (FindFile::FastFind(DestFileName,&FD))
+      {
+        if (FD.mtime >= Arc.FileHead.mtime)
+        {
+          // If directory already exists and its modification time is newer 
+          // than start of extraction, it is likely it was created 
+          // when creating a path to one of already extracted items. 
+          // In such case we'll better update its time even if archived 
+          // directory is older.
+
+          if (!FD.IsDir || FD.mtime<StartTime)
+            ExtrFile=false;
+        }
+      }
+      else
+        if (Cmd->FreshFiles)
+          ExtrFile=false;
+    }
+
+    if (!CheckUnpVer(Arc,ArcFileName))
+    {
+      ErrHandler.SetErrorCode(RARX_FATAL);
+#ifdef RARDLL
+      Cmd->DllError=ERAR_UNKNOWN_FORMAT;
+#endif
+      Arc.SeekToNext();
+      return !Arc.Solid; // Can try extracting next file only in non-solid archive.
+    }
+
+    while (true) // Repeat the password prompt for wrong passwords.
+    {
+      if (Arc.FileHead.Encrypted)
+      {
+        // Stop archive extracting if user cancelled a password prompt.
+#ifdef RARDLL
+        if (!ExtrDllGetPassword())
+        {
+          Cmd->DllError=ERAR_MISSING_PASSWORD;
+          return false;
+        }
+#else
+        if (!ExtrGetPassword(Arc,ArcFileName))
+        {
+          PasswordCancelled=true;
+          return false;
+        }
+#endif
+        // Skip only the current encrypted file if empty password is entered.
+        // Actually our "cancel" code above intercepts empty passwords too now,
+        // so we keep the code below just in case we'll decide process empty
+        // and cancelled passwords differently sometimes.
+        if (!Cmd->Password.IsSet())
+        {
+          ErrHandler.SetErrorCode(RARX_WARNING);
+#ifdef RARDLL
+          Cmd->DllError=ERAR_MISSING_PASSWORD;
+#endif
+          ExtrFile=false;
+        }
+      }
+
+
+      // Set a password before creating the file, so we can skip creating
+      // in case of wrong password.
+      SecPassword FilePassword=Cmd->Password;
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+      ConvertDosPassword(Arc,FilePassword);
+#endif
+
+      byte PswCheck[SIZE_PSWCHECK];
+      DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword,
+             Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL,
+             Arc.FileHead.InitV,Arc.FileHead.Lg2Count,
+             Arc.FileHead.HashKey,PswCheck);
+
+      // If header is damaged, we cannot rely on password check value,
+      // because it can be damaged too.
+      if (Arc.FileHead.Encrypted && Arc.FileHead.UsePswCheck &&
+          memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0 &&
+          !Arc.BrokenHeader)
+      {
+        // This message is used by Android GUI and Windows GUI and SFX to
+        // reset cached passwords. Update appropriate code if changed.
+        uiMsg(UIWAIT_BADPSW,ArcFileName);
+
+        if (!PasswordAll) // If entered manually and not through -p<pwd>.
+        {
+          Cmd->Password.Clean();
+          continue; // Request a password again.
+        }
+#ifdef RARDLL
+        // If we already have ERAR_EOPEN as result of missing volume,
+        // we should not replace it with less precise ERAR_BAD_PASSWORD.
+        if (Cmd->DllError!=ERAR_EOPEN)
+          Cmd->DllError=ERAR_BAD_PASSWORD;
+#endif
+        ErrHandler.SetErrorCode(RARX_BADPWD);
+        ExtrFile=false;
+      }
+      break;
+    }
+
+#ifdef RARDLL
+    if (*Cmd->DllDestName!=0)
+      wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName));
+#endif
+
+    File CurFile;
+
+    bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE;
+    if (LinkEntry && Arc.FileHead.RedirType!=FSREDIR_FILECOPY)
+    {
+      if (ExtrFile && Command!='P' && !Cmd->Test)
+      {
+        // Overwrite prompt for symbolic and hard links.
+        bool UserReject=false;
+        if (FileExist(DestFileName) && !UserReject)
+          FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime);
+        if (UserReject)
+          ExtrFile=false;
+      }
+    }
+    else
+      if (Arc.IsArcDir())
+      {
+        if (!ExtrFile || Command=='P' || Command=='I' || Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH)
+          return true;
+        TotalFileCount++;
+        ExtrCreateDir(Arc,ArcFileName);
+        // It is important to not increment MatchedArgs here, so we extract
+        // dir with its entire contents and not dir record only even if
+        // dir record precedes files.
+        return true;
+      }
+      else
+        if (ExtrFile) // Create files and file copies (FSREDIR_FILECOPY).
+          ExtrFile=ExtrCreateFile(Arc,CurFile);
+
+    if (!ExtrFile && Arc.Solid)
+    {
+      SkipSolid=true;
+      ExtrFile=true;
+
+      // We changed SkipSolid, so we need to call uiStartFileExtract
+      // with "Skip" parameter to change the operation status 
+      // from "extracting" to "skipping". For example, it can be necessary
+      // if user answered "No" to overwrite prompt when unpacking
+      // a solid archive.
+      if (!uiStartFileExtract(ArcFileName,false,false,true))
+        return false;
+    }
+    if (ExtrFile)
+    {
+      // Set it in test mode, so we also test subheaders such as NTFS streams
+      // after tested file.
+      if (Cmd->Test)
+        PrevProcessed=true;
+
+      bool TestMode=Cmd->Test || SkipSolid; // Unpack to memory, not to disk.
+
+      if (!SkipSolid)
+      {
+        if (!TestMode && Command!='P' && CurFile.IsDevice())
+        {
+          uiMsg(UIERROR_INVALIDNAME,Arc.FileName,DestFileName);
+          ErrHandler.WriteError(Arc.FileName,DestFileName);
+        }
+        TotalFileCount++;
+      }
+      FileCount++;
+      if (Command!='I')
+        if (SkipSolid)
+          mprintf(St(MExtrSkipFile),ArcFileName);
+        else
+          switch(Cmd->Test ? 'T':Command) // "Test" can be also enabled by -t switch.
+          {
+            case 'T':
+              mprintf(St(MExtrTestFile),ArcFileName);
+              break;
+#ifndef SFX_MODULE
+            case 'P':
+              mprintf(St(MExtrPrinting),ArcFileName);
+              break;
+#endif
+            case 'X':
+            case 'E':
+              mprintf(St(MExtrFile),DestFileName);
+              break;
+          }
+      if (!Cmd->DisablePercentage)
+        mprintf(L"     ");
+
+      DataIO.CurUnpRead=0;
+      DataIO.CurUnpWrite=0;
+      DataIO.UnpHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads);
+      DataIO.PackedDataHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads);
+      DataIO.SetPackedSizeToRead(Arc.FileHead.PackSize);
+      DataIO.SetFiles(&Arc,&CurFile);
+      DataIO.SetTestMode(TestMode);
+      DataIO.SetSkipUnpCRC(SkipSolid);
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
+      if (!TestMode && !Arc.BrokenHeader &&
+          Arc.FileHead.UnpSize>0xffffffff && (Fat32 || !NotFat32))
+      {
+        if (!Fat32) // Not detected yet.
+          NotFat32=!(Fat32=IsFAT(Cmd->ExtrPath));
+        if (Fat32)
+          uiMsg(UIMSG_FAT32SIZE); // Inform user about FAT32 size limit.
+      }
+#endif
+
+      if (!TestMode && !Arc.BrokenHeader &&
+          (Arc.FileHead.PackSize<<11)>Arc.FileHead.UnpSize &&
+          (Arc.FileHead.UnpSize<100000000 || Arc.FileLength()>Arc.FileHead.PackSize))
+        CurFile.Prealloc(Arc.FileHead.UnpSize);
+
+      CurFile.SetAllowDelete(!Cmd->KeepBroken);
+
+      bool FileCreateMode=!TestMode && !SkipSolid && Command!='P';
+      bool ShowChecksum=true; // Display checksum verification result.
+
+      bool LinkSuccess=true; // Assume success for test mode.
+      if (LinkEntry)
+      {
+        FILE_SYSTEM_REDIRECT Type=Arc.FileHead.RedirType;
+
+        if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY)
+        {
+          wchar NameExisting[NM];
+          ExtrPrepareName(Arc,Arc.FileHead.RedirName,NameExisting,ASIZE(NameExisting));
+          if (FileCreateMode && *NameExisting!=0) // *NameExisting can be 0 in case of excessive -ap switch.
+            if (Type==FSREDIR_HARDLINK)
+              LinkSuccess=ExtractHardlink(DestFileName,NameExisting,ASIZE(NameExisting));
+            else
+              LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,DestFileName,NameExisting,ASIZE(NameExisting));
+        }
+        else
+          if (Type==FSREDIR_UNIXSYMLINK || Type==FSREDIR_WINSYMLINK || Type==FSREDIR_JUNCTION)
+          {
+            if (FileCreateMode)
+              LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName);
+          }
+          else
+          {
+            uiMsg(UIERROR_UNKNOWNEXTRA, Arc.FileName, DestFileName);
+            LinkSuccess=false;
+          }
+          
+          if (!LinkSuccess || Arc.Format==RARFMT15 && !FileCreateMode)
+          {
+            // RAR 5.x links have a valid data checksum even in case of
+            // failure, because they do not store any data.
+            // We do not want to display "OK" in this case.
+            // For 4.x symlinks we verify the checksum only when extracting,
+            // but not when testing an archive.
+            ShowChecksum=false;
+          }
+          PrevProcessed=FileCreateMode && LinkSuccess;
+      }
+      else
+        if (!Arc.FileHead.SplitBefore)
+          if (Arc.FileHead.Method==0)
+            UnstoreFile(DataIO,Arc.FileHead.UnpSize);
+          else
+          {
+            Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid);
+            Unp->SetDestSize(Arc.FileHead.UnpSize);
+#ifndef SFX_MODULE
+            if (Arc.Format!=RARFMT50 && Arc.FileHead.UnpVer<=15)
+              Unp->DoUnpack(15,FileCount>1 && Arc.Solid);
+            else
+#endif
+              Unp->DoUnpack(Arc.FileHead.UnpVer,Arc.FileHead.Solid);
+          }
+
+      Arc.SeekToNext();
+
+      // We check for "split after" flag to detect partially extracted files
+      // from incomplete volume sets. For them file header contains packed
+      // data hash, which must not be compared against unpacked data hash
+      // to prevent accidental match. Moreover, for -m0 volumes packed data
+      // hash would match truncated unpacked data hash and lead to fake "OK"
+      // in incomplete volume set.
+      bool ValidCRC=!Arc.FileHead.SplitAfter && DataIO.UnpHash.Cmp(&Arc.FileHead.FileHash,Arc.FileHead.UseHashKey ? Arc.FileHead.HashKey:NULL);
+
+      // We set AnySolidDataUnpackedWell to true if we found at least one
+      // valid non-zero solid file in preceding solid stream. If it is true
+      // and if current encrypted file is broken, we do not need to hint
+      // about a wrong password and can report CRC error only.
+      if (!Arc.FileHead.Solid)
+        AnySolidDataUnpackedWell=false; // Reset the flag, because non-solid file is found.
+      else
+        if (Arc.FileHead.Method!=0 && Arc.FileHead.UnpSize>0 && ValidCRC)
+          AnySolidDataUnpackedWell=true;
+ 
+      bool BrokenFile=false;
+      
+      // Checksum is not calculated in skip solid mode for performance reason.
+      if (!SkipSolid && ShowChecksum)
+      {
+        if (ValidCRC)
+        {
+          if (Command!='P' && Command!='I')
+            mprintf(L"%s%s ",Cmd->DisablePercentage ? L" ":L"\b\b\b\b\b ",
+              Arc.FileHead.FileHash.Type==HASH_NONE ? L"  ?":St(MOk));
+        }
+        else
+        {
+          if (Arc.FileHead.Encrypted && (!Arc.FileHead.UsePswCheck || 
+              Arc.BrokenHeader) && !AnySolidDataUnpackedWell)
+            uiMsg(UIERROR_CHECKSUMENC,Arc.FileName,ArcFileName);
+          else
+            uiMsg(UIERROR_CHECKSUM,Arc.FileName,ArcFileName);
+          BrokenFile=true;
+          ErrHandler.SetErrorCode(RARX_CRC);
+#ifdef RARDLL
+          // If we already have ERAR_EOPEN as result of missing volume
+          // or ERAR_BAD_PASSWORD for RAR5 wrong password,
+          // we should not replace it with less precise ERAR_BAD_DATA.
+          if (Cmd->DllError!=ERAR_EOPEN && Cmd->DllError!=ERAR_BAD_PASSWORD)
+            Cmd->DllError=ERAR_BAD_DATA;
+#endif
+        }
+      }
+      else
+        mprintf(L"\b\b\b\b\b     ");
+
+      if (!TestMode && (Command=='X' || Command=='E') &&
+          (!LinkEntry || Arc.FileHead.RedirType==FSREDIR_FILECOPY && LinkSuccess) && 
+          (!BrokenFile || Cmd->KeepBroken))
+      {
+        // We could preallocate more space that really written to broken file.
+        if (BrokenFile)
+          CurFile.Truncate();
+
+#if defined(_WIN_ALL) || defined(_EMX)
+        if (Cmd->ClearArc)
+          Arc.FileHead.FileAttr&=~FILE_ATTRIBUTE_ARCHIVE;
+#endif
+
+
+        CurFile.SetOpenFileTime(
+          Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime,
+          Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime,
+          Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime);
+        CurFile.Close();
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+        if (Cmd->SetCompressedAttr &&
+            (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0)
+          SetFileCompression(CurFile.FileName,true);
+#endif
+        SetFileHeaderExtra(Cmd,Arc,CurFile.FileName);
+
+        CurFile.SetCloseFileTime(
+          Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime,
+          Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime);
+        if (!Cmd->IgnoreGeneralAttr && !SetFileAttr(CurFile.FileName,Arc.FileHead.FileAttr))
+          uiMsg(UIERROR_FILEATTR,Arc.FileName,CurFile.FileName);
+
+        PrevProcessed=true;
+      }
+    }
+  }
+  // It is important to increment it for files, but not dirs. So we extract
+  // dir with its entire contents, not just dir record only even if dir
+  // record precedes files.
+  if (MatchFound)
+    MatchedArgs++;
+  if (DataIO.NextVolumeMissing)
+    return false;
+  if (!ExtrFile)
+    if (!Arc.Solid)
+      Arc.SeekToNext();
+    else
+      if (!SkipSolid)
+        return false;
+  return true;
+}
+
+
+void CmdExtract::UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize)
+{
+  Array<byte> Buffer(File::CopyBufferSize());
+  while (true)
+  {
+    int ReadSize=DataIO.UnpRead(&Buffer[0],Buffer.Size());
+    if (ReadSize<=0)
+      break;
+    int WriteSize=ReadSize<DestUnpSize ? ReadSize:(int)DestUnpSize;
+    if (WriteSize>0)
+    {
+      DataIO.UnpWrite(&Buffer[0],WriteSize);
+      DestUnpSize-=WriteSize;
+    }
+  }
+}
+
+
+bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize)
+{
+  SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives.
+
+  File Existing;
+  if (!Existing.WOpen(NameExisting))
+  {
+    uiMsg(UIERROR_FILECOPY,ArcName,NameExisting,NameNew);
+    uiMsg(UIERROR_FILECOPYHINT,ArcName);
+#ifdef RARDLL
+    Cmd->DllError=ERAR_EREFERENCE;
+#endif
+    return false;
+  }
+
+  Array<char> Buffer(0x100000);
+  int64 CopySize=0;
+
+  while (true)
+  {
+    Wait();
+    int ReadSize=Existing.Read(&Buffer[0],Buffer.Size());
+    if (ReadSize==0)
+      break;
+    New.Write(&Buffer[0],ReadSize);
+    CopySize+=ReadSize;
+  }
+
+  return true;
+}
+
+
+void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize)
+{
+  wcsncpyz(DestName,Cmd->ExtrPath,DestSize);
+
+  if (*Cmd->ExtrPath!=0)
+  {
+     wchar LastChar=*PointToLastChar(Cmd->ExtrPath);
+    // We need IsPathDiv check here to correctly handle Unix forward slash
+    // in the end of destination path in Windows: rar x arc dest/
+    // IsDriveDiv is needed for current drive dir: rar x arc d:
+    if (!IsPathDiv(LastChar) && !IsDriveDiv(LastChar))
+    {
+      // Destination path can be without trailing slash if it come from GUI shell.
+      AddEndSlash(DestName,DestSize);
+    }
+  }
+
+#ifndef SFX_MODULE
+  if (Cmd->AppendArcNameToPath)
+  {
+    wcsncatz(DestName,PointToName(Arc.FirstVolumeName),DestSize);
+    SetExt(DestName,NULL,DestSize);
+    AddEndSlash(DestName,DestSize);
+  }
+#endif
+
+#ifndef SFX_MODULE
+  size_t ArcPathLength=wcslen(Cmd->ArcPath);
+  if (ArcPathLength>0)
+  {
+    size_t NameLength=wcslen(ArcFileName);
+
+    // Earlier we compared lengths only here, but then noticed a cosmetic bug
+    // in WinRAR. When extracting a file reference from subfolder with
+    // "Extract relative paths", so WinRAR sets ArcPath, if reference target
+    // is missing, error message removed ArcPath both from reference and target
+    // names. If target was stored in another folder, its name looked wrong.
+    if (NameLength>=ArcPathLength && 
+        wcsnicompc(Cmd->ArcPath,ArcFileName,ArcPathLength)==0 &&
+        (IsPathDiv(Cmd->ArcPath[ArcPathLength-1]) || 
+         IsPathDiv(ArcFileName[ArcPathLength]) || ArcFileName[ArcPathLength]==0))
+    {
+      ArcFileName+=Min(ArcPathLength,NameLength);
+      while (IsPathDiv(*ArcFileName))
+        ArcFileName++;
+      if (*ArcFileName==0) // Excessive -ap switch.
+      {
+        *DestName=0;
+        return;
+      }
+    }
+  }
+#endif
+
+  wchar Command=Cmd->Command[0];
+  // Use -ep3 only in systems, where disk letters are exist, not in Unix.
+  bool AbsPaths=Cmd->ExclPath==EXCL_ABSPATH && Command=='X' && IsDriveDiv(':');
+
+  // We do not use any user specified destination paths when extracting
+  // absolute paths in -ep3 mode.
+  if (AbsPaths)
+    *DestName=0;
+
+  if (Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH)
+    wcsncatz(DestName,PointToName(ArcFileName),DestSize);
+  else
+    wcsncatz(DestName,ArcFileName,DestSize);
+
+#ifdef _WIN_ALL
+  // Must do after Cmd->ArcPath processing above, so file name and arc path
+  // trailing spaces are in sync.
+  if (!Cmd->AllowIncompatNames)
+    MakeNameCompatible(DestName);
+#endif
+
+  wchar DiskLetter=toupperw(DestName[0]);
+
+  if (AbsPaths)
+  {
+    if (DestName[1]=='_' && IsPathDiv(DestName[2]) &&
+        DiskLetter>='A' && DiskLetter<='Z')
+      DestName[1]=':';
+    else
+      if (DestName[0]=='_' && DestName[1]=='_')
+      {
+        // Convert __server\share to \\server\share.
+        DestName[0]=CPATHDIVIDER;
+        DestName[1]=CPATHDIVIDER;
+      }
+  }
+}
+
+
+#ifdef RARDLL
+bool CmdExtract::ExtrDllGetPassword()
+{
+  if (!Cmd->Password.IsSet())
+  {
+    if (Cmd->Callback!=NULL)
+    {
+      wchar PasswordW[MAXPASSWORD];
+      *PasswordW=0;
+      if (Cmd->Callback(UCM_NEEDPASSWORDW,Cmd->UserData,(LPARAM)PasswordW,ASIZE(PasswordW))==-1)
+        *PasswordW=0;
+      if (*PasswordW==0)
+      {
+        char PasswordA[MAXPASSWORD];
+        *PasswordA=0;
+        if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1)
+          *PasswordA=0;
+        GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW));
+        cleandata(PasswordA,sizeof(PasswordA));
+      }
+      Cmd->Password.Set(PasswordW);
+      cleandata(PasswordW,sizeof(PasswordW));
+      Cmd->ManualPassword=true;
+    }
+    if (!Cmd->Password.IsSet())
+      return false;
+  }
+  return true;
+}
+#endif
+
+
+#ifndef RARDLL
+bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName)
+{
+  if (!Cmd->Password.IsSet())
+  {
+    if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password) || !Cmd->Password.IsSet())
+    {
+      // Suppress "test is ok" message in GUI if user entered
+      // an empty password or cancelled a password prompt.
+      uiMsg(UIERROR_INCERRCOUNT);
+
+      return false;
+    }
+    Cmd->ManualPassword=true;
+  }
+#if !defined(SILENT)
+  else
+    if (!PasswordAll && !Arc.FileHead.Solid)
+    {
+      eprintf(St(MUseCurPsw),ArcFileName);
+      switch(Cmd->AllYes ? 1 : Ask(St(MYesNoAll)))
+      {
+        case -1:
+          ErrHandler.Exit(RARX_USERBREAK);
+        case 2:
+          if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password))
+            return false;
+          break;
+        case 3:
+          PasswordAll=true;
+          break;
+      }
+    }
+#endif
+  return true;
+}
+#endif
+
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+void CmdExtract::ConvertDosPassword(Archive &Arc,SecPassword &DestPwd)
+{
+  if (Arc.Format==RARFMT15 && Arc.FileHead.HostOS==HOST_MSDOS)
+  {
+    // We need the password in OEM encoding if file was encrypted by
+    // native RAR/DOS (not extender based). Let's make the conversion.
+    wchar PlainPsw[MAXPASSWORD];
+    Cmd->Password.Get(PlainPsw,ASIZE(PlainPsw));
+    char PswA[MAXPASSWORD];
+    CharToOemBuffW(PlainPsw,PswA,ASIZE(PswA));
+    PswA[ASIZE(PswA)-1]=0;
+    CharToWide(PswA,PlainPsw,ASIZE(PlainPsw));
+    DestPwd.Set(PlainPsw);
+    cleandata(PlainPsw,sizeof(PlainPsw));
+    cleandata(PswA,sizeof(PswA));
+  }
+}
+#endif
+
+
+void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName)
+{
+  if (Cmd->Test)
+  {
+    mprintf(St(MExtrTestFile),ArcFileName);
+    mprintf(L" %s",St(MOk));
+    return;
+  }
+
+  MKDIR_CODE MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr);
+  bool DirExist=false;
+  if (MDCode!=MKDIR_SUCCESS)
+  {
+    DirExist=FileExist(DestFileName);
+    if (DirExist && !IsDir(GetFileAttr(DestFileName)))
+    {
+      // File with name same as this directory exists. Propose user
+      // to overwrite it.
+      bool UserReject;
+      FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime);
+      DirExist=false;
+    }
+    if (!DirExist)
+    {
+      CreatePath(DestFileName,true);
+      MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr);
+      if (MDCode!=MKDIR_SUCCESS)
+      {
+        wchar OrigName[ASIZE(DestFileName)];
+        wcsncpyz(OrigName,DestFileName,ASIZE(OrigName));
+        MakeNameUsable(DestFileName,true);
+        CreatePath(DestFileName,true);
+        MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr);
+#ifndef SFX_MODULE
+        if (MDCode==MKDIR_SUCCESS)
+          uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName);
+#endif
+      }
+    }
+  }
+  if (MDCode==MKDIR_SUCCESS)
+  {
+    mprintf(St(MCreatDir),DestFileName);
+    mprintf(L" %s",St(MOk));
+    PrevProcessed=true;
+  }
+  else
+    if (DirExist)
+    {
+      if (!Cmd->IgnoreGeneralAttr)
+        SetFileAttr(DestFileName,Arc.FileHead.FileAttr);
+      PrevProcessed=true;
+    }
+    else
+    {
+      uiMsg(UIERROR_DIRCREATE,Arc.FileName,DestFileName);
+      ErrHandler.SysErrMsg();
+#ifdef RARDLL
+      Cmd->DllError=ERAR_ECREATE;
+#endif
+      ErrHandler.SetErrorCode(RARX_CREATE);
+    }
+  if (PrevProcessed)
+  {
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+    if (Cmd->SetCompressedAttr &&
+        (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0 && WinNT())
+      SetFileCompression(DestFileName,true);
+#endif
+    SetFileHeaderExtra(Cmd,Arc,DestFileName);
+    SetDirTime(DestFileName,
+      Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime,
+      Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime,
+      Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime);
+  }
+}
+
+
+bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile)
+{
+  bool Success=true;
+  wchar Command=Cmd->Command[0];
+#if !defined(SFX_MODULE)
+  if (Command=='P')
+    CurFile.SetHandleType(FILE_HANDLESTD);
+#endif
+  if ((Command=='E' || Command=='X') && !Cmd->Test)
+  {
+    bool UserReject;
+    // Specify "write only" mode to avoid OpenIndiana NAS problems
+    // with SetFileTime and read+write files.
+    if (!FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true))
+    {
+      Success=false;
+      if (!UserReject)
+      {
+        ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName);
+#ifdef RARDLL
+        Cmd->DllError=ERAR_ECREATE;
+#endif
+        if (!IsNameUsable(DestFileName))
+        {
+          uiMsg(UIMSG_CORRECTINGNAME,Arc.FileName);
+
+          wchar OrigName[ASIZE(DestFileName)];
+          wcsncpyz(OrigName,DestFileName,ASIZE(OrigName));
+
+          MakeNameUsable(DestFileName,true);
+
+          CreatePath(DestFileName,true);
+          if (FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true))
+          {
+#ifndef SFX_MODULE
+            uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName);
+#endif
+            Success=true;
+          }
+          else
+            ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName);
+        }
+      }
+    }
+  }
+  return Success;
+}
+
+
+bool CmdExtract::CheckUnpVer(Archive &Arc,const wchar *ArcFileName)
+{
+  bool WrongVer;
+  if (Arc.Format==RARFMT50) // Both SFX and RAR can unpack RAR 5.0 archives.
+    WrongVer=Arc.FileHead.UnpVer>VER_UNPACK5;
+  else
+  {
+#ifdef SFX_MODULE   // SFX can unpack only RAR 2.9 archives.
+    WrongVer=Arc.FileHead.UnpVer!=VER_UNPACK;
+#else               // All formats since 1.3 for RAR.
+    WrongVer=Arc.FileHead.UnpVer<13 || Arc.FileHead.UnpVer>VER_UNPACK;
+#endif
+  }
+
+  // We can unpack stored files regardless of compression version field.
+  if (Arc.FileHead.Method==0)
+    WrongVer=false;
+
+  if (WrongVer)
+  {
+    ErrHandler.UnknownMethodMsg(Arc.FileName,ArcFileName);
+    uiMsg(UIERROR_NEWERRAR,Arc.FileName);
+  }
+  return !WrongVer;
+}
diff --git a/third_party/unrar/src/extract.hpp b/third_party/unrar/src/extract.hpp
new file mode 100644
index 0000000..85a21f5
--- /dev/null
+++ b/third_party/unrar/src/extract.hpp
@@ -0,0 +1,62 @@
+#ifndef _RAR_EXTRACT_
+#define _RAR_EXTRACT_
+
+enum EXTRACT_ARC_CODE {EXTRACT_ARC_NEXT,EXTRACT_ARC_REPEAT};
+
+class CmdExtract
+{
+  private:
+    EXTRACT_ARC_CODE ExtractArchive();
+    bool ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize);
+    void ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize);
+#ifdef RARDLL
+    bool ExtrDllGetPassword();
+#else
+    bool ExtrGetPassword(Archive &Arc,const wchar *ArcFileName);
+#endif
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+    void ConvertDosPassword(Archive &Arc,SecPassword &DestPwd);
+#endif
+    void ExtrCreateDir(Archive &Arc,const wchar *ArcFileName);
+    bool ExtrCreateFile(Archive &Arc,File &CurFile);
+    bool CheckUnpVer(Archive &Arc,const wchar *ArcFileName);
+
+    RarTime StartTime; // time when extraction started
+
+    CommandData *Cmd;
+
+    ComprDataIO DataIO;
+    Unpack *Unp;
+    unsigned long TotalFileCount;
+
+    unsigned long FileCount;
+    unsigned long MatchedArgs;
+    bool FirstFile;
+    bool AllMatchesExact;
+    bool ReconstructDone;
+
+    // If any non-zero solid file was successfully unpacked before current.
+    // If true and if current encrypted file is broken, obviously
+    // the password is correct and we can report broken CRC without
+    // any wrong password hints.
+    bool AnySolidDataUnpackedWell;
+
+    wchar ArcName[NM];
+
+    bool PasswordAll;
+    bool PrevProcessed; // If previous file was successfully extracted or tested.
+    wchar DestFileName[NM];
+    bool PasswordCancelled;
+#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
+    bool Fat32,NotFat32;
+#endif
+  public:
+    CmdExtract(CommandData *Cmd);
+    ~CmdExtract();
+    void DoExtract();
+    void ExtractArchiveInit(Archive &Arc);
+    bool ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat);
+    static void UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize);
+};
+
+#endif
diff --git a/third_party/unrar/src/filcreat.cpp b/third_party/unrar/src/filcreat.cpp
new file mode 100644
index 0000000..a64a7d4
--- /dev/null
+++ b/third_party/unrar/src/filcreat.cpp
@@ -0,0 +1,163 @@
+#include "rar.hpp"
+
+// If NewFile==NULL, we delete created file after user confirmation.
+// It is useful we we need to overwrite an existing folder or file,
+// but need user confirmation for that.
+bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize,
+                bool *UserReject,int64 FileSize,RarTime *FileTime,bool WriteOnly)
+{
+  if (UserReject!=NULL)
+    *UserReject=false;
+#ifdef _WIN_ALL
+  bool ShortNameChanged=false;
+#endif
+  while (FileExist(Name))
+  {
+#if defined(_WIN_ALL)
+    if (!ShortNameChanged)
+    {
+      // Avoid the infinite loop if UpdateExistingShortName returns
+      // the same name.
+      ShortNameChanged=true;
+
+      // Maybe our long name matches the short name of existing file.
+      // Let's check if we can change the short name.
+      if (UpdateExistingShortName(Name))
+        continue;
+    }
+    // Allow short name check again. It is necessary, because rename and
+    // autorename below can change the name, so we need to check it again.
+    ShortNameChanged=false;
+#endif
+    UIASKREP_RESULT Choice=uiAskReplaceEx(Cmd,Name,MaxNameSize,FileSize,FileTime,(NewFile==NULL ? UIASKREP_F_NORENAME:0));
+
+    if (Choice==UIASKREP_R_REPLACE)
+      break;
+    if (Choice==UIASKREP_R_SKIP)
+    {
+      if (UserReject!=NULL)
+        *UserReject=true;
+      return false;
+    }
+    if (Choice==UIASKREP_R_CANCEL)
+      ErrHandler.Exit(RARX_USERBREAK);
+  }
+
+  // Try to truncate the existing file first instead of delete,
+  // so we preserve existing file permissions such as NTFS permissions.
+  uint FileMode=WriteOnly ? FMF_WRITE|FMF_SHAREREAD:FMF_UPDATE|FMF_SHAREREAD;
+  if (NewFile!=NULL && NewFile->Create(Name,FileMode))
+    return true;
+
+  CreatePath(Name,true);
+  return NewFile!=NULL ? NewFile->Create(Name,FileMode):DelFile(Name);
+}
+
+
+bool GetAutoRenamedName(wchar *Name,size_t MaxNameSize)
+{
+  wchar NewName[NM];
+  size_t NameLength=wcslen(Name);
+  wchar *Ext=GetExt(Name);
+  if (Ext==NULL)
+    Ext=Name+NameLength;
+  for (uint FileVer=1;;FileVer++)
+  {
+    swprintf(NewName,ASIZE(NewName),L"%.*ls(%u)%ls",uint(Ext-Name),Name,FileVer,Ext);
+    if (!FileExist(NewName))
+    {
+      wcsncpyz(Name,NewName,MaxNameSize);
+      break;
+    }
+    if (FileVer>=1000000)
+      return false;
+  }
+  return true;
+}
+
+
+#if defined(_WIN_ALL)
+// If we find a file, which short name is equal to 'Name', we try to change
+// its short name, while preserving the long name. It helps when unpacking
+// an archived file, which long name is equal to short name of already
+// existing file. Otherwise we would overwrite the already existing file,
+// even though its long name does not match the name of unpacking file.
+bool UpdateExistingShortName(const wchar *Name)
+{
+  wchar LongPathName[NM];
+  DWORD Res=GetLongPathName(Name,LongPathName,ASIZE(LongPathName));
+  if (Res==0 || Res>=ASIZE(LongPathName))
+    return false;
+  wchar ShortPathName[NM];
+  Res=GetShortPathName(Name,ShortPathName,ASIZE(ShortPathName));
+  if (Res==0 || Res>=ASIZE(ShortPathName))
+    return false;
+  wchar *LongName=PointToName(LongPathName);
+  wchar *ShortName=PointToName(ShortPathName);
+
+  // We continue only if file has a short name, which does not match its
+  // long name, and this short name is equal to name of file which we need
+  // to create.
+  if (*ShortName==0 || wcsicomp(LongName,ShortName)==0 ||
+      wcsicomp(PointToName(Name),ShortName)!=0)
+    return false;
+
+  // Generate the temporary new name for existing file.
+  wchar NewName[NM];
+  *NewName=0;
+  for (int I=0;I<10000 && *NewName==0;I+=123)
+  {
+    // Here we copy the path part of file to create. We'll make the temporary
+    // file in the same folder.
+    wcsncpyz(NewName,Name,ASIZE(NewName));
+
+    // Here we set the random name part.
+    swprintf(PointToName(NewName),ASIZE(NewName),L"rtmp%d",I);
+    
+    // If such file is already exist, try next random name.
+    if (FileExist(NewName))
+      *NewName=0;
+  }
+
+  // If we could not generate the name not used by any other file, we return.
+  if (*NewName==0)
+    return false;
+  
+  // FastFind returns the name without path, but we need the fully qualified
+  // name for renaming, so we use the path from file to create and long name
+  // from existing file.
+  wchar FullName[NM];
+  wcsncpyz(FullName,Name,ASIZE(FullName));
+  SetName(FullName,LongName,ASIZE(FullName));
+  
+  // Rename the existing file to randomly generated name. Normally it changes
+  // the short name too.
+  if (!MoveFile(FullName,NewName))
+    return false;
+
+  // Now we need to create the temporary empty file with same name as
+  // short name of our already existing file. We do it to occupy its previous
+  // short name and not allow to use it again when renaming the file back to
+  // its original long name.
+  File KeepShortFile;
+  bool Created=false;
+  if (!FileExist(Name))
+    Created=KeepShortFile.Create(Name,FMF_WRITE|FMF_SHAREREAD);
+
+  // Now we rename the existing file from temporary name to original long name.
+  // Since its previous short name is occupied by another file, it should
+  // get another short name.
+  MoveFile(NewName,FullName);
+
+  if (Created)
+  {
+    // Delete the temporary zero length file occupying the short name,
+    KeepShortFile.Close();
+    KeepShortFile.Delete();
+  }
+  // We successfully changed the short name. Maybe sometimes we'll simplify
+  // this function by use of SetFileShortName Windows API call.
+  // But SetFileShortName is not available in older Windows.
+  return true;
+}
+#endif
diff --git a/third_party/unrar/src/filcreat.hpp b/third_party/unrar/src/filcreat.hpp
new file mode 100644
index 0000000..44f801d
--- /dev/null
+++ b/third_party/unrar/src/filcreat.hpp
@@ -0,0 +1,14 @@
+#ifndef _RAR_FILECREATE_
+#define _RAR_FILECREATE_
+
+bool FileCreate(RAROptions *Cmd,File *NewFile,wchar *Name,size_t MaxNameSize,
+                bool *UserReject,int64 FileSize=INT64NDF,
+                RarTime *FileTime=NULL,bool WriteOnly=false);
+
+bool GetAutoRenamedName(wchar *Name,size_t MaxNameSize);
+
+#if defined(_WIN_ALL)
+bool UpdateExistingShortName(const wchar *Name);
+#endif
+
+#endif
diff --git a/third_party/unrar/src/file.cpp b/third_party/unrar/src/file.cpp
new file mode 100644
index 0000000..e2bb42a
--- /dev/null
+++ b/third_party/unrar/src/file.cpp
@@ -0,0 +1,729 @@
+#include "rar.hpp"
+
+File::File()
+{
+  hFile=FILE_BAD_HANDLE;
+  *FileName=0;
+  NewFile=false;
+  LastWrite=false;
+  HandleType=FILE_HANDLENORMAL;
+  SkipClose=false;
+  IgnoreReadErrors=false;
+  ErrorType=FILE_SUCCESS;
+  OpenShared=false;
+  AllowDelete=true;
+  AllowExceptions=true;
+#ifdef _WIN_ALL
+  NoSequentialRead=false;
+  CreateMode=FMF_UNDEFINED;
+#endif
+}
+
+
+File::~File()
+{
+  if (hFile!=FILE_BAD_HANDLE && !SkipClose)
+    if (NewFile)
+      Delete();
+    else
+      Close();
+}
+
+
+void File::operator = (File &SrcFile)
+{
+  hFile=SrcFile.hFile;
+  NewFile=SrcFile.NewFile;
+  LastWrite=SrcFile.LastWrite;
+  HandleType=SrcFile.HandleType;
+  wcsncpyz(FileName,SrcFile.FileName,ASIZE(FileName));
+  SrcFile.SkipClose=true;
+}
+
+
+bool File::Open(const wchar *Name,uint Mode)
+{
+  ErrorType=FILE_SUCCESS;
+  FileHandle hNewFile;
+  bool OpenShared=File::OpenShared || (Mode & FMF_OPENSHARED)!=0;
+  bool UpdateMode=(Mode & FMF_UPDATE)!=0;
+  bool WriteMode=(Mode & FMF_WRITE)!=0;
+#ifdef _WIN_ALL
+  uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ;
+  if (UpdateMode)
+    Access|=GENERIC_WRITE;
+  uint ShareMode=(Mode & FMF_OPENEXCLUSIVE) ? 0 : FILE_SHARE_READ;
+  if (OpenShared)
+    ShareMode|=FILE_SHARE_WRITE;
+  uint Flags=NoSequentialRead ? 0:FILE_FLAG_SEQUENTIAL_SCAN;
+  hNewFile=CreateFile(Name,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL);
+
+  DWORD LastError;
+  if (hNewFile==FILE_BAD_HANDLE)
+  {
+    LastError=GetLastError();
+
+    wchar LongName[NM];
+    if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
+    {
+      hNewFile=CreateFile(LongName,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL);
+
+      // For archive names longer than 260 characters first CreateFile
+      // (without \\?\) fails and sets LastError to 3 (access denied).
+      // We need the correct "file not found" error code to decide
+      // if we create a new archive or quit with "cannot create" error.
+      // So we need to check the error code after \\?\ CreateFile again,
+      // otherwise we'll fail to create new archives with long names.
+      // But we cannot simply assign the new code to LastError,
+      // because it would break "..\arcname.rar" relative names processing.
+      // First CreateFile returns the correct "file not found" code for such
+      // names, but "\\?\" CreateFile returns ERROR_INVALID_NAME treating
+      // dots as a directory name. So we check only for "file not found"
+      // error here and for other errors use the first CreateFile result.
+      if (GetLastError()==ERROR_FILE_NOT_FOUND)
+        LastError=ERROR_FILE_NOT_FOUND;
+    }
+  }
+  if (hNewFile==FILE_BAD_HANDLE && LastError==ERROR_FILE_NOT_FOUND)
+    ErrorType=FILE_NOTFOUND;
+
+#else
+  int flags=UpdateMode ? O_RDWR:(WriteMode ? O_WRONLY:O_RDONLY);
+#ifdef O_BINARY
+  flags|=O_BINARY;
+#if defined(_AIX) && defined(_LARGE_FILE_API)
+  flags|=O_LARGEFILE;
+#endif
+#endif
+  char NameA[NM];
+  WideToChar(Name,NameA,ASIZE(NameA));
+
+  int handle=open(NameA,flags);
+#ifdef LOCK_EX
+
+#ifdef _OSF_SOURCE
+  extern "C" int flock(int, int);
+#endif
+
+  if (!OpenShared && UpdateMode && handle>=0 && flock(handle,LOCK_EX|LOCK_NB)==-1)
+  {
+    close(handle);
+    return false;
+  }
+#endif
+  if (handle==-1)
+    hNewFile=FILE_BAD_HANDLE;
+  else
+  {
+#ifdef FILE_USE_OPEN
+    hNewFile=handle;
+#else
+    hNewFile=fdopen(handle,UpdateMode ? UPDATEBINARY:READBINARY);
+#endif
+  }
+  if (hNewFile==FILE_BAD_HANDLE && errno==ENOENT)
+    ErrorType=FILE_NOTFOUND;
+#endif
+  NewFile=false;
+  HandleType=FILE_HANDLENORMAL;
+  SkipClose=false;
+  bool Success=hNewFile!=FILE_BAD_HANDLE;
+  if (Success)
+  {
+    hFile=hNewFile;
+    wcsncpyz(FileName,Name,ASIZE(FileName));
+  }
+  return Success;
+}
+
+
+#if !defined(SFX_MODULE)
+void File::TOpen(const wchar *Name)
+{
+  if (!WOpen(Name))
+    ErrHandler.Exit(RARX_OPEN);
+}
+#endif
+
+
+bool File::WOpen(const wchar *Name)
+{
+  if (Open(Name))
+    return true;
+  ErrHandler.OpenErrorMsg(Name);
+  return false;
+}
+
+
+bool File::Create(const wchar *Name,uint Mode)
+{
+  // OpenIndiana based NAS and CIFS shares fail to set the file time if file
+  // was created in read+write mode and some data was written and not flushed
+  // before SetFileTime call. So we should use the write only mode if we plan
+  // SetFileTime call and do not need to read from file.
+  bool WriteMode=(Mode & FMF_WRITE)!=0;
+  bool ShareRead=(Mode & FMF_SHAREREAD)!=0 || File::OpenShared;
+#ifdef _WIN_ALL
+  CreateMode=Mode;
+  uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ|GENERIC_WRITE;
+  DWORD ShareMode=ShareRead ? FILE_SHARE_READ:0;
+
+  // Windows automatically removes dots and spaces in the end of file name,
+  // So we detect such names and process them with \\?\ prefix.
+  wchar *LastChar=PointToLastChar(Name);
+  bool Special=*LastChar=='.' || *LastChar==' ';
+  
+  if (Special && (Mode & FMF_STANDARDNAMES)==0)
+    hFile=FILE_BAD_HANDLE;
+  else
+    hFile=CreateFile(Name,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL);
+
+  if (hFile==FILE_BAD_HANDLE)
+  {
+    wchar LongName[NM];
+    if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
+      hFile=CreateFile(LongName,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL);
+  }
+
+#else
+  char NameA[NM];
+  WideToChar(Name,NameA,ASIZE(NameA));
+#ifdef FILE_USE_OPEN
+  hFile=open(NameA,(O_CREAT|O_TRUNC) | (WriteMode ? O_WRONLY : O_RDWR),0666);
+#else
+  hFile=fopen(NameA,WriteMode ? WRITEBINARY:CREATEBINARY);
+#endif
+#endif
+  NewFile=true;
+  HandleType=FILE_HANDLENORMAL;
+  SkipClose=false;
+  wcsncpyz(FileName,Name,ASIZE(FileName));
+  return hFile!=FILE_BAD_HANDLE;
+}
+
+
+#if !defined(SFX_MODULE)
+void File::TCreate(const wchar *Name,uint Mode)
+{
+  if (!WCreate(Name,Mode))
+    ErrHandler.Exit(RARX_FATAL);
+}
+#endif
+
+
+bool File::WCreate(const wchar *Name,uint Mode)
+{
+  if (Create(Name,Mode))
+    return true;
+  ErrHandler.CreateErrorMsg(Name);
+  return false;
+}
+
+
+bool File::Close()
+{
+  bool Success=true;
+
+  if (hFile!=FILE_BAD_HANDLE)
+  {
+    if (!SkipClose)
+    {
+#ifdef _WIN_ALL
+      // We use the standard system handle for stdout in Windows
+      // and it must not  be closed here.
+      if (HandleType==FILE_HANDLENORMAL)
+        Success=CloseHandle(hFile)==TRUE;
+#else
+#ifdef FILE_USE_OPEN
+      Success=close(hFile)!=-1;
+#else
+      Success=fclose(hFile)!=EOF;
+#endif
+#endif
+    }
+    hFile=FILE_BAD_HANDLE;
+  }
+  HandleType=FILE_HANDLENORMAL;
+  if (!Success && AllowExceptions)
+    ErrHandler.CloseError(FileName);
+  return Success;
+}
+
+
+bool File::Delete()
+{
+  if (HandleType!=FILE_HANDLENORMAL)
+    return false;
+  if (hFile!=FILE_BAD_HANDLE)
+    Close();
+  if (!AllowDelete)
+    return false;
+  return DelFile(FileName);
+}
+
+
+bool File::Rename(const wchar *NewName)
+{
+  // No need to rename if names are already same.
+  bool Success=wcscmp(FileName,NewName)==0;
+
+  if (!Success)
+    Success=RenameFile(FileName,NewName);
+
+  if (Success)
+    wcscpy(FileName,NewName);
+
+  return Success;
+}
+
+
+bool File::Write(const void *Data,size_t Size)
+{
+  if (Size==0)
+    return true;
+  if (HandleType==FILE_HANDLESTD)
+  {
+#ifdef _WIN_ALL
+    hFile=GetStdHandle(STD_OUTPUT_HANDLE);
+#else
+    // Cannot use the standard stdout here, because it already has wide orientation.
+    if (hFile==FILE_BAD_HANDLE)
+    {
+#ifdef FILE_USE_OPEN
+      hFile=dup(STDOUT_FILENO); // Open new stdout stream.
+#else
+      hFile=fdopen(dup(STDOUT_FILENO),"w"); // Open new stdout stream.
+#endif
+    }
+#endif
+  }
+  bool Success;
+  while (1)
+  {
+    Success=false;
+#ifdef _WIN_ALL
+    DWORD Written=0;
+    if (HandleType!=FILE_HANDLENORMAL)
+    {
+      // writing to stdout can fail in old Windows if data block is too large
+      const size_t MaxSize=0x4000;
+      for (size_t I=0;I<Size;I+=MaxSize)
+      {
+        Success=WriteFile(hFile,(byte *)Data+I,(DWORD)Min(Size-I,MaxSize),&Written,NULL)==TRUE;
+        if (!Success)
+          break;
+      }
+    }
+    else
+      Success=WriteFile(hFile,Data,(DWORD)Size,&Written,NULL)==TRUE;
+#else
+#ifdef FILE_USE_OPEN
+    ssize_t Written=write(hFile,Data,Size);
+    Success=Written==Size;
+#else
+    int Written=fwrite(Data,1,Size,hFile);
+    Success=Written==Size && !ferror(hFile);
+#endif
+#endif
+    if (!Success && AllowExceptions && HandleType==FILE_HANDLENORMAL)
+    {
+#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(RARDLL)
+      int ErrCode=GetLastError();
+      int64 FilePos=Tell();
+      uint64 FreeSize=GetFreeDisk(FileName);
+      SetLastError(ErrCode);
+      if (FreeSize>Size && FilePos-Size<=0xffffffff && FilePos+Size>0xffffffff)
+        ErrHandler.WriteErrorFAT(FileName);
+#endif
+      if (ErrHandler.AskRepeatWrite(FileName,false))
+      {
+#if !defined(_WIN_ALL) && !defined(FILE_USE_OPEN)
+        clearerr(hFile);
+#endif
+        if (Written<Size && Written>0)
+          Seek(Tell()-Written,SEEK_SET);
+        continue;
+      }
+      ErrHandler.WriteError(NULL,FileName);
+    }
+    break;
+  }
+  LastWrite=true;
+  return Success; // It can return false only if AllowExceptions is disabled.
+}
+
+
+int File::Read(void *Data,size_t Size)
+{
+  int64 FilePos=0; // Initialized only to suppress some compilers warning.
+
+  if (IgnoreReadErrors)
+    FilePos=Tell();
+  int ReadSize;
+  while (true)
+  {
+    ReadSize=DirectRead(Data,Size);
+    if (ReadSize==-1)
+    {
+      ErrorType=FILE_READERROR;
+      if (AllowExceptions)
+        if (IgnoreReadErrors)
+        {
+          ReadSize=0;
+          for (size_t I=0;I<Size;I+=512)
+          {
+            Seek(FilePos+I,SEEK_SET);
+            size_t SizeToRead=Min(Size-I,512);
+            int ReadCode=DirectRead(Data,SizeToRead);
+            ReadSize+=(ReadCode==-1) ? 512:ReadCode;
+          }
+        }
+        else
+        {
+          if (HandleType==FILE_HANDLENORMAL && ErrHandler.AskRepeatRead(FileName))
+            continue;
+          ErrHandler.ReadError(FileName);
+        }
+    }
+    break;
+  }
+  return ReadSize;
+}
+
+
+// Returns -1 in case of error.
+int File::DirectRead(void *Data,size_t Size)
+{
+#ifdef _WIN_ALL
+  const size_t MaxDeviceRead=20000;
+  const size_t MaxLockedRead=32768;
+#endif
+  if (HandleType==FILE_HANDLESTD)
+  {
+#ifdef _WIN_ALL
+//    if (Size>MaxDeviceRead)
+//      Size=MaxDeviceRead;
+    hFile=GetStdHandle(STD_INPUT_HANDLE);
+#else
+#ifdef FILE_USE_OPEN
+    hFile=STDIN_FILENO;
+#else
+    hFile=stdin;
+#endif
+#endif
+  }
+#ifdef _WIN_ALL
+  // For pipes like 'type file.txt | rar -si arcname' ReadFile may return
+  // data in small ~4KB blocks. It may slightly reduce the compression ratio.
+  DWORD Read;
+  if (!ReadFile(hFile,Data,(DWORD)Size,&Read,NULL))
+  {
+    if (IsDevice() && Size>MaxDeviceRead)
+      return DirectRead(Data,MaxDeviceRead);
+    if (HandleType==FILE_HANDLESTD && GetLastError()==ERROR_BROKEN_PIPE)
+      return 0;
+
+    // We had a bug report about failure to archive 1C database lock file
+    // 1Cv8tmp.1CL, which is a zero length file with a region above 200 KB
+    // permanently locked. If our first read request uses too large buffer
+    // and if we are in -dh mode, so we were able to open the file,
+    // we'll fail with "Read error". So now we use try a smaller buffer size
+    // in case of lock error.
+    if (HandleType==FILE_HANDLENORMAL && Size>MaxLockedRead &&
+        GetLastError()==ERROR_LOCK_VIOLATION)
+      return DirectRead(Data,MaxLockedRead);
+
+    return -1;
+  }
+  return Read;
+#else
+#ifdef FILE_USE_OPEN
+  ssize_t ReadSize=read(hFile,Data,Size);
+  if (ReadSize==-1)
+    return -1;
+  return (int)ReadSize;
+#else
+  if (LastWrite)
+  {
+    fflush(hFile);
+    LastWrite=false;
+  }
+  clearerr(hFile);
+  size_t ReadSize=fread(Data,1,Size,hFile);
+  if (ferror(hFile))
+    return -1;
+  return (int)ReadSize;
+#endif
+#endif
+}
+
+
+void File::Seek(int64 Offset,int Method)
+{
+  if (!RawSeek(Offset,Method) && AllowExceptions)
+    ErrHandler.SeekError(FileName);
+}
+
+
+bool File::RawSeek(int64 Offset,int Method)
+{
+  if (hFile==FILE_BAD_HANDLE)
+    return true;
+  if (Offset<0 && Method!=SEEK_SET)
+  {
+    Offset=(Method==SEEK_CUR ? Tell():FileLength())+Offset;
+    Method=SEEK_SET;
+  }
+#ifdef _WIN_ALL
+  LONG HighDist=(LONG)(Offset>>32);
+  if (SetFilePointer(hFile,(LONG)Offset,&HighDist,Method)==0xffffffff &&
+      GetLastError()!=NO_ERROR)
+    return false;
+#else
+  LastWrite=false;
+#ifdef FILE_USE_OPEN
+  if (lseek(hFile,(off_t)Offset,Method)==-1)
+    return false;
+#elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE) && !defined(__VMS)
+  if (fseeko(hFile,Offset,Method)!=0)
+    return false;
+#else
+  if (fseek(hFile,(long)Offset,Method)!=0)
+    return false;
+#endif
+#endif
+  return true;
+}
+
+
+int64 File::Tell()
+{
+  if (hFile==FILE_BAD_HANDLE)
+    if (AllowExceptions)
+      ErrHandler.SeekError(FileName);
+    else
+      return -1;
+#ifdef _WIN_ALL
+  LONG HighDist=0;
+  uint LowDist=SetFilePointer(hFile,0,&HighDist,FILE_CURRENT);
+  if (LowDist==0xffffffff && GetLastError()!=NO_ERROR)
+    if (AllowExceptions)
+      ErrHandler.SeekError(FileName);
+    else
+      return -1;
+  return INT32TO64(HighDist,LowDist);
+#else
+#ifdef FILE_USE_OPEN
+  return lseek(hFile,0,SEEK_CUR);
+#elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE)
+  return ftello(hFile);
+#else
+  return ftell(hFile);
+#endif
+#endif
+}
+
+
+void File::Prealloc(int64 Size)
+{
+#ifdef _WIN_ALL
+  if (RawSeek(Size,SEEK_SET))
+  {
+    Truncate();
+    Seek(0,SEEK_SET);
+  }
+#endif
+
+#if defined(_UNIX) && defined(USE_FALLOCATE)
+  // fallocate is rather new call. Only latest kernels support it.
+  // So we are not using it by default yet.
+  int fd = GetFD();
+  if (fd >= 0)
+    fallocate(fd, 0, 0, Size);
+#endif
+}
+
+
+byte File::GetByte()
+{
+  byte Byte=0;
+  Read(&Byte,1);
+  return Byte;
+}
+
+
+void File::PutByte(byte Byte)
+{
+  Write(&Byte,1);
+}
+
+
+bool File::Truncate()
+{
+#ifdef _WIN_ALL
+  return SetEndOfFile(hFile)==TRUE;
+#else
+  return ftruncate(GetFD(),(off_t)Tell())==0;
+#endif
+}
+
+
+void File::Flush()
+{
+#ifdef _WIN_ALL
+  FlushFileBuffers(hFile);
+#else
+#ifndef FILE_USE_OPEN
+  fflush(hFile);
+#endif
+  fsync(GetFD());
+#endif
+}
+
+
+void File::SetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta)
+{
+#ifdef _WIN_ALL
+  // Workaround for OpenIndiana NAS time bug. If we cannot create a file
+  // in write only mode, we need to flush the write buffer before calling
+  // SetFileTime or file time will not be changed.
+  if (CreateMode!=FMF_UNDEFINED && (CreateMode & FMF_WRITE)==0)
+    FlushFileBuffers(hFile);
+
+  bool sm=ftm!=NULL && ftm->IsSet();
+  bool sc=ftc!=NULL && ftc->IsSet();
+  bool sa=fta!=NULL && fta->IsSet();
+  FILETIME fm,fc,fa;
+  if (sm)
+    ftm->GetWinFT(&fm);
+  if (sc)
+    ftc->GetWinFT(&fc);
+  if (sa)
+    fta->GetWinFT(&fa);
+  SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL);
+#endif
+}
+
+
+void File::SetCloseFileTime(RarTime *ftm,RarTime *fta)
+{
+// Android APP_PLATFORM := android-14 does not support futimens and futimes.
+// Newer platforms support futimens, but fail on Android 4.2.
+// We have to use utime for Android.
+// Also we noticed futimens fail to set timestamps on NTFS partition
+// mounted to virtual Linux x86 machine, but utimensat worked correctly.
+// So we set timestamps for already closed files in Unix.
+#ifdef _UNIX
+  SetCloseFileTimeByName(FileName,ftm,fta);
+#endif
+}
+
+
+void File::SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta)
+{
+#ifdef _UNIX
+  bool setm=ftm!=NULL && ftm->IsSet();
+  bool seta=fta!=NULL && fta->IsSet();
+  if (setm || seta)
+  {
+    char NameA[NM];
+    WideToChar(Name,NameA,ASIZE(NameA));
+
+#ifdef UNIX_TIME_NS
+    timespec times[2];
+    times[0].tv_sec=seta ? fta->GetUnix() : 0;
+    times[0].tv_nsec=seta ? long(fta->GetUnixNS()%1000000000) : UTIME_NOW;
+    times[1].tv_sec=setm ? ftm->GetUnix() : 0;
+    times[1].tv_nsec=setm ? long(ftm->GetUnixNS()%1000000000) : UTIME_NOW;
+    utimensat(AT_FDCWD,NameA,times,0);
+#else
+    utimbuf ut;
+    if (setm)
+      ut.modtime=ftm->GetUnix();
+    else
+      ut.modtime=fta->GetUnix(); // Need to set something, cannot left it 0.
+    if (seta)
+      ut.actime=fta->GetUnix();
+    else
+      ut.actime=ut.modtime; // Need to set something, cannot left it 0.
+    utime(NameA,&ut);
+#endif
+  }
+#endif
+}
+
+
+void File::GetOpenFileTime(RarTime *ft)
+{
+#ifdef _WIN_ALL
+  FILETIME FileTime;
+  GetFileTime(hFile,NULL,NULL,&FileTime);
+  ft->SetWinFT(&FileTime);
+#endif
+#if defined(_UNIX) || defined(_EMX)
+  struct stat st;
+  fstat(GetFD(),&st);
+  ft->SetUnix(st.st_mtime);
+#endif
+}
+
+
+int64 File::FileLength()
+{
+  SaveFilePos SavePos(*this);
+  Seek(0,SEEK_END);
+  return Tell();
+}
+
+
+bool File::IsDevice()
+{
+  if (hFile==FILE_BAD_HANDLE)
+    return false;
+#ifdef _WIN_ALL
+  uint Type=GetFileType(hFile);
+  return Type==FILE_TYPE_CHAR || Type==FILE_TYPE_PIPE;
+#else
+  return isatty(GetFD());
+#endif
+}
+
+
+#ifndef SFX_MODULE
+int64 File::Copy(File &Dest,int64 Length)
+{
+  Array<byte> Buffer(File::CopyBufferSize());
+  int64 CopySize=0;
+  bool CopyAll=(Length==INT64NDF);
+
+  while (CopyAll || Length>0)
+  {
+    Wait();
+    size_t SizeToRead=(!CopyAll && Length<(int64)Buffer.Size()) ? (size_t)Length:Buffer.Size();
+    byte *Buf=&Buffer[0];
+    int ReadSize=Read(Buf,SizeToRead);
+    if (ReadSize==0)
+      break;
+    size_t WriteSize=ReadSize;
+#ifdef _WIN_ALL
+    // For FAT32 USB flash drives in Windows if first write is 4 KB or more,
+    // write caching is disabled and "write through" is enabled, resulting
+    // in bad performance, especially for many small files. It happens when
+    // we create SFX archive on USB drive, because SFX module is written first.
+    // So we split the first write to small 1 KB followed by rest of data.
+    if (CopySize==0 && WriteSize>=4096)
+    {
+      const size_t FirstWrite=1024;
+      Dest.Write(Buf,FirstWrite);
+      Buf+=FirstWrite;
+      WriteSize-=FirstWrite;
+    }
+#endif
+    Dest.Write(Buf,WriteSize);
+    CopySize+=ReadSize;
+    if (!CopyAll)
+      Length-=ReadSize;
+  }
+  return CopySize;
+}
+#endif
diff --git a/third_party/unrar/src/file.hpp b/third_party/unrar/src/file.hpp
new file mode 100644
index 0000000..20734e2
--- /dev/null
+++ b/third_party/unrar/src/file.hpp
@@ -0,0 +1,137 @@
+#ifndef _RAR_FILE_
+#define _RAR_FILE_
+
+#define FILE_USE_OPEN
+
+#ifdef _WIN_ALL
+  typedef HANDLE FileHandle;
+  #define FILE_BAD_HANDLE INVALID_HANDLE_VALUE
+#elif defined(FILE_USE_OPEN)
+  typedef off_t FileHandle;
+  #define FILE_BAD_HANDLE -1
+#else
+  typedef FILE* FileHandle;
+  #define FILE_BAD_HANDLE NULL
+#endif
+
+class RAROptions;
+
+enum FILE_HANDLETYPE {FILE_HANDLENORMAL,FILE_HANDLESTD};
+
+enum FILE_ERRORTYPE {FILE_SUCCESS,FILE_NOTFOUND,FILE_READERROR};
+
+enum FILE_MODE_FLAGS {
+  // Request read only access to file. Default for Open.
+  FMF_READ=0,
+
+  // Request both read and write access to file. Default for Create.
+  FMF_UPDATE=1,
+
+  // Request write only access to file.
+  FMF_WRITE=2,
+
+  // Open files which are already opened for write by other programs.
+  FMF_OPENSHARED=4,
+
+  // Open files only if no other program is opened it even in shared mode.
+  FMF_OPENEXCLUSIVE=8,
+
+  // Provide read access to created file for other programs.
+  FMF_SHAREREAD=16,
+
+  // Use standard NTFS names without trailing dots and spaces.
+  FMF_STANDARDNAMES=32,
+
+  // Mode flags are not defined yet.
+  FMF_UNDEFINED=256
+};
+
+
+class File
+{
+  private:
+    FileHandle hFile;
+    bool LastWrite;
+    FILE_HANDLETYPE HandleType;
+    bool SkipClose;
+    bool IgnoreReadErrors;
+    bool NewFile;
+    bool AllowDelete;
+    bool AllowExceptions;
+#ifdef _WIN_ALL
+    bool NoSequentialRead;
+    uint CreateMode;
+#endif
+  protected:
+    bool OpenShared; // Set by 'Archive' class.
+  public:
+    wchar FileName[NM];
+
+    FILE_ERRORTYPE ErrorType;
+  public:
+    File();
+    virtual ~File();
+    void operator = (File &SrcFile);
+    virtual bool Open(const wchar *Name,uint Mode=FMF_READ);
+    void TOpen(const wchar *Name);
+    bool WOpen(const wchar *Name);
+    bool Create(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD);
+    void TCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD);
+    bool WCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD);
+    virtual bool Close();
+    bool Delete();
+    bool Rename(const wchar *NewName);
+    bool Write(const void *Data,size_t Size);
+    virtual int Read(void *Data,size_t Size);
+    int DirectRead(void *Data,size_t Size);
+    virtual void Seek(int64 Offset,int Method);
+    bool RawSeek(int64 Offset,int Method);
+    virtual int64 Tell();
+    void Prealloc(int64 Size);
+    byte GetByte();
+    void PutByte(byte Byte);
+    bool Truncate();
+    void Flush();
+    void SetOpenFileTime(RarTime *ftm,RarTime *ftc=NULL,RarTime *fta=NULL);
+    void SetCloseFileTime(RarTime *ftm,RarTime *fta=NULL);
+    static void SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta);
+    void GetOpenFileTime(RarTime *ft);
+    virtual bool IsOpened() {return hFile!=FILE_BAD_HANDLE;};
+    int64 FileLength();
+    void SetHandleType(FILE_HANDLETYPE Type) {HandleType=Type;}
+    FILE_HANDLETYPE GetHandleType() {return HandleType;}
+    bool IsDevice();
+    static bool RemoveCreated();
+    FileHandle GetHandle() {return hFile;}
+    void SetHandle(FileHandle Handle) {Close();hFile=Handle;}
+    void SetIgnoreReadErrors(bool Mode) {IgnoreReadErrors=Mode;}
+    int64 Copy(File &Dest,int64 Length=INT64NDF);
+    void SetAllowDelete(bool Allow) {AllowDelete=Allow;}
+    void SetExceptions(bool Allow) {AllowExceptions=Allow;}
+#ifdef _WIN_ALL
+    void RemoveSequentialFlag() {NoSequentialRead=true;}
+#endif
+#ifdef _UNIX
+    int GetFD()
+    {
+#ifdef FILE_USE_OPEN
+      return hFile;
+#else
+      return fileno(hFile);
+#endif
+    }
+#endif
+    static size_t CopyBufferSize()
+    {
+#ifdef _WIN_ALL
+      // USB flash performance is poor with 64 KB buffer, 256+ KB resolved it.
+      // For copying from HDD to same HDD the best performance was with 256 KB
+      // buffer in XP and with 1 MB buffer in Win10.
+      return WinNT()==WNT_WXP ? 0x40000:0x100000;
+#else
+      return 0x100000;
+#endif
+    }
+};
+
+#endif
diff --git a/third_party/unrar/src/filefn.cpp b/third_party/unrar/src/filefn.cpp
new file mode 100644
index 0000000..4eb7b9b
--- /dev/null
+++ b/third_party/unrar/src/filefn.cpp
@@ -0,0 +1,510 @@
+#include "rar.hpp"
+
+MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr)
+{
+#ifdef _WIN_ALL
+  // Windows automatically removes dots and spaces in the end of directory
+  // name. So we detect such names and process them with \\?\ prefix.
+  wchar *LastChar=PointToLastChar(Name);
+  bool Special=*LastChar=='.' || *LastChar==' ';
+  BOOL RetCode=Special ? FALSE : CreateDirectory(Name,NULL);
+  if (RetCode==0 && !FileExist(Name))
+  {
+    wchar LongName[NM];
+    if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
+      RetCode=CreateDirectory(LongName,NULL);
+  }
+  if (RetCode!=0) // Non-zero return code means success for CreateDirectory.
+  {
+    if (SetAttr)
+      SetFileAttr(Name,Attr);
+    return MKDIR_SUCCESS;
+  }
+  int ErrCode=GetLastError();
+  if (ErrCode==ERROR_FILE_NOT_FOUND || ErrCode==ERROR_PATH_NOT_FOUND)
+    return MKDIR_BADPATH;
+  return MKDIR_ERROR;
+#elif defined(_UNIX)
+  char NameA[NM];
+  WideToChar(Name,NameA,ASIZE(NameA));
+  mode_t uattr=SetAttr ? (mode_t)Attr:0777;
+  int ErrCode=mkdir(NameA,uattr);
+  if (ErrCode==-1)
+    return errno==ENOENT ? MKDIR_BADPATH:MKDIR_ERROR;
+  return MKDIR_SUCCESS;
+#else
+  return MKDIR_ERROR;
+#endif
+}
+
+
+bool CreatePath(const wchar *Path,bool SkipLastName)
+{
+  if (Path==NULL || *Path==0)
+    return false;
+
+#if defined(_WIN_ALL) || defined(_EMX)
+  uint DirAttr=0;
+#else
+  uint DirAttr=0777;
+#endif
+  
+  bool Success=true;
+
+  for (const wchar *s=Path;*s!=0;s++)
+  {
+    wchar DirName[NM];
+    if (s-Path>=ASIZE(DirName))
+      break;
+
+    // Process all kinds of path separators, so user can enter Unix style
+    // path in Windows or Windows in Unix. s>Path check avoids attempting
+    // creating an empty directory for paths starting from path separator.
+    if (IsPathDiv(*s) && s>Path)
+    {
+#ifdef _WIN_ALL
+      // We must not attempt to create "D:" directory, because first
+      // CreateDirectory will fail, so we'll use \\?\D:, which forces Wine
+      // to create "D:" directory.
+      if (s==Path+2 && Path[1]==':')
+        continue;
+#endif
+      wcsncpy(DirName,Path,s-Path);
+      DirName[s-Path]=0;
+
+      Success=MakeDir(DirName,true,DirAttr)==MKDIR_SUCCESS;
+      if (Success)
+      {
+        mprintf(St(MCreatDir),DirName);
+        mprintf(L" %s",St(MOk));
+      }
+    }
+  }
+  if (!SkipLastName && !IsPathDiv(*PointToLastChar(Path)))
+    Success=MakeDir(Path,true,DirAttr)==MKDIR_SUCCESS;
+  return Success;
+}
+
+
+void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta)
+{
+#if defined(_WIN_ALL)
+  bool sm=ftm!=NULL && ftm->IsSet();
+  bool sc=ftc!=NULL && ftc->IsSet();
+  bool sa=fta!=NULL && fta->IsSet();
+
+  uint DirAttr=GetFileAttr(Name);
+  bool ResetAttr=(DirAttr!=0xffffffff && (DirAttr & FILE_ATTRIBUTE_READONLY)!=0);
+  if (ResetAttr)
+    SetFileAttr(Name,0);
+
+  HANDLE hFile=CreateFile(Name,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,
+                          NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
+  if (hFile==INVALID_HANDLE_VALUE)
+  {
+    wchar LongName[NM];
+    if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
+      hFile=CreateFile(LongName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,
+                       NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
+  }
+
+  if (hFile==INVALID_HANDLE_VALUE)
+    return;
+  FILETIME fm,fc,fa;
+  if (sm)
+    ftm->GetWinFT(&fm);
+  if (sc)
+    ftc->GetWinFT(&fc);
+  if (sa)
+    fta->GetWinFT(&fa);
+  SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL);
+  CloseHandle(hFile);
+  if (ResetAttr)
+    SetFileAttr(Name,DirAttr);
+#endif
+#if defined(_UNIX) || defined(_EMX)
+  File::SetCloseFileTimeByName(Name,ftm,fta);
+#endif
+}
+
+
+bool IsRemovable(const wchar *Name)
+{
+#if defined(_WIN_ALL)
+  wchar Root[NM];
+  GetPathRoot(Name,Root,ASIZE(Root));
+  int Type=GetDriveType(*Root!=0 ? Root:NULL);
+  return Type==DRIVE_REMOVABLE || Type==DRIVE_CDROM;
+#else
+  return false;
+#endif
+}
+
+
+#ifndef SFX_MODULE
+int64 GetFreeDisk(const wchar *Name)
+{
+#ifdef _WIN_ALL
+  wchar Root[NM];
+  GetFilePath(Name,Root,ASIZE(Root));
+
+  ULARGE_INTEGER uiTotalSize,uiTotalFree,uiUserFree;
+  uiUserFree.u.LowPart=uiUserFree.u.HighPart=0;
+  if (GetDiskFreeSpaceEx(*Root!=0 ? Root:NULL,&uiUserFree,&uiTotalSize,&uiTotalFree) &&
+      uiUserFree.u.HighPart<=uiTotalFree.u.HighPart)
+    return INT32TO64(uiUserFree.u.HighPart,uiUserFree.u.LowPart);
+  return 0;
+#elif defined(_UNIX)
+  wchar Root[NM];
+  GetFilePath(Name,Root,ASIZE(Root));
+  char RootA[NM];
+  WideToChar(Root,RootA,ASIZE(RootA));
+  struct statvfs sfs;
+  if (statvfs(*RootA!=0 ? RootA:".",&sfs)!=0)
+    return 0;
+  int64 FreeSize=sfs.f_bsize;
+  FreeSize=FreeSize*sfs.f_bavail;
+  return FreeSize;
+#else
+  return 0;
+#endif
+}
+#endif
+
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
+// Return 'true' for FAT and FAT32, so we can adjust the maximum supported
+// file size to 4 GB for these file systems.
+bool IsFAT(const wchar *Name)
+{
+  wchar Root[NM];
+  GetPathRoot(Name,Root,ASIZE(Root));
+  wchar FileSystem[MAX_PATH+1];
+  if (GetVolumeInformation(Root,NULL,0,NULL,NULL,NULL,FileSystem,ASIZE(FileSystem)))
+    return wcscmp(FileSystem,L"FAT")==0 || wcscmp(FileSystem,L"FAT32")==0;
+  return false;
+}
+#endif
+
+
+bool FileExist(const wchar *Name)
+{
+#ifdef _WIN_ALL
+  return GetFileAttr(Name)!=0xffffffff;
+#elif defined(ENABLE_ACCESS)
+  char NameA[NM];
+  WideToChar(Name,NameA,ASIZE(NameA));
+  return access(NameA,0)==0;
+#else
+  FindData FD;
+  return FindFile::FastFind(Name,&FD);
+#endif
+}
+ 
+
+bool WildFileExist(const wchar *Name)
+{
+  if (IsWildcard(Name))
+  {
+    FindFile Find;
+    Find.SetMask(Name);
+    FindData fd;
+    return Find.Next(&fd);
+  }
+  return FileExist(Name);
+}
+
+
+bool IsDir(uint Attr)
+{
+#ifdef _WIN_ALL
+  return Attr!=0xffffffff && (Attr & FILE_ATTRIBUTE_DIRECTORY)!=0;
+#endif
+#if defined(_UNIX)
+  return (Attr & 0xF000)==0x4000;
+#endif
+}
+
+
+bool IsUnreadable(uint Attr)
+{
+#if defined(_UNIX) && defined(S_ISFIFO) && defined(S_ISSOCK) && defined(S_ISCHR)
+  return S_ISFIFO(Attr) || S_ISSOCK(Attr) || S_ISCHR(Attr);
+#endif
+  return false;
+}
+
+
+bool IsLink(uint Attr)
+{
+#ifdef _UNIX
+  return (Attr & 0xF000)==0xA000;
+#elif defined(_WIN_ALL)
+  return (Attr & FILE_ATTRIBUTE_REPARSE_POINT)!=0;
+#else
+  return false;
+#endif
+}
+
+
+
+
+
+
+bool IsDeleteAllowed(uint FileAttr)
+{
+#ifdef _WIN_ALL
+  return (FileAttr & (FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN))==0;
+#else
+  return (FileAttr & (S_IRUSR|S_IWUSR))==(S_IRUSR|S_IWUSR);
+#endif
+}
+
+
+void PrepareToDelete(const wchar *Name)
+{
+#if defined(_WIN_ALL) || defined(_EMX)
+  SetFileAttr(Name,0);
+#endif
+#ifdef _UNIX
+  if (Name!=NULL)
+  {
+    char NameA[NM];
+    WideToChar(Name,NameA,ASIZE(NameA));
+    chmod(NameA,S_IRUSR|S_IWUSR|S_IXUSR);
+  }
+#endif
+}
+
+
+uint GetFileAttr(const wchar *Name)
+{
+#ifdef _WIN_ALL
+  DWORD Attr=GetFileAttributes(Name);
+  if (Attr==0xffffffff)
+  {
+    wchar LongName[NM];
+    if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
+      Attr=GetFileAttributes(LongName);
+  }
+  return Attr;
+#else
+  char NameA[NM];
+  WideToChar(Name,NameA,ASIZE(NameA));
+  struct stat st;
+  if (stat(NameA,&st)!=0)
+    return 0;
+  return st.st_mode;
+#endif
+}
+
+
+bool SetFileAttr(const wchar *Name,uint Attr)
+{
+#ifdef _WIN_ALL
+  bool Success=SetFileAttributes(Name,Attr)!=0;
+  if (!Success)
+  {
+    wchar LongName[NM];
+    if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
+      Success=SetFileAttributes(LongName,Attr)!=0;
+  }
+  return Success;
+#elif defined(_UNIX)
+  char NameA[NM];
+  WideToChar(Name,NameA,ASIZE(NameA));
+  return chmod(NameA,(mode_t)Attr)==0;
+#else
+  return false;
+#endif
+}
+
+
+#if 0
+wchar *MkTemp(wchar *Name,size_t MaxSize)
+{
+  size_t Length=wcslen(Name);
+
+  RarTime CurTime;
+  CurTime.SetCurrentTime();
+
+  // We cannot use CurTime.GetWin() as is, because its lowest bits can
+  // have low informational value, like being a zero or few fixed numbers.
+  uint Random=(uint)(CurTime.GetWin()/100000);
+
+  // Using PID we guarantee that different RAR copies use different temp names
+  // even if started in exactly the same time.
+  uint PID=0;
+#ifdef _WIN_ALL
+  PID=(uint)GetCurrentProcessId();
+#elif defined(_UNIX)
+  PID=(uint)getpid();
+#endif
+
+  for (uint Attempt=0;;Attempt++)
+  {
+    uint Ext=Random%50000+Attempt;
+    wchar RndText[50];
+    swprintf(RndText,ASIZE(RndText),L"%u.%03u",PID,Ext);
+    if (Length+wcslen(RndText)>=MaxSize || Attempt==1000)
+      return NULL;
+    wcscpy(Name+Length,RndText);
+    if (!FileExist(Name))
+      break;
+  }
+  return Name;
+}
+#endif
+
+
+#if !defined(SFX_MODULE)
+void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size,uint Flags)
+{
+  SaveFilePos SavePos(*SrcFile);
+#ifndef SILENT
+  int64 FileLength=Size==INT64NDF ? SrcFile->FileLength() : Size;
+#endif
+
+  if ((Flags & (CALCFSUM_SHOWTEXT|CALCFSUM_SHOWPERCENT))!=0)
+    uiMsg(UIEVENT_FILESUMSTART);
+
+  if ((Flags & CALCFSUM_CURPOS)==0)
+    SrcFile->Seek(0,SEEK_SET);
+
+  const size_t BufSize=0x100000;
+  Array<byte> Data(BufSize);
+
+
+  DataHash HashCRC,HashBlake2;
+  HashCRC.Init(HASH_CRC32,Threads);
+  HashBlake2.Init(HASH_BLAKE2,Threads);
+
+  int64 BlockCount=0;
+  int64 TotalRead=0;
+  while (true)
+  {
+    size_t SizeToRead;
+    if (Size==INT64NDF)   // If we process the entire file.
+      SizeToRead=BufSize; // Then always attempt to read the entire buffer.
+    else
+      SizeToRead=(size_t)Min((int64)BufSize,Size);
+    int ReadSize=SrcFile->Read(&Data[0],SizeToRead);
+    if (ReadSize==0)
+      break;
+    TotalRead+=ReadSize;
+
+    if ((++BlockCount & 0xf)==0)
+    {
+#ifndef SILENT
+      if ((Flags & CALCFSUM_SHOWPROGRESS)!=0)
+        uiExtractProgress(TotalRead,FileLength,TotalRead,FileLength);
+      else
+      {
+        if ((Flags & CALCFSUM_SHOWPERCENT)!=0)
+          uiMsg(UIEVENT_FILESUMPROGRESS,ToPercent(TotalRead,FileLength));
+      }
+#endif
+      Wait();
+    }
+
+    if (CRC32!=NULL)
+      HashCRC.Update(&Data[0],ReadSize);
+    if (Blake2!=NULL)
+      HashBlake2.Update(&Data[0],ReadSize);
+
+    if (Size!=INT64NDF)
+      Size-=ReadSize;
+  }
+  if ((Flags & CALCFSUM_SHOWPERCENT)!=0)
+    uiMsg(UIEVENT_FILESUMEND);
+
+  if (CRC32!=NULL)
+    *CRC32=HashCRC.GetCRC32();
+  if (Blake2!=NULL)
+  {
+    HashValue Result;
+    HashBlake2.Result(&Result);
+    memcpy(Blake2,Result.Digest,sizeof(Result.Digest));
+  }
+}
+#endif
+
+
+bool RenameFile(const wchar *SrcName,const wchar *DestName)
+{
+#ifdef _WIN_ALL
+  bool Success=MoveFile(SrcName,DestName)!=0;
+  if (!Success)
+  {
+    wchar LongName1[NM],LongName2[NM];
+    if (GetWinLongPath(SrcName,LongName1,ASIZE(LongName1)) &&
+        GetWinLongPath(DestName,LongName2,ASIZE(LongName2)))
+      Success=MoveFile(LongName1,LongName2)!=0;
+  }
+  return Success;
+#else
+  char SrcNameA[NM],DestNameA[NM];
+  WideToChar(SrcName,SrcNameA,ASIZE(SrcNameA));
+  WideToChar(DestName,DestNameA,ASIZE(DestNameA));
+  bool Success=rename(SrcNameA,DestNameA)==0;
+  return Success;
+#endif
+}
+
+
+bool DelFile(const wchar *Name)
+{
+#ifdef _WIN_ALL
+  bool Success=DeleteFile(Name)!=0;
+  if (!Success)
+  {
+    wchar LongName[NM];
+    if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
+      Success=DeleteFile(LongName)!=0;
+  }
+  return Success;
+#else
+  char NameA[NM];
+  WideToChar(Name,NameA,ASIZE(NameA));
+  bool Success=remove(NameA)==0;
+  return Success;
+#endif
+}
+
+
+
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+bool SetFileCompression(const wchar *Name,bool State)
+{
+  HANDLE hFile=CreateFile(Name,FILE_READ_DATA|FILE_WRITE_DATA,
+                 FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
+                 FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL);
+  if (hFile==INVALID_HANDLE_VALUE)
+  {
+    wchar LongName[NM];
+    if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
+      hFile=CreateFile(LongName,FILE_READ_DATA|FILE_WRITE_DATA,
+                 FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
+                 FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_SEQUENTIAL_SCAN,NULL);
+  }
+  if (hFile==INVALID_HANDLE_VALUE)
+    return false;
+  SHORT NewState=State ? COMPRESSION_FORMAT_DEFAULT:COMPRESSION_FORMAT_NONE;
+  DWORD Result;
+  int RetCode=DeviceIoControl(hFile,FSCTL_SET_COMPRESSION,&NewState,
+                              sizeof(NewState),NULL,0,&Result,NULL);
+  CloseHandle(hFile);
+  return RetCode!=0;
+}
+#endif
+
+
+
+
+
+
+
+
+
+
diff --git a/third_party/unrar/src/filefn.hpp b/third_party/unrar/src/filefn.hpp
new file mode 100644
index 0000000..1bda9b5
--- /dev/null
+++ b/third_party/unrar/src/filefn.hpp
@@ -0,0 +1,50 @@
+#ifndef _RAR_FILEFN_
+#define _RAR_FILEFN_
+
+enum MKDIR_CODE {MKDIR_SUCCESS,MKDIR_ERROR,MKDIR_BADPATH};
+
+MKDIR_CODE MakeDir(const wchar *Name,bool SetAttr,uint Attr);
+bool CreatePath(const wchar *Path,bool SkipLastName);
+void SetDirTime(const wchar *Name,RarTime *ftm,RarTime *ftc,RarTime *fta);
+bool IsRemovable(const wchar *Name);
+
+#ifndef SFX_MODULE
+int64 GetFreeDisk(const wchar *Name);
+#endif
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
+bool IsFAT(const wchar *Root);
+#endif
+
+bool FileExist(const wchar *Name);
+bool WildFileExist(const wchar *Name);
+bool IsDir(uint Attr);
+bool IsUnreadable(uint Attr);
+bool IsLink(uint Attr);
+void SetSFXMode(const wchar *FileName);
+void EraseDiskContents(const wchar *FileName);
+bool IsDeleteAllowed(uint FileAttr);
+void PrepareToDelete(const wchar *Name);
+uint GetFileAttr(const wchar *Name);
+bool SetFileAttr(const wchar *Name,uint Attr);
+#if 0
+wchar* MkTemp(wchar *Name,size_t MaxSize);
+#endif
+
+enum CALCFSUM_FLAGS {CALCFSUM_SHOWTEXT=1,CALCFSUM_SHOWPERCENT=2,CALCFSUM_SHOWPROGRESS=4,CALCFSUM_CURPOS=8};
+
+void CalcFileSum(File *SrcFile,uint *CRC32,byte *Blake2,uint Threads,int64 Size=INT64NDF,uint Flags=0);
+
+bool RenameFile(const wchar *SrcName,const wchar *DestName);
+bool DelFile(const wchar *Name);
+bool DelDir(const wchar *Name);
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+bool SetFileCompression(const wchar *Name,bool State);
+#endif
+
+
+
+
+
+#endif
diff --git a/third_party/unrar/src/filestr.cpp b/third_party/unrar/src/filestr.cpp
new file mode 100644
index 0000000..a5d29d7
--- /dev/null
+++ b/third_party/unrar/src/filestr.cpp
@@ -0,0 +1,166 @@
+#include "rar.hpp"
+
+bool ReadTextFile(
+  const wchar *Name,
+  StringList *List,
+  bool Config,
+  bool AbortOnError,
+  RAR_CHARSET SrcCharset,
+  bool Unquote,
+  bool SkipComments,
+  bool ExpandEnvStr)
+{
+  wchar FileName[NM];
+  *FileName=0;
+
+  if (Name!=NULL)
+    if (Config)
+      GetConfigName(Name,FileName,ASIZE(FileName),true,false);
+    else
+      wcsncpyz(FileName,Name,ASIZE(FileName));
+
+  File SrcFile;
+  if (*FileName!=0)
+  {
+    bool OpenCode=AbortOnError ? SrcFile.WOpen(FileName):SrcFile.Open(FileName,0);
+
+    if (!OpenCode)
+    {
+      if (AbortOnError)
+        ErrHandler.Exit(RARX_OPEN);
+      return false;
+    }
+  }
+  else
+    SrcFile.SetHandleType(FILE_HANDLESTD);
+
+  uint DataSize=0,ReadSize;
+  const int ReadBlock=4096;
+
+  Array<byte> Data(ReadBlock);
+  while ((ReadSize=SrcFile.Read(&Data[DataSize],ReadBlock))!=0)
+  {
+    DataSize+=ReadSize;
+    Data.Add(ReadSize); // Always have ReadBlock available for next data.
+  }
+  // Set to really read size, so we can zero terminate it correctly.
+  Data.Alloc(DataSize);
+
+  int LittleEndian=DataSize>=2 && Data[0]==255 && Data[1]==254 ? 1:0;
+  int BigEndian=DataSize>=2 && Data[0]==254 && Data[1]==255 ? 1:0;
+  bool Utf8=DataSize>=3 && Data[0]==0xef && Data[1]==0xbb && Data[2]==0xbf;
+
+  if (SrcCharset==RCH_DEFAULT)
+    SrcCharset=DetectTextEncoding(&Data[0],DataSize);
+
+  Array<wchar> DataW;
+
+  if (SrcCharset==RCH_DEFAULT || SrcCharset==RCH_OEM || SrcCharset==RCH_ANSI)
+  {
+    Data.Push(0); // Zero terminate.
+#if defined(_WIN_ALL)
+    if (SrcCharset==RCH_OEM)
+      OemToCharA((char *)&Data[0],(char *)&Data[0]);
+#endif
+    DataW.Alloc(Data.Size());
+    CharToWide((char *)&Data[0],&DataW[0],DataW.Size());
+  }
+
+  if (SrcCharset==RCH_UNICODE)
+  {
+    size_t Start=2; // Skip byte order mark.
+    if (!LittleEndian && !BigEndian) // No byte order mask.
+    {
+      Start=0;
+      LittleEndian=1;
+    }
+    
+    DataW.Alloc(Data.Size()/2+1);
+    size_t End=Data.Size() & ~1; // We need even bytes number for UTF-16.
+    for (size_t I=Start;I<End;I+=2)
+      DataW[(I-Start)/2]=Data[I+BigEndian]+Data[I+LittleEndian]*256;
+    DataW[(End-Start)/2]=0;
+  }
+
+  if (SrcCharset==RCH_UTF8)
+  {
+    Data.Push(0); // Zero terminate data.
+    DataW.Alloc(Data.Size());
+    UtfToWide((const char *)(Data+(Utf8 ? 3:0)),&DataW[0],DataW.Size());
+  }
+
+  wchar *CurStr=&DataW[0];
+
+  while (*CurStr!=0)
+  {
+    wchar *NextStr=CurStr,*CmtPtr=NULL;
+    while (*NextStr!='\r' && *NextStr!='\n' && *NextStr!=0)
+    {
+      if (SkipComments && NextStr[0]=='/' && NextStr[1]=='/')
+      {
+        *NextStr=0;
+        CmtPtr=NextStr;
+      }
+      NextStr++;
+    }
+    bool Done=*NextStr==0;
+
+    *NextStr=0;
+    for (wchar *SpacePtr=(CmtPtr!=NULL ? CmtPtr:NextStr)-1;SpacePtr>=CurStr;SpacePtr--)
+    {
+      if (*SpacePtr!=' ' && *SpacePtr!='\t')
+        break;
+      *SpacePtr=0;
+    }
+    
+    if (Unquote && *CurStr=='\"')
+    {
+      size_t Length=wcslen(CurStr);
+      if (CurStr[Length-1]=='\"')
+      {
+        CurStr[Length-1]=0;
+        CurStr++;
+      }
+    }
+
+    bool Expanded=false;
+#if defined(_WIN_ALL)
+    if (ExpandEnvStr && *CurStr=='%') // Expand environment variables in Windows.
+    {
+      wchar ExpName[NM];
+      *ExpName=0;
+      DWORD Result=ExpandEnvironmentStrings(CurStr,ExpName,ASIZE(ExpName));
+      Expanded=Result!=0 && Result<ASIZE(ExpName);
+      if (Expanded && *ExpName!=0)
+        List->AddString(ExpName);
+    }
+#endif
+    if (!Expanded && *CurStr!=0)
+      List->AddString(CurStr);
+
+    if (Done)
+      break;
+    CurStr=NextStr+1;
+    while (*CurStr=='\r' || *CurStr=='\n')
+      CurStr++;
+  }
+  return true;
+}
+
+
+RAR_CHARSET DetectTextEncoding(const byte *Data,size_t DataSize)
+{
+  if (DataSize>3 && Data[0]==0xef && Data[1]==0xbb && Data[2]==0xbf &&
+      IsTextUtf8(Data+3,DataSize-3))
+    return RCH_UTF8;
+
+  bool LittleEndian=DataSize>2 && Data[0]==255 && Data[1]==254;
+  bool BigEndian=DataSize>2 && Data[0]==254 && Data[1]==255;
+
+  if (LittleEndian || BigEndian)  
+    for (size_t I=LittleEndian ? 3 : 2;I<DataSize;I+=2)
+      if (Data[I]<32 && Data[I]!='\r' && Data[I]!='\n')
+        return RCH_UNICODE; // High byte in UTF-16 char is found.
+
+  return RCH_DEFAULT;
+}
diff --git a/third_party/unrar/src/filestr.hpp b/third_party/unrar/src/filestr.hpp
new file mode 100644
index 0000000..febd0a2
--- /dev/null
+++ b/third_party/unrar/src/filestr.hpp
@@ -0,0 +1,17 @@
+#ifndef _RAR_FILESTR_
+#define _RAR_FILESTR_
+
+bool ReadTextFile(
+  const wchar *Name,
+  StringList *List,
+  bool Config,
+  bool AbortOnError=false,
+  RAR_CHARSET SrcCharset=RCH_DEFAULT,
+  bool Unquote=false,
+  bool SkipComments=false,
+  bool ExpandEnvStr=false
+);
+
+RAR_CHARSET DetectTextEncoding(const byte *Data,size_t DataSize);
+
+#endif
diff --git a/third_party/unrar/src/find.cpp b/third_party/unrar/src/find.cpp
new file mode 100644
index 0000000..812af6b
--- /dev/null
+++ b/third_party/unrar/src/find.cpp
@@ -0,0 +1,218 @@
+#include "rar.hpp"
+
+FindFile::FindFile()
+{
+  *FindMask=0;
+  FirstCall=true;
+#ifdef _WIN_ALL
+  hFind=INVALID_HANDLE_VALUE;
+#else
+  dirp=NULL;
+#endif
+}
+
+
+FindFile::~FindFile()
+{
+#ifdef _WIN_ALL
+  if (hFind!=INVALID_HANDLE_VALUE)
+    FindClose(hFind);
+#else
+  if (dirp!=NULL)
+    closedir(dirp);
+#endif
+}
+
+
+void FindFile::SetMask(const wchar *Mask)
+{
+  wcscpy(FindMask,Mask);
+  FirstCall=true;
+}
+
+
+bool FindFile::Next(FindData *fd,bool GetSymLink)
+{
+  fd->Error=false;
+  if (*FindMask==0)
+    return false;
+#ifdef _WIN_ALL
+  if (FirstCall)
+  {
+    if ((hFind=Win32Find(INVALID_HANDLE_VALUE,FindMask,fd))==INVALID_HANDLE_VALUE)
+      return false;
+  }
+  else
+    if (Win32Find(hFind,FindMask,fd)==INVALID_HANDLE_VALUE)
+      return false;
+#else
+  if (FirstCall)
+  {
+    wchar DirName[NM];
+    wcsncpyz(DirName,FindMask,ASIZE(DirName));
+    RemoveNameFromPath(DirName);
+    if (*DirName==0)
+      wcscpy(DirName,L".");
+    char DirNameA[NM];
+    WideToChar(DirName,DirNameA,ASIZE(DirNameA));
+    if ((dirp=opendir(DirNameA))==NULL)
+    {
+      fd->Error=(errno!=ENOENT);
+      return false;
+    }
+  }
+  while (1)
+  {
+    struct dirent *ent=readdir(dirp);
+    if (ent==NULL)
+      return false;
+    if (strcmp(ent->d_name,".")==0 || strcmp(ent->d_name,"..")==0)
+      continue;
+    wchar Name[NM];
+    if (!CharToWide(ent->d_name,Name,ASIZE(Name)))
+      uiMsg(UIERROR_INVALIDNAME,UINULL,Name);
+
+    if (CmpName(FindMask,Name,MATCH_NAMES))
+    {
+      wchar FullName[NM];
+      wcscpy(FullName,FindMask);
+      *PointToName(FullName)=0;
+      if (wcslen(FullName)+wcslen(Name)>=ASIZE(FullName)-1)
+      {
+        uiMsg(UIERROR_PATHTOOLONG,FullName,L"",Name);
+        return false;
+      }
+      wcscat(FullName,Name);
+      if (!FastFind(FullName,fd,GetSymLink))
+      {
+        ErrHandler.OpenErrorMsg(FullName);
+        continue;
+      }
+      wcscpy(fd->Name,FullName);
+      break;
+    }
+  }
+#endif
+  fd->Flags=0;
+  fd->IsDir=IsDir(fd->FileAttr);
+  fd->IsLink=IsLink(fd->FileAttr);
+
+  FirstCall=false;
+  wchar *NameOnly=PointToName(fd->Name);
+  if (wcscmp(NameOnly,L".")==0 || wcscmp(NameOnly,L"..")==0)
+    return Next(fd);
+  return true;
+}
+
+
+bool FindFile::FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink)
+{
+  fd->Error=false;
+#ifndef _UNIX
+  if (IsWildcard(FindMask))
+    return false;
+#endif    
+#ifdef _WIN_ALL
+  HANDLE hFind=Win32Find(INVALID_HANDLE_VALUE,FindMask,fd);
+  if (hFind==INVALID_HANDLE_VALUE)
+    return false;
+  FindClose(hFind);
+#else
+  char FindMaskA[NM];
+  WideToChar(FindMask,FindMaskA,ASIZE(FindMaskA));
+
+  struct stat st;
+  if (GetSymLink)
+  {
+#ifdef SAVE_LINKS
+    if (lstat(FindMaskA,&st)!=0)
+#else
+    if (stat(FindMaskA,&st)!=0)
+#endif
+    {
+      fd->Error=(errno!=ENOENT);
+      return false;
+    }
+  }
+  else
+    if (stat(FindMaskA,&st)!=0)
+    {
+      fd->Error=(errno!=ENOENT);
+      return false;
+    }
+  fd->FileAttr=st.st_mode;
+  fd->Size=st.st_size;
+
+#ifdef UNIX_TIME_NS
+  fd->mtime.SetUnixNS(st.st_mtim.tv_sec*(uint64)1000000000+st.st_mtim.tv_nsec);
+  fd->atime.SetUnixNS(st.st_atim.tv_sec*(uint64)1000000000+st.st_atim.tv_nsec);
+  fd->ctime.SetUnixNS(st.st_ctim.tv_sec*(uint64)1000000000+st.st_ctim.tv_nsec);
+#else
+  fd->mtime.SetUnix(st.st_mtime);
+  fd->atime.SetUnix(st.st_atime);
+  fd->ctime.SetUnix(st.st_ctime);
+#endif
+
+  wcsncpyz(fd->Name,FindMask,ASIZE(fd->Name));
+#endif
+  fd->Flags=0;
+  fd->IsDir=IsDir(fd->FileAttr);
+  fd->IsLink=IsLink(fd->FileAttr);
+
+  return true;
+}
+
+
+#ifdef _WIN_ALL
+HANDLE FindFile::Win32Find(HANDLE hFind,const wchar *Mask,FindData *fd)
+{
+  WIN32_FIND_DATA FindData;
+  if (hFind==INVALID_HANDLE_VALUE)
+  {
+    hFind=FindFirstFile(Mask,&FindData);
+    if (hFind==INVALID_HANDLE_VALUE)
+    {
+      wchar LongMask[NM];
+      if (GetWinLongPath(Mask,LongMask,ASIZE(LongMask)))
+        hFind=FindFirstFile(LongMask,&FindData);
+    }
+    if (hFind==INVALID_HANDLE_VALUE)
+    {
+      int SysErr=GetLastError();
+      // We must not issue an error for "file not found" and "path not found",
+      // because it is normal to not find anything for wildcard mask when
+      // archiving. Also searching for non-existent file is normal in some
+      // other modules, like WinRAR scanning for winrar_theme_description.txt
+      // to check if any themes are available.
+      fd->Error=SysErr!=ERROR_FILE_NOT_FOUND && 
+                SysErr!=ERROR_PATH_NOT_FOUND &&
+                SysErr!=ERROR_NO_MORE_FILES;
+    }
+  }
+  else
+    if (!FindNextFile(hFind,&FindData))
+    {
+      hFind=INVALID_HANDLE_VALUE;
+      fd->Error=GetLastError()!=ERROR_NO_MORE_FILES;
+    }
+
+  if (hFind!=INVALID_HANDLE_VALUE)
+  {
+    wcsncpyz(fd->Name,Mask,ASIZE(fd->Name));
+    SetName(fd->Name,FindData.cFileName,ASIZE(fd->Name));
+    fd->Size=INT32TO64(FindData.nFileSizeHigh,FindData.nFileSizeLow);
+    fd->FileAttr=FindData.dwFileAttributes;
+    fd->ftCreationTime=FindData.ftCreationTime;
+    fd->ftLastAccessTime=FindData.ftLastAccessTime;
+    fd->ftLastWriteTime=FindData.ftLastWriteTime;
+    fd->mtime.SetWinFT(&FindData.ftLastWriteTime);
+    fd->ctime.SetWinFT(&FindData.ftCreationTime);
+    fd->atime.SetWinFT(&FindData.ftLastAccessTime);
+
+
+  }
+  fd->Flags=0;
+  return hFind;
+}
+#endif
+
diff --git a/third_party/unrar/src/find.hpp b/third_party/unrar/src/find.hpp
new file mode 100644
index 0000000..250637f
--- /dev/null
+++ b/third_party/unrar/src/find.hpp
@@ -0,0 +1,49 @@
+#ifndef _RAR_FINDDATA_
+#define _RAR_FINDDATA_
+
+enum FINDDATA_FLAGS {
+  FDDF_SECONDDIR=1  // Second encounter of same directory in SCAN_GETDIRSTWICE ScanTree mode.
+};
+
+struct FindData
+{
+  wchar Name[NM];
+  uint64 Size;
+  uint FileAttr;
+  bool IsDir;
+  bool IsLink;
+  RarTime mtime;
+  RarTime ctime;
+  RarTime atime;
+#ifdef _WIN_ALL
+  FILETIME ftCreationTime; 
+  FILETIME ftLastAccessTime; 
+  FILETIME ftLastWriteTime; 
+#endif
+  uint Flags;
+  bool Error;
+};
+
+class FindFile
+{
+  private:
+#ifdef _WIN_ALL
+    static HANDLE Win32Find(HANDLE hFind,const wchar *Mask,FindData *fd);
+#endif
+
+    wchar FindMask[NM];
+    bool FirstCall;
+#ifdef _WIN_ALL
+    HANDLE hFind;
+#else
+    DIR *dirp;
+#endif
+  public:
+    FindFile();
+    ~FindFile();
+    void SetMask(const wchar *Mask);
+    bool Next(FindData *fd,bool GetSymLink=false);
+    static bool FastFind(const wchar *FindMask,FindData *fd,bool GetSymLink=false);
+};
+
+#endif
diff --git a/third_party/unrar/src/getbits.cpp b/third_party/unrar/src/getbits.cpp
new file mode 100644
index 0000000..e4db269
--- /dev/null
+++ b/third_party/unrar/src/getbits.cpp
@@ -0,0 +1,52 @@
+#include "rar.hpp"
+
+BitInput::BitInput(bool AllocBuffer)
+{
+  ExternalBuffer=false;
+  if (AllocBuffer)
+  {
+    // getbits32 attempts to read data from InAddr, ... InAddr+3 positions.
+    // So let's allocate 3 additional bytes for situation, when we need to
+    // read only 1 byte from the last position of buffer and avoid a crash
+    // from access to next 3 bytes, which contents we do not need.
+    size_t BufSize=MAX_SIZE+3;
+    InBuf=new byte[BufSize];
+
+    // Ensure that we get predictable results when accessing bytes in area
+    // not filled with read data.
+    memset(InBuf,0,BufSize);
+  }
+  else
+    InBuf=NULL;
+}
+
+
+BitInput::~BitInput()
+{
+  if (!ExternalBuffer)
+    delete[] InBuf;
+}
+
+
+void BitInput::faddbits(uint Bits)
+{
+  // Function wrapped version of inline addbits to save code size.
+  addbits(Bits);
+}
+
+
+uint BitInput::fgetbits()
+{
+  // Function wrapped version of inline getbits to save code size.
+  return getbits();
+}
+
+
+void BitInput::SetExternalBuffer(byte *Buf)
+{
+  if (InBuf!=NULL && !ExternalBuffer)
+    delete[] InBuf;
+  InBuf=Buf;
+  ExternalBuffer=true;
+}
+
diff --git a/third_party/unrar/src/getbits.hpp b/third_party/unrar/src/getbits.hpp
new file mode 100644
index 0000000..2e151da
--- /dev/null
+++ b/third_party/unrar/src/getbits.hpp
@@ -0,0 +1,68 @@
+#ifndef _RAR_GETBITS_
+#define _RAR_GETBITS_
+
+class BitInput
+{
+  public:
+    enum BufferSize {MAX_SIZE=0x8000}; // Size of input buffer.
+
+    int InAddr; // Curent byte position in the buffer.
+    int InBit;  // Current bit position in the current byte.
+
+    bool ExternalBuffer;
+  public:
+    BitInput(bool AllocBuffer);
+    ~BitInput();
+
+    byte *InBuf; // Dynamically allocated input buffer.
+
+    void InitBitInput()
+    {
+      InAddr=InBit=0;
+    }
+    
+    // Move forward by 'Bits' bits.
+    void addbits(uint Bits)
+    {
+      Bits+=InBit;
+      InAddr+=Bits>>3;
+      InBit=Bits&7;
+    }
+    
+    // Return 16 bits from current position in the buffer.
+    // Bit at (InAddr,InBit) has the highest position in returning data.
+    uint getbits()
+    {
+      uint BitField=(uint)InBuf[InAddr] << 16;
+      BitField|=(uint)InBuf[InAddr+1] << 8;
+      BitField|=(uint)InBuf[InAddr+2];
+      BitField >>= (8-InBit);
+      return BitField & 0xffff;
+    }
+
+    // Return 32 bits from current position in the buffer.
+    // Bit at (InAddr,InBit) has the highest position in returning data.
+    uint getbits32()
+    {
+      uint BitField=(uint)InBuf[InAddr] << 24;
+      BitField|=(uint)InBuf[InAddr+1] << 16;
+      BitField|=(uint)InBuf[InAddr+2] << 8;
+      BitField|=(uint)InBuf[InAddr+3];
+      BitField <<= InBit;
+      BitField|=(uint)InBuf[InAddr+4] >> (8-InBit);
+      return BitField & 0xffffffff;
+    }
+    
+    void faddbits(uint Bits);
+    uint fgetbits();
+    
+    // Check if buffer has enough space for IncPtr bytes. Returns 'true'
+    // if buffer will be overflown.
+    bool Overflow(uint IncPtr) 
+    {
+      return InAddr+IncPtr>=MAX_SIZE;
+    }
+
+    void SetExternalBuffer(byte *Buf);
+};
+#endif
diff --git a/third_party/unrar/src/global.cpp b/third_party/unrar/src/global.cpp
new file mode 100644
index 0000000..3975813a
--- /dev/null
+++ b/third_party/unrar/src/global.cpp
@@ -0,0 +1,7 @@
+#define INCLUDEGLOBAL
+
+#if defined(__BORLANDC__) || defined(_MSC_VER)
+#pragma hdrstop
+#endif
+
+#include "rar.hpp"
diff --git a/third_party/unrar/src/global.hpp b/third_party/unrar/src/global.hpp
new file mode 100644
index 0000000..35c6cf9
--- /dev/null
+++ b/third_party/unrar/src/global.hpp
@@ -0,0 +1,14 @@
+#ifndef _RAR_GLOBAL_
+#define _RAR_GLOBAL_
+
+#ifdef INCLUDEGLOBAL
+  #define EXTVAR
+#else
+  #define EXTVAR extern
+#endif
+
+EXTVAR ErrorHandler ErrHandler;
+
+
+
+#endif
diff --git a/third_party/unrar/src/hardlinks.cpp b/third_party/unrar/src/hardlinks.cpp
new file mode 100644
index 0000000..cf0b25a
--- /dev/null
+++ b/third_party/unrar/src/hardlinks.cpp
@@ -0,0 +1,34 @@
+bool ExtractHardlink(wchar *NameNew,wchar *NameExisting,size_t NameExistingSize)
+{
+  SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives.
+
+  if (!FileExist(NameExisting))
+    return false;
+  CreatePath(NameNew,true);
+
+#ifdef _WIN_ALL
+  bool Success=CreateHardLink(NameNew,NameExisting,NULL)!=0;
+  if (!Success)
+  {
+    uiMsg(UIERROR_HLINKCREATE,NameNew);
+    ErrHandler.SysErrMsg();
+    ErrHandler.SetErrorCode(RARX_CREATE);
+  }
+  return Success;
+#elif defined(_UNIX)
+  char NameExistingA[NM],NameNewA[NM];
+  WideToChar(NameExisting,NameExistingA,ASIZE(NameExistingA));
+  WideToChar(NameNew,NameNewA,ASIZE(NameNewA));
+  bool Success=link(NameExistingA,NameNewA)==0;
+  if (!Success)
+  {
+    uiMsg(UIERROR_HLINKCREATE,NameNew);
+    ErrHandler.SysErrMsg();
+    ErrHandler.SetErrorCode(RARX_CREATE);
+  }
+  return Success;
+#else
+  return false;
+#endif
+}
+
diff --git a/third_party/unrar/src/hash.cpp b/third_party/unrar/src/hash.cpp
new file mode 100644
index 0000000..42791f4f
--- /dev/null
+++ b/third_party/unrar/src/hash.cpp
@@ -0,0 +1,135 @@
+#include "rar.hpp"
+
+void HashValue::Init(HASH_TYPE Type)
+{
+  HashValue::Type=Type;
+
+  // Zero length data CRC32 is 0. It is important to set it when creating
+  // headers with no following data like directories or symlinks.
+  if (Type==HASH_RAR14 || Type==HASH_CRC32)
+    CRC32=0;
+  if (Type==HASH_BLAKE2)
+  {
+    // dd0e891776933f43c7d032b08a917e25741f8aa9a12c12e1cac8801500f2ca4f
+    // is BLAKE2sp hash of empty data. We init the structure to this value,
+    // so if we create a file or service header with no following data like
+    // "file copy" or "symlink", we set the checksum to proper value avoiding
+    // additional header type or size checks when extracting.
+    static byte EmptyHash[32]={
+      0xdd, 0x0e, 0x89, 0x17, 0x76, 0x93, 0x3f, 0x43,
+      0xc7, 0xd0, 0x32, 0xb0, 0x8a, 0x91, 0x7e, 0x25,
+      0x74, 0x1f, 0x8a, 0xa9, 0xa1, 0x2c, 0x12, 0xe1,
+      0xca, 0xc8, 0x80, 0x15, 0x00, 0xf2, 0xca, 0x4f
+    };
+    memcpy(Digest,EmptyHash,sizeof(Digest));
+  }
+}
+
+
+bool HashValue::operator == (const HashValue &cmp)
+{
+  if (Type==HASH_NONE || cmp.Type==HASH_NONE)
+    return true;
+  if (Type==HASH_RAR14 && cmp.Type==HASH_RAR14 || 
+      Type==HASH_CRC32 && cmp.Type==HASH_CRC32)
+    return CRC32==cmp.CRC32;
+  if (Type==HASH_BLAKE2 && cmp.Type==HASH_BLAKE2)
+    return memcmp(Digest,cmp.Digest,sizeof(Digest))==0;
+  return false;
+}
+
+
+DataHash::DataHash()
+{
+  blake2ctx=NULL;
+  HashType=HASH_NONE;
+#ifdef RAR_SMP
+  ThPool=NULL;
+  MaxThreads=0;
+#endif
+}
+
+
+DataHash::~DataHash()
+{
+#ifdef RAR_SMP
+  DestroyThreadPool(ThPool);
+#endif
+  cleandata(&CurCRC32, sizeof(CurCRC32));
+  if (blake2ctx!=NULL)
+  {
+    cleandata(blake2ctx, sizeof(blake2sp_state));
+    delete blake2ctx;
+  }
+}
+
+
+void DataHash::Init(HASH_TYPE Type,uint MaxThreads)
+{
+  if (blake2ctx==NULL)
+    blake2ctx=new blake2sp_state;
+  HashType=Type;
+  if (Type==HASH_RAR14)
+    CurCRC32=0;
+  if (Type==HASH_CRC32)
+    CurCRC32=0xffffffff; // Initial CRC32 value.
+  if (Type==HASH_BLAKE2)
+    blake2sp_init(blake2ctx);
+#ifdef RAR_SMP
+  DataHash::MaxThreads=Min(MaxThreads,MaxHashThreads);
+#endif
+}
+
+
+void DataHash::Update(const void *Data,size_t DataSize)
+{
+#ifndef SFX_MODULE
+  if (HashType==HASH_RAR14)
+    CurCRC32=Checksum14((ushort)CurCRC32,Data,DataSize);
+#endif
+  if (HashType==HASH_CRC32)
+    CurCRC32=CRC32(CurCRC32,Data,DataSize);
+
+  if (HashType==HASH_BLAKE2)
+  {
+#ifdef RAR_SMP
+    if (MaxThreads>1 && ThPool==NULL)
+      ThPool=CreateThreadPool();
+    blake2ctx->ThPool=ThPool;
+    blake2ctx->MaxThreads=MaxThreads;
+#endif
+    blake2sp_update( blake2ctx, (byte *)Data, DataSize);
+  }
+}
+
+
+void DataHash::Result(HashValue *Result)
+{
+  Result->Type=HashType;
+  if (HashType==HASH_RAR14)
+    Result->CRC32=CurCRC32;
+  if (HashType==HASH_CRC32)
+    Result->CRC32=CurCRC32^0xffffffff;
+  if (HashType==HASH_BLAKE2)
+  {
+    // Preserve the original context, so we can continue hashing if necessary.
+    blake2sp_state res=*blake2ctx;
+    blake2sp_final(&res,Result->Digest);
+  }
+}
+
+
+uint DataHash::GetCRC32()
+{
+  return HashType==HASH_CRC32 ? CurCRC32^0xffffffff : 0;
+}
+
+
+bool DataHash::Cmp(HashValue *CmpValue,byte *Key)
+{
+  HashValue Final;
+  Result(&Final);
+  if (Key!=NULL)
+    ConvertHashToMAC(&Final,Key);
+  return Final==*CmpValue;
+}
diff --git a/third_party/unrar/src/hash.hpp b/third_party/unrar/src/hash.hpp
new file mode 100644
index 0000000..b7d879f
--- /dev/null
+++ b/third_party/unrar/src/hash.hpp
@@ -0,0 +1,52 @@
+#ifndef _RAR_DATAHASH_
+#define _RAR_DATAHASH_
+
+enum HASH_TYPE {HASH_NONE,HASH_RAR14,HASH_CRC32,HASH_BLAKE2};
+
+struct HashValue
+{
+  void Init(HASH_TYPE Type);
+  bool operator == (const HashValue &cmp);
+  bool operator != (const HashValue &cmp) {return !(*this==cmp);}
+
+  HASH_TYPE Type;
+  union
+  {
+    uint CRC32;
+    byte Digest[SHA256_DIGEST_SIZE];
+  };
+};
+
+
+#ifdef RAR_SMP
+class ThreadPool;
+class DataHash;
+#endif
+
+
+class DataHash
+{
+  private:
+    HASH_TYPE HashType;
+    uint CurCRC32;
+    blake2sp_state *blake2ctx;
+
+#ifdef RAR_SMP
+    ThreadPool *ThPool;
+
+    uint MaxThreads;
+    // Upper limit for maximum threads to prevent wasting threads in pool.
+    static const uint MaxHashThreads=8;
+#endif
+  public:
+    DataHash();
+    ~DataHash();
+    void Init(HASH_TYPE Type,uint MaxThreads);
+    void Update(const void *Data,size_t DataSize);
+    void Result(HashValue *Result);
+    uint GetCRC32();
+    bool Cmp(HashValue *CmpValue,byte *Key);
+    HASH_TYPE Type() {return HashType;}
+};
+
+#endif
diff --git a/third_party/unrar/src/headers.cpp b/third_party/unrar/src/headers.cpp
new file mode 100644
index 0000000..b042dc3
--- /dev/null
+++ b/third_party/unrar/src/headers.cpp
@@ -0,0 +1,61 @@
+#include "rar.hpp"
+
+void FileHeader::Reset(size_t SubDataSize)
+{
+  SubData.Alloc(SubDataSize);
+  BaseBlock::Reset();
+  FileHash.Init(HASH_NONE);
+  mtime.Reset();
+  atime.Reset();
+  ctime.Reset();
+  SplitBefore=false;
+  SplitAfter=false;
+
+  UnknownUnpSize=0;
+
+  SubFlags=0; // Important for RAR 3.0 subhead.
+  
+  CryptMethod=CRYPT_NONE;
+  Encrypted=false;
+  SaltSet=false;
+  UsePswCheck=false;
+  UseHashKey=false;
+  Lg2Count=0;
+
+  Solid=false;
+  Dir=false;
+  WinSize=0;
+  Inherited=false;
+  SubBlock=false;
+  CommentInHeader=false;
+  Version=false;
+  LargeFile=false;
+
+  RedirType=FSREDIR_NONE;
+  DirTarget=false;
+  UnixOwnerSet=false;
+}
+
+
+FileHeader& FileHeader::operator = (FileHeader &hd)
+{
+  SubData.Reset();
+  memcpy(this,&hd,sizeof(*this));
+  SubData.CleanData();
+  SubData=hd.SubData;
+  return *this;
+}
+
+
+void MainHeader::Reset()
+{
+  HighPosAV=0;
+  PosAV=0;
+  CommentInHeader=false;
+  PackComment=false;
+  Locator=false;
+  QOpenOffset=0;
+  QOpenMaxSize=0;
+  RROffset=0;
+  RRMaxSize=0;
+}
diff --git a/third_party/unrar/src/headers.hpp b/third_party/unrar/src/headers.hpp
new file mode 100644
index 0000000..9486478
--- /dev/null
+++ b/third_party/unrar/src/headers.hpp
@@ -0,0 +1,380 @@
+#ifndef _RAR_HEADERS_
+#define _RAR_HEADERS_
+
+#define  SIZEOF_MARKHEAD3        7 // Size of RAR 4.x archive mark header.
+#define  SIZEOF_MAINHEAD14       7 // Size of RAR 1.4 main archive header.
+#define  SIZEOF_MAINHEAD3       13 // Size of RAR 4.x main archive header.
+#define  SIZEOF_FILEHEAD14      21 // Size of RAR 1.4 file header.
+#define  SIZEOF_FILEHEAD3       32 // Size of RAR 3.0 file header.
+#define  SIZEOF_SHORTBLOCKHEAD   7
+#define  SIZEOF_LONGBLOCKHEAD   11
+#define  SIZEOF_SUBBLOCKHEAD    14
+#define  SIZEOF_COMMHEAD        13
+#define  SIZEOF_PROTECTHEAD     26
+#define  SIZEOF_AVHEAD          14
+#define  SIZEOF_SIGNHEAD        15
+#define  SIZEOF_UOHEAD          18
+#define  SIZEOF_MACHEAD         22
+#define  SIZEOF_EAHEAD          24
+#define  SIZEOF_BEEAHEAD        24
+#define  SIZEOF_STREAMHEAD      26
+
+#define  VER_PACK               29
+#define  VER_PACK5              50 // It is stored as 0, but we subtract 50 when saving an archive.
+#define  VER_UNPACK             29
+#define  VER_UNPACK5            50 // It is stored as 0, but we add 50 when reading an archive.
+
+#define  MHD_VOLUME         0x0001U
+
+// Old style main archive comment embed into main archive header. Must not
+// be used in new archives anymore.
+#define  MHD_COMMENT        0x0002U
+
+#define  MHD_LOCK           0x0004U
+#define  MHD_SOLID          0x0008U
+#define  MHD_PACK_COMMENT   0x0010U
+#define  MHD_NEWNUMBERING   0x0010U
+#define  MHD_AV             0x0020U
+#define  MHD_PROTECT        0x0040U
+#define  MHD_PASSWORD       0x0080U
+#define  MHD_FIRSTVOLUME    0x0100U
+
+#define  LHD_SPLIT_BEFORE   0x0001U
+#define  LHD_SPLIT_AFTER    0x0002U
+#define  LHD_PASSWORD       0x0004U
+
+// Old style file comment embed into file header. Must not be used
+// in new archives anymore.
+#define  LHD_COMMENT        0x0008U
+
+// For non-file subheaders it denotes 'subblock having a parent file' flag.
+#define  LHD_SOLID          0x0010U
+
+
+#define  LHD_WINDOWMASK     0x00e0U
+#define  LHD_WINDOW64       0x0000U
+#define  LHD_WINDOW128      0x0020U
+#define  LHD_WINDOW256      0x0040U
+#define  LHD_WINDOW512      0x0060U
+#define  LHD_WINDOW1024     0x0080U
+#define  LHD_WINDOW2048     0x00a0U
+#define  LHD_WINDOW4096     0x00c0U
+#define  LHD_DIRECTORY      0x00e0U
+
+#define  LHD_LARGE          0x0100U
+#define  LHD_UNICODE        0x0200U
+#define  LHD_SALT           0x0400U
+#define  LHD_VERSION        0x0800U
+#define  LHD_EXTTIME        0x1000U
+
+#define  SKIP_IF_UNKNOWN    0x4000U
+#define  LONG_BLOCK         0x8000U
+
+#define  EARC_NEXT_VOLUME   0x0001U // Not last volume.
+#define  EARC_DATACRC       0x0002U // Store CRC32 of RAR archive (now is used only in volumes).
+#define  EARC_REVSPACE      0x0004U // Reserve space for end of REV file 7 byte record.
+#define  EARC_VOLNUMBER     0x0008U // Store a number of current volume.
+
+enum HEADER_TYPE {
+  // RAR 5.0 header types.
+  HEAD_MARK=0x00, HEAD_MAIN=0x01, HEAD_FILE=0x02, HEAD_SERVICE=0x03,
+  HEAD_CRYPT=0x04, HEAD_ENDARC=0x05, HEAD_UNKNOWN=0xff,
+
+  // RAR 1.5 - 4.x header types.
+  HEAD3_MARK=0x72,HEAD3_MAIN=0x73,HEAD3_FILE=0x74,HEAD3_CMT=0x75,
+  HEAD3_AV=0x76,HEAD3_OLDSERVICE=0x77,HEAD3_PROTECT=0x78,HEAD3_SIGN=0x79,
+  HEAD3_SERVICE=0x7a,HEAD3_ENDARC=0x7b
+};
+
+enum { EA_HEAD=0x100,UO_HEAD=0x101,MAC_HEAD=0x102,BEEA_HEAD=0x103,
+       NTACL_HEAD=0x104,STREAM_HEAD=0x105 };
+
+
+// Internal implementation, depends on archive format version.
+enum HOST_SYSTEM {
+  // RAR 5.0 host OS
+  HOST5_WINDOWS=0,HOST5_UNIX=1,
+
+  // RAR 3.0 host OS.
+  HOST_MSDOS=0,HOST_OS2=1,HOST_WIN32=2,HOST_UNIX=3,HOST_MACOS=4,
+  HOST_BEOS=5,HOST_MAX
+};
+
+// Unified archive format independent implementation.
+enum HOST_SYSTEM_TYPE {
+  HSYS_WINDOWS, HSYS_UNIX, HSYS_UNKNOWN
+};
+
+
+// We also use these values in extra field, so do not modify them.
+enum FILE_SYSTEM_REDIRECT {
+  FSREDIR_NONE=0, FSREDIR_UNIXSYMLINK, FSREDIR_WINSYMLINK, FSREDIR_JUNCTION,
+  FSREDIR_HARDLINK, FSREDIR_FILECOPY
+};
+
+
+#define SUBHEAD_TYPE_CMT      L"CMT"
+#define SUBHEAD_TYPE_QOPEN    L"QO"
+#define SUBHEAD_TYPE_ACL      L"ACL"
+#define SUBHEAD_TYPE_STREAM   L"STM"
+#define SUBHEAD_TYPE_UOWNER   L"UOW"
+#define SUBHEAD_TYPE_AV       L"AV"
+#define SUBHEAD_TYPE_RR       L"RR"
+#define SUBHEAD_TYPE_OS2EA    L"EA2"
+
+/* new file inherits a subblock when updating a host file */
+#define SUBHEAD_FLAGS_INHERITED    0x80000000
+
+#define SUBHEAD_FLAGS_CMT_UNICODE  0x00000001
+
+
+struct MarkHeader
+{
+  byte Mark[8];
+
+  // Following fields are virtual and not present in real blocks.
+  uint HeadSize;
+};
+
+
+struct BaseBlock
+{
+  uint HeadCRC;  // 'ushort' for RAR 1.5.
+  HEADER_TYPE HeaderType; // 1 byte for RAR 1.5.
+  uint Flags;    // 'ushort' for RAR 1.5.
+  uint HeadSize; // 'ushort' for RAR 1.5, up to 2 MB for RAR 5.0.
+
+  bool SkipIfUnknown;
+
+  void Reset()
+  {
+    SkipIfUnknown=false;
+  }
+};
+
+
+struct BlockHeader:BaseBlock
+{
+  uint DataSize;
+};
+
+
+struct MainHeader:BaseBlock
+{
+  ushort HighPosAV;
+  uint PosAV;
+  bool CommentInHeader;
+  bool PackComment; // For RAR 1.4 archive format only.
+  bool Locator;
+  uint64 QOpenOffset;  // Offset of quick list record.
+  uint64 QOpenMaxSize; // Maximum size of QOpen offset in locator extra field.
+  uint64 RROffset;     // Offset of recovery record.
+  uint64 RRMaxSize;    // Maximum size of RR offset in locator extra field.
+  void Reset();
+};
+
+
+struct FileHeader:BlockHeader
+{
+  byte HostOS;
+  byte UnpVer;
+  byte Method;
+  union {
+    uint FileAttr;
+    uint SubFlags;
+  };
+  wchar FileName[NM];
+
+  Array<byte> SubData;
+
+  RarTime mtime;
+  RarTime ctime;
+  RarTime atime;
+
+  int64 PackSize;
+  int64 UnpSize;
+  int64 MaxSize; // Reserve size bytes for vint of this size.
+
+  HashValue FileHash;
+
+  uint FileFlags;
+
+  bool SplitBefore;
+  bool SplitAfter;
+
+  bool UnknownUnpSize;
+
+  bool Encrypted;
+  CRYPT_METHOD CryptMethod;
+  bool SaltSet;
+  byte Salt[SIZE_SALT50];
+  byte InitV[SIZE_INITV];
+  bool UsePswCheck;
+  byte PswCheck[SIZE_PSWCHECK];
+
+  // Use HMAC calculated from HashKey and checksum instead of plain checksum.
+  bool UseHashKey;
+
+  // Key to convert checksum to HMAC. Derived from password with PBKDF2
+  // using additional iterations.
+  byte HashKey[SHA256_DIGEST_SIZE];
+
+  uint Lg2Count; // Log2 of PBKDF2 repetition count.
+
+  bool Solid;
+  bool Dir;
+  bool CommentInHeader; // RAR 2.0 file comment.
+  bool Version;   // name.ext;ver file name containing the version number.
+  size_t WinSize;
+  bool Inherited; // New file inherits a subblock when updating a host file (for subblocks only).
+
+  // 'true' if file sizes use 8 bytes instead of 4. Not used in RAR 5.0.
+  bool LargeFile;
+  
+  // 'true' for HEAD_SERVICE block, which is a child of preceding file block.
+  // RAR 4.x uses 'solid' flag to indicate child subheader blocks in archives.
+  bool SubBlock;
+
+  HOST_SYSTEM_TYPE HSType;
+
+  FILE_SYSTEM_REDIRECT RedirType;
+  wchar RedirName[NM];
+  bool DirTarget;
+
+  bool UnixOwnerSet,UnixOwnerNumeric,UnixGroupNumeric;
+  char UnixOwnerName[256],UnixGroupName[256];
+#ifdef _UNIX
+  uid_t UnixOwnerID;
+  gid_t UnixGroupID;
+#else // Need these Unix fields in Windows too for 'list' command.
+  uint UnixOwnerID;
+  uint UnixGroupID;
+#endif
+
+  void Reset(size_t SubDataSize=0);
+
+  bool CmpName(const wchar *Name)
+  {
+    return(wcscmp(FileName,Name)==0);
+  }
+
+  FileHeader& operator = (FileHeader &hd);
+};
+
+
+struct EndArcHeader:BaseBlock
+{
+  // Optional CRC32 of entire archive up to start of EndArcHeader block.
+  // Present in RAR 4.x archives if EARC_DATACRC flag is set.
+  uint ArcDataCRC;  
+  
+  uint VolNumber; // Optional number of current volume.
+
+  // 7 additional zero bytes can be stored here if EARC_REVSPACE is set.
+
+  bool NextVolume; // Not last volume.
+  bool DataCRC;
+  bool RevSpace;
+  bool StoreVolNumber;
+  void Reset()
+  {
+    BaseBlock::Reset();
+    NextVolume=false;
+    DataCRC=false;
+    RevSpace=false;
+    StoreVolNumber=false;
+  }
+};
+
+
+struct CryptHeader:BaseBlock
+{
+  bool UsePswCheck;
+  uint Lg2Count; // Log2 of PBKDF2 repetition count.
+  byte Salt[SIZE_SALT50];
+  byte PswCheck[SIZE_PSWCHECK];
+};
+
+
+// SubBlockHeader and its successors were used in RAR 2.x format.
+// RAR 4.x uses FileHeader with HEAD_SERVICE HeaderType for subblocks.
+struct SubBlockHeader:BlockHeader
+{
+  ushort SubType;
+  byte Level;
+};
+
+
+struct CommentHeader:BaseBlock
+{
+  ushort UnpSize;
+  byte UnpVer;
+  byte Method;
+  ushort CommCRC;
+};
+
+
+struct ProtectHeader:BlockHeader
+{
+  byte Version;
+  ushort RecSectors;
+  uint TotalBlocks;
+  byte Mark[8];
+};
+
+
+struct AVHeader:BaseBlock
+{
+  byte UnpVer;
+  byte Method;
+  byte AVVer;
+  uint AVInfoCRC;
+};
+
+
+struct SignHeader:BaseBlock
+{
+  uint CreationTime;
+  ushort ArcNameSize;
+  ushort UserNameSize;
+};
+
+
+struct UnixOwnersHeader:SubBlockHeader
+{
+  ushort OwnerNameSize;
+  ushort GroupNameSize;
+/* dummy */
+  char OwnerName[256];
+  char GroupName[256];
+};
+
+
+struct EAHeader:SubBlockHeader
+{
+  uint UnpSize;
+  byte UnpVer;
+  byte Method;
+  uint EACRC;
+};
+
+
+struct StreamHeader:SubBlockHeader
+{
+  uint UnpSize;
+  byte UnpVer;
+  byte Method;
+  uint StreamCRC;
+  ushort StreamNameSize;
+  char StreamName[260];
+};
+
+
+struct MacFInfoHeader:SubBlockHeader
+{
+  uint fileType;
+  uint fileCreator;
+};
+
+
+#endif
diff --git a/third_party/unrar/src/headers5.hpp b/third_party/unrar/src/headers5.hpp
new file mode 100644
index 0000000..9ea8d97
--- /dev/null
+++ b/third_party/unrar/src/headers5.hpp
@@ -0,0 +1,100 @@
+#ifndef _RAR_HEADERS5_
+#define _RAR_HEADERS5_
+
+#define  SIZEOF_MARKHEAD5        8  // RAR 5.0 signature length.
+#define  SIZEOF_SHORTBLOCKHEAD5  7  // Smallest RAR 5.0 block size.
+
+// RAR 5.0 block flags common for all blocks.
+
+// Additional extra area is present in the end of block header.
+#define HFL_EXTRA           0x0001
+// Additional data area is present in the end of block header.
+#define HFL_DATA            0x0002
+// Unknown blocks with this flag must be skipped when updating an archive.
+#define HFL_SKIPIFUNKNOWN   0x0004
+// Data area of this block is continuing from previous volume.
+#define HFL_SPLITBEFORE     0x0008
+// Data area of this block is continuing in next volume.
+#define HFL_SPLITAFTER      0x0010
+// Block depends on preceding file block.
+#define HFL_CHILD           0x0020
+// Preserve a child block if host is modified.
+#define HFL_INHERITED       0x0040
+
+// RAR 5.0 main archive header specific flags.
+#define MHFL_VOLUME         0x0001 // Volume.
+#define MHFL_VOLNUMBER      0x0002 // Volume number field is present. True for all volumes except first.
+#define MHFL_SOLID          0x0004 // Solid archive.
+#define MHFL_PROTECT        0x0008 // Recovery record is present.
+#define MHFL_LOCK           0x0010 // Locked archive.
+
+// RAR 5.0 file header specific flags.
+#define FHFL_DIRECTORY      0x0001 // Directory.
+#define FHFL_UTIME          0x0002 // Time field in Unix format is present.
+#define FHFL_CRC32          0x0004 // CRC32 field is present.
+#define FHFL_UNPUNKNOWN     0x0008 // Unknown unpacked size.
+
+// RAR 5.0 end of archive header specific flags.
+#define EHFL_NEXTVOLUME     0x0001 // Not last volume.
+
+// RAR 5.0 archive encryption header specific flags.
+#define CHFL_CRYPT_PSWCHECK 0x0001 // Password check data is present.
+
+
+// RAR 5.0 file compression flags.
+#define FCI_ALGO_BIT0       0x0001 // Version of compression algorithm.
+#define FCI_ALGO_BIT1       0x0002 // 0 .. 63.
+#define FCI_ALGO_BIT2       0x0004
+#define FCI_ALGO_BIT3       0x0008
+#define FCI_ALGO_BIT4       0x0010
+#define FCI_ALGO_BIT5       0x0020
+#define FCI_SOLID           0x0040 // Solid flag.
+#define FCI_METHOD_BIT0     0x0080 // Compression method.
+#define FCI_METHOD_BIT1     0x0100 // 0 .. 5 (6 and 7 are not used).
+#define FCI_METHOD_BIT2     0x0200
+#define FCI_DICT_BIT0       0x0400 // Dictionary size.
+#define FCI_DICT_BIT1       0x0800 // 128 KB .. 4 GB.
+#define FCI_DICT_BIT2       0x1000
+#define FCI_DICT_BIT3       0x2000
+
+// Main header extra field values.
+#define MHEXTRA_LOCATOR       0x01 // Position of quick list and other blocks.
+
+// Flags for MHEXTRA_LOCATOR.
+#define MHEXTRA_LOCATOR_QLIST 0x01 // Quick open offset is present.
+#define MHEXTRA_LOCATOR_RR    0x02 // Recovery record offset is present.
+
+// File and service header extra field values.
+#define FHEXTRA_CRYPT         0x01 // Encryption parameters.
+#define FHEXTRA_HASH          0x02 // File hash.
+#define FHEXTRA_HTIME         0x03 // High precision file time.
+#define FHEXTRA_VERSION       0x04 // File version information.
+#define FHEXTRA_REDIR         0x05 // File system redirection (links, etc.).
+#define FHEXTRA_UOWNER        0x06 // Unix owner and group information.
+#define FHEXTRA_SUBDATA       0x07 // Service header subdata array.
+
+
+// Hash type values for FHEXTRA_HASH.
+#define FHEXTRA_HASH_BLAKE2    0x00
+
+// Flags for FHEXTRA_HTIME.
+#define FHEXTRA_HTIME_UNIXTIME 0x01 // Use Unix time_t format.
+#define FHEXTRA_HTIME_MTIME    0x02 // mtime is present.
+#define FHEXTRA_HTIME_CTIME    0x04 // ctime is present.
+#define FHEXTRA_HTIME_ATIME    0x08 // atime is present.
+#define FHEXTRA_HTIME_UNIX_NS  0x10 // Unix format with nanosecond precision.
+
+// Flags for FHEXTRA_CRYPT.
+#define FHEXTRA_CRYPT_PSWCHECK 0x01 // Store password check data.
+#define FHEXTRA_CRYPT_HASHMAC  0x02 // Use MAC for unpacked data checksums.
+
+// Flags for FHEXTRA_REDIR.
+#define FHEXTRA_REDIR_DIR      0x01 // Link target is directory.
+
+// Flags for FHEXTRA_UOWNER.
+#define FHEXTRA_UOWNER_UNAME   0x01 // User name string is present.
+#define FHEXTRA_UOWNER_GNAME   0x02 // Group name string is present.
+#define FHEXTRA_UOWNER_NUMUID  0x04 // Numeric user ID is present.
+#define FHEXTRA_UOWNER_NUMGID  0x08 // Numeric group ID is present.
+
+#endif
diff --git a/third_party/unrar/src/isnt.cpp b/third_party/unrar/src/isnt.cpp
new file mode 100644
index 0000000..6fadec0
--- /dev/null
+++ b/third_party/unrar/src/isnt.cpp
@@ -0,0 +1,24 @@
+#include "rar.hpp"
+
+#ifdef _WIN_ALL
+DWORD WinNT()
+{
+  static int dwPlatformId=-1;
+  static DWORD dwMajorVersion,dwMinorVersion;
+  if (dwPlatformId==-1)
+  {
+    OSVERSIONINFO WinVer;
+    WinVer.dwOSVersionInfoSize=sizeof(WinVer);
+    GetVersionEx(&WinVer);
+    dwPlatformId=WinVer.dwPlatformId;
+    dwMajorVersion=WinVer.dwMajorVersion;
+    dwMinorVersion=WinVer.dwMinorVersion;
+  }
+  DWORD Result=0;
+  if (dwPlatformId==VER_PLATFORM_WIN32_NT)
+    Result=dwMajorVersion*0x100+dwMinorVersion;
+
+
+  return Result;
+}
+#endif
diff --git a/third_party/unrar/src/isnt.hpp b/third_party/unrar/src/isnt.hpp
new file mode 100644
index 0000000..85790da
--- /dev/null
+++ b/third_party/unrar/src/isnt.hpp
@@ -0,0 +1,13 @@
+#ifndef _RAR_ISNT_
+#define _RAR_ISNT_
+
+enum WINNT_VERSION {
+  WNT_NONE=0,WNT_NT351=0x0333,WNT_NT4=0x0400,WNT_W2000=0x0500,
+  WNT_WXP=0x0501,WNT_W2003=0x0502,WNT_VISTA=0x0600,WNT_W7=0x0601,
+  WNT_W8=0x0602,WNT_W81=0x0603,WNT_W10=0x0a00
+};
+
+DWORD WinNT();
+
+
+#endif
diff --git a/third_party/unrar/src/license.txt b/third_party/unrar/src/license.txt
new file mode 100644
index 0000000..0811276a
--- /dev/null
+++ b/third_party/unrar/src/license.txt
@@ -0,0 +1,42 @@
+ ******    *****   ******   UnRAR - free utility for RAR archives
+ **   **  **   **  **   **  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ******   *******  ******    License for use and distribution of
+ **   **  **   **  **   **   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ **   **  **   **  **   **         FREE portable version
+                                   ~~~~~~~~~~~~~~~~~~~~~
+
+      The source code of UnRAR utility is freeware. This means:
+
+   1. All copyrights to RAR and the utility UnRAR are exclusively
+      owned by the author - Alexander Roshal.
+
+   2. UnRAR source code may be used in any software to handle
+      RAR archives without limitations free of charge, but cannot be
+      used to develop RAR (WinRAR) compatible archiver and to
+      re-create RAR compression algorithm, which is proprietary.
+      Distribution of modified UnRAR source code in separate form
+      or as a part of other software is permitted, provided that
+      full text of this paragraph, starting from "UnRAR source code"
+      words, is included in license, or in documentation if license
+      is not available, and in source code comments of resulting package.
+
+   3. The UnRAR utility may be freely distributed. It is allowed
+      to distribute UnRAR inside of other software packages.
+
+   4. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS".
+      NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED.  YOU USE AT 
+      YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS, 
+      DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING
+      OR MISUSING THIS SOFTWARE.
+
+   5. Installing and using the UnRAR utility signifies acceptance of
+      these terms and conditions of the license.
+
+   6. If you don't agree with terms of the license you must remove
+      UnRAR files from your storage devices and cease to use the
+      utility.
+
+      Thank you for your interest in RAR and UnRAR.
+
+
+                                            Alexander L. Roshal
diff --git a/third_party/unrar/src/list.cpp b/third_party/unrar/src/list.cpp
new file mode 100644
index 0000000..561122b4
--- /dev/null
+++ b/third_party/unrar/src/list.cpp
@@ -0,0 +1,472 @@
+#include "rar.hpp"
+
+static void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare);
+static void ListSymLink(Archive &Arc);
+static void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize);
+static void ListOldSubHeader(Archive &Arc);
+static void ListNewSubHeader(CommandData *Cmd,Archive &Arc);
+
+void ListArchive(CommandData *Cmd)
+{
+  int64 SumPackSize=0,SumUnpSize=0;
+  uint ArcCount=0,SumFileCount=0;
+  bool Technical=(Cmd->Command[1]=='T');
+  bool ShowService=Technical && Cmd->Command[2]=='A';
+  bool Bare=(Cmd->Command[1]=='B');
+  bool Verbose=(Cmd->Command[0]=='V');
+
+  wchar ArcName[NM];
+  while (Cmd->GetArcName(ArcName,ASIZE(ArcName)))
+  {
+    if (Cmd->ManualPassword)
+      Cmd->Password.Clean(); // Clean user entered password before processing next archive.
+
+    Archive Arc(Cmd);
+#ifdef _WIN_ALL
+    Arc.RemoveSequentialFlag();
+#endif
+    if (!Arc.WOpen(ArcName))
+      continue;
+    bool FileMatched=true;
+    while (1)
+    {
+      int64 TotalPackSize=0,TotalUnpSize=0;
+      uint FileCount=0;
+      if (Arc.IsArchive(true))
+      {
+        bool TitleShown=false;
+        if (!Bare)
+        {
+          Arc.ViewComment();
+          mprintf(L"\n%s: %s",St(MListArchive),Arc.FileName);
+          mprintf(L"\n%s: ",St(MListDetails));
+          uint SetCount=0;
+          const wchar *Fmt=Arc.Format==RARFMT14 ? L"RAR 1.4":(Arc.Format==RARFMT15 ? L"RAR 4":L"RAR 5");
+          mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", Fmt);
+          if (Arc.Solid)
+            mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListSolid));
+          if (Arc.SFXSize>0)
+            mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListSFX));
+          if (Arc.Volume)
+            if (Arc.Format==RARFMT50)
+            {
+              // RAR 5.0 archives store the volume number in main header,
+              // so it is already available now.
+              if (SetCount++ > 0)
+                mprintf(L", ");
+              mprintf(St(MVolumeNumber),Arc.VolNumber+1);
+            }
+            else
+              mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListVolume));
+          if (Arc.Protected)
+            mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListRR));
+          if (Arc.Locked)
+            mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListLock));
+          if (Arc.Encrypted)
+            mprintf(L"%s%s", SetCount++ > 0 ? L", ":L"", St(MListEncHead));
+          mprintf(L"\n");
+        }
+
+        wchar VolNumText[50];
+        *VolNumText=0;
+        while(Arc.ReadHeader()>0)
+        {
+          HEADER_TYPE HeaderType=Arc.GetHeaderType();
+          if (HeaderType==HEAD_ENDARC)
+          {
+#ifndef SFX_MODULE
+            // Only RAR 1.5 archives store the volume number in end record.
+            if (Arc.EndArcHead.StoreVolNumber && Arc.Format==RARFMT15)
+              swprintf(VolNumText,ASIZE(VolNumText),L"%.10ls %u",St(MListVolume),Arc.VolNumber+1);
+#endif
+            if (Technical && ShowService)
+            {
+              mprintf(L"\n%12ls: %ls",St(MListService),L"EOF");
+              if (*VolNumText!=0)
+                mprintf(L"\n%12ls: %ls",St(MListFlags),VolNumText);
+              mprintf(L"\n");
+            }
+            break;
+          }
+          switch(HeaderType)
+          {
+            case HEAD_FILE:
+              FileMatched=Cmd->IsProcessFile(Arc.FileHead)!=0;
+              if (FileMatched)
+              {
+                ListFileHeader(Arc,Arc.FileHead,TitleShown,Verbose,Technical,Bare);
+                if (!Arc.FileHead.SplitBefore)
+                {
+                  TotalUnpSize+=Arc.FileHead.UnpSize;
+                  FileCount++;
+                }
+                TotalPackSize+=Arc.FileHead.PackSize;
+              }
+              break;
+            case HEAD_SERVICE:
+              if (FileMatched && !Bare)
+              {
+                if (Technical && ShowService)
+                  ListFileHeader(Arc,Arc.SubHead,TitleShown,Verbose,true,false);
+              }
+              break;
+          }
+          Arc.SeekToNext();
+        }
+        if (!Bare && !Technical)
+          if (TitleShown)
+          {
+            wchar UnpSizeText[20];
+            itoa(TotalUnpSize,UnpSizeText,ASIZE(UnpSizeText));
+        
+            wchar PackSizeText[20];
+            itoa(TotalPackSize,PackSizeText,ASIZE(PackSizeText));
+        
+            if (Verbose)
+            {
+              mprintf(L"\n----------- ---------  -------- ----- ---------- -----  --------  ----");
+              mprintf(L"\n%21ls %9ls %3d%%  %-27ls %u",UnpSizeText,
+                      PackSizeText,ToPercentUnlim(TotalPackSize,TotalUnpSize),
+                      VolNumText,FileCount);
+            }
+            else
+            {
+              mprintf(L"\n----------- ---------  ---------- -----  ----");
+              mprintf(L"\n%21ls  %-16ls  %u",UnpSizeText,VolNumText,FileCount);
+            }
+
+            SumFileCount+=FileCount;
+            SumUnpSize+=TotalUnpSize;
+            SumPackSize+=TotalPackSize;
+            mprintf(L"\n");
+          }
+          else
+            mprintf(St(MListNoFiles));
+
+        ArcCount++;
+
+#ifndef NOVOLUME
+        if (Cmd->VolSize!=0 && (Arc.FileHead.SplitAfter ||
+            Arc.GetHeaderType()==HEAD_ENDARC && Arc.EndArcHead.NextVolume) &&
+            MergeArchive(Arc,NULL,false,Cmd->Command[0]))
+          Arc.Seek(0,SEEK_SET);
+        else
+#endif
+          break;
+      }
+      else
+      {
+        if (Cmd->ArcNames.ItemsCount()<2 && !Bare)
+          mprintf(St(MNotRAR),Arc.FileName);
+        break;
+      }
+    }
+  }
+
+  // Clean user entered password. Not really required, just for extra safety.
+  if (Cmd->ManualPassword)
+    Cmd->Password.Clean();
+
+  if (ArcCount>1 && !Bare && !Technical)
+  {
+    wchar UnpSizeText[20],PackSizeText[20];
+    itoa(SumUnpSize,UnpSizeText,ASIZE(UnpSizeText));
+    itoa(SumPackSize,PackSizeText,ASIZE(PackSizeText));
+
+    if (Verbose)
+      mprintf(L"%21ls %9ls %3d%% %28ls %u",UnpSizeText,PackSizeText,
+              ToPercentUnlim(SumPackSize,SumUnpSize),L"",SumFileCount);
+    else
+      mprintf(L"%21ls %18s %lu",UnpSizeText,L"",SumFileCount);
+  }
+}
+
+
+enum LISTCOL_TYPE {
+  LCOL_NAME,LCOL_ATTR,LCOL_SIZE,LCOL_PACKED,LCOL_RATIO,LCOL_CSUM,LCOL_ENCR
+};
+
+
+void ListFileHeader(Archive &Arc,FileHeader &hd,bool &TitleShown,bool Verbose,bool Technical,bool Bare)
+{
+  wchar *Name=hd.FileName;
+  RARFORMAT Format=Arc.Format;
+
+  if (Bare)
+  {
+    mprintf(L"%s\n",Name);
+    return;
+  }
+
+  if (!TitleShown && !Technical)
+  {
+    if (Verbose)
+    {
+      mprintf(L"\n%ls",St(MListTitleV));
+      mprintf(L"\n----------- ---------  -------- ----- ---------- -----  --------  ----");
+    }
+    else
+    {
+      mprintf(L"\n%ls",St(MListTitleL));
+      mprintf(L"\n----------- ---------  ---------- -----  ----");
+    }
+    TitleShown=true;
+  }
+
+  wchar UnpSizeText[30],PackSizeText[30];
+  if (hd.UnpSize==INT64NDF)
+    wcscpy(UnpSizeText,L"?");
+  else
+    itoa(hd.UnpSize,UnpSizeText,ASIZE(UnpSizeText));
+  itoa(hd.PackSize,PackSizeText,ASIZE(PackSizeText));
+
+  wchar AttrStr[30];
+  if (hd.HeaderType==HEAD_SERVICE)
+    swprintf(AttrStr,ASIZE(AttrStr),L"%cB",hd.Inherited ? 'I' : '.');
+  else
+    ListFileAttr(hd.FileAttr,hd.HSType,AttrStr,ASIZE(AttrStr));
+
+  wchar RatioStr[10];
+
+  if (hd.SplitBefore && hd.SplitAfter)
+    wcscpy(RatioStr,L"<->");
+  else
+    if (hd.SplitBefore)
+      wcscpy(RatioStr,L"<--");
+    else
+      if (hd.SplitAfter)
+        wcscpy(RatioStr,L"-->");
+      else
+        swprintf(RatioStr,ASIZE(RatioStr),L"%d%%",ToPercentUnlim(hd.PackSize,hd.UnpSize));
+
+  wchar DateStr[50];
+  hd.mtime.GetText(DateStr,ASIZE(DateStr),Technical);
+
+  if (Technical)
+  {
+    mprintf(L"\n%12s: %s",St(MListName),Name);
+
+    bool FileBlock=hd.HeaderType==HEAD_FILE;
+
+    if (!FileBlock && Arc.SubHead.CmpName(SUBHEAD_TYPE_STREAM))
+    {
+      mprintf(L"\n%12ls: %ls",St(MListType),St(MListStream));
+      wchar StreamName[NM];
+      GetStreamNameNTFS(Arc,StreamName,ASIZE(StreamName));
+      mprintf(L"\n%12ls: %ls",St(MListTarget),StreamName);
+    }
+    else
+    {
+      const wchar *Type=St(FileBlock ? (hd.Dir ? MListDir:MListFile):MListService);
+    
+      if (hd.RedirType!=FSREDIR_NONE)
+        switch(hd.RedirType)
+        {
+          case FSREDIR_UNIXSYMLINK:
+            Type=St(MListUSymlink); break;
+          case FSREDIR_WINSYMLINK:
+            Type=St(MListWSymlink); break;
+          case FSREDIR_JUNCTION:
+            Type=St(MListJunction); break;
+          case FSREDIR_HARDLINK:
+            Type=St(MListHardlink); break;
+          case FSREDIR_FILECOPY:
+            Type=St(MListCopy);     break;
+        }
+      mprintf(L"\n%12ls: %ls",St(MListType),Type);
+      if (hd.RedirType!=FSREDIR_NONE)
+        if (Format==RARFMT15)
+        {
+          char LinkTargetA[NM];
+          if (Arc.FileHead.Encrypted)
+          {
+            // Link data are encrypted. We would need to ask for password
+            // and initialize decryption routine to display the link target.
+            strncpyz(LinkTargetA,"*<-?->",ASIZE(LinkTargetA));
+          }
+          else
+          {
+            int DataSize=(int)Min(hd.PackSize,ASIZE(LinkTargetA)-1);
+            Arc.Read(LinkTargetA,DataSize);
+            LinkTargetA[DataSize > 0 ? DataSize : 0] = 0;
+          }
+          wchar LinkTarget[NM];
+          CharToWide(LinkTargetA,LinkTarget,ASIZE(LinkTarget));
+          mprintf(L"\n%12ls: %ls",St(MListTarget),LinkTarget);
+        }
+        else
+          mprintf(L"\n%12ls: %ls",St(MListTarget),hd.RedirName);
+    }
+    if (!hd.Dir)
+    {
+      mprintf(L"\n%12ls: %ls",St(MListSize),UnpSizeText);
+      mprintf(L"\n%12ls: %ls",St(MListPacked),PackSizeText);
+      mprintf(L"\n%12ls: %ls",St(MListRatio),RatioStr);
+    }
+    if (hd.mtime.IsSet())
+      mprintf(L"\n%12ls: %ls",St(MListMtime),DateStr);
+    if (hd.ctime.IsSet())
+    {
+      hd.ctime.GetText(DateStr,ASIZE(DateStr),true);
+      mprintf(L"\n%12ls: %ls",St(MListCtime),DateStr);
+    }
+    if (hd.atime.IsSet())
+    {
+      hd.atime.GetText(DateStr,ASIZE(DateStr),true);
+      mprintf(L"\n%12ls: %ls",St(MListAtime),DateStr);
+    }
+    mprintf(L"\n%12ls: %ls",St(MListAttr),AttrStr);
+    if (hd.FileHash.Type==HASH_CRC32)
+      mprintf(L"\n%12ls: %8.8X",
+        hd.UseHashKey ? L"CRC32 MAC":hd.SplitAfter ? L"Pack-CRC32":L"CRC32",
+        hd.FileHash.CRC32);
+    if (hd.FileHash.Type==HASH_BLAKE2)
+    {
+      wchar BlakeStr[BLAKE2_DIGEST_SIZE*2+1];
+      BinToHex(hd.FileHash.Digest,BLAKE2_DIGEST_SIZE,NULL,BlakeStr,ASIZE(BlakeStr));
+      mprintf(L"\n%12ls: %ls",
+        hd.UseHashKey ? L"BLAKE2 MAC":hd.SplitAfter ? L"Pack-BLAKE2":L"BLAKE2",
+        BlakeStr);
+    }
+
+    const wchar *HostOS=L"";
+    if (Format==RARFMT50 && hd.HSType!=HSYS_UNKNOWN)
+      HostOS=hd.HSType==HSYS_WINDOWS ? L"Windows":L"Unix";
+    if (Format==RARFMT15)
+    {
+      static const wchar *RarOS[]={
+        L"DOS",L"OS/2",L"Windows",L"Unix",L"Mac OS",L"BeOS",L"WinCE",L"",L"",L""
+      };
+      if (hd.HostOS<ASIZE(RarOS))
+        HostOS=RarOS[hd.HostOS];
+    }
+    if (*HostOS!=0)
+      mprintf(L"\n%12ls: %ls",St(MListHostOS),HostOS);
+
+    mprintf(L"\n%12ls: RAR %ls(v%d) -m%d -md=%d%s",St(MListCompInfo),
+            Format==RARFMT15 ? L"3.0":L"5.0",hd.UnpVer,hd.Method,
+            hd.WinSize>=0x100000 ? hd.WinSize/0x100000:hd.WinSize/0x400,
+            hd.WinSize>=0x100000 ? L"M":L"K");
+
+    if (hd.Solid || hd.Encrypted)
+    {
+      mprintf(L"\n%12ls: ",St(MListFlags));
+      if (hd.Solid)
+        mprintf(L"%ls ",St(MListSolid));
+      if (hd.Encrypted)
+        mprintf(L"%ls ",St(MListEnc));
+    }
+
+    if (hd.Version)
+    {
+      uint Version=ParseVersionFileName(Name,false);
+      if (Version!=0)
+        mprintf(L"\n%12ls: %u",St(MListFileVer),Version);
+    }
+
+    if (hd.UnixOwnerSet)
+    {
+      mprintf(L"\n%12ls: ",L"Unix owner");
+      if (*hd.UnixOwnerName!=0)
+        mprintf(L"%ls:",GetWide(hd.UnixOwnerName));
+      if (*hd.UnixGroupName!=0)
+        mprintf(L"%ls",GetWide(hd.UnixGroupName));
+      if ((*hd.UnixOwnerName!=0 || *hd.UnixGroupName!=0) && (hd.UnixOwnerNumeric || hd.UnixGroupNumeric))
+        mprintf(L"  ");
+      if (hd.UnixOwnerNumeric)
+        mprintf(L"#%d:",hd.UnixOwnerID);
+      if (hd.UnixGroupNumeric)
+        mprintf(L"#%d:",hd.UnixGroupID);
+    }
+
+    mprintf(L"\n");
+    return;
+  }
+
+  mprintf(L"\n%c%10ls %9ls ",hd.Encrypted ? '*' : ' ',AttrStr,UnpSizeText);
+
+  if (Verbose)
+    mprintf(L"%9ls %4ls ",PackSizeText,RatioStr);
+
+  mprintf(L" %ls  ",DateStr);
+
+  if (Verbose)
+  {
+    if (hd.FileHash.Type==HASH_CRC32)
+      mprintf(L"%8.8X  ",hd.FileHash.CRC32);
+    else
+      if (hd.FileHash.Type==HASH_BLAKE2)
+      {
+        byte *S=hd.FileHash.Digest;
+        mprintf(L"%02x%02x..%02x  ",S[0],S[1],S[31]);
+      }
+      else
+        mprintf(L"????????  ");
+  }
+  mprintf(L"%ls",Name);
+}
+
+/*
+void ListSymLink(Archive &Arc)
+{
+  if (Arc.FileHead.HSType==HSYS_UNIX && (Arc.FileHead.FileAttr & 0xF000)==0xA000)
+    if (Arc.FileHead.Encrypted)
+    {
+      // Link data are encrypted. We would need to ask for password
+      // and initialize decryption routine to display the link target.
+      mprintf(L"\n%22ls %ls",L"-->",L"*<-?->");
+    }
+    else
+    {
+      char FileName[NM];
+      uint DataSize=(uint)Min(Arc.FileHead.PackSize,sizeof(FileName)-1);
+      Arc.Read(FileName,DataSize);
+      FileName[DataSize]=0;
+      mprintf(L"\n%22ls %ls",L"-->",GetWide(FileName));
+    }
+}
+*/
+
+void ListFileAttr(uint A,HOST_SYSTEM_TYPE HostType,wchar *AttrStr,size_t AttrSize)
+{
+  switch(HostType)
+  {
+    case HSYS_WINDOWS:
+      swprintf(AttrStr,AttrSize,L"%c%c%c%c%c%c%c",
+              (A & 0x2000)!=0 ? 'I' : '.',  // Not content indexed.
+              (A & 0x0800)!=0 ? 'C' : '.',  // Compressed.
+              (A & 0x0020)!=0 ? 'A' : '.',  // Archive.
+              (A & 0x0010)!=0 ? 'D' : '.',  // Directory.
+              (A & 0x0004)!=0 ? 'S' : '.',  // System.
+              (A & 0x0002)!=0 ? 'H' : '.',  // Hidden.
+              (A & 0x0001)!=0 ? 'R' : '.'); // Read-only.
+      break;
+    case HSYS_UNIX:
+      switch (A & 0xF000)
+      {
+        case 0x4000:
+          AttrStr[0]='d';
+          break;
+        case 0xA000:
+          AttrStr[0]='l';
+          break;
+        default:
+          AttrStr[0]='-';
+          break;
+      }
+      swprintf(AttrStr+1,AttrSize-1,L"%c%c%c%c%c%c%c%c%c",
+              (A & 0x0100) ? 'r' : '-',
+              (A & 0x0080) ? 'w' : '-',
+              (A & 0x0040) ? ((A & 0x0800)!=0 ? 's':'x'):((A & 0x0800)!=0 ? 'S':'-'),
+              (A & 0x0020) ? 'r' : '-',
+              (A & 0x0010) ? 'w' : '-',
+              (A & 0x0008) ? ((A & 0x0400)!=0 ? 's':'x'):((A & 0x0400)!=0 ? 'S':'-'),
+              (A & 0x0004) ? 'r' : '-',
+              (A & 0x0002) ? 'w' : '-',
+              (A & 0x0001) ? ((A & 0x200)!=0 ? 't' : 'x') : '-');
+      break;
+    case HSYS_UNKNOWN:
+      wcscpy(AttrStr,L"?");
+      break;
+  }
+}
diff --git a/third_party/unrar/src/list.hpp b/third_party/unrar/src/list.hpp
new file mode 100644
index 0000000..7721ae5
--- /dev/null
+++ b/third_party/unrar/src/list.hpp
@@ -0,0 +1,6 @@
+#ifndef _RAR_LIST_
+#define _RAR_LIST_
+
+void ListArchive(CommandData *Cmd);
+
+#endif
diff --git a/third_party/unrar/src/loclang.hpp b/third_party/unrar/src/loclang.hpp
new file mode 100644
index 0000000..763e8c6
--- /dev/null
+++ b/third_party/unrar/src/loclang.hpp
@@ -0,0 +1,381 @@
+#define   MYesNo             L"_Yes_No"
+#define   MYesNoAll          L"_Yes_No_All"
+#define   MYesNoAllQ         L"_Yes_No_All_nEver_Quit"
+#define   MYesNoAllRenQ      L"_Yes_No_All_nEver_Rename_Quit"
+#define   MContinueQuit      L"_Continue_Quit"
+#define   MRetryAbort        L"_Retry_Abort"
+#define   MCopyright         L"\nRAR %s   Copyright (c) 1993-%d Alexander Roshal   %d %s %d"
+#define   MRegTo             L"\nRegistered to %s\n"
+#define   MShare             L"\nTrial version             Type 'rar -?' for help\n"
+#define   MRegKeyWarning     L"\nAvailable license key is valid only for %s\n"
+#define   MUCopyright        L"\nUNRAR %s freeware      Copyright (c) 1993-%d Alexander Roshal\n"
+#define   MBeta              L"beta"
+#define   Mx86               L"x86"
+#define   Mx64               L"x64"
+#define   MMonthJan          L"Jan"
+#define   MMonthFeb          L"Feb"
+#define   MMonthMar          L"Mar"
+#define   MMonthApr          L"Apr"
+#define   MMonthMay          L"May"
+#define   MMonthJun          L"Jun"
+#define   MMonthJul          L"Jul"
+#define   MMonthAug          L"Aug"
+#define   MMonthSep          L"Sep"
+#define   MMonthOct          L"Oct"
+#define   MMonthNov          L"Nov"
+#define   MMonthDec          L"Dec"
+#define   MRARTitle1         L"\nUsage:     rar <command> -<switch 1> -<switch N> <archive> <files...>"
+#define   MUNRARTitle1       L"\nUsage:     unrar <command> -<switch 1> -<switch N> <archive> <files...>"
+#define   MRARTitle2         L"\n               <@listfiles...> <path_to_extract\\>"
+#define   MCHelpCmd          L"\n\n<Commands>"
+#define   MCHelpCmdA         L"\n  a             Add files to archive"
+#define   MCHelpCmdC         L"\n  c             Add archive comment"
+#define   MCHelpCmdCH        L"\n  ch            Change archive parameters"
+#define   MCHelpCmdCW        L"\n  cw            Write archive comment to file"
+#define   MCHelpCmdD         L"\n  d             Delete files from archive"
+#define   MCHelpCmdE         L"\n  e             Extract files without archived paths"
+#define   MCHelpCmdF         L"\n  f             Freshen files in archive"
+#define   MCHelpCmdI         L"\n  i[par]=<str>  Find string in archives"
+#define   MCHelpCmdK         L"\n  k             Lock archive"
+#define   MCHelpCmdL         L"\n  l[t[a],b]     List archive contents [technical[all], bare]"
+#define   MCHelpCmdM         L"\n  m[f]          Move to archive [files only]"
+#define   MCHelpCmdP         L"\n  p             Print file to stdout"
+#define   MCHelpCmdR         L"\n  r             Repair archive"
+#define   MCHelpCmdRC        L"\n  rc            Reconstruct missing volumes"
+#define   MCHelpCmdRN        L"\n  rn            Rename archived files"
+#define   MCHelpCmdRR        L"\n  rr[N]         Add data recovery record"
+#define   MCHelpCmdRV        L"\n  rv[N]         Create recovery volumes"
+#define   MCHelpCmdS         L"\n  s[name|-]     Convert archive to or from SFX"
+#define   MCHelpCmdT         L"\n  t             Test archive files"
+#define   MCHelpCmdU         L"\n  u             Update files in archive"
+#define   MCHelpCmdV         L"\n  v[t[a],b]     Verbosely list archive contents [technical[all],bare]"
+#define   MCHelpCmdX         L"\n  x             Extract files with full path"
+#define   MCHelpSw           L"\n\n<Switches>"
+#define   MCHelpSwm          L"\n  -             Stop switches scanning"
+#define   MCHelpSwAT         L"\n  @[+]          Disable [enable] file lists"
+#define   MCHelpSwAC         L"\n  ac            Clear Archive attribute after compression or extraction"
+#define   MCHelpSwAD         L"\n  ad            Append archive name to destination path"
+#define   MCHelpSwAG         L"\n  ag[format]    Generate archive name using the current date"
+#define   MCHelpSwAI         L"\n  ai            Ignore file attributes"
+#define   MCHelpSwAO         L"\n  ao            Add files with Archive attribute set"
+#define   MCHelpSwAP         L"\n  ap<path>      Set path inside archive"
+#define   MCHelpSwAS         L"\n  as            Synchronize archive contents"
+#define   MCHelpSwCm         L"\n  c-            Disable comments show"
+#define   MCHelpSwCFGm       L"\n  cfg-          Disable read configuration"
+#define   MCHelpSwCL         L"\n  cl            Convert names to lower case"
+#define   MCHelpSwCU         L"\n  cu            Convert names to upper case"
+#define   MCHelpSwDF         L"\n  df            Delete files after archiving"
+#define   MCHelpSwDH         L"\n  dh            Open shared files"
+#define   MCHelpSwDR         L"\n  dr            Delete files to Recycle Bin"
+#define   MCHelpSwDS         L"\n  ds            Disable name sort for solid archive"
+#define   MCHelpSwDW         L"\n  dw            Wipe files after archiving"
+#define   MCHelpSwEa         L"\n  e[+]<attr>    Set file exclude and include attributes"
+#define   MCHelpSwED         L"\n  ed            Do not add empty directories"
+#define   MCHelpSwEN         L"\n  en            Do not put 'end of archive' block"
+#define   MCHelpSwEP         L"\n  ep            Exclude paths from names"
+#define   MCHelpSwEP1        L"\n  ep1           Exclude base directory from names"
+#define   MCHelpSwEP2        L"\n  ep2           Expand paths to full"
+#define   MCHelpSwEP3        L"\n  ep3           Expand paths to full including the drive letter"
+#define   MCHelpSwF          L"\n  f             Freshen files"
+#define   MCHelpSwHP         L"\n  hp[password]  Encrypt both file data and headers"
+#define   MCHelpSwHT         L"\n  ht[b|c]       Select hash type [BLAKE2,CRC32] for file checksum"
+#define   MCHelpSwIDP        L"\n  id[c,d,p,q]   Disable messages"
+#define   MCHelpSwIEML       L"\n  ieml[addr]    Send archive by email"
+#define   MCHelpSwIERR       L"\n  ierr          Send all messages to stderr"
+#define   MCHelpSwILOG       L"\n  ilog[name]    Log errors to file"
+#define   MCHelpSwINUL       L"\n  inul          Disable all messages"
+#define   MCHelpSwIOFF       L"\n  ioff[n]       Turn PC off after completing an operation"
+#define   MCHelpSwISND       L"\n  isnd          Enable sound"
+#define   MCHelpSwIVER       L"\n  iver          Display the version number"
+#define   MCHelpSwK          L"\n  k             Lock archive"
+#define   MCHelpSwKB         L"\n  kb            Keep broken extracted files"
+#define   MCHelpSwLog        L"\n  log[f][=name] Write names to log file"
+#define   MCHelpSwMn         L"\n  m<0..5>       Set compression level (0-store...3-default...5-maximal)"
+#define   MCHelpSwMA         L"\n  ma[4|5]       Specify a version of archiving format"
+#define   MCHelpSwMC         L"\n  mc<par>       Set advanced compression parameters"
+#define   MCHelpSwMD         L"\n  md<n>[k,m,g]  Dictionary size in KB, MB or GB"
+#define   MCHelpSwMS         L"\n  ms[ext;ext]   Specify file types to store"
+#define   MCHelpSwMT         L"\n  mt<threads>   Set the number of threads"
+#define   MCHelpSwN          L"\n  n<file>       Additionally filter included files"
+#define   MCHelpSwNa         L"\n  n@            Read additional filter masks from stdin"
+#define   MCHelpSwNal        L"\n  n@<list>      Read additional filter masks from list file"
+#define   MCHelpSwO          L"\n  o[+|-]        Set the overwrite mode"
+#define   MCHelpSwOC         L"\n  oc            Set NTFS Compressed attribute"
+#define   MCHelpSwOH         L"\n  oh            Save hard links as the link instead of the file"
+#define   MCHelpSwOI         L"\n  oi[0-4][:min] Save identical files as references"
+#define   MCHelpSwOL         L"\n  ol[a]         Process symbolic links as the link [absolute paths]"
+#define   MCHelpSwONI        L"\n  oni           Allow potentially incompatible names"
+#define   MCHelpSwOR         L"\n  or            Rename files automatically"
+#define   MCHelpSwOS         L"\n  os            Save NTFS streams"
+#define   MCHelpSwOW         L"\n  ow            Save or restore file owner and group"
+#define   MCHelpSwP          L"\n  p[password]   Set password"
+#define   MCHelpSwPm         L"\n  p-            Do not query password"
+#define   MCHelpSwQO         L"\n  qo[-|+]       Add quick open information [none|force]"
+#define   MCHelpSwR          L"\n  r             Recurse subdirectories"
+#define   MCHelpSwRm         L"\n  r-            Disable recursion"
+#define   MCHelpSwR0         L"\n  r0            Recurse subdirectories for wildcard names only"
+#define   MCHelpSwRI         L"\n  ri<P>[:<S>]   Set priority (0-default,1-min..15-max) and sleep time in ms"
+#define   MCHelpSwRR         L"\n  rr[N]         Add data recovery record"
+#define   MCHelpSwRV         L"\n  rv[N]         Create recovery volumes"
+#define   MCHelpSwS          L"\n  s[<N>,v[-],e] Create solid archive"
+#define   MCHelpSwSm         L"\n  s-            Disable solid archiving"
+#define   MCHelpSwSC         L"\n  sc<chr>[obj]  Specify the character set"
+#define   MCHelpSwSFX        L"\n  sfx[name]     Create SFX archive"
+#define   MCHelpSwSI         L"\n  si[name]      Read data from standard input (stdin)"
+#define   MCHelpSwSL         L"\n  sl<size>      Process files with size less than specified"
+#define   MCHelpSwSM         L"\n  sm<size>      Process files with size more than specified"
+#define   MCHelpSwT          L"\n  t             Test files after archiving"
+#define   MCHelpSwTK         L"\n  tk            Keep original archive time"
+#define   MCHelpSwTL         L"\n  tl            Set archive time to latest file"
+#define   MCHelpSwTN         L"\n  tn<time>      Process files newer than <time>"
+#define   MCHelpSwTO         L"\n  to<time>      Process files older than <time>"
+#define   MCHelpSwTA         L"\n  ta<date>      Process files modified after <date> in YYYYMMDDHHMMSS format"
+#define   MCHelpSwTB         L"\n  tb<date>      Process files modified before <date> in YYYYMMDDHHMMSS format"
+#define   MCHelpSwTS         L"\n  ts[m|c|a]     Save or restore file time (modification, creation, access)"
+#define   MCHelpSwU          L"\n  u             Update files"
+#define   MCHelpSwV          L"\n  v             Create volumes with size autodetection or list all volumes"
+#define   MCHelpSwVUnr       L"\n  v             List all volumes"
+#define   MCHelpSwVn         L"\n  v<size>[k,b]  Create volumes with size=<size>*1000 [*1024, *1]"
+#define   MCHelpSwVD         L"\n  vd            Erase disk contents before creating volume"
+#define   MCHelpSwVER        L"\n  ver[n]        File version control"
+#define   MCHelpSwVN         L"\n  vn            Use the old style volume naming scheme"
+#define   MCHelpSwVP         L"\n  vp            Pause before each volume"
+#define   MCHelpSwW          L"\n  w<path>       Assign work directory"
+#define   MCHelpSwX          L"\n  x<file>       Exclude specified file"
+#define   MCHelpSwXa         L"\n  x@            Read file names to exclude from stdin"
+#define   MCHelpSwXal        L"\n  x@<list>      Exclude files listed in specified list file"
+#define   MCHelpSwY          L"\n  y             Assume Yes on all queries"
+#define   MCHelpSwZ          L"\n  z[file]       Read archive comment from file"
+#define   MBadArc            L"\nERROR: Bad archive %s\n"
+#define   MAskPsw            L"Enter password (will not be echoed)"
+#define   MAskPswFor         L"\nEnter password (will not be echoed) for %s: "
+#define   MReAskPsw          L"\nReenter password: "
+#define   MNotMatchPsw       L"\nERROR: Passwords do not match\n"
+#define   MErrWrite          L"Write error in the file %s"
+#define   MErrRead           L"Read error in the file %s"
+#define   MErrSeek           L"Seek error in the file %s"
+#define   MErrFClose         L"Cannot close the file %s"
+#define   MErrOutMem         L"Not enough memory"
+#define   MErrBrokenArc      L"Corrupt archive - use 'Repair' command"
+#define   MProgAborted       L"Program aborted"
+#define   MErrRename         L"\nCannot rename %s to %s"
+#define   MAbsNextVol        L"\nCannot find volume %s"
+#define   MBreak             L"\nUser break\n"
+#define   MAskCreatVol       L"\nCreate next volume ?"
+#define   MAskNextDisk       L"\nDisk full. Insert next"
+#define   MCreatVol          L"\n\nCreating %sarchive %s\n"
+#define   MAskNextVol        L"\nInsert disk with %s"
+#define   MTestVol           L"\n\nTesting archive %s\n"
+#define   MExtrVol           L"\n\nExtracting from %s\n"
+#define   MConverting        L"\nConverting %s"
+#define   MCvtToSFX          L"\nConvert archives to SFX"
+#define   MCvtFromSFX        L"\nRemoving SFX module"
+#define   MNotSFX            L"\n%s is not SFX archive"
+#define   MNotRAR            L"\n%s is not RAR archive"
+#define   MNotFirstVol       L"\n%s is not the first volume"
+#define   MCvtOldFormat      L"\n%s - cannot convert to SFX archive with old format"
+#define   MCannotCreate      L"\nCannot create %s"
+#define   MCannotOpen        L"\nCannot open %s"
+#define   MUnknownMeth       L"\nUnknown method in %s"
+#define   MNewRarFormat      L"\nUnsupported archive format. Please update RAR to a newer version."
+#define   MOk                L" OK"
+#define   MDone              L"\nDone"
+#define   MLockingArc        L"\nLocking archive"
+#define   MNotMdfOld         L"\n\nERROR: Cannot modify old format archive"
+#define   MNotMdfLock        L"\n\nERROR: Locked archive"
+#define   MNotMdfVol         L"\n\nERROR: Cannot modify volume"
+#define   MPackAskReg        L"\nEvaluation copy. Please register.\n"
+#define   MCreateArchive     L"\nCreating %sarchive %s\n"
+#define   MUpdateArchive     L"\nUpdating %sarchive %s\n"
+#define   MAddSolid          L"solid "
+#define   MAddFile           L"\nAdding    %-58s     "
+#define   MUpdFile           L"\nUpdating  %-58s     "
+#define   MAddPoints         L"\n...       %-58s     "
+#define   MMoveDelFiles      L"\n\nDeleting files %s..."
+#define   MMoveDelDirs       L"and directories"
+#define   MMoveDelFile       L"\nDeleting %-30s"
+#define   MMoveDeleted       L"    deleted"
+#define   MMoveNotDeleted    L"    NOT DELETED"
+#define   MClearAttrib       L"\n\nClearing attributes..."
+#define   MMoveDelDir        L"\nDeleting directory %-30s"
+#define   MWarErrFOpen       L"\nWARNING: Cannot open %d %s"
+#define   MErrOpenFiles      L"files"
+#define   MErrOpenFile       L"file"
+#define   MAddNoFiles        L"\nWARNING: No files"
+#define   MMdfEncrSol        L"\n%s: encrypted"
+#define   MCannotMdfEncrSol  L"\nCannot modify solid archive containing encrypted files"
+#define   MAddAnalyze        L"\nAnalyzing archived files: "
+#define   MRepacking         L"\nRepacking archived files: "
+#define   MCRCFailed         L"\n%-20s - checksum error"
+#define   MExtrTest          L"\n\nTesting archive %s\n"
+#define   MExtracting        L"\n\nExtracting from %s\n"
+#define   MUseCurPsw         L"\n%s - use current password ?"
+#define   MCreatDir          L"\nCreating    %-56s"
+#define   MExtrSkipFile      L"\nSkipping    %-56s"
+#define   MExtrTestFile      L"\nTesting     %-56s"
+#define   MExtrFile          L"\nExtracting  %-56s"
+#define   MExtrPoints        L"\n...         %-56s"
+#define   MExtrErrMkDir      L"\nCannot create directory %s"
+#define   MExtrPrinting      L"\n------ Printing %s\n\n"
+#define   MEncrBadCRC        L"\nChecksum error in the encrypted file %s. Corrupt file or wrong password."
+#define   MExtrNoFiles       L"\nNo files to extract"
+#define   MExtrAllOk         L"\nAll OK"
+#define   MExtrTotalErr      L"\nTotal errors: %ld"
+#define   MAskReplace        L"\n\nWould you like to replace the existing file %s\n%6s bytes, modified on %s\nwith a new one\n%6s bytes, modified on %s\n"
+#define   MAskOverwrite      L"\nOverwrite %s ?"
+#define   MAskNewName        L"\nEnter new name: "
+#define   MHeaderBroken      L"\nCorrupt header is found"
+#define   MMainHeaderBroken  L"\nMain archive header is corrupt"
+#define   MLogFileHead       L"\n%s - the file header is corrupt"
+#define   MLogProtectHead    L"The data recovery header is corrupt"
+#define   MReadStdinCmt      L"\nReading comment from stdin\n"
+#define   MReadCommFrom      L"\nReading comment from %s"
+#define   MDelComment        L"\nDeleting comment from %s"
+#define   MAddComment        L"\nAdding comment to %s"
+#define   MFCommAdd          L"\nAdding file comments"
+#define   MAskFComm          L"\n\nReading comment for %s : %s from stdin\n"
+#define   MLogCommBrk        L"\nThe archive comment is corrupt"
+#define   MCommAskCont       L"\nPress 'Enter' to continue or 'Q' to quit:"
+#define   MWriteCommTo       L"\nWrite comment to %s"
+#define   MCommNotPres       L"\nComment is not present"
+#define   MDelFrom           L"\nDeleting from %s"
+#define   MDeleting          L"\nDeleting %s"
+#define   MEraseArc          L"\nErasing empty archive %s"
+#define   MNoDelFiles        L"\nNo files to delete"
+#define   MLogTitle          L"--------  %2d %s %d, archive %s"
+#define   MPathTooLong       L"\nERROR: Path too long\n"
+#define   MListArchive       L"Archive"
+#define   MListDetails       L"Details"
+#define   MListSolid         L"solid"
+#define   MListSFX           L"SFX"
+#define   MListVolume        L"volume"
+#define   MListRR            L"recovery record"
+#define   MListLock          L"lock"
+#define   MListEnc           L"encrypted"
+#define   MListEncHead       L"encrypted headers"
+#define   MListTitleL        L" Attributes      Size     Date    Time   Name"
+#define   MListTitleV        L" Attributes      Size    Packed Ratio    Date    Time   Checksum  Name"
+#define   MListName          L"Name"
+#define   MListType          L"Type"
+#define   MListFile          L"File"
+#define   MListDir           L"Directory"
+#define   MListUSymlink      L"Unix symbolic link"
+#define   MListWSymlink      L"Windows symbolic link"
+#define   MListJunction      L"NTFS junction point"
+#define   MListHardlink      L"Hard link"
+#define   MListCopy          L"File reference"
+#define   MListStream        L"NTFS alternate data stream"
+#define   MListTarget        L"Target"
+#define   MListSize          L"Size"
+#define   MListPacked        L"Packed size"
+#define   MListRatio         L"Ratio"
+#define   MListMtime         L"mtime"
+#define   MListCtime         L"ctime"
+#define   MListAtime         L"atime"
+#define   MListAttr          L"Attributes"
+#define   MListFlags         L"Flags"
+#define   MListCompInfo      L"Compression"
+#define   MListHostOS        L"Host OS"
+#define   MListFileVer       L"File version"
+#define   MListService       L"Service"
+#define   MListUOHead        L"\n   Unix Owner/Group data:    %-14s %-14s"
+#define   MListNTACLHead     L"\n   NTFS security data"
+#define   MListStrmHead      L"\n   NTFS stream: %s"
+#define   MListUnkHead       L"\n   Unknown subheader type: 0x%04x"
+#define   MFileComment       L"\nComment: "
+#define   MYes               L"Yes"
+#define   MNo                L"No"
+#define   MListNoFiles       L"  0 files\n"
+#define   MRprReconstr       L"\nReconstructing %s"
+#define   MRprBuild          L"\nBuilding %s"
+#define   MRprOldFormat      L"\nCannot repair archive with old format"
+#define   MRprFind           L"\nFound  %s"
+#define   MRprAskIsSol       L"\nThe archive header is corrupt. Mark archive as solid ?"
+#define   MRprNoFiles        L"\nNo files found"
+#define   MLogUnexpEOF       L"\nUnexpected end of archive"
+#define   MRepAskReconst     L"\nReconstruct archive structure ?"
+#define   MRecScanning       L"\nScanning..."
+#define   MRecRNotFound      L"\nData recovery record not found"
+#define   MRecRFound         L"\nData recovery record found"
+#define   MRecSecDamage      L"\nSector %ld (offsets %lX...%lX) damaged"
+#define   MRecCorrected      L" - data recovered"
+#define   MRecFailed         L" - cannot recover data"
+#define   MAddRecRec         L"\nAdding data recovery record"
+#define   MEraseForVolume    L"\n\nErasing contents of drive %c:\n"
+#define   MGetOwnersError    L"\nWARNING: Cannot get %s owner and group\n"
+#define   MErrGetOwnerID     L"\nWARNING: Cannot get owner %s ID\n"
+#define   MErrGetGroupID     L"\nWARNING: Cannot get group %s ID\n"
+#define   MOwnersBroken      L"\nERROR: %s group and owner data are corrupt\n"
+#define   MSetOwnersError    L"\nWARNING: Cannot set %s owner and group\n"
+#define   MErrLnkRead        L"\nWARNING: Cannot read symbolic link %s"
+#define   MSymLinkExists     L"\nWARNING: Symbolic link %s already exists"
+#define   MAskRetryCreate    L"\nCannot create %s. Retry ?"
+#define   MListMACHead1      L"\n Mac OS file type:  %c%c%c%c  ; "
+#define   MListMACHead2      L"file creator:  %c%c%c%c\n"
+#define   MDataBadCRC        L"\n%-20s : packed data checksum error in volume %s"
+#define   MFileRO            L"\n%s is read-only"
+#define   MACLGetError       L"\nWARNING: Cannot get %s security data\n"
+#define   MACLSetError       L"\nWARNING: Cannot set %s security data\n"
+#define   MACLBroken         L"\nERROR: %s security data are corrupt\n"
+#define   MACLUnknown        L"\nWARNING: Unknown format of %s security data\n"
+#define   MStreamBroken      L"\nERROR: %s stream data are corrupt\n"
+#define   MStreamUnknown     L"\nWARNING: Unknown format of %s stream data\n"
+#define   MInvalidName       L"\nERROR: Invalid file name %s"
+#define   MProcessArc        L"\n\nProcessing archive %s"
+#define   MCorrectingName    L"\nWARNING: Attempting to correct the invalid file name"
+#define   MUnpCannotMerge    L"\nWARNING: You need to start extraction from a previous volume to unpack %s"
+#define   MUnknownOption     L"\nERROR: Unknown option: %s"
+#define   MSubHeadCorrupt    L"\nERROR: Corrupt data header found, ignored"
+#define   MSubHeadUnknown    L"\nWARNING: Unknown data header format, ignored"
+#define   MSubHeadDataCRC    L"\nERROR: Corrupt %s data block"
+#define   MSubHeadType       L"\nData header type: %s"
+#define   MScanError         L"\nCannot read contents of %s"
+#define   MNotVolume         L"\n%s is not volume"
+#define   MRecVolDiffSets    L"\nERROR: %s and %s belong to different sets"
+#define   MRecVolMissing     L"\n%d volumes missing"
+#define   MRecVolFound       L"\n%d recovery volumes found"
+#define   MRecVolAllExist    L"\nNothing to reconstruct"
+#define   MRecVolCannotFix   L"\nReconstruction impossible"
+#define   MReconstructing    L"\nReconstructing..."
+#define   MCreating          L"\nCreating %s"
+#define   MRenaming          L"\nRenaming %s to %s"
+#define   MNTFSRequired      L"\nWrite error: only NTFS file system supports files larger than 4 GB"
+#define   MFAT32Size         L"\nWARNING: FAT32 file system does not support 4 GB or larger files"
+#define   MErrChangeAttr     L"\nWARNING: Cannot change attributes of %s"
+#define   MWrongSFXVer       L"\nERROR: default SFX module does not support RAR %d.%d archives"
+#define   MCannotEncName     L"\nCannot encrypt archive already containing encrypted files"
+#define   MCannotEmail       L"\nCannot email the file %s"
+#define   MCopyrightS        L"\nRAR SFX archive" 
+#define   MSHelpCmd          L"\n\n<Commands>" 
+#define   MSHelpCmdE         L"\n  -x      Extract from archive (default)" 
+#define   MSHelpCmdT         L"\n  -t      Test archive files" 
+#define   MSHelpCmdV         L"\n  -v      Verbosely list contents of archive" 
+#define   MRecVolLimit       L"\nTotal number of usual and recovery volumes must not exceed %d"
+#define   MVolumeNumber      L"volume %d"
+#define   MCannotDelete      L"\nCannot delete %s"
+#define   MCalcCRC           L"\nCalculating the checksum"
+#define   MTooLargeSFXArc    L"\nWARNING: Too large SFX archive. Windows cannot run the executable file exceeding 4 GB."
+#define   MCalcCRCAllVol     L"\nCalculating checksums of all volumes."
+#define   MNotEnoughDisk     L"\nERROR: Not enough disk space for %s."
+#define   MNewerRAR          L"\nYou may need a newer version of RAR."
+#define   MUnkEncMethod      L"\nUnknown encryption method in %s"
+#define   MWrongPassword     L"\nThe specified password is incorrect."
+#define   MRepairing         L"\nRepairing"
+#define   MAreaDamaged       L"\nCorrupt %d bytes at %08x %08x"
+#define   MBlocksRecovered   L"\n%d blocks recovered."
+#define   MRRDamaged         L"\nRecovery record is corrupt."
+#define   MTestingRR         L"\nTesting the recovery record"
+#define   MFailed            L"Failed"
+#define   MIncompatSwitch    L"\n%s switch is not supported for RAR %d.x archive format."
+#define   MSearchDupFiles    L"\nSearching for identical files"
+#define   MNumFound          L"%d found."
+#define   MUnknownExtra      L"\nUnknown extra field in %s."
+#define   MCorruptExtra      L"\nCorrupt %s extra field in %s."
+#define   MCopyError         L"\nCannot copy %s to %s."
+#define   MCopyErrorHint     L"\nYou need to unpack the entire archive to create file reference entries."
+#define   MCopyingData       L"\nCopying data"
+#define   MErrCreateLnkS     L"\nCannot create symbolic link %s"
+#define   MErrCreateLnkH     L"\nCannot create hard link %s"
+#define   MNeedAdmin         L"\nYou may need to run RAR as administrator"
+#define   MDictOutMem        L"\nNot enough memory for %d MB compression dictionary, changed to %d MB."
+#define   MUseSmalllerDict   L"\nPlease use a smaller compression dictionary."
diff --git a/third_party/unrar/src/log.cpp b/third_party/unrar/src/log.cpp
new file mode 100644
index 0000000..8bbe8ee
--- /dev/null
+++ b/third_party/unrar/src/log.cpp
@@ -0,0 +1,37 @@
+#include "rar.hpp"
+
+
+static wchar LogName[NM];
+static RAR_CHARSET LogCharset=RCH_DEFAULT;
+
+void InitLogOptions(const wchar *LogFileName,RAR_CHARSET CSet)
+{
+  wcsncpyz(LogName,LogFileName,ASIZE(LogName));
+  LogCharset=CSet;
+}
+
+
+#ifndef SILENT
+void Log(const wchar *ArcName,const wchar *fmt,...)
+{
+  // Preserve the error code for possible following system error message.
+  int Code=ErrHandler.GetSystemErrorCode();
+
+  uiAlarm(UIALARM_ERROR);
+
+  // This buffer is for format string only, not for entire output,
+  // so it can be short enough.
+  wchar fmtw[1024];
+  PrintfPrepareFmt(fmt,fmtw,ASIZE(fmtw));
+
+  safebuf wchar Msg[2*NM+1024];
+  va_list arglist;
+  va_start(arglist,fmt);
+  vswprintf(Msg,ASIZE(Msg),fmtw,arglist);
+  va_end(arglist);
+  eprintf(L"%ls",Msg);
+  ErrHandler.SetSystemErrorCode(Code);
+}
+#endif
+
+
diff --git a/third_party/unrar/src/log.hpp b/third_party/unrar/src/log.hpp
new file mode 100644
index 0000000..008ef11a
--- /dev/null
+++ b/third_party/unrar/src/log.hpp
@@ -0,0 +1,12 @@
+#ifndef _RAR_LOG_
+#define _RAR_LOG_
+
+void InitLogOptions(const wchar *LogFileName,RAR_CHARSET CSet);
+
+#ifdef SILENT
+inline void Log(const wchar *ArcName,const wchar *fmt,...) {}
+#else
+void Log(const wchar *ArcName,const wchar *fmt,...);
+#endif
+
+#endif
diff --git a/third_party/unrar/src/makefile b/third_party/unrar/src/makefile
new file mode 100644
index 0000000..f70755f
--- /dev/null
+++ b/third_party/unrar/src/makefile
@@ -0,0 +1,173 @@
+#
+# Makefile for UNIX - unrar
+
+# Linux using GCC
+CXX=c++
+CXXFLAGS=-O2 -Wno-logical-op-parentheses -Wno-switch -Wno-dangling-else
+LIBFLAGS=-fPIC
+DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -DRAR_SMP
+STRIP=strip
+AR=ar
+LDFLAGS=-pthread
+DESTDIR=/usr
+
+# Linux using LCC
+#CXX=lcc
+#CXXFLAGS=-O2
+#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
+#STRIP=strip
+#AR=ar
+#DESTDIR=/usr
+
+# CYGWIN using GCC
+#CXX=c++
+#CXXFLAGS=-O2
+#LIBFLAGS=
+#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -DRAR_SMP
+#STRIP=strip
+#AR=ar
+#LDFLAGS=-pthread
+#DESTDIR=/usr
+
+# HP UX using aCC
+#CXX=aCC
+#CXXFLAGS=-AA +O2 +Onolimit
+#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
+#STRIP=strip
+#AR=ar
+#DESTDIR=/usr
+
+# IRIX using GCC
+#CXX=g++
+#CXXFLAGS=-O2 
+#DEFINES=-DBIG_ENDIAN -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_BSD_COMPAT -D_XOPEN_SOURCE -D_XOPEN_SOURCE_EXTENDED=1
+#STRIP=strip
+#AR=ar
+#DESTDIR=/usr
+
+# IRIX using MIPSPro (experimental)
+#CXX=CC
+#CXXFLAGS=-O2 -mips3 -woff 1234,1156,3284 -LANG:std
+#DEFINES=-DBIG_ENDIAN -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_BSD_COMPAT -Dint64=int64_t
+#STRIP=strip
+#AR=ar
+#DESTDIR=/usr
+
+# AIX using xlC (IBM VisualAge C++ 5.0)
+#CXX=xlC
+#CXXFLAGS=-O -qinline -qro -qroconst -qmaxmem=16384 -qcpluscmt
+#DEFINES=-D_LARGE_FILES -D_LARGE_FILE_API
+#LIBS=-lbsd
+#STRIP=strip
+#AR=ar
+#DESTDIR=/usr
+
+# Solaris using CC
+#CXX=CC
+#CXXFLAGS=-fast -erroff=wvarhidemem
+#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
+#STRIP=strip
+#AR=ar
+#DESTDIR=/usr
+
+# Solaris using GCC (optimized for UltraSPARC 1 CPU)
+#CXX=g++
+#CXXFLAGS=-O3 -mcpu=v9 -mtune=ultrasparc -m32
+#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
+#STRIP=/usr/ccs/bin/strip
+#AR=/usr/ccs/bin/ar
+#DESTDIR=/usr
+
+# Tru64 5.1B using GCC3
+#CXX=g++
+#CXXFLAGS=-O2 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_XOPEN_SOURCE=500
+#STRIP=strip
+#AR=ar
+#LDFLAGS=-rpath /usr/local/gcc/lib
+#DESTDIR=/usr
+
+# Tru64 5.1B using DEC C++
+#CXX=cxx
+#CXXFLAGS=-O4 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -Dint64=long
+#STRIP=strip
+#AR=ar
+#LDFLAGS=
+#DESTDIR=/usr
+
+# QNX 6.x using GCC
+#CXX=g++
+#CXXFLAGS=-O2 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -fexceptions
+#STRIP=strip
+#AR=ar
+#LDFLAGS=-fexceptions
+#DESTDIR=/usr
+
+# Cross-compile
+# Linux using arm-linux-g++
+#CXX=arm-linux-g++
+#CXXFLAGS=-O2
+#DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
+#STRIP=arm-linux-strip
+#AR=arm-linux-ar
+#LDFLAGS=-static
+#DESTDIR=/usr
+
+##########################
+
+COMPILE=$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFINES)
+LINK=$(CXX)
+
+WHAT=UNRAR
+
+UNRAR_OBJ=filestr.o recvol.o rs.o scantree.o qopen.o
+LIB_OBJ=filestr.o scantree.o dll.o qopen.o
+
+OBJECTS=rar.o strlist.o strfn.o pathfn.o smallfn.o global.o file.o filefn.o filcreat.o \
+	archive.o arcread.o unicode.o system.o isnt.o crypt.o crc.o rawread.o encname.o \
+	resource.o match.o timefn.o rdwrfn.o consio.o options.o errhnd.o rarvm.o secpassword.o \
+	rijndael.o getbits.o sha1.o sha256.o blake2s.o hash.o extinfo.o extract.o volume.o \
+  list.o find.o unpack.o headers.o threadpool.o rs16.o cmddata.o ui.o
+
+.cpp.o:
+	$(COMPILE) -D$(WHAT) -c $<
+
+all:	unrar
+
+install:	install-unrar
+
+uninstall:	uninstall-unrar
+
+clean:
+	@rm -f *.o *.bak *~
+
+unrar:	clean $(OBJECTS) $(UNRAR_OBJ)
+	@rm -f unrar
+	$(LINK) -o unrar $(LDFLAGS) $(OBJECTS) $(UNRAR_OBJ) $(LIBS)	
+	$(STRIP) unrar
+
+sfx:	WHAT=SFX_MODULE
+sfx:	clean $(OBJECTS)
+	@rm -f default.sfx
+	$(LINK) -o default.sfx $(LDFLAGS) $(OBJECTS)
+	$(STRIP) default.sfx
+
+lib:	WHAT=RARDLL
+lib:	CXXFLAGS+=$(LIBFLAGS)
+lib:	clean $(OBJECTS) $(LIB_OBJ)
+	@rm -f libunrar.so
+	@rm -f libunrar.a
+	$(LINK) -shared -o libunrar.so $(LDFLAGS) $(OBJECTS) $(LIB_OBJ)
+	$(AR) rcs libunrar.a $(OBJECTS) $(LIB_OBJ)
+
+install-unrar:
+			install -D unrar $(DESTDIR)/bin/unrar
+
+uninstall-unrar:
+			rm -f $(DESTDIR)/bin/unrar
+
+install-lib:
+		install libunrar.so $(DESTDIR)/lib
+		install libunrar.a $(DESTDIR)/lib
+
+uninstall-lib:
+		rm -f $(DESTDIR)/lib/libunrar.so
diff --git a/third_party/unrar/src/match.cpp b/third_party/unrar/src/match.cpp
new file mode 100644
index 0000000..4369a57
--- /dev/null
+++ b/third_party/unrar/src/match.cpp
@@ -0,0 +1,145 @@
+#include "rar.hpp"
+
+static bool match(const wchar *pattern,const wchar *string,bool ForceCase);
+static int mwcsicompc(const wchar *Str1,const wchar *Str2,bool ForceCase);
+static int mwcsnicompc(const wchar *Str1,const wchar *Str2,size_t N,bool ForceCase);
+
+inline uint touppercw(uint ch,bool ForceCase)
+{
+  if (ForceCase)
+    return ch;
+#if defined(_UNIX)
+  return ch;
+#else
+  return toupperw(ch);
+#endif
+}
+
+
+bool CmpName(const wchar *Wildcard,const wchar *Name,int CmpMode)
+{
+  bool ForceCase=(CmpMode&MATCH_FORCECASESENSITIVE)!=0;
+
+  CmpMode&=MATCH_MODEMASK;
+
+  if (CmpMode!=MATCH_NAMES)
+  {
+    size_t WildLength=wcslen(Wildcard);
+    if (CmpMode!=MATCH_EXACT && CmpMode!=MATCH_EXACTPATH &&
+        mwcsnicompc(Wildcard,Name,WildLength,ForceCase)==0)
+    {
+      // For all modes except MATCH_NAMES, MATCH_EXACT and MATCH_EXACTPATH
+      // "path1" mask must match "path1\path2\filename.ext" and "path1" names.
+      wchar NextCh=Name[WildLength];
+      if (NextCh==L'\\' || NextCh==L'/' || NextCh==0)
+        return(true);
+    }
+
+    // Nothing more to compare for MATCH_SUBPATHONLY.
+    if (CmpMode==MATCH_SUBPATHONLY)
+      return(false);
+
+    wchar Path1[NM],Path2[NM];
+    GetFilePath(Wildcard,Path1,ASIZE(Path1));
+    GetFilePath(Name,Path2,ASIZE(Path2));
+
+    if ((CmpMode==MATCH_EXACT || CmpMode==MATCH_EXACTPATH) &&
+        mwcsicompc(Path1,Path2,ForceCase)!=0)
+      return(false);
+    if (CmpMode==MATCH_SUBPATH || CmpMode==MATCH_WILDSUBPATH)
+      if (IsWildcard(Path1))
+        return(match(Wildcard,Name,ForceCase));
+      else
+        if (CmpMode==MATCH_SUBPATH || IsWildcard(Wildcard))
+        {
+          if (*Path1 && mwcsnicompc(Path1,Path2,wcslen(Path1),ForceCase)!=0)
+            return(false);
+        }
+        else
+          if (mwcsicompc(Path1,Path2,ForceCase)!=0)
+            return(false);
+  }
+  wchar *Name1=PointToName(Wildcard);
+  wchar *Name2=PointToName(Name);
+
+  // Always return false for RAR temporary files to exclude them
+  // from archiving operations.
+  if (mwcsnicompc(L"__rar_",Name2,6,false)==0)
+    return(false);
+
+  if (CmpMode==MATCH_EXACT)
+    return(mwcsicompc(Name1,Name2,ForceCase)==0);
+
+  return(match(Name1,Name2,ForceCase));
+}
+
+
+bool match(const wchar *pattern,const wchar *string,bool ForceCase)
+{
+  for (;; ++string)
+  {
+    wchar stringc=touppercw(*string,ForceCase);
+    wchar patternc=touppercw(*pattern++,ForceCase);
+    switch (patternc)
+    {
+      case 0:
+        return(stringc==0);
+      case '?':
+        if (stringc == 0)
+          return(false);
+        break;
+      case '*':
+        if (*pattern==0)
+          return(true);
+        if (*pattern=='.')
+        {
+          if (pattern[1]=='*' && pattern[2]==0)
+            return(true);
+          const wchar *dot=wcschr(string,'.');
+          if (pattern[1]==0)
+            return (dot==NULL || dot[1]==0);
+          if (dot!=NULL)
+          {
+            string=dot;
+            if (wcspbrk(pattern,L"*?")==NULL && wcschr(string+1,'.')==NULL)
+              return(mwcsicompc(pattern+1,string+1,ForceCase)==0);
+          }
+        }
+
+        while (*string)
+          if (match(pattern,string++,ForceCase))
+            return(true);
+        return(false);
+      default:
+        if (patternc != stringc)
+        {
+          // Allow "name." mask match "name" and "name.\" match "name\".
+          if (patternc=='.' && (stringc==0 || stringc=='\\' || stringc=='.'))
+            return(match(pattern,string,ForceCase));
+          else
+            return(false);
+        }
+        break;
+    }
+  }
+}
+
+
+int mwcsicompc(const wchar *Str1,const wchar *Str2,bool ForceCase)
+{
+  if (ForceCase)
+    return wcscmp(Str1,Str2);
+  return wcsicompc(Str1,Str2);
+}
+
+
+int mwcsnicompc(const wchar *Str1,const wchar *Str2,size_t N,bool ForceCase)
+{
+  if (ForceCase)
+    return wcsncmp(Str1,Str2,N);
+#if defined(_UNIX)
+  return wcsncmp(Str1,Str2,N);
+#else
+  return wcsnicomp(Str1,Str2,N);
+#endif
+}
diff --git a/third_party/unrar/src/match.hpp b/third_party/unrar/src/match.hpp
new file mode 100644
index 0000000..65493ff
--- /dev/null
+++ b/third_party/unrar/src/match.hpp
@@ -0,0 +1,34 @@
+#ifndef _RAR_MATCH_
+#define _RAR_MATCH_
+
+enum {
+   MATCH_NAMES,        // Paths are ignored.
+                       // Compares names only using wildcards.
+
+   MATCH_SUBPATHONLY,  // Paths must match either exactly or path in wildcard
+                       // must be present in the beginning of file path.
+                       // For example, "c:\path1\*" or "c:\path1" will match 
+                       // "c:\path1\path2\file".
+                       // Names are not compared.
+
+   MATCH_EXACT,        // Paths must match exactly.
+                       // Names must match exactly.
+
+   MATCH_EXACTPATH,    // Paths must match exactly.
+                       // Names are compared using wildcards.
+
+   MATCH_SUBPATH,      // Names must be the same, but path in mask is allowed
+                       // to be only a part of name path. In other words,
+                       // we match all files matching the file mask 
+                       // in current folder and subfolders.
+
+   MATCH_WILDSUBPATH   // Works as MATCH_SUBPATH if file mask contains
+                       // wildcards and as MATCH_EXACTPATH otherwise.
+};
+
+#define MATCH_MODEMASK           0x0000ffff
+#define MATCH_FORCECASESENSITIVE 0x80000000
+
+bool CmpName(const wchar *Wildcard,const wchar *Name,int CmpMode);
+
+#endif
diff --git a/third_party/unrar/src/model.cpp b/third_party/unrar/src/model.cpp
new file mode 100644
index 0000000..e91b44070
--- /dev/null
+++ b/third_party/unrar/src/model.cpp
@@ -0,0 +1,621 @@
+/****************************************************************************
+ *  This file is part of PPMd project                                       *
+ *  Written and distributed to public domain by Dmitry Shkarin 1997,        *
+ *  1999-2000                                                               *
+ *  Contents: model description and encoding/decoding routines              *
+ ****************************************************************************/
+
+static const int MAX_O=64; /* maximum allowed model order */
+const uint TOP=1 << 24, BOT=1 << 15;
+
+template <class T>
+inline void _PPMD_SWAP(T& t1,T& t2) { T tmp=t1; t1=t2; t2=tmp; }
+
+
+inline RARPPM_CONTEXT* RARPPM_CONTEXT::createChild(ModelPPM *Model,RARPPM_STATE* pStats,
+                                             RARPPM_STATE& FirstState)
+{
+  RARPPM_CONTEXT* pc = (RARPPM_CONTEXT*) Model->SubAlloc.AllocContext();
+  if ( pc ) 
+  {
+    pc->NumStats=1;                     
+    pc->OneState=FirstState;
+    pc->Suffix=this;                    
+    pStats->Successor=pc;
+  }
+  return pc;
+}
+
+
+ModelPPM::ModelPPM()
+{
+  MinContext=NULL;
+  MaxContext=NULL;
+  MedContext=NULL;
+}
+
+
+void ModelPPM::RestartModelRare()
+{
+  int i, k, m;
+  memset(CharMask,0,sizeof(CharMask));
+  SubAlloc.InitSubAllocator();
+  InitRL=-(MaxOrder < 12 ? MaxOrder:12)-1;
+  MinContext = MaxContext = (RARPPM_CONTEXT*) SubAlloc.AllocContext();
+  if (MinContext == NULL)
+    throw std::bad_alloc();
+  MinContext->Suffix=NULL;
+  OrderFall=MaxOrder;
+  MinContext->U.SummFreq=(MinContext->NumStats=256)+1;
+  FoundState=MinContext->U.Stats=(RARPPM_STATE*)SubAlloc.AllocUnits(256/2);
+  if (FoundState == NULL)
+    throw std::bad_alloc();
+  for (RunLength=InitRL, PrevSuccess=i=0;i < 256;i++) 
+  {
+    MinContext->U.Stats[i].Symbol=i;      
+    MinContext->U.Stats[i].Freq=1;
+    MinContext->U.Stats[i].Successor=NULL;
+  }
+  
+  static const ushort InitBinEsc[]={
+    0x3CDD,0x1F3F,0x59BF,0x48F3,0x64A1,0x5ABC,0x6632,0x6051
+  };
+
+  for (i=0;i < 128;i++)
+    for (k=0;k < 8;k++)
+      for (m=0;m < 64;m += 8)
+        BinSumm[i][k+m]=BIN_SCALE-InitBinEsc[k]/(i+2);
+  for (i=0;i < 25;i++)
+    for (k=0;k < 16;k++)            
+      SEE2Cont[i][k].init(5*i+10);
+}
+
+
+void ModelPPM::StartModelRare(int MaxOrder)
+{
+  int i, k, m ,Step;
+  EscCount=1;
+/*
+  if (MaxOrder < 2) 
+  {
+    memset(CharMask,0,sizeof(CharMask));
+    OrderFall=ModelPPM::MaxOrder;
+    MinContext=MaxContext;
+    while (MinContext->Suffix != NULL)
+    {
+      MinContext=MinContext->Suffix;
+      OrderFall--;
+    }
+    FoundState=MinContext->U.Stats;
+    MinContext=MaxContext;
+  } 
+  else 
+*/
+  {
+    ModelPPM::MaxOrder=MaxOrder;
+    RestartModelRare();
+    NS2BSIndx[0]=2*0;
+    NS2BSIndx[1]=2*1;
+    memset(NS2BSIndx+2,2*2,9);
+    memset(NS2BSIndx+11,2*3,256-11);
+    for (i=0;i < 3;i++)
+      NS2Indx[i]=i;
+    for (m=i, k=Step=1;i < 256;i++) 
+    {
+      NS2Indx[i]=m;
+      if ( !--k ) 
+      { 
+        k = ++Step;
+        m++; 
+      }
+    }
+    memset(HB2Flag,0,0x40);
+    memset(HB2Flag+0x40,0x08,0x100-0x40);
+    DummySEE2Cont.Shift=PERIOD_BITS;
+  }
+}
+
+
+void RARPPM_CONTEXT::rescale(ModelPPM *Model)
+{
+  int OldNS=NumStats, i=NumStats-1, Adder, EscFreq;
+  RARPPM_STATE* p1, * p;
+  for (p=Model->FoundState;p != U.Stats;p--)
+    _PPMD_SWAP(p[0],p[-1]);
+  U.Stats->Freq += 4;
+  U.SummFreq += 4;
+  EscFreq=U.SummFreq-p->Freq;
+  Adder=(Model->OrderFall != 0);
+  U.SummFreq = (p->Freq=(p->Freq+Adder) >> 1);
+  do 
+  {
+    EscFreq -= (++p)->Freq;
+    U.SummFreq += (p->Freq=(p->Freq+Adder) >> 1);
+    if (p[0].Freq > p[-1].Freq) 
+    {
+      RARPPM_STATE tmp=*(p1=p);
+      do 
+      { 
+        p1[0]=p1[-1]; 
+      } while (--p1 != U.Stats && tmp.Freq > p1[-1].Freq);
+      *p1=tmp;
+    }
+  } while ( --i );
+  if (p->Freq == 0) 
+  {
+    do 
+    { 
+      i++; 
+    } while ((--p)->Freq == 0);
+    EscFreq += i;
+    if ((NumStats -= i) == 1) 
+    {
+      RARPPM_STATE tmp=*U.Stats;
+      do 
+      { 
+        tmp.Freq-=(tmp.Freq >> 1); 
+        EscFreq>>=1; 
+      } while (EscFreq > 1);
+      Model->SubAlloc.FreeUnits(U.Stats,(OldNS+1) >> 1);
+      *(Model->FoundState=&OneState)=tmp;  return;
+    }
+  }
+  U.SummFreq += (EscFreq -= (EscFreq >> 1));
+  int n0=(OldNS+1) >> 1, n1=(NumStats+1) >> 1;
+  if (n0 != n1)
+    U.Stats = (RARPPM_STATE*) Model->SubAlloc.ShrinkUnits(U.Stats,n0,n1);
+  Model->FoundState=U.Stats;
+}
+
+
+inline RARPPM_CONTEXT* ModelPPM::CreateSuccessors(bool Skip,RARPPM_STATE* p1)
+{
+#ifdef __ICL
+  static
+#endif
+  RARPPM_STATE UpState;
+  RARPPM_CONTEXT* pc=MinContext, * UpBranch=FoundState->Successor;
+  RARPPM_STATE * p, * ps[MAX_O], ** pps=ps;
+  if ( !Skip ) 
+  {
+    *pps++ = FoundState;
+    if ( !pc->Suffix )
+      goto NO_LOOP;
+  }
+  if ( p1 ) 
+  {
+    p=p1;
+    pc=pc->Suffix;
+    goto LOOP_ENTRY;
+  }
+  do 
+  {
+    pc=pc->Suffix;
+    if (pc->NumStats != 1) 
+    {
+      if ((p=pc->U.Stats)->Symbol != FoundState->Symbol)
+        do 
+        {
+          p++; 
+        } while (p->Symbol != FoundState->Symbol);
+    } 
+    else
+      p=&(pc->OneState);
+LOOP_ENTRY:
+    if (p->Successor != UpBranch) 
+    {
+      pc=p->Successor;
+      break;
+    }
+    *pps++ = p;
+  } while ( pc->Suffix );
+NO_LOOP:
+  if (pps == ps)
+    return pc;
+  UpState.Symbol=*(byte*) UpBranch;
+  UpState.Successor=(RARPPM_CONTEXT*) (((byte*) UpBranch)+1);
+  if (pc->NumStats != 1) 
+  {
+    if ((byte*) pc <= SubAlloc.pText)
+      return(NULL);
+    if ((p=pc->U.Stats)->Symbol != UpState.Symbol)
+    do 
+    { 
+      p++; 
+    } while (p->Symbol != UpState.Symbol);
+    uint cf=p->Freq-1;
+    uint s0=pc->U.SummFreq-pc->NumStats-cf;
+    UpState.Freq=1+((2*cf <= s0)?(5*cf > s0):((2*cf+3*s0-1)/(2*s0)));
+  } 
+  else
+    UpState.Freq=pc->OneState.Freq;
+  do 
+  {
+    pc = pc->createChild(this,*--pps,UpState);
+    if ( !pc )
+      return NULL;
+  } while (pps != ps);
+  return pc;
+}
+
+
+inline void ModelPPM::UpdateModel()
+{
+  RARPPM_STATE fs = *FoundState, *p = NULL;
+  RARPPM_CONTEXT *pc, *Successor;
+  uint ns1, ns, cf, sf, s0;
+  if (fs.Freq < MAX_FREQ/4 && (pc=MinContext->Suffix) != NULL) 
+  {
+    if (pc->NumStats != 1) 
+    {
+      if ((p=pc->U.Stats)->Symbol != fs.Symbol) 
+      {
+        do 
+        { 
+          p++; 
+        } while (p->Symbol != fs.Symbol);
+        if (p[0].Freq >= p[-1].Freq) 
+        {
+          _PPMD_SWAP(p[0],p[-1]); 
+          p--;
+        }
+      }
+      if (p->Freq < MAX_FREQ-9) 
+      {
+        p->Freq += 2;               
+        pc->U.SummFreq += 2;
+      }
+    } 
+    else 
+    {
+      p=&(pc->OneState);
+      p->Freq += (p->Freq < 32);
+    }
+  }
+  if ( !OrderFall ) 
+  {
+    MinContext=MaxContext=FoundState->Successor=CreateSuccessors(TRUE,p);
+    if ( !MinContext )
+      goto RESTART_MODEL;
+    return;
+  }
+  *SubAlloc.pText++ = fs.Symbol;                   
+  Successor = (RARPPM_CONTEXT*) SubAlloc.pText;
+  if (SubAlloc.pText >= SubAlloc.FakeUnitsStart)                
+    goto RESTART_MODEL;
+  if ( fs.Successor ) 
+  {
+    if ((byte*) fs.Successor <= SubAlloc.pText &&
+        (fs.Successor=CreateSuccessors(FALSE,p)) == NULL)
+      goto RESTART_MODEL;
+    if ( !--OrderFall ) 
+    {
+      Successor=fs.Successor;
+      SubAlloc.pText -= (MaxContext != MinContext);
+    }
+  } 
+  else 
+  {
+    FoundState->Successor=Successor;
+    fs.Successor=MinContext;
+  }
+  s0=MinContext->U.SummFreq-(ns=MinContext->NumStats)-(fs.Freq-1);
+  for (pc=MaxContext;pc != MinContext;pc=pc->Suffix) 
+  {
+    if ((ns1=pc->NumStats) != 1) 
+    {
+      if ((ns1 & 1) == 0) 
+      {
+        pc->U.Stats=(RARPPM_STATE*) SubAlloc.ExpandUnits(pc->U.Stats,ns1 >> 1);
+        if ( !pc->U.Stats )           
+          goto RESTART_MODEL;
+      }
+      pc->U.SummFreq += (2*ns1 < ns)+2*((4*ns1 <= ns) & (pc->U.SummFreq <= 8*ns1));
+    } 
+    else 
+    {
+      p=(RARPPM_STATE*) SubAlloc.AllocUnits(1);
+      if ( !p )
+        goto RESTART_MODEL;
+      *p=pc->OneState;
+      pc->U.Stats=p;
+      if (p->Freq < MAX_FREQ/4-1)
+        p->Freq += p->Freq;
+      else
+        p->Freq  = MAX_FREQ-4;
+      pc->U.SummFreq=p->Freq+InitEsc+(ns > 3);
+    }
+    cf=2*fs.Freq*(pc->U.SummFreq+6);
+    sf=s0+pc->U.SummFreq;
+    if (cf < 6*sf) 
+    {
+      cf=1+(cf > sf)+(cf >= 4*sf);
+      pc->U.SummFreq += 3;
+    }
+    else 
+    {
+      cf=4+(cf >= 9*sf)+(cf >= 12*sf)+(cf >= 15*sf);
+      pc->U.SummFreq += cf;
+    }
+    p=pc->U.Stats+ns1;
+    p->Successor=Successor;
+    p->Symbol = fs.Symbol;
+    p->Freq = cf;
+    pc->NumStats=++ns1;
+  }
+  MaxContext=MinContext=fs.Successor;
+  return;
+RESTART_MODEL:
+  RestartModelRare();
+  EscCount=0;
+}
+
+
+// Tabulated escapes for exponential symbol distribution
+static const byte ExpEscape[16]={ 25,14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 };
+#define GET_MEAN(SUMM,SHIFT,ROUND) ((SUMM+(1 << (SHIFT-ROUND))) >> (SHIFT))
+
+
+
+inline void RARPPM_CONTEXT::decodeBinSymbol(ModelPPM *Model)
+{
+  RARPPM_STATE& rs=OneState;
+  Model->HiBitsFlag=Model->HB2Flag[Model->FoundState->Symbol];
+  ushort& bs=Model->BinSumm[rs.Freq-1][Model->PrevSuccess+
+           Model->NS2BSIndx[Suffix->NumStats-1]+
+           Model->HiBitsFlag+2*Model->HB2Flag[rs.Symbol]+
+           ((Model->RunLength >> 26) & 0x20)];
+  if (Model->Coder.GetCurrentShiftCount(TOT_BITS) < bs) 
+  {
+    Model->FoundState=&rs;
+    rs.Freq += (rs.Freq < 128);
+    Model->Coder.SubRange.LowCount=0;
+    Model->Coder.SubRange.HighCount=bs;
+    bs = GET_SHORT16(bs+INTERVAL-GET_MEAN(bs,PERIOD_BITS,2));
+    Model->PrevSuccess=1;
+    Model->RunLength++;
+  } 
+  else 
+  {
+    Model->Coder.SubRange.LowCount=bs;
+    bs = GET_SHORT16(bs-GET_MEAN(bs,PERIOD_BITS,2));
+    Model->Coder.SubRange.HighCount=BIN_SCALE;
+    Model->InitEsc=ExpEscape[bs >> 10];
+    Model->NumMasked=1;
+    Model->CharMask[rs.Symbol]=Model->EscCount;
+    Model->PrevSuccess=0;
+    Model->FoundState=NULL;
+  }
+}
+
+
+inline void RARPPM_CONTEXT::update1(ModelPPM *Model,RARPPM_STATE* p)
+{
+  (Model->FoundState=p)->Freq += 4;              
+  U.SummFreq += 4;
+  if (p[0].Freq > p[-1].Freq) 
+  {
+    _PPMD_SWAP(p[0],p[-1]);                   
+    Model->FoundState=--p;
+    if (p->Freq > MAX_FREQ)             
+      rescale(Model);
+  }
+}
+
+
+
+
+inline bool RARPPM_CONTEXT::decodeSymbol1(ModelPPM *Model)
+{
+  Model->Coder.SubRange.scale=U.SummFreq;
+  RARPPM_STATE* p=U.Stats;
+  int i, HiCnt;
+  int count=Model->Coder.GetCurrentCount();
+  if (count>=(int)Model->Coder.SubRange.scale)
+    return(false);
+  if (count < (HiCnt=p->Freq)) 
+  {
+    Model->PrevSuccess=(2*(Model->Coder.SubRange.HighCount=HiCnt) > Model->Coder.SubRange.scale);
+    Model->RunLength += Model->PrevSuccess;
+    (Model->FoundState=p)->Freq=(HiCnt += 4);
+    U.SummFreq += 4;
+    if (HiCnt > MAX_FREQ)
+      rescale(Model);
+    Model->Coder.SubRange.LowCount=0;
+    return(true);
+  }
+  else
+    if (Model->FoundState==NULL)
+      return(false);
+  Model->PrevSuccess=0;
+  i=NumStats-1;
+  while ((HiCnt += (++p)->Freq) <= count)
+    if (--i == 0) 
+    {
+      Model->HiBitsFlag=Model->HB2Flag[Model->FoundState->Symbol];
+      Model->Coder.SubRange.LowCount=HiCnt;
+      Model->CharMask[p->Symbol]=Model->EscCount;
+      i=(Model->NumMasked=NumStats)-1;
+      Model->FoundState=NULL;
+      do 
+      { 
+        Model->CharMask[(--p)->Symbol]=Model->EscCount; 
+      } while ( --i );
+      Model->Coder.SubRange.HighCount=Model->Coder.SubRange.scale;
+      return(true);
+    }
+  Model->Coder.SubRange.LowCount=(Model->Coder.SubRange.HighCount=HiCnt)-p->Freq;
+  update1(Model,p);
+  return(true);
+}
+
+
+inline void RARPPM_CONTEXT::update2(ModelPPM *Model,RARPPM_STATE* p)
+{
+  (Model->FoundState=p)->Freq += 4;              
+  U.SummFreq += 4;
+  if (p->Freq > MAX_FREQ)                 
+    rescale(Model);
+  Model->EscCount++;
+  Model->RunLength=Model->InitRL;
+}
+
+
+inline RARPPM_SEE2_CONTEXT* RARPPM_CONTEXT::makeEscFreq2(ModelPPM *Model,int Diff)
+{
+  RARPPM_SEE2_CONTEXT* psee2c;
+  if (NumStats != 256) 
+  {
+    psee2c=Model->SEE2Cont[Model->NS2Indx[Diff-1]]+
+           (Diff < Suffix->NumStats-NumStats)+
+           2*(U.SummFreq < 11*NumStats)+4*(Model->NumMasked > Diff)+
+           Model->HiBitsFlag;
+    Model->Coder.SubRange.scale=psee2c->getMean();
+  }
+  else 
+  {
+    psee2c=&Model->DummySEE2Cont;
+    Model->Coder.SubRange.scale=1;
+  }
+  return psee2c;
+}
+
+
+
+
+inline bool RARPPM_CONTEXT::decodeSymbol2(ModelPPM *Model)
+{
+  int count, HiCnt, i=NumStats-Model->NumMasked;
+  RARPPM_SEE2_CONTEXT* psee2c=makeEscFreq2(Model,i);
+  RARPPM_STATE* ps[256], ** pps=ps, * p=U.Stats-1;
+  HiCnt=0;
+  do 
+  {
+    do 
+    { 
+      p++; 
+    } while (Model->CharMask[p->Symbol] == Model->EscCount);
+    HiCnt += p->Freq;
+    *pps++ = p;
+  } while ( --i );
+  Model->Coder.SubRange.scale += HiCnt;
+  count=Model->Coder.GetCurrentCount();
+  if (count>=(int)Model->Coder.SubRange.scale)
+    return(false);
+  p=*(pps=ps);
+  if (count < HiCnt) 
+  {
+    HiCnt=0;
+    while ((HiCnt += p->Freq) <= count) 
+      p=*++pps;
+    Model->Coder.SubRange.LowCount = (Model->Coder.SubRange.HighCount=HiCnt)-p->Freq;
+    psee2c->update();
+    update2(Model,p);
+  }
+  else
+  {
+    Model->Coder.SubRange.LowCount=HiCnt;
+    Model->Coder.SubRange.HighCount=Model->Coder.SubRange.scale;
+    i=NumStats-Model->NumMasked;
+    pps--;
+    do 
+    { 
+      Model->CharMask[(*++pps)->Symbol]=Model->EscCount; 
+    } while ( --i );
+    psee2c->Summ += Model->Coder.SubRange.scale;
+    Model->NumMasked = NumStats;
+  }
+  return(true);
+}
+
+
+inline void ModelPPM::ClearMask()
+{
+  EscCount=1;                             
+  memset(CharMask,0,sizeof(CharMask));
+}
+
+
+
+
+// reset PPM variables after data error allowing safe resuming
+// of further data processing
+void ModelPPM::CleanUp()
+{
+  SubAlloc.StopSubAllocator();
+  SubAlloc.StartSubAllocator(1);
+  StartModelRare(2);
+}
+
+
+bool ModelPPM::DecodeInit(Unpack *UnpackRead,int &EscChar)
+{
+  int MaxOrder=UnpackRead->GetChar();
+  bool Reset=(MaxOrder & 0x20)!=0;
+
+  int MaxMB;
+  if (Reset)
+    MaxMB=UnpackRead->GetChar();
+  else
+    if (SubAlloc.GetAllocatedMemory()==0)
+      return(false);
+  if (MaxOrder & 0x40)
+    EscChar=UnpackRead->GetChar();
+  Coder.InitDecoder(UnpackRead);
+  if (Reset)
+  {
+    MaxOrder=(MaxOrder & 0x1f)+1;
+    if (MaxOrder>16)
+      MaxOrder=16+(MaxOrder-16)*3;
+    if (MaxOrder==1)
+    {
+      SubAlloc.StopSubAllocator();
+      return(false);
+    }
+    SubAlloc.StartSubAllocator(MaxMB+1);
+    StartModelRare(MaxOrder);
+  }
+  return(MinContext!=NULL);
+}
+
+
+int ModelPPM::DecodeChar()
+{
+  if ((byte*)MinContext <= SubAlloc.pText || (byte*)MinContext>SubAlloc.HeapEnd)
+    return(-1);
+  if (MinContext->NumStats != 1)      
+  {
+    if ((byte*)MinContext->U.Stats <= SubAlloc.pText || (byte*)MinContext->U.Stats>SubAlloc.HeapEnd)
+      return(-1);
+    if (!MinContext->decodeSymbol1(this))
+      return(-1);
+  }
+  else                                
+    MinContext->decodeBinSymbol(this);
+  Coder.Decode();
+  while ( !FoundState ) 
+  {
+    ARI_DEC_NORMALIZE(Coder.code,Coder.low,Coder.range,Coder.UnpackRead);
+    do
+    {
+      OrderFall++;                
+      MinContext=MinContext->Suffix;
+      if ((byte*)MinContext <= SubAlloc.pText || (byte*)MinContext>SubAlloc.HeapEnd)
+        return(-1);
+    } while (MinContext->NumStats == NumMasked);
+    if (!MinContext->decodeSymbol2(this))
+      return(-1);
+    Coder.Decode();
+  }
+  int Symbol=FoundState->Symbol;
+  if (!OrderFall && (byte*) FoundState->Successor > SubAlloc.pText)
+    MinContext=MaxContext=FoundState->Successor;
+  else
+  {
+    UpdateModel();
+    if (EscCount == 0)
+      ClearMask();
+  }
+  ARI_DEC_NORMALIZE(Coder.code,Coder.low,Coder.range,Coder.UnpackRead);
+  return(Symbol);
+}
diff --git a/third_party/unrar/src/model.hpp b/third_party/unrar/src/model.hpp
new file mode 100644
index 0000000..52abc89
--- /dev/null
+++ b/third_party/unrar/src/model.hpp
@@ -0,0 +1,122 @@
+#ifndef _RAR_PPMMODEL_
+#define _RAR_PPMMODEL_
+
+#include "coder.hpp"
+#include "suballoc.hpp"
+
+#ifdef ALLOW_MISALIGNED
+#pragma pack(1)
+#endif
+
+struct RARPPM_DEF
+{
+  static const int INT_BITS=7, PERIOD_BITS=7, TOT_BITS=INT_BITS+PERIOD_BITS,
+    INTERVAL=1 << INT_BITS, BIN_SCALE=1 << TOT_BITS, MAX_FREQ=124;
+};
+
+struct RARPPM_SEE2_CONTEXT : RARPPM_DEF
+{ // SEE-contexts for PPM-contexts with masked symbols
+  ushort Summ;
+  byte Shift, Count;
+  void init(int InitVal)
+  {
+    Summ=InitVal << (Shift=PERIOD_BITS-4);
+    Count=4;
+  }
+  uint getMean()
+  {
+    uint RetVal=GET_SHORT16(Summ) >> Shift;
+    Summ -= RetVal;
+    return RetVal+(RetVal == 0);
+  }
+  void update()
+  {
+    if (Shift < PERIOD_BITS && --Count == 0)
+    {
+      Summ += Summ;
+      Count=3 << Shift++;
+    }
+  }
+};
+
+
+class ModelPPM;
+struct RARPPM_CONTEXT;
+
+struct RARPPM_STATE
+{
+  byte Symbol;
+  byte Freq;
+  RARPPM_CONTEXT* Successor;
+};
+
+
+struct RARPPM_CONTEXT : RARPPM_DEF
+{
+    ushort NumStats;
+
+    struct FreqData
+    {
+      ushort SummFreq;
+      RARPPM_STATE RARPPM_PACK_ATTR * Stats;
+    };
+    
+    union
+    {
+      FreqData U;
+      RARPPM_STATE OneState;
+    };
+
+    RARPPM_CONTEXT* Suffix;
+    inline void encodeBinSymbol(ModelPPM *Model,int symbol);  // MaxOrder:
+    inline void encodeSymbol1(ModelPPM *Model,int symbol);    //  ABCD    context
+    inline void encodeSymbol2(ModelPPM *Model,int symbol);    //   BCD    suffix
+    inline void decodeBinSymbol(ModelPPM *Model);  //   BCDE   successor
+    inline bool decodeSymbol1(ModelPPM *Model);    // other orders:
+    inline bool decodeSymbol2(ModelPPM *Model);    //   BCD    context
+    inline void update1(ModelPPM *Model,RARPPM_STATE* p); //    CD    suffix
+    inline void update2(ModelPPM *Model,RARPPM_STATE* p); //   BCDE   successor
+    void rescale(ModelPPM *Model);
+    inline RARPPM_CONTEXT* createChild(ModelPPM *Model,RARPPM_STATE* pStats,RARPPM_STATE& FirstState);
+    inline RARPPM_SEE2_CONTEXT* makeEscFreq2(ModelPPM *Model,int Diff);
+};
+
+#ifdef ALLOW_MISALIGNED
+#ifdef _AIX
+#pragma pack(pop)
+#else
+#pragma pack()
+#endif
+#endif
+
+class ModelPPM : RARPPM_DEF
+{
+  private:
+    friend struct RARPPM_CONTEXT;
+    
+    RARPPM_SEE2_CONTEXT SEE2Cont[25][16], DummySEE2Cont;
+    
+    struct RARPPM_CONTEXT *MinContext, *MedContext, *MaxContext;
+    RARPPM_STATE* FoundState;      // found next state transition
+    int NumMasked, InitEsc, OrderFall, MaxOrder, RunLength, InitRL;
+    byte CharMask[256], NS2Indx[256], NS2BSIndx[256], HB2Flag[256];
+    byte EscCount, PrevSuccess, HiBitsFlag;
+    ushort BinSumm[128][64];               // binary SEE-contexts
+
+    RangeCoder Coder;
+    SubAllocator SubAlloc;
+
+    void RestartModelRare();
+    void StartModelRare(int MaxOrder);
+    inline RARPPM_CONTEXT* CreateSuccessors(bool Skip,RARPPM_STATE* p1);
+
+    inline void UpdateModel();
+    inline void ClearMask();
+  public:
+    ModelPPM();
+    void CleanUp(); // reset PPM variables after data error
+    bool DecodeInit(Unpack *UnpackRead,int &EscChar);
+    int DecodeChar();
+};
+
+#endif
diff --git a/third_party/unrar/src/options.cpp b/third_party/unrar/src/options.cpp
new file mode 100644
index 0000000..b53edc6
--- /dev/null
+++ b/third_party/unrar/src/options.cpp
@@ -0,0 +1,35 @@
+#include "rar.hpp"
+
+RAROptions::RAROptions()
+{
+  Init();
+}
+
+
+RAROptions::~RAROptions()
+{
+  // It is important for security reasons, so we do not have the unnecessary
+  // password data left in memory.
+  memset(this,0,sizeof(RAROptions));
+}
+
+
+void RAROptions::Init()
+{
+  memset(this,0,sizeof(RAROptions));
+  WinSize=0x2000000;
+  Overwrite=OVERWRITE_DEFAULT;
+  Method=3;
+  MsgStream=MSG_STDOUT;
+  ConvertNames=NAMES_ORIGINALCASE;
+  xmtime=EXTTIME_HIGH3;
+  FileSizeLess=INT64NDF;
+  FileSizeMore=INT64NDF;
+  HashType=HASH_CRC32;
+#ifdef RAR_SMP
+  Threads=GetNumberOfThreads();
+#endif
+#ifdef USE_QOPEN
+  QOpenMode=QOPEN_AUTO;
+#endif
+}
diff --git a/third_party/unrar/src/options.hpp b/third_party/unrar/src/options.hpp
new file mode 100644
index 0000000..781a59e
--- /dev/null
+++ b/third_party/unrar/src/options.hpp
@@ -0,0 +1,210 @@
+#ifndef _RAR_OPTIONS_
+#define _RAR_OPTIONS_
+
+#define DEFAULT_RECOVERY     -3
+
+#define DEFAULT_RECVOLUMES  -10
+
+#define VOLSIZE_AUTO   INT64NDF // Automatically detect the volume size.
+
+enum PATH_EXCL_MODE {
+  EXCL_UNCHANGED=0,    // Process paths as is (default).
+  EXCL_SKIPWHOLEPATH,  // -ep  (exclude the path completely)
+  EXCL_BASEPATH,       // -ep1 (exclude the base part of path)
+  EXCL_SAVEFULLPATH,   // -ep2 (the full path without the disk letter)
+  EXCL_ABSPATH,        // -ep3 (the full path with the disk letter)
+
+  EXCL_SKIPABSPATH     // Works as EXCL_BASEPATH for fully qualified paths
+                       // and as EXCL_UNCHANGED for relative paths.
+                       // Used by WinRAR GUI only.
+};
+
+enum {SOLID_NONE=0,SOLID_NORMAL=1,SOLID_COUNT=2,SOLID_FILEEXT=4,
+      SOLID_VOLUME_DEPENDENT=8,SOLID_VOLUME_INDEPENDENT=16};
+
+enum {ARCTIME_NONE=0,ARCTIME_KEEP,ARCTIME_LATEST};
+
+enum EXTTIME_MODE {
+  EXTTIME_NONE=0,EXTTIME_1S,EXTTIME_HIGH1,EXTTIME_HIGH2,EXTTIME_HIGH3
+};
+
+enum {NAMES_ORIGINALCASE=0,NAMES_UPPERCASE,NAMES_LOWERCASE};
+
+enum MESSAGE_TYPE {MSG_STDOUT=0,MSG_STDERR,MSG_ERRONLY,MSG_NULL};
+
+enum RECURSE_MODE 
+{
+  RECURSE_NONE=0,    // no recurse switches
+  RECURSE_DISABLE,   // switch -r-
+  RECURSE_ALWAYS,    // switch -r
+  RECURSE_WILDCARDS, // switch -r0
+};
+
+enum OVERWRITE_MODE 
+{
+  OVERWRITE_DEFAULT=0, // Ask when extracting, silently overwrite when archiving.
+  OVERWRITE_ALL,
+  OVERWRITE_NONE,
+  OVERWRITE_AUTORENAME,
+  OVERWRITE_FORCE_ASK
+};
+
+
+enum QOPEN_MODE { QOPEN_NONE, QOPEN_AUTO, QOPEN_ALWAYS };
+
+enum RAR_CHARSET { RCH_DEFAULT=0,RCH_ANSI,RCH_OEM,RCH_UNICODE,RCH_UTF8 };
+
+#define     MAX_FILTER_TYPES           16
+enum FilterState {FILTER_DEFAULT=0,FILTER_AUTO,FILTER_FORCE,FILTER_DISABLE};
+
+
+enum SAVECOPY_MODE {
+  SAVECOPY_NONE=0, SAVECOPY_SILENT, SAVECOPY_LIST, SAVECOPY_LISTEXIT,
+  SAVECOPY_DUPLISTEXIT
+};
+
+enum POWER_MODE {
+  POWERMODE_KEEP=0,POWERMODE_OFF,POWERMODE_HIBERNATE,POWERMODE_SLEEP
+};
+
+struct FilterMode
+{
+  FilterState State;
+  int Param1;
+  int Param2;
+};
+
+#define MAX_GENERATE_MASK  128
+
+
+class RAROptions
+{
+  public:
+    RAROptions();
+    ~RAROptions();
+    void Init();
+
+    uint ExclFileAttr;
+    uint InclFileAttr;
+    bool InclAttrSet;
+    size_t WinSize;
+    wchar TempPath[NM];
+    wchar SFXModule[NM];
+
+#ifdef USE_QOPEN
+    QOPEN_MODE QOpenMode;
+#endif
+
+    bool ArcInMem;
+#ifdef USE_ARCMEM
+    void SetArcInMem(byte *Data,size_t Size)
+    {
+      ArcMemData=Data;
+      ArcMemSize=Size;
+      ArcInMem=Data!=NULL && Size>0;
+    }
+    byte *ArcMemData;
+    size_t ArcMemSize;
+#endif
+
+    bool ConfigDisabled; // Switch -cfg-.
+    wchar ExtrPath[NM];
+    wchar CommentFile[NM];
+    RAR_CHARSET CommentCharset;
+    RAR_CHARSET FilelistCharset;
+    RAR_CHARSET ErrlogCharset;
+    RAR_CHARSET RedirectCharset;
+
+    wchar ArcPath[NM];
+    SecPassword Password;
+    bool EncryptHeaders;
+    
+    bool ManualPassword; // Password entered manually during operation, might need to clean for next archive.
+
+    wchar LogName[NM];
+    MESSAGE_TYPE MsgStream;
+    bool Sound;
+    OVERWRITE_MODE Overwrite;
+    int Method;
+    HASH_TYPE HashType;
+    int Recovery;
+    int RecVolNumber;
+    bool DisablePercentage;
+    bool DisableCopyright;
+    bool DisableDone;
+    bool PrintVersion;
+    int Solid;
+    int SolidCount;
+    bool ClearArc;
+    bool AddArcOnly;
+    bool DisableComment;
+    bool FreshFiles;
+    bool UpdateFiles;
+    PATH_EXCL_MODE ExclPath;
+    RECURSE_MODE Recurse;
+    int64 VolSize;
+    Array<int64> NextVolSizes;
+    uint CurVolNum;
+    bool AllYes;
+    bool MoreInfo; // -im, show more information, used only in "WinRAR t" now.
+    bool DisableSortSolid;
+    int ArcTime;
+    int ConvertNames;
+    bool ProcessOwners;
+    bool SaveSymLinks;
+    bool SaveHardLinks;
+    bool AbsoluteLinks;
+    int Priority;
+    int SleepTime;
+    bool KeepBroken;
+    bool OpenShared;
+    bool DeleteFiles;
+
+#ifdef _WIN_ALL
+    bool AllowIncompatNames; // Allow names with trailing dots and spaces.
+#endif
+
+
+#ifndef SFX_MODULE
+    bool GenerateArcName;
+    wchar GenerateMask[MAX_GENERATE_MASK];
+#endif
+    bool SyncFiles;
+    bool ProcessEA;
+    bool SaveStreams;
+    bool SetCompressedAttr;
+    bool IgnoreGeneralAttr;
+    RarTime FileTimeBefore;
+    RarTime FileTimeAfter;
+    int64 FileSizeLess;
+    int64 FileSizeMore;
+    bool Lock;
+    bool Test;
+    bool VolumePause;
+    FilterMode FilterModes[MAX_FILTER_TYPES];
+    wchar EmailTo[NM];
+    uint VersionControl;
+    bool AppendArcNameToPath;
+    POWER_MODE Shutdown;
+    EXTTIME_MODE xmtime;
+    EXTTIME_MODE xctime;
+    EXTTIME_MODE xatime;
+    wchar CompressStdin[NM];
+
+    uint Threads; // We use it to init hash even if RAR_SMP is not defined.
+
+
+
+
+
+#ifdef RARDLL
+    wchar DllDestName[NM];
+    int DllOpMode;
+    int DllError;
+    LPARAM UserData;
+    UNRARCALLBACK Callback;
+    CHANGEVOLPROC ChangeVolProc;
+    PROCESSDATAPROC ProcessDataProc;
+#endif
+};
+#endif
diff --git a/third_party/unrar/src/os.hpp b/third_party/unrar/src/os.hpp
new file mode 100644
index 0000000..f235f625
--- /dev/null
+++ b/third_party/unrar/src/os.hpp
@@ -0,0 +1,255 @@
+#ifndef _RAR_OS_
+#define _RAR_OS_
+
+#define FALSE 0
+#define TRUE  1
+
+#ifdef __EMX__
+  #define INCL_BASE
+#endif
+
+#if defined(RARDLL) && !defined(SILENT)
+#define SILENT
+#endif
+
+#include <new>
+
+
+#if defined(_WIN_ALL) || defined(_EMX)
+
+#define LITTLE_ENDIAN
+#define NM  2048
+
+#ifdef _WIN_ALL
+
+#define STRICT
+#define UNICODE
+#undef WINVER
+#undef _WIN32_WINNT
+#define WINVER 0x0501
+#define _WIN32_WINNT 0x0501
+
+#if !defined(ZIPSFX)
+#define RAR_SMP
+#endif
+
+#define WIN32_LEAN_AND_MEAN
+
+#include <windows.h>
+#include <prsht.h>
+#include <shlwapi.h>
+#pragma comment(lib, "Shlwapi.lib")
+#include <PowrProf.h>
+#pragma comment(lib, "PowrProf.lib")
+#include <shellapi.h>
+#include <shlobj.h>
+#include <winioctl.h>
+#include <wincrypt.h>
+#include <wchar.h>
+#include <wctype.h>
+
+
+#endif // _WIN_ALL
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dos.h>
+
+#if !defined(_EMX) && !defined(_MSC_VER)
+  #include <dir.h>
+#endif
+#ifdef _MSC_VER
+  #if _MSC_VER<1500
+    #define for if (0) ; else for
+  #endif
+  #include <direct.h>
+  #include <intrin.h>
+
+  #define USE_SSE
+  #define SSE_ALIGNMENT 16
+#else
+  #include <dirent.h>
+#endif // _MSC_VER
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <dos.h>
+#include <io.h>
+#include <time.h>
+#include <signal.h>
+
+
+#define SAVE_LINKS
+
+#define ENABLE_ACCESS
+
+#define DefConfigName  L"rar.ini"
+#define DefLogName     L"rar.log"
+
+
+#define SPATHDIVIDER L"\\"
+#define CPATHDIVIDER '\\'
+#define MASKALL      L"*"
+
+#define READBINARY   "rb"
+#define READTEXT     "rt"
+#define UPDATEBINARY "r+b"
+#define CREATEBINARY "w+b"
+#define WRITEBINARY  "wb"
+#define APPENDTEXT   "at"
+
+#if defined(_WIN_ALL)
+  #ifdef _MSC_VER
+    #define _stdfunction __cdecl
+    #define _forceinline __forceinline
+  #else
+    #define _stdfunction _USERENTRY
+    #define _forceinline inline
+  #endif
+#else
+  #define _stdfunction
+  #define _forceinline inline
+#endif
+
+#endif // defined(_WIN_ALL) || defined(_EMX)
+
+#ifdef _UNIX
+
+#define  NM  2048
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#if defined(__QNXNTO__)
+  #include <sys/param.h>
+#endif
+#if defined(RAR_SMP) && defined(__APPLE__)
+  #include <sys/sysctl.h>
+#endif
+#ifndef SFX_MODULE
+    #include <sys/statvfs.h>
+#endif
+#include <pwd.h>
+#include <grp.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <time.h>
+#include <signal.h>
+#include <utime.h>
+#include <locale.h>
+
+
+#ifdef  S_IFLNK
+#define SAVE_LINKS
+#endif
+
+#if defined(__linux) || defined(__FreeBSD__)
+#include <sys/time.h>
+#define USE_LUTIMES
+#endif
+
+#define ENABLE_ACCESS
+
+#define DefConfigName  L".rarrc"
+#define DefLogName     L".rarlog"
+
+
+#define SPATHDIVIDER L"/"
+#define CPATHDIVIDER '/'
+#define MASKALL      L"*"
+
+#define READBINARY   "r"
+#define READTEXT     "r"
+#define UPDATEBINARY "r+"
+#define CREATEBINARY "w+"
+#define WRITEBINARY  "w"
+#define APPENDTEXT   "a"
+
+#define _stdfunction 
+#define _forceinline inline
+
+#ifdef _APPLE
+  #if defined(__BIG_ENDIAN__) && !defined(BIG_ENDIAN)
+    #define BIG_ENDIAN
+    #undef LITTLE_ENDIAN
+  #endif
+  #if defined(__i386__) && !defined(LITTLE_ENDIAN)
+    #define LITTLE_ENDIAN
+    #undef BIG_ENDIAN
+  #endif
+#endif
+
+#if defined(__sparc) || defined(sparc) || defined(__hpux)
+  #ifndef BIG_ENDIAN
+     #define BIG_ENDIAN
+  #endif
+#endif
+
+#if _POSIX_C_SOURCE >= 200809L
+  #define UNIX_TIME_NS // Nanosecond time precision in Unix.
+#endif
+
+#endif // _UNIX
+
+#if 0
+  #define MSGID_INT
+  typedef int MSGID;
+#else
+  typedef const wchar* MSGID;
+#endif
+
+#ifndef SSE_ALIGNMENT // No SSE use and no special data alignment is required.
+  #define SSE_ALIGNMENT 1
+#endif
+
+#define safebuf static
+
+// Solaris defines _LITTLE_ENDIAN or _BIG_ENDIAN.
+#if defined(_LITTLE_ENDIAN) && !defined(LITTLE_ENDIAN)
+  #define LITTLE_ENDIAN
+#endif
+#if defined(_BIG_ENDIAN) && !defined(BIG_ENDIAN)
+  #define BIG_ENDIAN
+#endif
+
+#if !defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN)
+  #if defined(__i386) || defined(i386) || defined(__i386__) || defined(__x86_64)
+    #define LITTLE_ENDIAN
+  #elif defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN || defined(__LITTLE_ENDIAN__)
+    #define LITTLE_ENDIAN
+  #elif defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN || defined(__BIG_ENDIAN__)
+    #define BIG_ENDIAN
+  #else
+    #error "Neither LITTLE_ENDIAN nor BIG_ENDIAN are defined. Define one of them."
+  #endif
+#endif
+
+#if defined(LITTLE_ENDIAN) && defined(BIG_ENDIAN)
+  #if defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN
+    #undef LITTLE_ENDIAN
+  #elif defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN
+    #undef BIG_ENDIAN
+  #else
+    #error "Both LITTLE_ENDIAN and BIG_ENDIAN are defined. Undef one of them."
+  #endif
+#endif
+
+#if !defined(BIG_ENDIAN) && defined(_WIN_ALL) || defined(__i386__) || defined(__x86_64__)
+// Allow not aligned integer access, increases speed in some operations.
+#define ALLOW_MISALIGNED
+#endif
+
+#endif // _RAR_OS_
diff --git a/third_party/unrar/src/pathfn.cpp b/third_party/unrar/src/pathfn.cpp
new file mode 100644
index 0000000..28680672
--- /dev/null
+++ b/third_party/unrar/src/pathfn.cpp
@@ -0,0 +1,984 @@
+#include "rar.hpp"
+
+wchar* PointToName(const wchar *Path)
+{
+  for (int I=(int)wcslen(Path)-1;I>=0;I--)
+    if (IsPathDiv(Path[I]))
+      return (wchar*)&Path[I+1];
+  return (wchar*)((*Path && IsDriveDiv(Path[1])) ? Path+2:Path);
+}
+
+
+wchar* PointToLastChar(const wchar *Path)
+{
+  size_t Length=wcslen(Path);
+  return (wchar*)(Length>0 ? Path+Length-1:Path);
+}
+
+
+wchar* ConvertPath(const wchar *SrcPath,wchar *DestPath)
+{
+  const wchar *DestPtr=SrcPath;
+
+  // Prevent \..\ in any part of path string.
+  for (const wchar *s=DestPtr;*s!=0;s++)
+    if (IsPathDiv(s[0]) && s[1]=='.' && s[2]=='.' && IsPathDiv(s[3]))
+      DestPtr=s+4;
+
+  // Remove <d>:\ and any sequence of . and \ in the beginning of path string.
+  while (*DestPtr!=0)
+  {
+    const wchar *s=DestPtr;
+    if (s[0]!=0 && IsDriveDiv(s[1]))
+      s+=2;
+    if (s[0]=='\\' && s[1]=='\\')
+    {
+      const wchar *Slash=wcschr(s+2,'\\');
+      if (Slash!=NULL && (Slash=wcschr(Slash+1,'\\'))!=NULL)
+        s=Slash+1;
+    }
+    for (const wchar *t=s;*t!=0;t++)
+      if (IsPathDiv(*t))
+        s=t+1;
+      else
+        if (*t!='.')
+          break;
+    if (s==DestPtr)
+      break;
+    DestPtr=s;
+  }
+
+  // Code above does not remove last "..", doing here.
+  if (DestPtr[0]=='.' && DestPtr[1]=='.' && DestPtr[2]==0)
+    DestPtr+=2;
+  
+  if (DestPath!=NULL)
+  {
+    // SrcPath and DestPath can point to same memory area,
+    // so we use the temporary buffer for copying.
+    wchar TmpStr[NM];
+    wcsncpyz(TmpStr,DestPtr,ASIZE(TmpStr));
+    wcscpy(DestPath,TmpStr);
+  }
+  return (wchar *)DestPtr;
+}
+
+
+void SetName(wchar *FullName,const wchar *Name,size_t MaxSize)
+{
+  wchar *NamePtr=PointToName(FullName);
+  wcsncpyz(NamePtr,Name,MaxSize-(NamePtr-FullName));
+}
+
+
+void SetExt(wchar *Name,const wchar *NewExt,size_t MaxSize)
+{
+  if (Name==NULL || *Name==0)
+    return;
+  wchar *Dot=GetExt(Name);
+  if (Dot!=NULL)
+    *Dot=0;
+  if (NewExt!=NULL)
+  {
+    wcsncatz(Name,L".",MaxSize);
+    wcsncatz(Name,NewExt,MaxSize);
+  }
+}
+
+
+#ifndef SFX_MODULE
+void SetSFXExt(wchar *SFXName,size_t MaxSize)
+{
+  if (SFXName==NULL || *SFXName==0)
+    return;
+
+#ifdef _UNIX
+  SetExt(SFXName,L"sfx",MaxSize);
+#endif
+
+#if defined(_WIN_ALL) || defined(_EMX)
+  SetExt(SFXName,L"exe",MaxSize);
+#endif
+}
+#endif
+
+
+// 'Ext' is an extension with the leading dot, like L".rar".
+wchar *GetExt(const wchar *Name)
+{
+  return Name==NULL ? NULL:wcsrchr(PointToName(Name),'.');
+}
+
+
+// 'Ext' is an extension without the leading dot, like L"rar".
+bool CmpExt(const wchar *Name,const wchar *Ext)
+{
+  wchar *NameExt=GetExt(Name);
+  return NameExt!=NULL && wcsicomp(NameExt+1,Ext)==0;
+}
+
+
+bool IsWildcard(const wchar *Str)
+{
+  return Str==NULL ? false:wcspbrk(Str,L"*?")!=NULL;
+}
+
+
+bool IsPathDiv(int Ch)
+{
+#ifdef _WIN_ALL
+  return Ch=='\\' || Ch=='/';
+#else
+  return Ch==CPATHDIVIDER;
+#endif
+}
+
+
+bool IsDriveDiv(int Ch)
+{
+#ifdef _UNIX
+  return false;
+#else
+  return Ch==':';
+#endif
+}
+
+
+bool IsDriveLetter(const wchar *Path)
+{
+  wchar Letter=etoupperw(Path[0]);
+  return Letter>='A' && Letter<='Z' && IsDriveDiv(Path[1]);
+}
+
+
+int GetPathDisk(const wchar *Path)
+{
+  if (IsDriveLetter(Path))
+    return etoupperw(*Path)-'A';
+  else
+    return -1;
+}
+
+
+void AddEndSlash(wchar *Path,size_t MaxLength)
+{
+  size_t Length=wcslen(Path);
+  if (Length>0 && Path[Length-1]!=CPATHDIVIDER && Length+1<MaxLength)
+    wcscat(Path,SPATHDIVIDER);
+}
+
+
+void MakeName(const wchar *Path,const wchar *Name,wchar *Pathname,size_t MaxSize)
+{
+  // 'Name' and 'Pathname' can point to same memory area. This is why we use
+  // the temporary buffer instead of constructing the name in 'Pathname'.
+  wchar OutName[NM];
+  wcsncpyz(OutName,Path,ASIZE(OutName));
+  AddEndSlash(OutName,ASIZE(OutName));
+  wcsncatz(OutName,Name,ASIZE(OutName));
+  wcsncpyz(Pathname,OutName,MaxSize);
+}
+
+
+// Returns file path including the trailing path separator symbol.
+void GetFilePath(const wchar *FullName,wchar *Path,size_t MaxLength)
+{
+  if (MaxLength==0)
+    return;
+  size_t PathLength=Min(MaxLength-1,size_t(PointToName(FullName)-FullName));
+  wcsncpy(Path,FullName,PathLength);
+  Path[PathLength]=0;
+}
+
+
+// Removes name and returns file path without the trailing
+// path separator symbol.
+void RemoveNameFromPath(wchar *Path)
+{
+  wchar *Name=PointToName(Path);
+  if (Name>=Path+2 && (!IsDriveDiv(Path[1]) || Name>=Path+4))
+    Name--;
+  *Name=0;
+}
+
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+bool GetAppDataPath(wchar *Path,size_t MaxSize,bool Create)
+{
+  LPMALLOC g_pMalloc;
+  SHGetMalloc(&g_pMalloc);
+  LPITEMIDLIST ppidl;
+  *Path=0;
+  bool Success=false;
+  if (SHGetSpecialFolderLocation(NULL,CSIDL_APPDATA,&ppidl)==NOERROR &&
+      SHGetPathFromIDList(ppidl,Path) && *Path!=0)
+  {
+    AddEndSlash(Path,MaxSize);
+    wcsncatz(Path,L"WinRAR",MaxSize);
+    Success=FileExist(Path);
+    if (!Success && Create)
+      Success=MakeDir(Path,false,0)==MKDIR_SUCCESS;
+  }
+  g_pMalloc->Free(ppidl);
+  return Success;
+}
+#endif
+
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+void GetRarDataPath(wchar *Path,size_t MaxSize,bool Create)
+{
+  *Path=0;
+
+  HKEY hKey;
+  if (RegOpenKeyEx(HKEY_CURRENT_USER,L"Software\\WinRAR\\Paths",0,
+                   KEY_QUERY_VALUE,&hKey)==ERROR_SUCCESS)
+  {
+    DWORD DataSize=(DWORD)MaxSize,Type;
+    RegQueryValueEx(hKey,L"AppData",0,&Type,(BYTE *)Path,&DataSize);
+    RegCloseKey(hKey);
+  }
+
+  if (*Path==0 || !FileExist(Path))
+    if (!GetAppDataPath(Path,MaxSize,Create))
+    {
+      GetModuleFileName(NULL,Path,(DWORD)MaxSize);
+      RemoveNameFromPath(Path);
+    }
+}
+#endif
+
+
+#ifndef SFX_MODULE
+bool EnumConfigPaths(uint Number,wchar *Path,size_t MaxSize,bool Create)
+{
+#ifdef _UNIX
+  static const wchar *ConfPath[]={
+    L"/etc", L"/etc/rar", L"/usr/lib", L"/usr/local/lib", L"/usr/local/etc"
+  };
+  if (Number==0)
+  {
+    char *EnvStr=getenv("HOME");
+    if (EnvStr!=NULL)
+      CharToWide(EnvStr,Path,MaxSize);
+    else
+      wcsncpyz(Path,ConfPath[0],MaxSize);
+    return true;
+  }
+  Number--;
+  if (Number>=ASIZE(ConfPath))
+    return false;
+  wcsncpyz(Path,ConfPath[Number], MaxSize);
+  return true;
+#elif defined(_WIN_ALL)
+  if (Number>1)
+    return false;
+  if (Number==0)
+    GetRarDataPath(Path,MaxSize,Create);
+  else
+  {
+    GetModuleFileName(NULL,Path,(DWORD)MaxSize);
+    RemoveNameFromPath(Path);
+  }
+  return true;
+#else
+  return false;
+#endif
+}
+#endif
+
+
+#ifndef SFX_MODULE
+void GetConfigName(const wchar *Name,wchar *FullName,size_t MaxSize,bool CheckExist,bool Create)
+{
+  *FullName=0;
+  for (uint I=0;EnumConfigPaths(I,FullName,MaxSize,Create);I++)
+  {
+    AddEndSlash(FullName,MaxSize);
+    wcsncatz(FullName,Name,MaxSize);
+    if (!CheckExist || WildFileExist(FullName))
+      break;
+  }
+}
+#endif
+
+
+// Returns a pointer to rightmost digit of volume number.
+wchar* GetVolNumPart(const wchar *ArcName)
+{
+  // Pointing to last name character.
+  const wchar *ChPtr=ArcName+wcslen(ArcName)-1;
+
+  // Skipping the archive extension.
+  while (!IsDigit(*ChPtr) && ChPtr>ArcName)
+    ChPtr--;
+
+  // Skipping the numeric part of name.
+  const wchar *NumPtr=ChPtr;
+  while (IsDigit(*NumPtr) && NumPtr>ArcName)
+    NumPtr--;
+
+  // Searching for first numeric part in names like name.part##of##.rar.
+  // Stop search on the first dot.
+  while (NumPtr>ArcName && *NumPtr!='.')
+  {
+    if (IsDigit(*NumPtr))
+    {
+      // Validate the first numeric part only if it has a dot somewhere 
+      // before it.
+      wchar *Dot=wcschr(PointToName(ArcName),'.');
+      if (Dot!=NULL && Dot<NumPtr)
+        ChPtr=NumPtr;
+      break;
+    }
+    NumPtr--;
+  }
+  return (wchar *)ChPtr;
+}
+
+
+void NextVolumeName(wchar *ArcName,uint MaxLength,bool OldNumbering)
+{
+  wchar *ChPtr;
+  if ((ChPtr=GetExt(ArcName))==NULL)
+  {
+    wcsncatz(ArcName,L".rar",MaxLength);
+    ChPtr=GetExt(ArcName);
+  }
+  else
+    if (ChPtr[1]==0 && wcslen(ArcName)<MaxLength-3 || wcsicomp(ChPtr+1,L"exe")==0 || wcsicomp(ChPtr+1,L"sfx")==0)
+      wcscpy(ChPtr+1,L"rar");
+  if (!OldNumbering)
+  {
+    ChPtr=GetVolNumPart(ArcName);
+
+    while ((++(*ChPtr))=='9'+1)
+    {
+      *ChPtr='0';
+      ChPtr--;
+      if (ChPtr<ArcName || !IsDigit(*ChPtr))
+      {
+        for (wchar *EndPtr=ArcName+wcslen(ArcName);EndPtr!=ChPtr;EndPtr--)
+          *(EndPtr+1)=*EndPtr;
+        *(ChPtr+1)='1';
+        break;
+      }
+    }
+  }
+  else
+    if (!IsDigit(*(ChPtr+2)) || !IsDigit(*(ChPtr+3)))
+      wcscpy(ChPtr+2,L"00");
+    else
+    {
+      ChPtr+=3;
+      while ((++(*ChPtr))=='9'+1)
+        if (*(ChPtr-1)=='.')
+        {
+          *ChPtr='A';
+          break;
+        }
+        else
+        {
+          *ChPtr='0';
+          ChPtr--;
+        }
+    }
+}
+
+
+bool IsNameUsable(const wchar *Name)
+{
+#ifndef _UNIX
+  if (Name[0] && Name[1] && wcschr(Name+2,':')!=NULL)
+    return false;
+  for (const wchar *s=Name;*s!=0;s++)
+  {
+    if ((uint)*s<32)
+      return false;
+    if ((*s==' ' || *s=='.') && IsPathDiv(s[1]))
+      return false;
+  }
+#endif
+  return *Name!=0 && wcspbrk(Name,L"?*<>|\"")==NULL;
+}
+
+
+void MakeNameUsable(char *Name,bool Extended)
+{
+#ifdef _WIN_ALL
+  // In Windows we also need to convert characters not defined in current
+  // code page. This double conversion changes them to '?', which is
+  // catched by code below.
+  size_t NameLength=strlen(Name);
+  wchar NameW[NM];
+  CharToWide(Name,NameW,ASIZE(NameW));
+  WideToChar(NameW,Name,NameLength+1);
+  Name[NameLength]=0;
+#endif
+  for (char *s=Name;*s!=0;s=charnext(s))
+  {
+    if (strchr(Extended ? "?*<>|\"":"?*",*s)!=NULL || Extended && (byte)*s<32)
+      *s='_';
+#ifdef _EMX
+    if (*s=='=')
+      *s='_';
+#endif
+#ifndef _UNIX
+    if (s-Name>1 && *s==':')
+      *s='_';
+    // Remove ' ' and '.' before path separator, but allow .\ and ..\.
+    if ((*s==' ' || *s=='.' && s>Name && !IsPathDiv(s[-1]) && s[-1]!='.') && IsPathDiv(s[1]))
+      *s='_';
+#endif
+  }
+}
+
+
+void MakeNameUsable(wchar *Name,bool Extended)
+{
+  for (wchar *s=Name;*s!=0;s++)
+  {
+    if (wcschr(Extended ? L"?*<>|\"":L"?*",*s)!=NULL || Extended && (uint)*s<32)
+      *s='_';
+#ifndef _UNIX
+    if (s-Name>1 && *s==':')
+      *s='_';
+#if 0  // We already can create such files.
+    // Remove ' ' and '.' before path separator, but allow .\ and ..\.
+    if (IsPathDiv(s[1]) && (*s==' ' || *s=='.' && s>Name &&
+        !IsPathDiv(s[-1]) && (s[-1]!='.' || s>Name+1 && !IsPathDiv(s[-2]))))
+      *s='_';
+#endif
+#endif
+  }
+}
+
+
+void UnixSlashToDos(const char *SrcName,char *DestName,size_t MaxLength)
+{
+  size_t Copied=0;
+  for (;Copied<MaxLength-1 && SrcName[Copied]!=0;Copied++)
+    DestName[Copied]=SrcName[Copied]=='/' ? '\\':SrcName[Copied];
+  DestName[Copied]=0;
+}
+
+
+void DosSlashToUnix(const char *SrcName,char *DestName,size_t MaxLength)
+{
+  size_t Copied=0;
+  for (;Copied<MaxLength-1 && SrcName[Copied]!=0;Copied++)
+    DestName[Copied]=SrcName[Copied]=='\\' ? '/':SrcName[Copied];
+  DestName[Copied]=0;
+}
+
+
+void UnixSlashToDos(const wchar *SrcName,wchar *DestName,size_t MaxLength)
+{
+  size_t Copied=0;
+  for (;Copied<MaxLength-1 && SrcName[Copied]!=0;Copied++)
+    DestName[Copied]=SrcName[Copied]=='/' ? '\\':SrcName[Copied];
+  DestName[Copied]=0;
+}
+
+
+void DosSlashToUnix(const wchar *SrcName,wchar *DestName,size_t MaxLength)
+{
+  size_t Copied=0;
+  for (;Copied<MaxLength-1 && SrcName[Copied]!=0;Copied++)
+    DestName[Copied]=SrcName[Copied]=='\\' ? '/':SrcName[Copied];
+  DestName[Copied]=0;
+}
+
+
+void ConvertNameToFull(const wchar *Src,wchar *Dest,size_t MaxSize)
+{
+  if (Src==NULL || *Src==0)
+  {
+    if (MaxSize>0)
+      *Dest=0;
+    return;
+  }
+#ifdef _WIN_ALL
+  {
+    wchar FullName[NM],*NamePtr;
+    DWORD Code=GetFullPathName(Src,ASIZE(FullName),FullName,&NamePtr);
+    if (Code==0 || Code>ASIZE(FullName))
+    {
+      wchar LongName[NM];
+      if (GetWinLongPath(Src,LongName,ASIZE(LongName)))
+        Code=GetFullPathName(LongName,ASIZE(FullName),FullName,&NamePtr);
+    }
+    if (Code!=0 && Code<ASIZE(FullName))
+      wcsncpyz(Dest,FullName,MaxSize);
+    else
+      if (Src!=Dest)
+        wcsncpyz(Dest,Src,MaxSize);
+  }
+#elif defined(_UNIX)
+  if (IsFullPath(Src))
+    *Dest=0;
+  else
+  {
+    char CurDirA[NM];
+    if (getcwd(CurDirA,ASIZE(CurDirA))==NULL)
+      *CurDirA=0;
+    CharToWide(CurDirA,Dest,MaxSize);
+    AddEndSlash(Dest,MaxSize);
+  }
+  wcsncatz(Dest,Src,MaxSize);
+#else
+  wcsncpyz(Dest,Src,MaxSize);
+#endif
+}
+
+
+bool IsFullPath(const wchar *Path)
+{
+/*
+  wchar PathOnly[NM];
+  GetFilePath(Path,PathOnly,ASIZE(PathOnly));
+  if (IsWildcard(PathOnly))
+    return true;
+*/
+#if defined(_WIN_ALL) || defined(_EMX)
+  return Path[0]=='\\' && Path[1]=='\\' || IsDriveLetter(Path) && IsPathDiv(Path[2]);
+#else
+  return IsPathDiv(Path[0]);
+#endif
+}
+
+
+bool IsFullRootPath(const wchar *Path)
+{
+  return IsFullPath(Path) || IsPathDiv(Path[0]);
+}
+
+
+void GetPathRoot(const wchar *Path,wchar *Root,size_t MaxSize)
+{
+  *Root=0;
+  if (IsDriveLetter(Path))
+    swprintf(Root,MaxSize,L"%c:\\",*Path);
+  else
+    if (Path[0]=='\\' && Path[1]=='\\')
+    {
+      const wchar *Slash=wcschr(Path+2,'\\');
+      if (Slash!=NULL)
+      {
+        size_t Length;
+        if ((Slash=wcschr(Slash+1,'\\'))!=NULL)
+          Length=Slash-Path+1;
+        else
+          Length=wcslen(Path);
+        if (Length>=MaxSize)
+          Length=0;
+        wcsncpy(Root,Path,Length);
+        Root[Length]=0;
+      }
+    }
+}
+
+
+int ParseVersionFileName(wchar *Name,bool Truncate)
+{
+  int Version=0;
+  wchar *VerText=wcsrchr(Name,';');
+  if (VerText!=NULL)
+  {
+    if (Version==0)
+      Version=atoiw(VerText+1);
+    if (Truncate)
+      *VerText=0;
+  }
+  return Version;
+}
+
+
+#if !defined(SFX_MODULE)
+// Get the name of first volume. Return the leftmost digit of volume number.
+wchar* VolNameToFirstName(const wchar *VolName,wchar *FirstName,size_t MaxSize,bool NewNumbering)
+{
+  if (FirstName!=VolName)
+    wcsncpyz(FirstName,VolName,MaxSize);
+  wchar *VolNumStart=FirstName;
+  if (NewNumbering)
+  {
+    wchar N='1';
+
+    // From the rightmost digit of volume number to the left.
+    for (wchar *ChPtr=GetVolNumPart(FirstName);ChPtr>FirstName;ChPtr--)
+      if (IsDigit(*ChPtr))
+      {
+        *ChPtr=N; // Set the rightmost digit to '1' and others to '0'.
+        N='0';
+      }
+      else
+        if (N=='0')
+        {
+          VolNumStart=ChPtr+1; // Store the position of leftmost digit in volume number.
+          break;
+        }
+  }
+  else
+  {
+    // Old volume numbering scheme. Just set the extension to ".rar".
+    SetExt(FirstName,L"rar",MaxSize);
+    VolNumStart=GetExt(FirstName);
+  }
+  if (!FileExist(FirstName))
+  {
+    // If the first volume, which name we just generated, is not exist,
+    // check if volume with same name and any other extension is available.
+    // It can help in case of *.exe or *.sfx first volume.
+    wchar Mask[NM];
+    wcsncpyz(Mask,FirstName,ASIZE(Mask));
+    SetExt(Mask,L"*",ASIZE(Mask));
+    FindFile Find;
+    Find.SetMask(Mask);
+    FindData FD;
+    while (Find.Next(&FD))
+    {
+      Archive Arc;
+      if (Arc.Open(FD.Name,0) && Arc.IsArchive(true) && Arc.FirstVolume)
+      {
+        wcsncpyz(FirstName,FD.Name,MaxSize);
+        break;
+      }
+    }
+  }
+  return VolNumStart;
+}
+#endif
+
+
+#ifndef SFX_MODULE
+static void GenArcName(wchar *ArcName,const wchar *GenerateMask,uint ArcNumber,bool &ArcNumPresent)
+{
+  bool Prefix=false;
+  if (*GenerateMask=='+')
+  {
+    Prefix=true;    // Add the time string before the archive name.
+    GenerateMask++; // Skip '+' in the beginning of time mask.
+  }
+
+  wchar Mask[MAX_GENERATE_MASK];
+  wcsncpyz(Mask,*GenerateMask!=0 ? GenerateMask:L"yyyymmddhhmmss",ASIZE(Mask));
+
+  bool QuoteMode=false,Hours=false;
+  for (uint I=0;Mask[I]!=0;I++)
+  {
+    if (Mask[I]=='{' || Mask[I]=='}')
+    {
+      QuoteMode=(Mask[I]=='{');
+      continue;
+    }
+    if (QuoteMode)
+      continue;
+    int CurChar=toupperw(Mask[I]);
+    if (CurChar=='H')
+      Hours=true;
+
+    if (Hours && CurChar=='M')
+    {
+      // Replace minutes with 'I'. We use 'M' both for months and minutes,
+      // so we treat as minutes only those 'M' which are found after hours.
+      Mask[I]='I';
+    }
+    if (CurChar=='N')
+    {
+      uint Digits=GetDigits(ArcNumber);
+      uint NCount=0;
+      while (toupperw(Mask[I+NCount])=='N')
+        NCount++;
+
+      // Here we ensure that we have enough 'N' characters to fit all digits
+      // of archive number. We'll replace them by actual number later
+      // in this function.
+      if (NCount<Digits)
+      {
+        wmemmove(Mask+I+Digits,Mask+I+NCount,wcslen(Mask+I+NCount)+1);
+        wmemset(Mask+I,'N',Digits);
+      }
+      I+=Max(Digits,NCount)-1;
+      ArcNumPresent=true;
+      continue;
+    }
+  }
+
+  RarTime CurTime;
+  CurTime.SetCurrentTime();
+  RarLocalTime rlt;
+  CurTime.GetLocal(&rlt);
+
+  wchar Ext[NM],*Dot=GetExt(ArcName);
+  *Ext=0;
+  if (Dot==NULL)
+    wcscpy(Ext,*PointToName(ArcName)==0 ? L".rar":L"");
+  else
+  {
+    wcsncpyz(Ext,Dot,ASIZE(Ext));
+    *Dot=0;
+  }
+
+  int WeekDay=rlt.wDay==0 ? 6:rlt.wDay-1;
+  int StartWeekDay=rlt.yDay-WeekDay;
+  if (StartWeekDay<0)
+    if (StartWeekDay<=-4)
+      StartWeekDay+=IsLeapYear(rlt.Year-1) ? 366:365;
+    else
+      StartWeekDay=0;
+  int CurWeek=StartWeekDay/7+1;
+  if (StartWeekDay%7>=4)
+    CurWeek++;
+
+  char Field[10][6];
+
+  sprintf(Field[0],"%04u",rlt.Year);
+  sprintf(Field[1],"%02u",rlt.Month);
+  sprintf(Field[2],"%02u",rlt.Day);
+  sprintf(Field[3],"%02u",rlt.Hour);
+  sprintf(Field[4],"%02u",rlt.Minute);
+  sprintf(Field[5],"%02u",rlt.Second);
+  sprintf(Field[6],"%02u",(uint)CurWeek);
+  sprintf(Field[7],"%u",(uint)WeekDay+1);
+  sprintf(Field[8],"%03u",rlt.yDay+1);
+  sprintf(Field[9],"%05u",ArcNumber);
+
+  const wchar *MaskChars=L"YMDHISWAEN";
+
+  int CField[sizeof(Field)/sizeof(Field[0])];
+  memset(CField,0,sizeof(CField));
+  QuoteMode=false;
+  for (int I=0;Mask[I]!=0;I++)
+  {
+    if (Mask[I]=='{' || Mask[I]=='}')
+    {
+      QuoteMode=(Mask[I]=='{');
+      continue;
+    }
+    if (QuoteMode)
+      continue;
+    const wchar *ChPtr=wcschr(MaskChars,toupperw(Mask[I]));
+    if (ChPtr!=NULL)
+      CField[ChPtr-MaskChars]++;
+   }
+
+  wchar DateText[MAX_GENERATE_MASK];
+  *DateText=0;
+  QuoteMode=false;
+  for (size_t I=0,J=0;Mask[I]!=0 && J<ASIZE(DateText)-1;I++)
+  {
+    if (Mask[I]=='{' || Mask[I]=='}')
+    {
+      QuoteMode=(Mask[I]=='{');
+      continue;
+    }
+    const wchar *ChPtr=wcschr(MaskChars,toupperw(Mask[I]));
+    if (ChPtr==NULL || QuoteMode)
+    {
+      DateText[J]=Mask[I];
+#ifdef _WIN_ALL
+      // We do not allow ':' in Windows because of NTFS streams.
+      // Users had problems after specifying hh:mm mask.
+      if (DateText[J]==':')
+        DateText[J]='_';
+#endif
+    }
+    else
+    {
+      size_t FieldPos=ChPtr-MaskChars;
+      int CharPos=(int)strlen(Field[FieldPos])-CField[FieldPos]--;
+      if (FieldPos==1 && toupperw(Mask[I+1])=='M' && toupperw(Mask[I+2])=='M')
+      {
+        wcsncpyz(DateText+J,GetMonthName(rlt.Month-1),ASIZE(DateText)-J);
+        J=wcslen(DateText);
+        I+=2;
+        continue;
+      }
+      if (CharPos<0)
+        DateText[J]=Mask[I];
+      else
+        DateText[J]=Field[FieldPos][CharPos];
+    }
+    DateText[++J]=0;
+  }
+
+  if (Prefix)
+  {
+    wchar NewName[NM];
+    GetFilePath(ArcName,NewName,ASIZE(NewName));
+    AddEndSlash(NewName,ASIZE(NewName));
+    wcsncatz(NewName,DateText,ASIZE(NewName));
+    wcsncatz(NewName,PointToName(ArcName),ASIZE(NewName));
+    wcscpy(ArcName,NewName);
+  }
+  else
+    wcscat(ArcName,DateText);
+  wcscat(ArcName,Ext);
+}
+
+
+void GenerateArchiveName(wchar *ArcName,size_t MaxSize,const wchar *GenerateMask,bool Archiving)
+{
+  // Must be enough space for archive name plus all stuff in mask plus
+  // extra overhead produced by mask 'N' (archive number) characters.
+  // One 'N' character can result in several numbers if we process more
+  // than 9 archives.
+  wchar NewName[NM+MAX_GENERATE_MASK+20];
+
+  uint ArcNumber=1;
+  while (true) // Loop for 'N' (archive number) processing.
+  {
+    wcsncpyz(NewName,ArcName,ASIZE(NewName));
+    
+    bool ArcNumPresent=false;
+
+    GenArcName(NewName,GenerateMask,ArcNumber,ArcNumPresent);
+    
+    if (!ArcNumPresent)
+      break;
+    if (!FileExist(NewName))
+    {
+      if (!Archiving && ArcNumber>1)
+      {
+        // If we perform non-archiving operation, we need to use the last
+        // existing archive before the first unused name. So we generate
+        // the name for (ArcNumber-1) below.
+        wcsncpyz(NewName,NullToEmpty(ArcName),ASIZE(NewName));
+        GenArcName(NewName,GenerateMask,ArcNumber-1,ArcNumPresent);
+      }
+      break;
+    }
+    ArcNumber++;
+  }
+  wcsncpyz(ArcName,NewName,MaxSize);
+}
+#endif
+
+
+wchar* GetWideName(const char *Name,const wchar *NameW,wchar *DestW,size_t DestSize)
+{
+  if (NameW!=NULL && *NameW!=0)
+  {
+    if (DestW!=NameW)
+      wcsncpy(DestW,NameW,DestSize);
+  }
+  else
+    if (Name!=NULL)
+      CharToWide(Name,DestW,DestSize);
+    else
+      *DestW=0;
+
+  // Ensure that we return a zero terminate string for security reasons.
+  if (DestSize>0)
+    DestW[DestSize-1]=0;
+
+  return DestW;
+}
+
+
+#ifdef _WIN_ALL
+// We should return 'true' even if resulting path is shorter than MAX_PATH,
+// because we can also use this function to open files with non-standard
+// characters, even if their path length is normal.
+bool GetWinLongPath(const wchar *Src,wchar *Dest,size_t MaxSize)
+{
+  if (*Src==0)
+    return false;
+  const wchar *Prefix=L"\\\\?\\";
+  const size_t PrefixLength=4;
+  bool FullPath=IsDriveLetter(Src) && IsPathDiv(Src[2]);
+  size_t SrcLength=wcslen(Src);
+  if (IsFullPath(Src)) // Paths in d:\path\name format.
+  {
+    if (IsDriveLetter(Src))
+    {
+      if (MaxSize<=PrefixLength+SrcLength)
+        return false;
+      wcsncpy(Dest,Prefix,PrefixLength);
+      wcscpy(Dest+PrefixLength,Src);
+      return true;
+    }
+    else
+      if (Src[0]=='\\' && Src[1]=='\\')
+      {
+        if (MaxSize<=PrefixLength+SrcLength+2)
+          return false;
+        wcsncpy(Dest,Prefix,PrefixLength);
+        wcscpy(Dest+PrefixLength,L"UNC");
+        wcscpy(Dest+PrefixLength+3,Src+1);
+        return true;
+      }
+    // We may be here only if we modify IsFullPath in the future.
+    return false;
+  }
+  else
+  {
+    wchar CurDir[NM];
+    DWORD DirCode=GetCurrentDirectory(ASIZE(CurDir)-1,CurDir);
+    if (DirCode==0 || DirCode>ASIZE(CurDir)-1)
+      return false;
+
+    if (IsPathDiv(Src[0])) // Paths in \path\name format.
+    {
+      if (MaxSize<=PrefixLength+SrcLength+2)
+        return false;
+      wcsncpy(Dest,Prefix,PrefixLength);
+      wcsncpy(Dest+PrefixLength,CurDir,2); // Copy drive letter 'd:'.
+      wcscpy(Dest+PrefixLength+2,Src);
+      return true;
+    }
+    else  // Paths in path\name format.
+    {
+      AddEndSlash(CurDir,ASIZE(CurDir));
+      if (MaxSize<=PrefixLength+wcslen(CurDir)+SrcLength)
+        return false;
+      wcsncpy(Dest,Prefix,PrefixLength);
+      wcscpy(Dest+PrefixLength,CurDir);
+
+      if (Src[0]=='.' && IsPathDiv(Src[1])) // Remove leading .\ in pathname.
+        Src+=2;
+
+      wcsncatz(Dest,Src,MaxSize);
+      return true;
+    }
+  }
+  return false;
+}
+
+
+// Convert Unix, OS X and Android decomposed chracters to Windows precomposed.
+void ConvertToPrecomposed(wchar *Name,size_t NameSize)
+{
+  wchar FileName[NM];
+  if (WinNT()>=WNT_VISTA && // MAP_PRECOMPOSED is not supported in XP.
+      FoldString(MAP_PRECOMPOSED,Name,-1,FileName,ASIZE(FileName))!=0)
+  {
+    FileName[ASIZE(FileName)-1]=0;
+    wcsncpyz(Name,FileName,NameSize);
+  }
+}
+
+
+// Remove trailing spaces and dots in file name and in dir names in path.
+void MakeNameCompatible(wchar *Name)
+{
+  int Src=0,Dest=0;
+  while (true)
+  {
+    if (IsPathDiv(Name[Src]) || Name[Src]==0)
+      for (int I=Dest-1;I>0 && (Name[I]==' ' || Name[I]=='.');I--)
+      {
+        // Permit path1/./path2 and ../path1 paths.
+        if (Name[I]=='.' && (IsPathDiv(Name[I-1]) || Name[I-1]=='.' && I==1))
+          break;
+        Dest--;
+      }
+    Name[Dest]=Name[Src];
+    if (Name[Src]==0)
+      break;
+    Src++;
+    Dest++;
+  }
+}
+#endif
diff --git a/third_party/unrar/src/pathfn.hpp b/third_party/unrar/src/pathfn.hpp
new file mode 100644
index 0000000..f082694
--- /dev/null
+++ b/third_party/unrar/src/pathfn.hpp
@@ -0,0 +1,76 @@
+#ifndef _RAR_PATHFN_
+#define _RAR_PATHFN_
+
+wchar* PointToName(const wchar *Path);
+wchar* PointToLastChar(const wchar *Path);
+wchar* ConvertPath(const wchar *SrcPath,wchar *DestPath);
+void SetName(wchar *FullName,const wchar *Name,size_t MaxSize);
+void SetExt(wchar *Name,const wchar *NewExt,size_t MaxSize);
+void SetSFXExt(wchar *SFXName,size_t MaxSize);
+wchar *GetExt(const wchar *Name);
+bool CmpExt(const wchar *Name,const wchar *Ext);
+bool IsWildcard(const wchar *Str);
+bool IsPathDiv(int Ch);
+bool IsDriveDiv(int Ch);
+bool IsDriveLetter(const wchar *Path);
+int GetPathDisk(const wchar *Path);
+void AddEndSlash(wchar *Path,size_t MaxLength);
+void MakeName(const wchar *Path,const wchar *Name,wchar *Pathname,size_t MaxSize);
+void GetFilePath(const wchar *FullName,wchar *Path,size_t MaxLength);
+void RemoveNameFromPath(wchar *Path);
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+bool GetAppDataPath(wchar *Path,size_t MaxSize,bool Create);
+void GetRarDataPath(wchar *Path,size_t MaxSize,bool Create);
+#endif
+#ifndef SFX_MODULE
+bool EnumConfigPaths(uint Number,wchar *Path,size_t MaxSize,bool Create);
+void GetConfigName(const wchar *Name,wchar *FullName,size_t MaxSize,bool CheckExist,bool Create);
+#endif
+wchar* GetVolNumPart(const wchar *ArcName);
+void NextVolumeName(wchar *ArcName,uint MaxLength,bool OldNumbering);
+bool IsNameUsable(const wchar *Name);
+void MakeNameUsable(char *Name,bool Extended);
+void MakeNameUsable(wchar *Name,bool Extended);
+
+void UnixSlashToDos(const char *SrcName,char *DestName,size_t MaxLength);
+void DosSlashToUnix(const char *SrcName,char *DestName,size_t MaxLength);
+void UnixSlashToDos(const wchar *SrcName,wchar *DestName,size_t MaxLength);
+void DosSlashToUnix(const wchar *SrcName,wchar *DestName,size_t MaxLength);
+
+inline void SlashToNative(const char *SrcName,char *DestName,size_t MaxLength)
+{
+#ifdef _WIN_ALL
+  UnixSlashToDos(SrcName,DestName,MaxLength);
+#else
+  DosSlashToUnix(SrcName,DestName,MaxLength);
+#endif
+}
+
+inline void SlashToNative(const wchar *SrcName,wchar *DestName,size_t MaxLength)
+{
+#ifdef _WIN_ALL
+  UnixSlashToDos(SrcName,DestName,MaxLength);
+#else
+  DosSlashToUnix(SrcName,DestName,MaxLength);
+#endif
+}
+
+void ConvertNameToFull(const wchar *Src,wchar *Dest,size_t MaxSize);
+bool IsFullPath(const wchar *Path);
+bool IsFullRootPath(const wchar *Path);
+void GetPathRoot(const wchar *Path,wchar *Root,size_t MaxSize);
+int ParseVersionFileName(wchar *Name,bool Truncate);
+wchar* VolNameToFirstName(const wchar *VolName,wchar *FirstName,size_t MaxSize,bool NewNumbering);
+wchar* GetWideName(const char *Name,const wchar *NameW,wchar *DestW,size_t DestSize);
+
+#ifndef SFX_MODULE
+void GenerateArchiveName(wchar *ArcName,size_t MaxSize,const wchar *GenerateMask,bool Archiving);
+#endif
+
+#ifdef _WIN_ALL
+bool GetWinLongPath(const wchar *Src,wchar *Dest,size_t MaxSize);
+void ConvertToPrecomposed(wchar *Name,size_t NameSize);
+void MakeNameCompatible(wchar *Name);
+#endif
+
+#endif
diff --git a/third_party/unrar/src/qopen.cpp b/third_party/unrar/src/qopen.cpp
new file mode 100644
index 0000000..97851ca1
--- /dev/null
+++ b/third_party/unrar/src/qopen.cpp
@@ -0,0 +1,285 @@
+#include "rar.hpp"
+
+QuickOpen::QuickOpen()
+{
+  Buf=NULL;
+  Init(NULL,false);
+}
+
+
+QuickOpen::~QuickOpen()
+{
+  Close();
+  delete[] Buf;
+}
+
+
+void QuickOpen::Init(Archive *Arc,bool WriteMode)
+{
+  if (Arc!=NULL) // Unless called from constructor.
+    Close();
+
+  QuickOpen::Arc=Arc;
+  QuickOpen::WriteMode=WriteMode;
+
+  ListStart=NULL;
+  ListEnd=NULL;
+
+  if (Buf==NULL)
+    Buf=new byte[MaxBufSize];
+
+  CurBufSize=0; // Current size of buffered data in write mode.
+
+  Loaded=false;
+}
+
+
+void QuickOpen::Close()
+{
+  QuickOpenItem *Item=ListStart;
+  while (Item!=NULL)
+  {
+    QuickOpenItem *Next=Item->Next;
+    delete[] Item->Header;
+    delete Item;
+    Item=Next;
+  }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+void QuickOpen::Load(uint64 BlockPos)
+{
+  if (!Loaded)
+  {
+    // If loading for the first time, perform additional intialization.
+    SeekPos=Arc->Tell();
+    UnsyncSeekPos=false;
+
+    SaveFilePos SavePos(*Arc);
+    Arc->Seek(BlockPos,SEEK_SET);
+
+    // If BlockPos points to original main header, we'll have the infinite
+    // recursion, because ReadHeader() for main header will attempt to load
+    // QOpen and call QuickOpen::Load again. If BlockPos points to long chain
+    // of other main headers, we'll have multiple recursive calls of this
+    // function wasting resources. So we prohibit QOpen temporarily to
+    // prevent this. ReadHeader() calls QOpen.Init and sets MainHead Locator
+    // and QOpenOffset fields, so we cannot use them to prohibit QOpen.
+    Arc->SetProhibitQOpen(true);
+    size_t ReadSize=Arc->ReadHeader();
+    Arc->SetProhibitQOpen(false);
+
+    if (ReadSize==0 || Arc->GetHeaderType()!=HEAD_SERVICE ||
+        !Arc->SubHead.CmpName(SUBHEAD_TYPE_QOPEN))
+      return;
+    QLHeaderPos=Arc->CurBlockPos;
+    RawDataStart=Arc->Tell();
+    RawDataSize=Arc->SubHead.UnpSize;
+
+    Loaded=true; // Set only after all file processing calls like Tell, Seek, ReadHeader.
+  }
+
+  if (Arc->SubHead.Encrypted)
+  {
+    RAROptions *Cmd=Arc->GetRAROptions();
+#ifndef RAR_NOCRYPT
+    if (Cmd->Password.IsSet())
+      Crypt.SetCryptKeys(false,CRYPT_RAR50,&Cmd->Password,Arc->SubHead.Salt,
+                         Arc->SubHead.InitV,Arc->SubHead.Lg2Count,
+                         Arc->SubHead.HashKey,Arc->SubHead.PswCheck);
+    else
+#endif
+      return;
+  }
+
+  RawDataPos=0;
+  ReadBufSize=0;
+  ReadBufPos=0;
+  LastReadHeader.Reset();
+  LastReadHeaderPos=0;
+
+  ReadBuffer();
+}
+
+
+bool QuickOpen::Read(void *Data,size_t Size,size_t &Result)
+{
+  if (!Loaded)
+    return false;
+  // Find next suitable cached block.
+  while (LastReadHeaderPos+LastReadHeader.Size()<=SeekPos)
+    if (!ReadNext())
+      break;
+  if (!Loaded)
+  {
+    // If something wrong happened, let's set the correct file pointer
+    // and stop further quick open processing.
+    if (UnsyncSeekPos)
+      Arc->File::Seek(SeekPos,SEEK_SET);
+    return false;
+  }
+
+  if (SeekPos>=LastReadHeaderPos && SeekPos+Size<=LastReadHeaderPos+LastReadHeader.Size())
+  {
+    memcpy(Data,LastReadHeader+size_t(SeekPos-LastReadHeaderPos),Size);
+    Result=Size;
+    SeekPos+=Size;
+    UnsyncSeekPos=true;
+  }
+  else
+  {
+    if (UnsyncSeekPos)
+    {
+      Arc->File::Seek(SeekPos,SEEK_SET);
+      UnsyncSeekPos=false;
+    }
+    int ReadSize=Arc->File::Read(Data,Size);
+    if (ReadSize<0)
+    {
+      Loaded=false;
+      return false;
+    }
+    Result=ReadSize;
+    SeekPos+=ReadSize;
+  }
+  
+  return true;
+}
+
+
+bool QuickOpen::Seek(int64 Offset,int Method)
+{
+  if (!Loaded)
+    return false;
+
+  // Normally we process an archive sequentially from beginning to end,
+  // so we read quick open data sequentially. But some operations like
+  // archive updating involve several passes. So if we detect that file
+  // pointer is moved back, we reload quick open data from beginning.
+  if (Method==SEEK_SET && (uint64)Offset<SeekPos && (uint64)Offset<LastReadHeaderPos)
+    Load(QLHeaderPos);
+
+  if (Method==SEEK_SET)
+    SeekPos=Offset;
+  if (Method==SEEK_CUR)
+    SeekPos+=Offset;
+  UnsyncSeekPos=true;
+
+  if (Method==SEEK_END)
+  {
+    Arc->File::Seek(Offset,SEEK_END);
+    SeekPos=Arc->File::Tell();
+    UnsyncSeekPos=false;
+  }
+  return true;
+}
+
+
+bool QuickOpen::Tell(int64 *Pos)
+{
+  if (!Loaded)
+    return false;
+  *Pos=SeekPos;
+  return true;
+}
+
+
+uint QuickOpen::ReadBuffer()
+{
+  SaveFilePos SavePos(*Arc);
+  Arc->File::Seek(RawDataStart+RawDataPos,SEEK_SET);
+  size_t SizeToRead=(size_t)Min(RawDataSize-RawDataPos,MaxBufSize-ReadBufSize);
+  if (Arc->SubHead.Encrypted)
+    SizeToRead &= ~CRYPT_BLOCK_MASK;
+  if (SizeToRead==0)
+    return 0;
+  int ReadSize=Arc->File::Read(Buf+ReadBufSize,SizeToRead);
+  if (ReadSize<=0)
+    return 0;
+#ifndef RAR_NOCRYPT
+  if (Arc->SubHead.Encrypted)
+    Crypt.DecryptBlock(Buf+ReadBufSize,ReadSize & ~CRYPT_BLOCK_MASK);
+#endif
+  RawDataPos+=ReadSize;
+  ReadBufSize+=ReadSize;
+  return ReadSize;
+}
+
+
+// Fill RawRead object from buffer.
+bool QuickOpen::ReadRaw(RawRead &Raw)
+{
+  if (MaxBufSize-ReadBufPos<0x100) // We are close to end of buffer.
+  {
+    // Ensure that we have enough data to read CRC and header size.
+    size_t DataLeft=ReadBufSize-ReadBufPos;
+    memcpy(Buf,Buf+ReadBufPos,DataLeft);
+    ReadBufPos=0;
+    ReadBufSize=DataLeft;
+    ReadBuffer();
+  }
+  const size_t FirstReadSize=7;
+  if (ReadBufPos+FirstReadSize>ReadBufSize)
+    return false;
+  Raw.Read(Buf+ReadBufPos,FirstReadSize);
+  ReadBufPos+=FirstReadSize;
+
+  uint SavedCRC=Raw.Get4();
+  uint SizeBytes=Raw.GetVSize(4);
+  uint64 BlockSize=Raw.GetV();
+  int SizeToRead=int(BlockSize);
+  SizeToRead-=FirstReadSize-SizeBytes-4; // Adjust overread size bytes if any.
+  if (SizeToRead<0 || SizeBytes==0 || BlockSize==0)
+  {
+    Loaded=false; // Invalid data.
+    return false;
+  }
+
+  // If rest of block data crosses buffer boundary, read it in loop.
+  size_t DataLeft=ReadBufSize-ReadBufPos;
+  while (SizeToRead>0)
+  {
+    size_t CurSizeToRead=Min(DataLeft,(size_t)SizeToRead);
+    Raw.Read(Buf+ReadBufPos,CurSizeToRead);
+    ReadBufPos+=CurSizeToRead;
+    SizeToRead-=int(CurSizeToRead);
+    if (SizeToRead>0) // We read the entire buffer and still need more data.
+    {
+      ReadBufPos=0;
+      ReadBufSize=0;
+      if (ReadBuffer()==0)
+        return false;
+    }
+  }
+
+  return SavedCRC==Raw.GetCRC50();
+}
+
+
+// Read next cached header.
+bool QuickOpen::ReadNext()
+{
+  RawRead Raw(NULL);
+  if (!ReadRaw(Raw)) // Read internal quick open header preceding stored block.
+    return false;
+  uint Flags=(uint)Raw.GetV();
+  uint64 Offset=Raw.GetV();
+  size_t HeaderSize=(size_t)Raw.GetV();
+  LastReadHeader.Alloc(HeaderSize);
+  Raw.GetB(&LastReadHeader[0],HeaderSize);
+  // Calculate the absolute position as offset from quick open service header.
+  LastReadHeaderPos=QLHeaderPos-Offset;
+  return true;
+}
diff --git a/third_party/unrar/src/qopen.hpp b/third_party/unrar/src/qopen.hpp
new file mode 100644
index 0000000..e04af18
--- /dev/null
+++ b/third_party/unrar/src/qopen.hpp
@@ -0,0 +1,61 @@
+#ifndef _RAR_QOPEN_
+#define _RAR_QOPEN_
+
+struct QuickOpenItem
+{
+  byte *Header;
+  size_t HeaderSize;
+  uint64 ArcPos;
+  QuickOpenItem *Next;
+};
+
+
+class Archive;
+class RawRead;
+
+class QuickOpen
+{
+  private:
+    void Close();
+
+
+    uint ReadBuffer();
+    bool ReadRaw(RawRead &Raw);
+    bool ReadNext();
+
+    Archive *Arc;
+    bool WriteMode;
+
+    QuickOpenItem *ListStart;
+    QuickOpenItem *ListEnd;
+    
+    byte *Buf;
+    static const size_t MaxBufSize=0x10000; // Must be multiple of CRYPT_BLOCK_SIZE.
+    size_t CurBufSize;
+#ifndef RAR_NOCRYPT // For shell extension.
+    CryptData Crypt;
+#endif
+
+    bool Loaded;
+    uint64 QLHeaderPos;
+    uint64 RawDataStart;
+    uint64 RawDataSize;
+    uint64 RawDataPos;
+    size_t ReadBufSize;
+    size_t ReadBufPos;
+    Array<byte> LastReadHeader;
+    uint64 LastReadHeaderPos;
+    uint64 SeekPos;
+    bool UnsyncSeekPos; // QOpen SeekPos does not match an actual file pointer.
+  public:
+    QuickOpen();
+    ~QuickOpen();
+    void Init(Archive *Arc,bool WriteMode);
+    void Load(uint64 BlockPos);
+    void Unload() { Loaded=false; }
+    bool Read(void *Data,size_t Size,size_t &Result);
+    bool Seek(int64 Offset,int Method);
+    bool Tell(int64 *Pos);
+};
+
+#endif
diff --git a/third_party/unrar/src/rar.cpp b/third_party/unrar/src/rar.cpp
new file mode 100644
index 0000000..a410576
--- /dev/null
+++ b/third_party/unrar/src/rar.cpp
@@ -0,0 +1,111 @@
+#include "rar.hpp"
+
+#if !defined(RARDLL)
+int main(int argc, char *argv[])
+{
+
+#ifdef _UNIX
+  setlocale(LC_ALL,"");
+#endif
+
+  InitConsole();
+  ErrHandler.SetSignalHandlers(true);
+
+#ifdef SFX_MODULE
+  wchar ModuleName[NM];
+#ifdef _WIN_ALL
+  GetModuleFileName(NULL,ModuleName,ASIZE(ModuleName));
+#else
+  CharToWide(argv[0],ModuleName,ASIZE(ModuleName));
+#endif
+#endif
+
+#ifdef _WIN_ALL
+  SetErrorMode(SEM_NOALIGNMENTFAULTEXCEPT|SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);
+
+
+#endif
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+  // Must be initialized, normal initialization can be skipped in case of
+  // exception.
+  POWER_MODE ShutdownOnClose=POWERMODE_KEEP;
+#endif
+
+  try 
+  {
+  
+    CommandData *Cmd=new CommandData;
+#ifdef SFX_MODULE
+    wcscpy(Cmd->Command,L"X");
+    char *Switch=argc>1 ? argv[1]:NULL;
+    if (Switch!=NULL && Cmd->IsSwitch(Switch[0]))
+    {
+      int UpperCmd=etoupper(Switch[1]);
+      switch(UpperCmd)
+      {
+        case 'T':
+        case 'V':
+          Cmd->Command[0]=UpperCmd;
+          break;
+        case '?':
+          Cmd->OutHelp(RARX_SUCCESS);
+          break;
+      }
+    }
+    Cmd->AddArcName(ModuleName);
+    Cmd->ParseDone();
+    Cmd->AbsoluteLinks=true; // If users runs SFX, he trusts an archive source.
+#else // !SFX_MODULE
+    Cmd->ParseCommandLine(true,argc,argv);
+    if (!Cmd->ConfigDisabled)
+    {
+      Cmd->ReadConfig();
+      Cmd->ParseEnvVar();
+    }
+    Cmd->ParseCommandLine(false,argc,argv);
+#endif
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+    ShutdownOnClose=Cmd->Shutdown;
+#endif
+
+    uiInit(Cmd->Sound);
+    InitLogOptions(Cmd->LogName,Cmd->ErrlogCharset);
+    ErrHandler.SetSilent(Cmd->AllYes || Cmd->MsgStream==MSG_NULL);
+
+    Cmd->OutTitle();
+/*
+    byte Buf[10000];
+    File Src;
+    Src.TOpen(L"123.rar");
+    int Size=Src.Read(Buf,sizeof(Buf));
+    Cmd->SetArcInMem(Buf,Size);
+*/
+    Cmd->ProcessCommand();
+    delete Cmd;
+  }
+  catch (RAR_EXIT ErrCode)
+  {
+    ErrHandler.SetErrorCode(ErrCode);
+  }
+  catch (std::bad_alloc&)
+  {
+    ErrHandler.MemoryErrorMsg();
+    ErrHandler.SetErrorCode(RARX_MEMORY);
+  }
+  catch (...)
+  {
+    ErrHandler.SetErrorCode(RARX_FATAL);
+  }
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+  if (ShutdownOnClose!=POWERMODE_KEEP && ErrHandler.IsShutdownEnabled())
+    Shutdown(ShutdownOnClose);
+#endif
+  ErrHandler.MainExit=true;
+  return ErrHandler.GetErrorCode();
+}
+#endif
+
+
diff --git a/third_party/unrar/src/rar.hpp b/third_party/unrar/src/rar.hpp
new file mode 100644
index 0000000..99b3ee7
--- /dev/null
+++ b/third_party/unrar/src/rar.hpp
@@ -0,0 +1,97 @@
+#ifndef _RAR_RARCOMMON_
+#define _RAR_RARCOMMON_
+
+#include "raros.hpp"
+#include "rartypes.hpp"
+#include "os.hpp"
+
+#ifdef RARDLL
+#include "dll.hpp"
+#endif
+
+#include "version.hpp"
+#include "rardefs.hpp"
+#include "rarlang.hpp"
+#include "unicode.hpp"
+#include "errhnd.hpp"
+#include "secpassword.hpp"
+#include "array.hpp"
+#include "timefn.hpp"
+#include "sha1.hpp"
+#include "sha256.hpp"
+#include "blake2s.hpp"
+#include "hash.hpp"
+#include "options.hpp"
+#include "rijndael.hpp"
+#include "crypt.hpp"
+#include "headers5.hpp"
+#include "headers.hpp"
+#include "pathfn.hpp"
+#include "strfn.hpp"
+#include "strlist.hpp"
+#ifdef _WIN_ALL
+#include "isnt.hpp"
+#endif
+#include "file.hpp"
+#include "crc.hpp"
+#include "ui.hpp"
+#include "filefn.hpp"
+#include "filestr.hpp"
+#include "find.hpp"
+#include "scantree.hpp"
+#include "savepos.hpp"
+#include "getbits.hpp"
+#include "rdwrfn.hpp"
+#ifdef USE_QOPEN
+#include "qopen.hpp"
+#endif
+#ifdef USE_ARCMEM
+#include "arcmem.hpp"
+#endif
+#include "archive.hpp"
+#include "match.hpp"
+#include "cmddata.hpp"
+#include "filcreat.hpp"
+#include "consio.hpp"
+#include "system.hpp"
+#include "log.hpp"
+#include "rawint.hpp"
+#include "rawread.hpp"
+#include "encname.hpp"
+#include "resource.hpp"
+#include "compress.hpp"
+
+#include "rarvm.hpp"
+#include "model.hpp"
+
+#include "threadpool.hpp"
+
+#include "unpack.hpp"
+
+
+
+#include "extinfo.hpp"
+#include "extract.hpp"
+
+
+
+#include "list.hpp"
+
+
+#include "rs.hpp"
+#include "rs16.hpp"
+#include "recvol.hpp"
+#include "volume.hpp"
+#include "smallfn.hpp"
+
+#include "global.hpp"
+
+#if 0
+#include "benchmark.hpp"
+#endif
+
+
+
+
+
+#endif
diff --git a/third_party/unrar/src/rardefs.hpp b/third_party/unrar/src/rardefs.hpp
new file mode 100644
index 0000000..aa7c329e
--- /dev/null
+++ b/third_party/unrar/src/rardefs.hpp
@@ -0,0 +1,32 @@
+#ifndef _RAR_DEFS_
+#define _RAR_DEFS_
+
+#define  Min(x,y) (((x)<(y)) ? (x):(y))
+#define  Max(x,y) (((x)>(y)) ? (x):(y))
+
+// Universal replacement of abs function for non-int arguments.
+#define  Abs(x) (((x)<0) ? -(x):(x))
+
+#define  ASIZE(x) (sizeof(x)/sizeof(x[0]))
+
+// MAXPASSWORD is expected to be multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE (16)
+// for CryptProtectMemory in SecPassword.
+#define  MAXPASSWORD       128
+
+#define  MAXSFXSIZE        0x200000
+
+#define  MAXCMTSIZE        0x40000
+
+#define  DefSFXName        L"default.sfx"
+#define  DefSortListName   L"rarfiles.lst"
+
+
+#ifndef SFX_MODULE
+#define USE_QOPEN
+#endif
+#define USE_ARCMEM
+
+// Produce the value, which is equal or larger than 'v' and aligned to 'a'.
+#define ALIGN_VALUE(v,a) (size_t(v) + ( (~size_t(v) + 1) & (a - 1) ) )
+
+#endif
diff --git a/third_party/unrar/src/rarlang.hpp b/third_party/unrar/src/rarlang.hpp
new file mode 100644
index 0000000..6151d15a
--- /dev/null
+++ b/third_party/unrar/src/rarlang.hpp
@@ -0,0 +1,10 @@
+#ifndef _RAR_LANG_
+#define _RAR_LANG_
+
+  #ifdef USE_RC
+    #include "rarres.hpp"
+  #else
+    #include "loclang.hpp"
+  #endif
+
+#endif
diff --git a/third_party/unrar/src/raros.hpp b/third_party/unrar/src/raros.hpp
new file mode 100644
index 0000000..4f4f2ae
--- /dev/null
+++ b/third_party/unrar/src/raros.hpp
@@ -0,0 +1,36 @@
+#ifndef _RAR_RAROS_
+#define _RAR_RAROS_
+
+#ifdef __EMX__
+  #define _EMX
+#endif
+
+#ifdef __DJGPP__
+  #define _DJGPP
+  #define _EMX
+#endif
+
+#if defined(__WIN32__) || defined(_WIN32)
+  #define _WIN_ALL // Defined for all Windows platforms, 32 and 64 bit, mobile and desktop.
+  #ifdef _M_X64
+    #define _WIN_64
+  #else
+    #define _WIN_32
+  #endif
+#endif
+
+#if defined(ANDROID) || defined(__ANDROID__)
+  #define _UNIX
+  #define _ANDROID
+#endif
+
+#ifdef __APPLE__
+  #define _UNIX
+  #define _APPLE
+#endif
+
+#if !defined(_EMX) && !defined(_WIN_ALL) && !defined(_BEOS) && !defined(_APPLE)
+  #define _UNIX
+#endif
+
+#endif
diff --git a/third_party/unrar/src/rarpch.cpp b/third_party/unrar/src/rarpch.cpp
new file mode 100644
index 0000000..c070cf7
--- /dev/null
+++ b/third_party/unrar/src/rarpch.cpp
@@ -0,0 +1,2 @@
+// We use rarpch.cpp to create precompiled headers for MS Visual C++.
+#include "rar.hpp"
diff --git a/third_party/unrar/src/rartypes.hpp b/third_party/unrar/src/rartypes.hpp
new file mode 100644
index 0000000..1ec97c9
--- /dev/null
+++ b/third_party/unrar/src/rartypes.hpp
@@ -0,0 +1,35 @@
+#ifndef _RAR_TYPES_
+#define _RAR_TYPES_
+
+#include <stdint.h>
+
+typedef uint8_t          byte;   // Unsigned 8 bits.
+typedef uint16_t         ushort; // Preferably 16 bits, but can be more.
+typedef unsigned int     uint;   // 32 bits or more.
+typedef uint32_t         uint32; // 32 bits exactly.
+typedef int32_t          int32;  // Signed 32 bits exactly.
+typedef uint64_t         uint64; // 64 bits exactly.
+typedef int64_t          int64;  // Signed 64 bits exactly.
+typedef wchar_t          wchar;  // Unicode character
+
+// Get lowest 16 bits.
+#define GET_SHORT16(x) (sizeof(ushort)==2 ? (ushort)(x):((x)&0xffff))
+
+// Make 64 bit integer from two 32 bit.
+#define INT32TO64(high,low) ((((uint64)(high))<<32)+((uint64)low))
+
+// Maximum int64 value.
+#define MAX_INT64 int64(INT32TO64(0x7fffffff,0xffffffff))
+
+// Special int64 value, large enough to never be found in real life.
+// We use it in situations, when we need to indicate that parameter 
+// is not defined and probably should be calculated inside of function.
+// Lower part is intentionally 0x7fffffff, not 0xffffffff, to make it 
+// compatible with 32 bit int64.
+#define INT64NDF INT32TO64(0x7fffffff,0x7fffffff)
+
+// Maximum uint64 value.
+#define MAX_UINT64 INT32TO64(0xffffffff,0xffffffff)
+#define UINT64NDF MAX_UINT64
+
+#endif
diff --git a/third_party/unrar/src/rarvm.cpp b/third_party/unrar/src/rarvm.cpp
new file mode 100644
index 0000000..8d8675a
--- /dev/null
+++ b/third_party/unrar/src/rarvm.cpp
@@ -0,0 +1,364 @@
+#include "rar.hpp"
+
+RarVM::RarVM()
+{
+  Mem=NULL;
+}
+
+
+RarVM::~RarVM()
+{
+  delete[] Mem;
+}
+
+
+void RarVM::Init()
+{
+  if (Mem==NULL)
+    Mem=new byte[VM_MEMSIZE+4];
+}
+
+
+void RarVM::Execute(VM_PreparedProgram *Prg)
+{
+  memcpy(R,Prg->InitR,sizeof(Prg->InitR));
+  Prg->FilteredData=NULL;
+  if (Prg->Type!=VMSF_NONE)
+  {
+    bool Success=ExecuteStandardFilter(Prg->Type);
+    uint BlockSize=Prg->InitR[4] & VM_MEMMASK;
+    Prg->FilteredDataSize=BlockSize;
+    if (Prg->Type==VMSF_DELTA || Prg->Type==VMSF_RGB || Prg->Type==VMSF_AUDIO)
+      Prg->FilteredData=2*BlockSize>VM_MEMSIZE || !Success ? Mem:Mem+BlockSize;
+    else
+      Prg->FilteredData=Mem;
+  }
+}
+
+
+void RarVM::Prepare(byte *Code,uint CodeSize,VM_PreparedProgram *Prg)
+{
+  // Calculate the single byte XOR checksum to check validity of VM code.
+  byte XorSum=0;
+  for (uint I=1;I<CodeSize;I++)
+    XorSum^=Code[I];
+
+  if (XorSum!=Code[0])
+    return;
+
+  struct StandardFilters
+  {
+    uint Length;
+    uint CRC;
+    VM_StandardFilters Type;
+  } static StdList[]={
+    53, 0xad576887, VMSF_E8,
+    57, 0x3cd7e57e, VMSF_E8E9,
+   120, 0x3769893f, VMSF_ITANIUM,
+    29, 0x0e06077d, VMSF_DELTA,
+   149, 0x1c2c5dc8, VMSF_RGB,
+   216, 0xbc85e701, VMSF_AUDIO
+  };
+  uint CodeCRC=CRC32(0xffffffff,Code,CodeSize)^0xffffffff;
+  for (uint I=0;I<ASIZE(StdList);I++)
+    if (StdList[I].CRC==CodeCRC && StdList[I].Length==CodeSize)
+    {
+      Prg->Type=StdList[I].Type;
+      break;
+    }
+}
+
+
+uint RarVM::ReadData(BitInput &Inp)
+{
+  uint Data=Inp.fgetbits();
+  switch(Data&0xc000)
+  {
+    case 0:
+      Inp.faddbits(6);
+      return (Data>>10)&0xf;
+    case 0x4000:
+      if ((Data&0x3c00)==0)
+      {
+        Data=0xffffff00|((Data>>2)&0xff);
+        Inp.faddbits(14);
+      }
+      else
+      {
+        Data=(Data>>6)&0xff;
+        Inp.faddbits(10);
+      }
+      return Data;
+    case 0x8000:
+      Inp.faddbits(2);
+      Data=Inp.fgetbits();
+      Inp.faddbits(16);
+      return Data;
+    default:
+      Inp.faddbits(2);
+      Data=(Inp.fgetbits()<<16);
+      Inp.faddbits(16);
+      Data|=Inp.fgetbits();
+      Inp.faddbits(16);
+      return Data;
+  }
+}
+
+
+void RarVM::SetMemory(size_t Pos,byte *Data,size_t DataSize)
+{
+  if (Pos<VM_MEMSIZE && Data!=Mem+Pos)
+  {
+    // We can have NULL Data for invalid filters with DataSize==0. While most
+    // sensible memmove implementations do not care about data if size is 0,
+    // let's follow the standard and check the size first.
+    size_t CopySize=Min(DataSize,VM_MEMSIZE-Pos);
+    if (CopySize!=0)
+      memmove(Mem+Pos,Data,CopySize);
+  }
+}
+
+
+bool RarVM::ExecuteStandardFilter(VM_StandardFilters FilterType)
+{
+  switch(FilterType)
+  {
+    case VMSF_E8:
+    case VMSF_E8E9:
+      {
+        byte *Data=Mem;
+        uint DataSize=R[4],FileOffset=R[6];
+
+        if (DataSize>VM_MEMSIZE || DataSize<4)
+          return false;
+
+        const uint FileSize=0x1000000;
+        byte CmpByte2=FilterType==VMSF_E8E9 ? 0xe9:0xe8;
+        for (uint CurPos=0;CurPos<DataSize-4;)
+        {
+          byte CurByte=*(Data++);
+          CurPos++;
+          if (CurByte==0xe8 || CurByte==CmpByte2)
+          {
+            uint Offset=CurPos+FileOffset;
+            uint Addr=RawGet4(Data);
+
+            // We check 0x80000000 bit instead of '< 0' comparison
+            // not assuming int32 presence or uint size and endianness.
+            if ((Addr & 0x80000000)!=0)              // Addr<0
+            {
+              if (((Addr+Offset) & 0x80000000)==0)   // Addr+Offset>=0
+                RawPut4(Addr+FileSize,Data);
+            }
+            else
+              if (((Addr-FileSize) & 0x80000000)!=0) // Addr<FileSize
+                RawPut4(Addr-Offset,Data);
+            Data+=4;
+            CurPos+=4;
+          }
+        }
+      }
+      break;
+    case VMSF_ITANIUM:
+      {
+        byte *Data=Mem;
+        uint DataSize=R[4],FileOffset=R[6];
+
+        if (DataSize>VM_MEMSIZE || DataSize<21)
+          return false;
+
+        uint CurPos=0;
+
+        FileOffset>>=4;
+
+        while (CurPos<DataSize-21)
+        {
+          int Byte=(Data[0]&0x1f)-0x10;
+          if (Byte>=0)
+          {
+            static byte Masks[16]={4,4,6,6,0,0,7,7,4,4,0,0,4,4,0,0};
+            byte CmdMask=Masks[Byte];
+            if (CmdMask!=0)
+              for (uint I=0;I<=2;I++)
+                if (CmdMask & (1<<I))
+                {
+                  uint StartPos=I*41+5;
+                  uint OpType=FilterItanium_GetBits(Data,StartPos+37,4);
+                  if (OpType==5)
+                  {
+                    uint Offset=FilterItanium_GetBits(Data,StartPos+13,20);
+                    FilterItanium_SetBits(Data,(Offset-FileOffset)&0xfffff,StartPos+13,20);
+                  }
+                }
+          }
+          Data+=16;
+          CurPos+=16;
+          FileOffset++;
+        }
+      }
+      break;
+    case VMSF_DELTA:
+      {
+        uint DataSize=R[4],Channels=R[0],SrcPos=0,Border=DataSize*2;
+        if (DataSize>VM_MEMSIZE/2 || Channels>MAX3_UNPACK_CHANNELS || Channels==0)
+          return false;
+
+        // Bytes from same channels are grouped to continual data blocks,
+        // so we need to place them back to their interleaving positions.
+        for (uint CurChannel=0;CurChannel<Channels;CurChannel++)
+        {
+          byte PrevByte=0;
+          for (uint DestPos=DataSize+CurChannel;DestPos<Border;DestPos+=Channels)
+            Mem[DestPos]=(PrevByte-=Mem[SrcPos++]);
+        }
+      }
+      break;
+    case VMSF_RGB:
+      {
+        uint DataSize=R[4],Width=R[0]-3,PosR=R[1];
+        if (DataSize>VM_MEMSIZE/2 || DataSize<3 || Width>DataSize || PosR>2)
+          return false;
+        byte *SrcData=Mem,*DestData=SrcData+DataSize;
+        const uint Channels=3;
+        for (uint CurChannel=0;CurChannel<Channels;CurChannel++)
+        {
+          uint PrevByte=0;
+
+          for (uint I=CurChannel;I<DataSize;I+=Channels)
+          {
+            uint Predicted;
+            if (I>=Width+3)
+            {
+              byte *UpperData=DestData+I-Width;
+              uint UpperByte=*UpperData;
+              uint UpperLeftByte=*(UpperData-3);
+              Predicted=PrevByte+UpperByte-UpperLeftByte;
+              int pa=abs((int)(Predicted-PrevByte));
+              int pb=abs((int)(Predicted-UpperByte));
+              int pc=abs((int)(Predicted-UpperLeftByte));
+              if (pa<=pb && pa<=pc)
+                Predicted=PrevByte;
+              else
+                if (pb<=pc)
+                  Predicted=UpperByte;
+                else
+                  Predicted=UpperLeftByte;
+            }
+            else
+              Predicted=PrevByte;
+            DestData[I]=PrevByte=(byte)(Predicted-*(SrcData++));
+          }
+        }
+        for (uint I=PosR,Border=DataSize-2;I<Border;I+=3)
+        {
+          byte G=DestData[I+1];
+          DestData[I]+=G;
+          DestData[I+2]+=G;
+        }
+      }
+      break;
+    case VMSF_AUDIO:
+      {
+        uint DataSize=R[4],Channels=R[0];
+        byte *SrcData=Mem,*DestData=SrcData+DataSize;
+        // In fact, audio channels never exceed 4.
+        if (DataSize>VM_MEMSIZE/2 || Channels>128 || Channels==0)
+          return false;
+        for (uint CurChannel=0;CurChannel<Channels;CurChannel++)
+        {
+          uint PrevByte=0,PrevDelta=0,Dif[7];
+          int D1=0,D2=0,D3;
+          int K1=0,K2=0,K3=0;
+          memset(Dif,0,sizeof(Dif));
+
+          for (uint I=CurChannel,ByteCount=0;I<DataSize;I+=Channels,ByteCount++)
+          {
+            D3=D2;
+            D2=PrevDelta-D1;
+            D1=PrevDelta;
+
+            uint Predicted=8*PrevByte+K1*D1+K2*D2+K3*D3;
+            Predicted=(Predicted>>3) & 0xff;
+
+            uint CurByte=*(SrcData++);
+
+            Predicted-=CurByte;
+            DestData[I]=Predicted;
+            PrevDelta=(signed char)(Predicted-PrevByte);
+            PrevByte=Predicted;
+
+            int D=(signed char)CurByte;
+            // Left shift of negative value is undefined behavior in C++,
+            // so we cast it to unsigned to follow the standard.
+            D=(uint)D<<3;
+
+            Dif[0]+=abs(D);
+            Dif[1]+=abs(D-D1);
+            Dif[2]+=abs(D+D1);
+            Dif[3]+=abs(D-D2);
+            Dif[4]+=abs(D+D2);
+            Dif[5]+=abs(D-D3);
+            Dif[6]+=abs(D+D3);
+
+            if ((ByteCount & 0x1f)==0)
+            {
+              uint MinDif=Dif[0],NumMinDif=0;
+              Dif[0]=0;
+              for (uint J=1;J<ASIZE(Dif);J++)
+              {
+                if (Dif[J]<MinDif)
+                {
+                  MinDif=Dif[J];
+                  NumMinDif=J;
+                }
+                Dif[J]=0;
+              }
+              switch(NumMinDif)
+              {
+                case 1: if (K1>=-16) K1--; break;
+                case 2: if (K1 < 16) K1++; break;
+                case 3: if (K2>=-16) K2--; break;
+                case 4: if (K2 < 16) K2++; break;
+                case 5: if (K3>=-16) K3--; break;
+                case 6: if (K3 < 16) K3++; break;
+              }
+            }
+          }
+        }
+      }
+      break;
+  }
+  return true;
+}
+
+
+uint RarVM::FilterItanium_GetBits(byte *Data,uint BitPos,uint BitCount)
+{
+  uint InAddr=BitPos/8;
+  uint InBit=BitPos&7;
+  uint BitField=(uint)Data[InAddr++];
+  BitField|=(uint)Data[InAddr++] << 8;
+  BitField|=(uint)Data[InAddr++] << 16;
+  BitField|=(uint)Data[InAddr] << 24;
+  BitField >>= InBit;
+  return BitField & (0xffffffff>>(32-BitCount));
+}
+
+
+void RarVM::FilterItanium_SetBits(byte *Data,uint BitField,uint BitPos,uint BitCount)
+{
+  uint InAddr=BitPos/8;
+  uint InBit=BitPos&7;
+  uint AndMask=0xffffffff>>(32-BitCount);
+  AndMask=~(AndMask<<InBit);
+
+  BitField<<=InBit;
+
+  for (uint I=0;I<4;I++)
+  {
+    Data[InAddr+I]&=AndMask;
+    Data[InAddr+I]|=BitField;
+    AndMask=(AndMask>>8)|0xff000000;
+    BitField>>=8;
+  }
+}
diff --git a/third_party/unrar/src/rarvm.hpp b/third_party/unrar/src/rarvm.hpp
new file mode 100644
index 0000000..e65c4b1a
--- /dev/null
+++ b/third_party/unrar/src/rarvm.hpp
@@ -0,0 +1,44 @@
+#ifndef _RAR_VM_
+#define _RAR_VM_
+
+#define VM_MEMSIZE                  0x40000
+#define VM_MEMMASK           (VM_MEMSIZE-1)
+
+enum VM_StandardFilters {
+  VMSF_NONE, VMSF_E8, VMSF_E8E9, VMSF_ITANIUM, VMSF_RGB, VMSF_AUDIO, 
+  VMSF_DELTA
+};
+
+struct VM_PreparedProgram
+{
+  VM_PreparedProgram() 
+  {
+    FilteredDataSize=0;
+    Type=VMSF_NONE;
+  }
+  VM_StandardFilters Type;
+  uint InitR[7];
+  byte *FilteredData;
+  uint FilteredDataSize;
+};
+
+class RarVM
+{
+  private:
+    bool ExecuteStandardFilter(VM_StandardFilters FilterType);
+    uint FilterItanium_GetBits(byte *Data,uint BitPos,uint BitCount);
+    void FilterItanium_SetBits(byte *Data,uint BitField,uint BitPos,uint BitCount);
+
+    byte *Mem;
+    uint R[8];
+  public:
+    RarVM();
+    ~RarVM();
+    void Init();
+    void Prepare(byte *Code,uint CodeSize,VM_PreparedProgram *Prg);
+    void Execute(VM_PreparedProgram *Prg);
+    void SetMemory(size_t Pos,byte *Data,size_t DataSize);
+    static uint ReadData(BitInput &Inp);
+};
+
+#endif
diff --git a/third_party/unrar/src/rawint.hpp b/third_party/unrar/src/rawint.hpp
new file mode 100644
index 0000000..3037988
--- /dev/null
+++ b/third_party/unrar/src/rawint.hpp
@@ -0,0 +1,122 @@
+#ifndef _RAR_RAWINT_
+#define _RAR_RAWINT_
+
+#define  rotls(x,n,xsize)  (((x)<<(n)) | ((x)>>(xsize-(n))))
+#define  rotrs(x,n,xsize)  (((x)>>(n)) | ((x)<<(xsize-(n))))
+#define  rotl32(x,n)       rotls(x,n,32)
+#define  rotr32(x,n)       rotrs(x,n,32)
+
+inline uint RawGet2(const void *Data)
+{
+  byte *D=(byte *)Data;
+  return D[0]+(D[1]<<8);
+}
+
+
+inline uint32 RawGet4(const void *Data)
+{
+#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED)
+  byte *D=(byte *)Data;
+  return D[0]+(D[1]<<8)+(D[2]<<16)+(D[3]<<24);
+#else
+  return *(uint32 *)Data;
+#endif
+}
+
+
+inline uint64 RawGet8(const void *Data)
+{
+#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED)
+  byte *D=(byte *)Data;
+  return INT32TO64(RawGet4(D+4),RawGet4(D));
+#else
+  return *(uint64 *)Data;
+#endif
+}
+
+
+inline void RawPut2(uint Field,void *Data)
+{
+  byte *D=(byte *)Data;
+  D[0]=(byte)(Field);
+  D[1]=(byte)(Field>>8);
+}
+
+
+inline void RawPut4(uint32 Field,void *Data)
+{
+#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED)
+  byte *D=(byte *)Data;
+  D[0]=(byte)(Field);
+  D[1]=(byte)(Field>>8);
+  D[2]=(byte)(Field>>16);
+  D[3]=(byte)(Field>>24);
+#else
+  *(uint32 *)Data=Field;
+#endif
+}
+
+
+inline void RawPut8(uint64 Field,void *Data)
+{
+#if defined(BIG_ENDIAN) || !defined(ALLOW_MISALIGNED)
+  byte *D=(byte *)Data;
+  D[0]=(byte)(Field);
+  D[1]=(byte)(Field>>8);
+  D[2]=(byte)(Field>>16);
+  D[3]=(byte)(Field>>24);
+  D[4]=(byte)(Field>>32);
+  D[5]=(byte)(Field>>40);
+  D[6]=(byte)(Field>>48);
+  D[7]=(byte)(Field>>56);
+#else
+  *(uint64 *)Data=Field;
+#endif
+}
+
+
+#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED)
+#define USE_MEM_BYTESWAP
+#endif
+
+// Load 4 big endian bytes from memory and return uint32.
+inline uint32 RawGetBE4(const byte *m)
+{
+#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER)
+  return _byteswap_ulong(*(uint32 *)m);
+#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2)
+  return __builtin_bswap32(*(uint32 *)m);
+#else
+  return uint32(m[0]<<24) | uint32(m[1]<<16) | uint32(m[2]<<8) | m[3];
+#endif
+}
+
+
+// Save integer to memory as big endian.
+inline void RawPutBE4(uint32 i,byte *mem)
+{
+#if defined(USE_MEM_BYTESWAP) && defined(_MSC_VER)
+  *(uint32*)mem = _byteswap_ulong(i);
+#elif defined(USE_MEM_BYTESWAP) && (__GNUC__ > 3) && (__GNUC_MINOR__ > 2)
+  *(uint32*)mem = __builtin_bswap32(i);
+#else
+  mem[0]=byte(i>>24);
+  mem[1]=byte(i>>16);
+  mem[2]=byte(i>>8);
+  mem[3]=byte(i);
+#endif
+}
+
+
+inline uint32 ByteSwap32(uint32 i)
+{
+#ifdef _MSC_VER
+  return _byteswap_ulong(i);
+#elif (__GNUC__ > 3) && (__GNUC_MINOR__ > 2)
+  return  __builtin_bswap32(i);
+#else
+  return (rotl32(i,24)&0xFF00FF00)|(rotl32(i,8)&0x00FF00FF);
+#endif
+}
+
+#endif
diff --git a/third_party/unrar/src/rawread.cpp b/third_party/unrar/src/rawread.cpp
new file mode 100644
index 0000000..d99bac8
--- /dev/null
+++ b/third_party/unrar/src/rawread.cpp
@@ -0,0 +1,197 @@
+#include "rar.hpp"
+
+RawRead::RawRead()
+{
+  RawRead::SrcFile=NULL;
+  Reset();
+}
+
+
+RawRead::RawRead(File *SrcFile)
+{
+  RawRead::SrcFile=SrcFile;
+  Reset();
+}
+
+
+void RawRead::Reset()
+{
+  Data.SoftReset();
+  ReadPos=0;
+  DataSize=0;
+  Crypt=NULL;
+}
+
+
+size_t RawRead::Read(size_t Size)
+{
+  size_t ReadSize=0;
+#if !defined(RAR_NOCRYPT)
+  if (Crypt!=NULL)
+  {
+    // Full size of buffer with already read data including data read 
+    // for encryption block alignment.
+    size_t FullSize=Data.Size();
+
+    // Data read for alignment and not processed yet.
+    size_t DataLeft=FullSize-DataSize;
+
+    if (Size>DataLeft) // Need to read more than we already have.
+    {
+      size_t SizeToRead=Size-DataLeft;
+      size_t AlignedReadSize=SizeToRead+((~SizeToRead+1) & CRYPT_BLOCK_MASK);
+      Data.Add(AlignedReadSize);
+      ReadSize=SrcFile->Read(&Data[FullSize],AlignedReadSize);
+      Crypt->DecryptBlock(&Data[FullSize],AlignedReadSize);
+      DataSize+=ReadSize==0 ? 0:Size;
+    }
+    else // Use buffered data, no real read.
+    {
+      ReadSize=Size;
+      DataSize+=Size;
+    }
+  }
+  else
+#endif
+    if (Size!=0)
+    {
+      Data.Add(Size);
+      ReadSize=SrcFile->Read(&Data[DataSize],Size);
+      DataSize+=ReadSize;
+    }
+  return ReadSize;
+}
+
+
+void RawRead::Read(byte *SrcData,size_t Size)
+{
+  if (Size!=0)
+  {
+    Data.Add(Size);
+    memcpy(&Data[DataSize],SrcData,Size);
+    DataSize+=Size;
+  }
+}
+
+
+byte RawRead::Get1()
+{
+  return ReadPos<DataSize ? Data[ReadPos++]:0;
+}
+
+
+ushort RawRead::Get2()
+{
+  if (ReadPos+1<DataSize)
+  {
+    ushort Result=Data[ReadPos]+(Data[ReadPos+1]<<8);
+    ReadPos+=2;
+    return Result;
+  }
+  return 0;
+}
+
+
+uint RawRead::Get4()
+{
+  if (ReadPos+3<DataSize)
+  {
+    uint Result=Data[ReadPos]+(Data[ReadPos+1]<<8)+(Data[ReadPos+2]<<16)+
+                (Data[ReadPos+3]<<24);
+    ReadPos+=4;
+    return Result;
+  }
+  return 0;
+}
+
+
+uint64 RawRead::Get8()
+{
+  uint Low=Get4(),High=Get4();
+  return INT32TO64(High,Low);
+}
+
+
+uint64 RawRead::GetV()
+{
+  uint64 Result=0;
+  // Need to check Shift<64, because for shift greater than or equal to
+  // the width of the promoted left operand, the behavior is undefined.
+  for (uint Shift=0;ReadPos<DataSize && Shift<64;Shift+=7)
+  {
+    byte CurByte=Data[ReadPos++];
+    Result+=uint64(CurByte & 0x7f)<<Shift;
+    if ((CurByte & 0x80)==0)
+      return Result; // Decoded successfully.
+  }
+  return 0; // Out of buffer border.
+}
+
+
+// Return a number of bytes in current variable length integer.
+uint RawRead::GetVSize(size_t Pos)
+{
+  for (size_t CurPos=Pos;CurPos<DataSize;CurPos++)
+    if ((Data[CurPos] & 0x80)==0)
+      return int(CurPos-Pos+1);
+  return 0; // Buffer overflow.
+}
+
+
+size_t RawRead::GetB(void *Field,size_t Size)
+{
+  byte *F=(byte *)Field;
+  size_t CopySize=Min(DataSize-ReadPos,Size);
+  if (CopySize>0)
+    memcpy(F,&Data[ReadPos],CopySize);
+  if (Size>CopySize)
+    memset(F+CopySize,0,Size-CopySize);
+  ReadPos+=CopySize;
+  return CopySize;
+}
+
+
+void RawRead::GetW(wchar *Field,size_t Size)
+{
+  if (ReadPos+2*Size-1<DataSize)
+  {
+    RawToWide(&Data[ReadPos],Field,Size);
+    ReadPos+=sizeof(wchar)*Size;
+  }
+  else
+    memset(Field,0,sizeof(wchar)*Size);
+}
+
+
+uint RawRead::GetCRC15(bool ProcessedOnly) // RAR 1.5 block CRC.
+{
+  if (DataSize<=2)
+    return 0;
+  uint HeaderCRC=CRC32(0xffffffff,&Data[2],(ProcessedOnly ? ReadPos:DataSize)-2);
+  return ~HeaderCRC & 0xffff;
+}
+
+
+uint RawRead::GetCRC50() // RAR 5.0 block CRC.
+{
+  if (DataSize<=4)
+    return 0xffffffff;
+  return CRC32(0xffffffff,&Data[4],DataSize-4) ^ 0xffffffff;
+}
+
+
+// Read vint from arbitrary byte array.
+uint64 RawGetV(const byte *Data,uint &ReadPos,uint DataSize,bool &Overflow)
+{
+  Overflow=false;
+  uint64 Result=0;
+  for (uint Shift=0;ReadPos<DataSize;Shift+=7)
+  {
+    byte CurByte=Data[ReadPos++];
+    Result+=uint64(CurByte & 0x7f)<<Shift;
+    if ((CurByte & 0x80)==0)
+      return Result; // Decoded successfully.
+  }
+  Overflow=true;
+  return 0; // Out of buffer border.
+}
diff --git a/third_party/unrar/src/rawread.hpp b/third_party/unrar/src/rawread.hpp
new file mode 100644
index 0000000..b319898
--- /dev/null
+++ b/third_party/unrar/src/rawread.hpp
@@ -0,0 +1,41 @@
+#ifndef _RAR_RAWREAD_
+#define _RAR_RAWREAD_
+
+class RawRead
+{
+  private:
+    Array<byte> Data;
+    File *SrcFile;
+    size_t DataSize;
+    size_t ReadPos;
+    CryptData *Crypt;
+  public:
+    RawRead();
+    RawRead(File *SrcFile);
+    void Reset();
+    size_t Read(size_t Size);
+    void Read(byte *SrcData,size_t Size);
+    byte   Get1();
+    ushort Get2();
+    uint   Get4();
+    uint64 Get8();
+    uint64 GetV();
+    uint   GetVSize(size_t Pos);
+    size_t GetB(void *Field,size_t Size);
+    void GetW(wchar *Field,size_t Size);
+    uint GetCRC15(bool ProcessedOnly);
+    uint GetCRC50();
+    byte* GetDataPtr() {return &Data[0];}
+    size_t Size() {return DataSize;}
+    size_t PaddedSize() {return Data.Size()-DataSize;}
+    size_t DataLeft() {return DataSize-ReadPos;}
+    size_t GetPos() {return ReadPos;}
+    void SetPos(size_t Pos) {ReadPos=Pos;}
+    void Skip(size_t Size) {ReadPos+=Size;}
+    void Rewind() {SetPos(0);}
+    void SetCrypt(CryptData *Crypt) {RawRead::Crypt=Crypt;}
+};
+
+uint64 RawGetV(const byte *Data,uint &ReadPos,uint DataSize,bool &Overflow);
+
+#endif
diff --git a/third_party/unrar/src/rdwrfn.cpp b/third_party/unrar/src/rdwrfn.cpp
new file mode 100644
index 0000000..f75f6645
--- /dev/null
+++ b/third_party/unrar/src/rdwrfn.cpp
@@ -0,0 +1,320 @@
+#include "rar.hpp"
+
+ComprDataIO::ComprDataIO()
+{
+#ifndef RAR_NOCRYPT
+  Crypt=new CryptData;
+  Decrypt=new CryptData;
+#endif
+
+  Init();
+}
+
+
+void ComprDataIO::Init()
+{
+  UnpackFromMemory=false;
+  UnpackToMemory=false;
+  UnpPackedSize=0;
+  ShowProgress=true;
+  TestMode=false;
+  SkipUnpCRC=false;
+  NoFileHeader=false;
+  PackVolume=false;
+  UnpVolume=false;
+  NextVolumeMissing=false;
+  SrcFile=NULL;
+  DestFile=NULL;
+  UnpWrSize=0;
+  Command=NULL;
+  Encryption=false;
+  Decryption=false;
+  CurPackRead=CurPackWrite=CurUnpRead=CurUnpWrite=0;
+  LastPercent=-1;
+  SubHead=NULL;
+  SubHeadPos=NULL;
+  CurrentCommand=0;
+  ProcessedArcSize=TotalArcSize=0;
+}
+
+
+ComprDataIO::~ComprDataIO()
+{
+#ifndef RAR_NOCRYPT
+  delete Crypt;
+  delete Decrypt;
+#endif
+}
+
+
+
+
+int ComprDataIO::UnpRead(byte *Addr,size_t Count)
+{
+#ifndef RAR_NOCRYPT
+  // In case of encryption we need to align read size to encryption 
+  // block size. We can do it by simple masking, because unpack read code
+  // always reads more than CRYPT_BLOCK_SIZE, so we do not risk to make it 0.
+  if (Decryption)
+    Count &= ~CRYPT_BLOCK_MASK;
+#endif
+  
+  int ReadSize=0,TotalRead=0;
+  byte *ReadAddr;
+  ReadAddr=Addr;
+  while (Count > 0)
+  {
+    Archive *SrcArc=(Archive *)SrcFile;
+
+    if (UnpackFromMemory)
+    {
+      memcpy(Addr,UnpackFromMemoryAddr,UnpackFromMemorySize);
+      ReadSize=(int)UnpackFromMemorySize;
+      UnpackFromMemorySize=0;
+    }
+    else
+    {
+      size_t SizeToRead=((int64)Count>UnpPackedSize) ? (size_t)UnpPackedSize:Count;
+      if (SizeToRead > 0)
+      {
+        if (UnpVolume && Decryption && (int64)Count>UnpPackedSize)
+        {
+          // We need aligned blocks for decryption and we want "Keep broken
+          // files" to work efficiently with missing encrypted volumes.
+          // So for last data block in volume we adjust the size to read to
+          // next equal or smaller block producing aligned total block size.
+          // So we'll ask for next volume only when processing few unaligned
+          // bytes left in the end, when most of data is already extracted.
+          size_t NewTotalRead = TotalRead + SizeToRead;
+          size_t Adjust = NewTotalRead - (NewTotalRead  & ~CRYPT_BLOCK_MASK);
+          size_t NewSizeToRead = SizeToRead - Adjust;
+          if ((int)NewSizeToRead > 0)
+            SizeToRead = NewSizeToRead;
+        }
+
+        if (!SrcFile->IsOpened())
+          return -1;
+        ReadSize=SrcFile->Read(ReadAddr,SizeToRead);
+        FileHeader *hd=SubHead!=NULL ? SubHead:&SrcArc->FileHead;
+        if (!NoFileHeader && hd->SplitAfter)
+          PackedDataHash.Update(ReadAddr,ReadSize);
+      }
+    }
+    CurUnpRead+=ReadSize;
+    TotalRead+=ReadSize;
+#ifndef NOVOLUME
+    // These variable are not used in NOVOLUME mode, so it is better
+    // to exclude commands below to avoid compiler warnings.
+    ReadAddr+=ReadSize;
+    Count-=ReadSize;
+#endif
+    UnpPackedSize-=ReadSize;
+
+    // Do not ask for next volume if we read something from current volume.
+    // If next volume is missing, we need to process all data from current
+    // volume before aborting. It helps to recover all possible data
+    // in "Keep broken files" mode. But if we process encrypted data,
+    // we ask for next volume also if we have non-aligned encryption block.
+    // Since we adjust data size for decryption earlier above,
+    // it does not hurt "Keep broken files" mode efficiency.
+    if (UnpVolume && UnpPackedSize == 0 && 
+        (ReadSize==0 || Decryption && (TotalRead & CRYPT_BLOCK_MASK) != 0) )
+    {
+#ifndef NOVOLUME
+      if (!MergeArchive(*SrcArc,this,true,CurrentCommand))
+#endif
+      {
+        NextVolumeMissing=true;
+        return -1;
+      }
+    }
+    else
+      break;
+  }
+  Archive *SrcArc=(Archive *)SrcFile;
+  if (SrcArc!=NULL)
+    ShowUnpRead(SrcArc->CurBlockPos+CurUnpRead,UnpArcSize);
+  if (ReadSize!=-1)
+  {
+    ReadSize=TotalRead;
+#ifndef RAR_NOCRYPT
+    if (Decryption)
+      Decrypt->DecryptBlock(Addr,ReadSize);
+#endif
+  }
+  Wait();
+  return ReadSize;
+}
+
+
+#if defined(RARDLL) && defined(_MSC_VER) && !defined(_WIN_64)
+// Disable the run time stack check for unrar.dll, so we can manipulate
+// with ProcessDataProc call type below. Run time check would intercept
+// a wrong ESP before we restore it.
+#pragma runtime_checks( "s", off )
+#endif
+
+void ComprDataIO::UnpWrite(byte *Addr,size_t Count)
+{
+
+#ifdef RARDLL
+  RAROptions *Cmd=((Archive *)SrcFile)->GetRAROptions();
+  if (Cmd->DllOpMode!=RAR_SKIP)
+  {
+    if (Cmd->Callback!=NULL &&
+        Cmd->Callback(UCM_PROCESSDATA,Cmd->UserData,(LPARAM)Addr,Count)==-1)
+      ErrHandler.Exit(RARX_USERBREAK);
+    if (Cmd->ProcessDataProc!=NULL)
+    {
+      // Here we preserve ESP value. It is necessary for those developers,
+      // who still define ProcessDataProc callback as "C" type function,
+      // even though in year 2001 we announced in unrar.dll whatsnew.txt
+      // that it will be PASCAL type (for compatibility with Visual Basic).
+#if defined(_MSC_VER)
+#ifndef _WIN_64
+      __asm mov ebx,esp
+#endif
+#elif defined(_WIN_ALL) && defined(__BORLANDC__)
+      _EBX=_ESP;
+#endif
+      int RetCode=Cmd->ProcessDataProc(Addr,(int)Count);
+
+      // Restore ESP after ProcessDataProc with wrongly defined calling
+      // convention broken it.
+#if defined(_MSC_VER)
+#ifndef _WIN_64
+      __asm mov esp,ebx
+#endif
+#elif defined(_WIN_ALL) && defined(__BORLANDC__)
+      _ESP=_EBX;
+#endif
+      if (RetCode==0)
+        ErrHandler.Exit(RARX_USERBREAK);
+    }
+  }
+#endif // RARDLL
+
+  UnpWrAddr=Addr;
+  UnpWrSize=Count;
+  if (UnpackToMemory)
+  {
+    if (Count <= UnpackToMemorySize)
+    {
+      memcpy(UnpackToMemoryAddr,Addr,Count);
+      UnpackToMemoryAddr+=Count;
+      UnpackToMemorySize-=Count;
+    }
+  }
+  else
+    if (!TestMode)
+      DestFile->Write(Addr,Count);
+  CurUnpWrite+=Count;
+  if (!SkipUnpCRC)
+    UnpHash.Update(Addr,Count);
+  ShowUnpWrite();
+  Wait();
+}
+
+#if defined(RARDLL) && defined(_MSC_VER) && !defined(_WIN_64)
+// Restore the run time stack check for unrar.dll.
+#pragma runtime_checks( "s", restore )
+#endif
+
+
+
+
+
+
+void ComprDataIO::ShowUnpRead(int64 ArcPos,int64 ArcSize)
+{
+  if (ShowProgress && SrcFile!=NULL)
+  {
+    if (TotalArcSize!=0)
+    {
+      // important when processing several archives or multivolume archive
+      ArcSize=TotalArcSize;
+      ArcPos+=ProcessedArcSize;
+    }
+
+    Archive *SrcArc=(Archive *)SrcFile;
+    RAROptions *Cmd=SrcArc->GetRAROptions();
+
+    int CurPercent=ToPercent(ArcPos,ArcSize);
+    if (!Cmd->DisablePercentage && CurPercent!=LastPercent)
+    {
+      uiExtractProgress(CurUnpWrite,SrcArc->FileHead.UnpSize,ArcPos,ArcSize);
+      LastPercent=CurPercent;
+    }
+  }
+}
+
+
+void ComprDataIO::ShowUnpWrite()
+{
+}
+
+
+
+
+
+
+
+
+
+
+void ComprDataIO::SetFiles(File *SrcFile,File *DestFile)
+{
+  if (SrcFile!=NULL)
+    ComprDataIO::SrcFile=SrcFile;
+  if (DestFile!=NULL)
+    ComprDataIO::DestFile=DestFile;
+  LastPercent=-1;
+}
+
+
+void ComprDataIO::GetUnpackedData(byte **Data,size_t *Size)
+{
+  *Data=UnpWrAddr;
+  *Size=UnpWrSize;
+}
+
+
+void ComprDataIO::SetEncryption(bool Encrypt,CRYPT_METHOD Method,
+     SecPassword *Password,const byte *Salt,const byte *InitV,
+     uint Lg2Cnt,byte *HashKey,byte *PswCheck)
+{
+#ifndef RAR_NOCRYPT
+  if (Encrypt)
+    Encryption=Crypt->SetCryptKeys(true,Method,Password,Salt,InitV,Lg2Cnt,HashKey,PswCheck);
+  else
+    Decryption=Decrypt->SetCryptKeys(false,Method,Password,Salt,InitV,Lg2Cnt,HashKey,PswCheck);
+#endif
+}
+
+
+#if !defined(SFX_MODULE) && !defined(RAR_NOCRYPT)
+void ComprDataIO::SetAV15Encryption()
+{
+  Decryption=true;
+  Decrypt->SetAV15Encryption();
+}
+#endif
+
+
+#if !defined(SFX_MODULE) && !defined(RAR_NOCRYPT)
+void ComprDataIO::SetCmt13Encryption()
+{
+  Decryption=true;
+  Decrypt->SetCmt13Encryption();
+}
+#endif
+
+
+
+
+void ComprDataIO::SetUnpackToMemory(byte *Addr,uint Size)
+{
+  UnpackToMemory=true;
+  UnpackToMemoryAddr=Addr;
+  UnpackToMemorySize=Size;
+}
diff --git a/third_party/unrar/src/rdwrfn.hpp b/third_party/unrar/src/rdwrfn.hpp
new file mode 100644
index 0000000..070010ea
--- /dev/null
+++ b/third_party/unrar/src/rdwrfn.hpp
@@ -0,0 +1,99 @@
+#ifndef _RAR_DATAIO_
+#define _RAR_DATAIO_
+
+class CmdAdd;
+class Unpack;
+
+#if 0
+// We use external i/o calls for Benchmark command.
+#define COMPRDATAIO_EXTIO
+#endif
+
+class ComprDataIO
+{
+  private:
+    void ShowUnpRead(int64 ArcPos,int64 ArcSize);
+    void ShowUnpWrite();
+
+
+    bool UnpackFromMemory;
+    size_t UnpackFromMemorySize;
+    byte *UnpackFromMemoryAddr;
+
+    bool UnpackToMemory;
+    size_t UnpackToMemorySize;
+    byte *UnpackToMemoryAddr;
+
+    size_t UnpWrSize;
+    byte *UnpWrAddr;
+
+    int64 UnpPackedSize;
+
+    bool ShowProgress;
+    bool TestMode;
+    bool SkipUnpCRC;
+    bool NoFileHeader;
+
+    File *SrcFile;
+    File *DestFile;
+
+    CmdAdd *Command;
+
+    FileHeader *SubHead;
+    int64 *SubHeadPos;
+
+#ifndef RAR_NOCRYPT
+    CryptData *Crypt;
+    CryptData *Decrypt;
+#endif
+
+
+    int LastPercent;
+
+    wchar CurrentCommand;
+
+  public:
+    ComprDataIO();
+    ~ComprDataIO();
+    void Init();
+    int UnpRead(byte *Addr,size_t Count);
+    void UnpWrite(byte *Addr,size_t Count);
+    void EnableShowProgress(bool Show) {ShowProgress=Show;}
+    void GetUnpackedData(byte **Data,size_t *Size);
+    void SetPackedSizeToRead(int64 Size) {UnpPackedSize=Size;}
+    void SetTestMode(bool Mode) {TestMode=Mode;}
+    void SetSkipUnpCRC(bool Skip) {SkipUnpCRC=Skip;}
+    void SetNoFileHeader(bool Mode) {NoFileHeader=Mode;}
+    void SetFiles(File *SrcFile,File *DestFile);
+    void SetCommand(CmdAdd *Cmd) {Command=Cmd;}
+    void SetSubHeader(FileHeader *hd,int64 *Pos) {SubHead=hd;SubHeadPos=Pos;}
+    void SetEncryption(bool Encrypt,CRYPT_METHOD Method,SecPassword *Password,
+         const byte *Salt,const byte *InitV,uint Lg2Cnt,byte *HashKey,byte *PswCheck);
+    void SetAV15Encryption();
+    void SetCmt13Encryption();
+    void SetUnpackToMemory(byte *Addr,uint Size);
+    void SetCurrentCommand(wchar Cmd) {CurrentCommand=Cmd;}
+
+
+    bool PackVolume;
+    bool UnpVolume;
+    bool NextVolumeMissing;
+    int64 UnpArcSize;
+    int64 CurPackRead,CurPackWrite,CurUnpRead,CurUnpWrite;
+
+
+    // Size of already processed archives.
+    // Used to calculate the total operation progress.
+    int64 ProcessedArcSize;
+
+    int64 TotalArcSize;
+
+    DataHash PackedDataHash; // Packed write and unpack read hash.
+    DataHash PackHash; // Pack read hash.
+    DataHash UnpHash;  // Unpack write hash.
+
+    bool Encryption;
+    bool Decryption;
+};
+
+#endif
diff --git a/third_party/unrar/src/readme.txt b/third_party/unrar/src/readme.txt
new file mode 100644
index 0000000..a1f820a
--- /dev/null
+++ b/third_party/unrar/src/readme.txt
@@ -0,0 +1,50 @@
+
+                       Portable UnRAR version
+
+
+   1. General
+
+   This package includes freeware Unrar C++ source and makefile for
+   several Unix compilers.
+
+   Unrar source is subset of RAR and generated from RAR source automatically,
+   by a small program removing blocks like '#ifndef UNRAR ... #endif'.
+   Such method is not perfect and you may find some RAR related stuff
+   unnecessary in Unrar, especially in header files.
+
+   If you wish to port Unrar to a new platform, you may need to edit
+   '#define LITTLE_ENDIAN' in os.hpp and data type definitions
+   in rartypes.hpp.
+
+   if computer architecture does not allow not aligned data access,
+   you need to undefine ALLOW_NOT_ALIGNED_INT and define
+   STRICT_ALIGNMENT_REQUIRED in os.h.
+
+   UnRAR.vcproj and UnRARDll.vcproj are projects for Microsoft Visual C++.
+   UnRARDll.vcproj lets to build unrar.dll library.
+
+
+   2. Unrar binaries
+
+   If you compiled Unrar for OS, which is not present in "Downloads"
+   and "RAR extras" on www.rarlab.com, we will appreciate if you send
+   us the compiled executable to place it to our site.
+
+
+   3. Acknowledgements
+
+   This source includes parts of code written by other authors.
+   Please see acknow.txt file for details.
+
+
+   4. Legal stuff
+
+   Unrar source may be used in any software to handle RAR archives
+   without limitations free of charge, but cannot be used to re-create
+   the RAR compression algorithm, which is proprietary. Distribution
+   of modified Unrar source in separate form or as a part of other
+   software is permitted, provided that it is clearly stated in
+   the documentation and source comments that the code may not be used
+   to develop a RAR (WinRAR) compatible archiver.
+
+   More detailed license text is available in license.txt.
diff --git a/third_party/unrar/src/recvol.cpp b/third_party/unrar/src/recvol.cpp
new file mode 100644
index 0000000..166eef4
--- /dev/null
+++ b/third_party/unrar/src/recvol.cpp
@@ -0,0 +1,111 @@
+#include "rar.hpp"
+
+#include "recvol3.cpp"
+#include "recvol5.cpp"
+
+
+
+bool RecVolumesRestore(RAROptions *Cmd,const wchar *Name,bool Silent)
+{
+  Archive Arc(Cmd);
+  if (!Arc.Open(Name))
+  {
+    if (!Silent)
+      ErrHandler.OpenErrorMsg(Name);
+    return false;
+  }
+
+  RARFORMAT Fmt=RARFMT15;
+  if (Arc.IsArchive(true))
+    Fmt=Arc.Format;
+  else
+  {
+    byte Sign[REV5_SIGN_SIZE];
+    Arc.Seek(0,SEEK_SET);
+    if (Arc.Read(Sign,REV5_SIGN_SIZE)==REV5_SIGN_SIZE && memcmp(Sign,REV5_SIGN,REV5_SIGN_SIZE)==0)
+      Fmt=RARFMT50;
+  }
+  Arc.Close();
+
+  // We define RecVol as local variable for proper stack unwinding when
+  // handling exceptions. So it can close and delete files on Cancel.
+  if (Fmt==RARFMT15)
+  {
+    RecVolumes3 RecVol(false);
+    return RecVol.Restore(Cmd,Name,Silent);
+  }
+  else
+  {
+    RecVolumes5 RecVol(false);
+    return RecVol.Restore(Cmd,Name,Silent);
+  }
+}
+
+
+void RecVolumesTest(RAROptions *Cmd,Archive *Arc,const wchar *Name)
+{
+  wchar RevName[NM];
+  *RevName=0;
+  if (Arc!=NULL)
+  {
+    // We received .rar or .exe volume as a parameter, trying to find
+    // the matching .rev file number 1.
+    bool NewNumbering=Arc->NewNumbering;
+
+    wchar ArcName[NM];
+    wcsncpyz(ArcName,Name,ASIZE(ArcName));
+
+    wchar *VolNumStart=VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),NewNumbering);
+    wchar RecVolMask[NM];
+    wcsncpyz(RecVolMask,ArcName,ASIZE(RecVolMask));
+    size_t BaseNamePartLength=VolNumStart-ArcName;
+    wcsncpyz(RecVolMask+BaseNamePartLength,L"*.rev",ASIZE(RecVolMask)-BaseNamePartLength);
+
+    FindFile Find;
+    Find.SetMask(RecVolMask);
+    FindData RecData;
+
+    while (Find.Next(&RecData))
+    {
+      wchar *Num=GetVolNumPart(RecData.Name);
+      if (*Num!='1') // Name must have "0...01" numeric part.
+        continue;
+      bool FirstVol=true;
+      while (--Num>=RecData.Name && IsDigit(*Num))
+        if (*Num!='0')
+        {
+          FirstVol=false;
+          break;
+        }
+      if (FirstVol)
+      {
+        wcsncpyz(RevName,RecData.Name,ASIZE(RevName));
+        Name=RevName;
+        break;
+      }
+    }
+    if (*RevName==0) // First .rev file not found.
+      return;
+  }
+  
+  File RevFile;
+  if (!RevFile.Open(Name))
+  {
+    ErrHandler.OpenErrorMsg(Name); // It also sets RARX_OPEN.
+    return;
+  }
+  mprintf(L"\n");
+  byte Sign[REV5_SIGN_SIZE];
+  bool Rev5=RevFile.Read(Sign,REV5_SIGN_SIZE)==REV5_SIGN_SIZE && memcmp(Sign,REV5_SIGN,REV5_SIGN_SIZE)==0;
+  RevFile.Close();
+  if (Rev5)
+  {
+    RecVolumes5 RecVol(true);
+    RecVol.Test(Cmd,Name);
+  }
+  else
+  {
+    RecVolumes3 RecVol(true);
+    RecVol.Test(Cmd,Name);
+  }
+}
diff --git a/third_party/unrar/src/recvol.hpp b/third_party/unrar/src/recvol.hpp
new file mode 100644
index 0000000..7f2f1adb
--- /dev/null
+++ b/third_party/unrar/src/recvol.hpp
@@ -0,0 +1,87 @@
+#ifndef _RAR_RECVOL_
+#define _RAR_RECVOL_
+
+#define REV5_SIGN      "Rar!\x1aRev"
+#define REV5_SIGN_SIZE             8
+
+class RecVolumes3
+{
+  private:
+    File *SrcFile[256];
+    Array<byte> Buf;
+
+#ifdef RAR_SMP
+    ThreadPool *RSThreadPool;
+#endif
+  public:
+    RecVolumes3(bool TestOnly);
+    ~RecVolumes3();
+    void Make(RAROptions *Cmd,wchar *ArcName);
+    bool Restore(RAROptions *Cmd,const wchar *Name,bool Silent);
+    void Test(RAROptions *Cmd,const wchar *Name);
+};
+
+
+struct RecVolItem
+{
+  File *f;
+  wchar Name[NM];
+  uint CRC;
+  uint64 FileSize;
+  bool New;   // Newly created RAR volume.
+  bool Valid; // If existing RAR volume is valid.
+};
+
+
+class RecVolumes5;
+struct RecRSThreadData
+{
+  RecVolumes5 *RecRSPtr;
+  RSCoder16 *RS;
+  bool Encode;
+  uint DataNum;
+  const byte *Data;
+  size_t StartPos;
+  size_t Size;
+};
+
+class RecVolumes5
+{
+  private:
+    void ProcessRS(RAROptions *Cmd,uint DataNum,const byte *Data,uint MaxRead,bool Encode);
+    void ProcessRS(RAROptions *Cmd,uint MaxRead,bool Encode);
+    uint ReadHeader(File *RecFile,bool FirstRev);
+
+    Array<RecVolItem> RecItems;
+
+    byte *RealReadBuffer; // Real pointer returned by 'new'.
+    byte *ReadBuffer;     // Pointer aligned for SSE instructions.
+
+    byte *RealBuf;        // Real pointer returned by 'new'.
+    byte *Buf;            // Store ECC or recovered data here, aligned for SSE.
+    size_t RecBufferSize; // Buffer area allocated for single volume.
+
+    uint DataCount;   // Number of archives.
+    uint RecCount;    // Number of recovery volumes.
+    uint TotalCount;  // Total number of archives and recovery volumes.
+
+    bool *ValidFlags; // Volume validity flags for recovering.
+    uint MissingVolumes; // Number of missing or bad RAR volumes.
+
+#ifdef RAR_SMP
+    ThreadPool *RecThreadPool;
+#endif
+    RecRSThreadData ThreadData[MaxPoolThreads]; // Store thread parameters.
+  public: // 'public' only because called from thread functions.
+    void ProcessAreaRS(RecRSThreadData *td);
+  public:
+    RecVolumes5(bool TestOnly);
+    ~RecVolumes5();
+    bool Restore(RAROptions *Cmd,const wchar *Name,bool Silent);
+    void Test(RAROptions *Cmd,const wchar *Name);
+};
+
+bool RecVolumesRestore(RAROptions *Cmd,const wchar *Name,bool Silent);
+void RecVolumesTest(RAROptions *Cmd,Archive *Arc,const wchar *Name);
+
+#endif
diff --git a/third_party/unrar/src/recvol3.cpp b/third_party/unrar/src/recvol3.cpp
new file mode 100644
index 0000000..0d18f07
--- /dev/null
+++ b/third_party/unrar/src/recvol3.cpp
@@ -0,0 +1,543 @@
+// Buffer size for all volumes involved.
+static const size_t TotalBufferSize=0x4000000;
+
+class RSEncode // Encode or decode data area, one object per one thread.
+{
+  private:
+    RSCoder RSC;
+  public:
+    void EncodeBuf();
+    void DecodeBuf();
+
+    void Init(int RecVolNumber) {RSC.Init(RecVolNumber);}
+    byte *Buf;
+    byte *OutBuf;
+    int BufStart;
+    int BufEnd;
+    int FileNumber;
+    int RecVolNumber;
+    size_t RecBufferSize;
+    int *Erasures;
+    int EraSize;
+};
+
+
+#ifdef RAR_SMP
+THREAD_PROC(RSEncodeThread)
+{
+  RSEncode *rs=(RSEncode *)Data;
+  rs->EncodeBuf();
+}
+
+THREAD_PROC(RSDecodeThread)
+{
+  RSEncode *rs=(RSEncode *)Data;
+  rs->DecodeBuf();
+}
+#endif
+
+RecVolumes3::RecVolumes3(bool TestOnly)
+{
+  memset(SrcFile,0,sizeof(SrcFile));
+  if (TestOnly)
+  {
+#ifdef RAR_SMP
+    RSThreadPool=NULL;
+#endif
+  }
+  else
+  {
+    Buf.Alloc(TotalBufferSize);
+    memset(SrcFile,0,sizeof(SrcFile));
+#ifdef RAR_SMP
+    RSThreadPool=CreateThreadPool();
+#endif
+  }
+}
+
+
+RecVolumes3::~RecVolumes3()
+{
+  for (size_t I=0;I<ASIZE(SrcFile);I++)
+    delete SrcFile[I];
+#ifdef RAR_SMP
+  DestroyThreadPool(RSThreadPool);
+#endif
+}
+
+
+
+
+void RSEncode::EncodeBuf()
+{
+  for (int BufPos=BufStart;BufPos<BufEnd;BufPos++)
+  {
+    byte Data[256],Code[256];
+    for (int I=0;I<FileNumber;I++)
+      Data[I]=Buf[I*RecBufferSize+BufPos];
+    RSC.Encode(Data,FileNumber,Code);
+    for (int I=0;I<RecVolNumber;I++)
+      OutBuf[I*RecBufferSize+BufPos]=Code[I];
+  }
+}
+
+
+// Check for names like arc5_3_1.rev created by RAR 3.0.
+static bool IsNewStyleRev(const wchar *Name)
+{
+  wchar *Ext=GetExt(Name);
+  if (Ext==NULL)
+    return true;
+  int DigitGroup=0;
+  for (Ext--;Ext>Name;Ext--)
+    if (!IsDigit(*Ext))
+      if (*Ext=='_' && IsDigit(*(Ext-1)))
+        DigitGroup++;
+      else
+        break;
+  return DigitGroup<2;
+}
+
+
+bool RecVolumes3::Restore(RAROptions *Cmd,const wchar *Name,bool Silent)
+{
+  wchar ArcName[NM];
+  wcsncpyz(ArcName,Name,ASIZE(ArcName));
+  wchar *Ext=GetExt(ArcName);
+  bool NewStyle=false; // New style .rev volumes are supported since RAR 3.10.
+  bool RevName=Ext!=NULL && wcsicomp(Ext,L".rev")==0;
+  if (RevName)
+  {
+    NewStyle=IsNewStyleRev(ArcName);
+    while (Ext>ArcName+1 && (IsDigit(*(Ext-1)) || *(Ext-1)=='_'))
+      Ext--;
+    wcscpy(Ext,L"*.*");
+    
+    FindFile Find;
+    Find.SetMask(ArcName);
+    FindData fd;
+    while (Find.Next(&fd))
+    {
+      Archive Arc(Cmd);
+      if (Arc.WOpen(fd.Name) && Arc.IsArchive(true))
+      {
+        wcsncpyz(ArcName,fd.Name,ASIZE(ArcName));
+        break;
+      }
+    }
+  }
+
+  Archive Arc(Cmd);
+  if (!Arc.WCheckOpen(ArcName))
+    return false;
+  if (!Arc.Volume)
+  {
+    uiMsg(UIERROR_NOTVOLUME,ArcName);
+    return false;
+  }
+  bool NewNumbering=Arc.NewNumbering;
+  Arc.Close();
+
+  wchar *VolNumStart=VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),NewNumbering);
+  wchar RecVolMask[NM];
+  wcsncpyz(RecVolMask,ArcName,ASIZE(RecVolMask));
+  size_t BaseNamePartLength=VolNumStart-ArcName;
+  wcsncpyz(RecVolMask+BaseNamePartLength,L"*.rev",ASIZE(RecVolMask)-BaseNamePartLength);
+
+  int64 RecFileSize=0;
+
+  // We cannot display "Calculating CRC..." message here, because we do not
+  // know if we'll find any recovery volumes. We'll display it after finding
+  // the first recovery volume.
+  bool CalcCRCMessageDone=false;
+
+  FindFile Find;
+  Find.SetMask(RecVolMask);
+  FindData RecData;
+  int FileNumber=0,RecVolNumber=0,FoundRecVolumes=0,MissingVolumes=0;
+  wchar PrevName[NM];
+  while (Find.Next(&RecData))
+  {
+    wchar *CurName=RecData.Name;
+    int P[3];
+    if (!RevName && !NewStyle)
+    {
+      NewStyle=true;
+
+      wchar *Dot=GetExt(CurName);
+      if (Dot!=NULL)
+      {
+        int LineCount=0;
+        Dot--;
+        while (Dot>CurName && *Dot!='.')
+        {
+          if (*Dot=='_')
+            LineCount++;
+          Dot--;
+        }
+        if (LineCount==2)
+          NewStyle=false;
+      }
+    }
+    if (NewStyle)
+    {
+      if (!CalcCRCMessageDone)
+      {
+        uiMsg(UIMSG_RECVOLCALCCHECKSUM);
+        CalcCRCMessageDone=true;
+      }
+      
+      uiMsg(UIMSG_STRING,CurName);
+
+      File CurFile;
+      CurFile.TOpen(CurName);
+      CurFile.Seek(0,SEEK_END);
+      int64 Length=CurFile.Tell();
+      CurFile.Seek(Length-7,SEEK_SET);
+      for (int I=0;I<3;I++)
+        P[2-I]=CurFile.GetByte()+1;
+      uint FileCRC=0;
+      for (int I=0;I<4;I++)
+        FileCRC|=CurFile.GetByte()<<(I*8);
+      uint CalcCRC;
+      CalcFileSum(&CurFile,&CalcCRC,NULL,Cmd->Threads,Length-4);
+      if (FileCRC!=CalcCRC)
+      {
+        uiMsg(UIMSG_CHECKSUM,CurName);
+        continue;
+      }
+    }
+    else
+    {
+      wchar *Dot=GetExt(CurName);
+      if (Dot==NULL)
+        continue;
+      bool WrongParam=false;
+      for (size_t I=0;I<ASIZE(P);I++)
+      {
+        do
+        {
+          Dot--;
+        } while (IsDigit(*Dot) && Dot>=CurName+BaseNamePartLength);
+        P[I]=atoiw(Dot+1);
+        if (P[I]==0 || P[I]>255)
+          WrongParam=true;
+      }
+      if (WrongParam)
+        continue;
+    }
+    if (P[1]+P[2]>255)
+      continue;
+    if (RecVolNumber!=0 && RecVolNumber!=P[1] || FileNumber!=0 && FileNumber!=P[2])
+    {
+      uiMsg(UIERROR_RECVOLDIFFSETS,CurName,PrevName);
+      return false;
+    }
+    RecVolNumber=P[1];
+    FileNumber=P[2];
+    wcscpy(PrevName,CurName);
+    File *NewFile=new File;
+    NewFile->TOpen(CurName);
+    SrcFile[FileNumber+P[0]-1]=NewFile;
+    FoundRecVolumes++;
+
+    if (RecFileSize==0)
+      RecFileSize=NewFile->FileLength();
+  }
+  if (!Silent || FoundRecVolumes!=0)
+    uiMsg(UIMSG_RECVOLFOUND,FoundRecVolumes);
+  if (FoundRecVolumes==0)
+    return(false);
+
+  bool WriteFlags[256];
+  memset(WriteFlags,0,sizeof(WriteFlags));
+
+  wchar LastVolName[NM];
+  *LastVolName=0;
+
+  for (int CurArcNum=0;CurArcNum<FileNumber;CurArcNum++)
+  {
+    Archive *NewFile=new Archive(Cmd);
+    bool ValidVolume=FileExist(ArcName);
+    if (ValidVolume)
+    {
+      NewFile->TOpen(ArcName);
+      ValidVolume=NewFile->IsArchive(false);
+      if (ValidVolume)
+      {
+        while (NewFile->ReadHeader()!=0)
+        {
+          if (NewFile->GetHeaderType()==HEAD_ENDARC)
+          {
+            uiMsg(UIMSG_STRING,ArcName);
+
+            if (NewFile->EndArcHead.DataCRC)
+            {
+              uint CalcCRC;
+              CalcFileSum(NewFile,&CalcCRC,NULL,Cmd->Threads,NewFile->CurBlockPos);
+              if (NewFile->EndArcHead.ArcDataCRC!=CalcCRC)
+              {
+                ValidVolume=false;
+                uiMsg(UIMSG_CHECKSUM,ArcName);
+              }
+            }
+            break;
+          }
+          NewFile->SeekToNext();
+        }
+      }
+      if (!ValidVolume)
+      {
+        NewFile->Close();
+        wchar NewName[NM];
+        wcscpy(NewName,ArcName);
+        wcscat(NewName,L".bad");
+
+        uiMsg(UIMSG_BADARCHIVE,ArcName);
+        uiMsg(UIMSG_RENAMING,ArcName,NewName);
+        RenameFile(ArcName,NewName);
+      }
+      NewFile->Seek(0,SEEK_SET);
+    }
+    if (!ValidVolume)
+    {
+      // It is important to return 'false' instead of aborting here,
+      // so if we are called from extraction, we will be able to continue
+      // extracting. It may happen if .rar and .rev are on read-only disks
+      // like CDs.
+      if (!NewFile->Create(ArcName,FMF_WRITE|FMF_SHAREREAD))
+      {
+        // We need to display the title of operation before the error message,
+        // to make clear for user that create error is related to recovery 
+        // volumes. This is why we cannot use WCreate call here. Title must be
+        // before create error, not after that.
+
+        uiMsg(UIERROR_RECVOLFOUND,FoundRecVolumes); // Intentionally not displayed in console mode.
+        uiMsg(UIERROR_RECONSTRUCTING);
+        ErrHandler.CreateErrorMsg(ArcName);
+        return false;
+      }
+
+      WriteFlags[CurArcNum]=true;
+      MissingVolumes++;
+
+      if (CurArcNum==FileNumber-1)
+        wcscpy(LastVolName,ArcName);
+
+      uiMsg(UIMSG_MISSINGVOL,ArcName);
+      uiMsg(UIEVENT_NEWARCHIVE,ArcName);
+    }
+    SrcFile[CurArcNum]=(File*)NewFile;
+    NextVolumeName(ArcName,ASIZE(ArcName),!NewNumbering);
+  }
+
+  uiMsg(UIMSG_RECVOLMISSING,MissingVolumes);
+
+  if (MissingVolumes==0)
+  {
+    uiMsg(UIERROR_RECVOLALLEXIST);
+    return false;
+  }
+
+  if (MissingVolumes>FoundRecVolumes)
+  {
+    uiMsg(UIERROR_RECVOLFOUND,FoundRecVolumes); // Intentionally not displayed in console mode.
+    uiMsg(UIERROR_RECVOLCANNOTFIX);
+    return false;
+  }
+
+  uiMsg(UIMSG_RECONSTRUCTING);
+
+  int TotalFiles=FileNumber+RecVolNumber;
+  int Erasures[256],EraSize=0;
+
+  for (int I=0;I<TotalFiles;I++)
+    if (WriteFlags[I] || SrcFile[I]==NULL)
+      Erasures[EraSize++]=I;
+
+  int64 ProcessedSize=0;
+  int LastPercent=-1;
+  mprintf(L"     ");
+  // Size of per file buffer.
+  size_t RecBufferSize=TotalBufferSize/TotalFiles;
+
+#ifdef RAR_SMP
+  uint ThreadNumber=Cmd->Threads;
+  RSEncode rse[MaxPoolThreads];
+#else
+  uint ThreadNumber=1;
+  RSEncode rse[1];
+#endif
+  for (uint I=0;I<ThreadNumber;I++)
+    rse[I].Init(RecVolNumber);
+
+  while (true)
+  {
+    Wait();
+    int MaxRead=0;
+    for (int I=0;I<TotalFiles;I++)
+      if (WriteFlags[I] || SrcFile[I]==NULL)
+        memset(&Buf[I*RecBufferSize],0,RecBufferSize);
+      else
+      {
+        int ReadSize=SrcFile[I]->Read(&Buf[I*RecBufferSize],RecBufferSize);
+        if ((size_t)ReadSize!=RecBufferSize)
+          memset(&Buf[I*RecBufferSize+ReadSize],0,RecBufferSize-ReadSize);
+        if (ReadSize>MaxRead)
+          MaxRead=ReadSize;
+      }
+    if (MaxRead==0)
+      break;
+
+    int CurPercent=ToPercent(ProcessedSize,RecFileSize);
+    if (!Cmd->DisablePercentage && CurPercent!=LastPercent)
+    {
+      uiProcessProgress("RC",ProcessedSize,RecFileSize);
+      LastPercent=CurPercent;
+    }
+    ProcessedSize+=MaxRead;
+
+    int BlockStart=0;
+    int BlockSize=MaxRead/ThreadNumber;
+    if (BlockSize<0x100)
+      BlockSize=MaxRead;
+    
+    for (uint CurThread=0;BlockStart<MaxRead;CurThread++)
+    {
+      // Last thread processes all left data including increasement
+      // from rounding error.
+      if (CurThread==ThreadNumber-1)
+        BlockSize=MaxRead-BlockStart;
+
+      RSEncode *curenc=rse+CurThread;
+      curenc->Buf=&Buf[0];
+      curenc->BufStart=BlockStart;
+      curenc->BufEnd=BlockStart+BlockSize;
+      curenc->FileNumber=TotalFiles;
+      curenc->RecBufferSize=RecBufferSize;
+      curenc->Erasures=Erasures;
+      curenc->EraSize=EraSize;
+
+#ifdef RAR_SMP
+      if (ThreadNumber>1)
+        RSThreadPool->AddTask(RSDecodeThread,(void*)curenc);
+      else
+        curenc->DecodeBuf();
+#else
+      curenc->DecodeBuf();
+#endif
+
+      BlockStart+=BlockSize;
+    }
+
+#ifdef RAR_SMP
+    RSThreadPool->WaitDone();
+#endif // RAR_SMP
+    
+    for (int I=0;I<FileNumber;I++)
+      if (WriteFlags[I])
+        SrcFile[I]->Write(&Buf[I*RecBufferSize],MaxRead);
+  }
+  for (int I=0;I<RecVolNumber+FileNumber;I++)
+    if (SrcFile[I]!=NULL)
+    {
+      File *CurFile=SrcFile[I];
+      if (NewStyle && WriteFlags[I])
+      {
+        int64 Length=CurFile->Tell();
+        CurFile->Seek(Length-7,SEEK_SET);
+        for (int J=0;J<7;J++)
+          CurFile->PutByte(0);
+      }
+      CurFile->Close();
+      SrcFile[I]=NULL;
+    }
+  if (*LastVolName!=0)
+  {
+    // Truncate the last volume to its real size.
+    Archive Arc(Cmd);
+    if (Arc.Open(LastVolName,FMF_UPDATE) && Arc.IsArchive(true) &&
+        Arc.SearchBlock(HEAD_ENDARC))
+    {
+      Arc.Seek(Arc.NextBlockPos,SEEK_SET);
+      char Buf[8192];
+      int ReadSize=Arc.Read(Buf,sizeof(Buf));
+      int ZeroCount=0;
+      while (ZeroCount<ReadSize && Buf[ZeroCount]==0)
+        ZeroCount++;
+      if (ZeroCount==ReadSize)
+      {
+        Arc.Seek(Arc.NextBlockPos,SEEK_SET);
+        Arc.Truncate();
+      }
+    }
+  }
+#if !defined(SILENT)
+  if (!Cmd->DisablePercentage)
+    mprintf(L"\b\b\b\b100%%");
+  if (!Silent && !Cmd->DisableDone)
+    mprintf(St(MDone));
+#endif
+  return true;
+}
+
+
+void RSEncode::DecodeBuf()
+{
+  for (int BufPos=BufStart;BufPos<BufEnd;BufPos++)
+  {
+    byte Data[256];
+    for (int I=0;I<FileNumber;I++)
+      Data[I]=Buf[I*RecBufferSize+BufPos];
+    RSC.Decode(Data,FileNumber,Erasures,EraSize);
+    for (int I=0;I<EraSize;I++)
+      Buf[Erasures[I]*RecBufferSize+BufPos]=Data[Erasures[I]];
+  }
+}
+
+
+void RecVolumes3::Test(RAROptions *Cmd,const wchar *Name)
+{
+  if (!IsNewStyleRev(Name)) // RAR 3.0 name#_#_#.rev do not include CRC32.
+  {
+    ErrHandler.UnknownMethodMsg(Name,Name);
+    return;
+  }
+
+  wchar VolName[NM];
+  wcsncpyz(VolName,Name,ASIZE(VolName));
+
+  while (FileExist(VolName))
+  {
+    File CurFile;
+    if (!CurFile.Open(VolName))
+    {
+      ErrHandler.OpenErrorMsg(VolName); // It also sets RARX_OPEN.
+      continue;
+    }
+    if (!uiStartFileExtract(VolName,false,true,false))
+      return;
+    mprintf(St(MExtrTestFile),VolName);
+    mprintf(L"     ");
+    CurFile.Seek(0,SEEK_END);
+    int64 Length=CurFile.Tell();
+    CurFile.Seek(Length-4,SEEK_SET);
+    uint FileCRC=0;
+    for (int I=0;I<4;I++)
+      FileCRC|=CurFile.GetByte()<<(I*8);
+
+    uint CalcCRC;
+    CalcFileSum(&CurFile,&CalcCRC,NULL,1,Length-4,Cmd->DisablePercentage ? 0 : CALCFSUM_SHOWPROGRESS);
+    if (FileCRC==CalcCRC)
+    {
+      mprintf(L"%s%s ",L"\b\b\b\b\b ",St(MOk));
+    }
+    else
+    {
+      uiMsg(UIERROR_CHECKSUM,VolName,VolName);
+      ErrHandler.SetErrorCode(RARX_CRC);
+    }
+
+    NextVolumeName(VolName,ASIZE(VolName),false);
+  }
+}
diff --git a/third_party/unrar/src/recvol5.cpp b/third_party/unrar/src/recvol5.cpp
new file mode 100644
index 0000000..fd74c1b
--- /dev/null
+++ b/third_party/unrar/src/recvol5.cpp
@@ -0,0 +1,521 @@
+static const uint MaxVolumes=65535;
+
+RecVolumes5::RecVolumes5(bool TestOnly)
+{
+  RealBuf=NULL;
+  RealReadBuffer=NULL;
+
+  DataCount=0;
+  RecCount=0;
+  TotalCount=0;
+  RecBufferSize=0;
+
+  for (uint I=0;I<ASIZE(ThreadData);I++)
+  {
+    ThreadData[I].RecRSPtr=this;
+    ThreadData[I].RS=NULL;
+  }
+
+  if (TestOnly)
+  {
+#ifdef RAR_SMP
+    RecThreadPool=NULL;
+#endif
+  }
+  else
+  {
+#ifdef RAR_SMP
+    RecThreadPool=CreateThreadPool();
+#endif
+    RealBuf=new byte[TotalBufferSize+SSE_ALIGNMENT];
+    Buf=(byte *)ALIGN_VALUE(RealBuf,SSE_ALIGNMENT);
+  }
+}
+
+
+RecVolumes5::~RecVolumes5()
+{
+  delete[] RealBuf;
+  delete[] RealReadBuffer;
+  for (uint I=0;I<RecItems.Size();I++)
+    delete RecItems[I].f;
+  for (uint I=0;I<ASIZE(ThreadData);I++)
+    delete ThreadData[I].RS;
+#ifdef RAR_SMP
+  DestroyThreadPool(RecThreadPool);
+#endif
+}
+
+
+
+
+#ifdef RAR_SMP
+THREAD_PROC(RecThreadRS)
+{
+  RecRSThreadData *td=(RecRSThreadData *)Data;
+  td->RecRSPtr->ProcessAreaRS(td);
+}
+#endif
+
+
+void RecVolumes5::ProcessRS(RAROptions *Cmd,uint DataNum,const byte *Data,uint MaxRead,bool Encode)
+{
+/*
+  RSCoder16 RS;
+  RS.Init(DataCount,RecCount,Encode ? NULL:ValidFlags);
+  uint Count=Encode ? RecCount : MissingVolumes;
+  for (uint I=0;I<Count;I++)
+    RS.UpdateECC(DataNum, I, Data, Buf+I*RecBufferSize, MaxRead);
+*/
+
+#ifdef RAR_SMP
+  uint ThreadNumber=Cmd->Threads;
+#else
+  uint ThreadNumber=1;
+#endif
+
+  const uint MinThreadBlock=0x1000;
+  ThreadNumber=Min(ThreadNumber,MaxRead/MinThreadBlock);
+
+  if (ThreadNumber<1)
+    ThreadNumber=1;
+  uint ThreadDataSize=MaxRead/ThreadNumber;
+  ThreadDataSize+=(ThreadDataSize&1); // Must be even for 16-bit RS coder.
+#ifdef USE_SSE
+  ThreadDataSize=ALIGN_VALUE(ThreadDataSize,SSE_ALIGNMENT); // Alignment for SSE operations.
+#endif
+  if (ThreadDataSize<MinThreadBlock)
+    ThreadDataSize=MinThreadBlock;
+
+  for (size_t I=0,CurPos=0;I<ThreadNumber && CurPos<MaxRead;I++)
+  {
+    RecRSThreadData *td=ThreadData+I;
+    if (td->RS==NULL)
+    {
+      td->RS=new RSCoder16;
+      td->RS->Init(DataCount,RecCount,Encode ? NULL:ValidFlags);
+    }
+    td->DataNum=DataNum;
+    td->Data=Data;
+    td->Encode=Encode;
+    td->StartPos=CurPos;
+
+    size_t EndPos=CurPos+ThreadDataSize;
+    if (EndPos>MaxRead || I==ThreadNumber-1)
+      EndPos=MaxRead;
+
+    td->Size=EndPos-CurPos;
+
+    CurPos=EndPos;
+
+#ifdef RAR_SMP
+    if (ThreadNumber>1)
+      RecThreadPool->AddTask(RecThreadRS,(void*)td);
+    else
+      ProcessAreaRS(td);
+#else
+    ProcessAreaRS(td);
+#endif
+  }
+#ifdef RAR_SMP
+    RecThreadPool->WaitDone();
+#endif // RAR_SMP
+}
+
+
+void RecVolumes5::ProcessAreaRS(RecRSThreadData *td)
+{
+  uint Count=td->Encode ? RecCount : MissingVolumes;
+  for (uint I=0;I<Count;I++)
+    td->RS->UpdateECC(td->DataNum, I, td->Data+td->StartPos, Buf+I*RecBufferSize+td->StartPos, td->Size);
+}
+
+
+
+
+bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent)
+{
+  wchar ArcName[NM];
+  wcsncpyz(ArcName,Name,ASIZE(ArcName));
+
+  wchar *Num=GetVolNumPart(ArcName);
+  if (Num==ArcName)
+    return false; // Number part is missing in the name.
+  while (Num>ArcName && IsDigit(*(Num-1)))
+    Num--;
+  if (Num==ArcName)
+    return false; // Entire volume name is numeric, not possible for REV file.
+  wcsncpyz(Num,L"*.*",ASIZE(ArcName)-(Num-ArcName));
+  
+  wchar FirstVolName[NM];
+  *FirstVolName=0;
+
+  int64 RecFileSize=0;
+
+  FindFile VolFind;
+  VolFind.SetMask(ArcName);
+  FindData fd;
+  uint FoundRecVolumes=0;
+  while (VolFind.Next(&fd))
+  {
+    Wait();
+
+    Archive *Vol=new Archive(Cmd);
+    int ItemPos=-1;
+    if (Vol->WOpen(fd.Name))
+    {
+      if (CmpExt(fd.Name,L"rev"))
+      {
+        uint RecNum=ReadHeader(Vol,FoundRecVolumes==0);
+        if (RecNum!=0)
+        {
+          if (FoundRecVolumes==0)
+            RecFileSize=Vol->FileLength();
+
+          ItemPos=RecNum;
+          FoundRecVolumes++;
+        }
+      }
+      else
+        if (Vol->IsArchive(true) && (Vol->SFXSize>0 || CmpExt(fd.Name,L"rar")))
+        {
+          if (!Vol->Volume && !Vol->BrokenHeader)
+          {
+            uiMsg(UIERROR_NOTVOLUME,ArcName);
+            return false;
+          }
+          // We work with archive as with raw data file, so we do not want
+          // to spend time to QOpen I/O redirection.
+          Vol->QOpenUnload();
+      
+          Vol->Seek(0,SEEK_SET);
+
+          // RAR volume found. Get its number, store the handle in appropriate
+          // array slot, clean slots in between if we had to grow the array.
+          wchar *Num=GetVolNumPart(fd.Name);
+          uint VolNum=0;
+          for (uint K=1;Num>=fd.Name && IsDigit(*Num);K*=10,Num--)
+            VolNum+=(*Num-'0')*K;
+          if (VolNum==0 || VolNum>MaxVolumes)
+            continue;
+          size_t CurSize=RecItems.Size();
+          if (VolNum>CurSize)
+          {
+            RecItems.Alloc(VolNum);
+            for (size_t I=CurSize;I<VolNum;I++)
+              RecItems[I].f=NULL;
+          }
+          ItemPos=VolNum-1;
+
+          if (*FirstVolName==0)
+            VolNameToFirstName(fd.Name,FirstVolName,ASIZE(FirstVolName),true);
+        }
+    }
+    if (ItemPos==-1)
+      delete Vol; // Skip found file, it is not RAR or REV volume.
+    else
+      if ((uint)ItemPos<RecItems.Size()) // Check if found more REV than needed.
+      {
+        // Store found RAR or REV volume.
+        RecVolItem *Item=RecItems+ItemPos;
+        Item->f=Vol;
+        Item->New=false;
+        wcsncpyz(Item->Name,fd.Name,ASIZE(Item->Name));
+      }
+  }
+
+  if (!Silent || FoundRecVolumes!=0)
+    uiMsg(UIMSG_RECVOLFOUND,FoundRecVolumes);
+  if (FoundRecVolumes==0)
+    return false;
+
+  uiMsg(UIMSG_RECVOLCALCCHECKSUM);
+
+  MissingVolumes=0;
+  for (uint I=0;I<TotalCount;I++)
+  {
+    RecVolItem *Item=&RecItems[I];
+    if (Item->f!=NULL)
+    {
+      uiMsg(UIMSG_STRING,Item->Name);
+
+      uint RevCRC;
+      CalcFileSum(Item->f,&RevCRC,NULL,Cmd->Threads,INT64NDF,CALCFSUM_CURPOS);
+      Item->Valid=RevCRC==Item->CRC;
+      if (!Item->Valid)
+      {
+        uiMsg(UIMSG_CHECKSUM,Item->Name);
+
+        // Close only corrupt REV volumes here. We'll close and rename corrupt
+        // RAR volumes later, if we'll know that recovery is possible.
+        if (I>=DataCount)
+        {
+          Item->f->Close();
+          Item->f=NULL;
+          FoundRecVolumes--;
+        }
+      }
+    }
+    if (I<DataCount && (Item->f==NULL || !Item->Valid))
+      MissingVolumes++;
+  }
+
+  uiMsg(UIMSG_RECVOLMISSING,MissingVolumes);
+
+  if (MissingVolumes==0)
+  {
+    uiMsg(UIERROR_RECVOLALLEXIST);
+    return false;
+  }
+
+  if (MissingVolumes>FoundRecVolumes)
+  {
+    uiMsg(UIERROR_RECVOLFOUND,FoundRecVolumes); // Intentionally not displayed in console mode.
+    uiMsg(UIERROR_RECVOLCANNOTFIX);
+    return false;
+  }
+
+  uiMsg(UIMSG_RECONSTRUCTING);
+
+  // Create missing and rename bad volumes.
+  uint64 MaxVolSize=0;
+  for (uint I=0;I<DataCount;I++)
+  {
+    RecVolItem *Item=&RecItems[I];
+    if (Item->FileSize>MaxVolSize)
+      MaxVolSize=Item->FileSize;
+    if (Item->f!=NULL && !Item->Valid)
+    {
+      Item->f->Close();
+
+      wchar NewName[NM];
+      wcscpy(NewName,Item->Name);
+      wcscat(NewName,L".bad");
+
+      uiMsg(UIMSG_BADARCHIVE,Item->Name);
+      uiMsg(UIMSG_RENAMING,Item->Name,NewName);
+      RenameFile(Item->Name,NewName);
+      delete Item->f;
+      Item->f=NULL;
+    }
+
+    if ((Item->New=(Item->f==NULL))) // Additional parentheses to avoid GCC warning.
+    {
+      wcsncpyz(Item->Name,FirstVolName,ASIZE(Item->Name));
+      uiMsg(UIMSG_CREATING,Item->Name);
+      uiMsg(UIEVENT_NEWARCHIVE,Item->Name);
+      File *NewVol=new File;
+      bool UserReject;
+      if (!FileCreate(Cmd,NewVol,Item->Name,ASIZE(Item->Name),&UserReject))
+      {
+        if (!UserReject)
+          ErrHandler.CreateErrorMsg(Item->Name);
+        ErrHandler.Exit(UserReject ? RARX_USERBREAK:RARX_CREATE);
+      }
+      NewVol->Prealloc(Item->FileSize);
+      Item->f=NewVol;
+      Item->New=true;
+    }
+    NextVolumeName(FirstVolName,ASIZE(FirstVolName),false);
+  }
+
+
+  int64 ProcessedSize=0;
+  int LastPercent=-1;
+  mprintf(L"     ");
+
+  // Even though we already preliminary calculated missing volume number,
+  // let's do it again now, when we have the final and exact information.
+  MissingVolumes=0;
+
+  ValidFlags=new bool[TotalCount];
+  for (uint I=0;I<TotalCount;I++)
+  {
+    ValidFlags[I]=RecItems[I].f!=NULL && !RecItems[I].New;
+    if (I<DataCount && !ValidFlags[I])
+      MissingVolumes++;
+  }
+
+  // Size of per file buffer.
+  RecBufferSize=TotalBufferSize/MissingVolumes;
+  if ((RecBufferSize&1)==1) // Must be even for our RS16 codec.
+    RecBufferSize--;
+#ifdef USE_SSE
+  RecBufferSize&=~(SSE_ALIGNMENT-1); // Align for SSE.
+#endif
+
+  uint *Data=new uint[TotalCount];
+
+  RSCoder16 RS;
+  if (!RS.Init(DataCount,RecCount,ValidFlags))
+  {
+    delete[] ValidFlags;
+    delete[] Data;
+    return false; // Should not happen, we check parameter validity above.
+  }
+
+  RealReadBuffer=new byte[RecBufferSize+SSE_ALIGNMENT];
+  byte *ReadBuf=(byte *)ALIGN_VALUE(RealReadBuffer,SSE_ALIGNMENT);
+
+  while (true)
+  {
+    Wait();
+
+    int MaxRead=0;
+    for (uint I=0,J=DataCount;I<DataCount;I++)
+    {
+      uint VolNum=I;
+      if (!ValidFlags[I]) // If next RAR volume is missing or invalid.
+      {
+        while (!ValidFlags[J]) // Find next valid REV volume.
+          J++;
+        VolNum=J++; // Use next valid REV volume data instead of RAR.
+      }
+      RecVolItem *Item=RecItems+VolNum;
+
+      byte *B=&ReadBuf[0];
+      int ReadSize=0;
+      if (Item->f!=NULL && !Item->New)
+        ReadSize=Item->f->Read(B,RecBufferSize);
+      if (ReadSize!=RecBufferSize)
+        memset(B+ReadSize,0,RecBufferSize-ReadSize);
+      if (ReadSize>MaxRead)
+        MaxRead=ReadSize;
+
+      // We can have volumes of different size. Let's use data chunk
+      // for largest volume size.
+      uint DataToProcess=(uint)Min(RecBufferSize,MaxVolSize-ProcessedSize);
+      ProcessRS(Cmd,I,B,DataToProcess,false);
+    }
+    if (MaxRead==0)
+      break;
+
+    for (uint I=0,J=0;I<DataCount;I++)
+      if (!ValidFlags[I])
+      {
+        RecVolItem *Item=RecItems+I;
+        size_t WriteSize=(size_t)Min(MaxRead,Item->FileSize);
+        Item->f->Write(Buf+(J++)*RecBufferSize,WriteSize);
+        Item->FileSize-=WriteSize;
+      }
+
+    int CurPercent=ToPercent(ProcessedSize,RecFileSize);
+    if (!Cmd->DisablePercentage && CurPercent!=LastPercent)
+    {
+      uiProcessProgress("RV",ProcessedSize,RecFileSize);
+      LastPercent=CurPercent;
+    }
+    ProcessedSize+=MaxRead;
+  }
+
+  for (uint I=0;I<TotalCount;I++)
+    if (RecItems[I].f!=NULL)
+      RecItems[I].f->Close();
+
+  delete[] ValidFlags;
+  delete[] Data;
+#if !defined(SILENT)
+  if (!Cmd->DisablePercentage)
+    mprintf(L"\b\b\b\b100%%");
+  if (!Silent && !Cmd->DisableDone)
+    mprintf(St(MDone));
+#endif
+  return true;
+}
+
+
+uint RecVolumes5::ReadHeader(File *RecFile,bool FirstRev)
+{
+  const size_t FirstReadSize=REV5_SIGN_SIZE+8;
+  byte ShortBuf[FirstReadSize];
+  if (RecFile->Read(ShortBuf,FirstReadSize)!=FirstReadSize)
+    return 0;
+  if (memcmp(ShortBuf,REV5_SIGN,REV5_SIGN_SIZE)!=0)
+    return 0;
+  uint HeaderSize=RawGet4(ShortBuf+REV5_SIGN_SIZE+4);
+  if (HeaderSize>0x100000 || HeaderSize<=5)
+    return 0;
+  uint BlockCRC=RawGet4(ShortBuf+REV5_SIGN_SIZE);
+
+  RawRead Raw(RecFile);
+  if (Raw.Read(HeaderSize)!=HeaderSize)
+    return 0;
+
+  // Calculate CRC32 of entire header including 4 byte size field.
+  uint CalcCRC=CRC32(0xffffffff,ShortBuf+REV5_SIGN_SIZE+4,4);
+  if ((CRC32(CalcCRC,Raw.GetDataPtr(),HeaderSize)^0xffffffff)!=BlockCRC)
+    return 0;
+
+  if (Raw.Get1()!=1) // Version check.
+    return 0;
+  DataCount=Raw.Get2();
+  RecCount=Raw.Get2();
+  TotalCount=DataCount+RecCount;
+  uint RecNum=Raw.Get2(); // Number of recovery volume.
+  if (RecNum>=TotalCount || TotalCount>MaxVolumes)
+    return 0;
+  uint RevCRC=Raw.Get4(); // CRC of current REV volume.
+
+  if (FirstRev)
+  {
+    // If we have read the first valid REV file, init data structures
+    // using information from REV header.
+    size_t CurSize=RecItems.Size();
+    RecItems.Alloc(TotalCount);
+    for (size_t I=CurSize;I<TotalCount;I++)
+      RecItems[I].f=NULL;
+    for (uint I=0;I<DataCount;I++)
+    {
+      RecItems[I].FileSize=Raw.Get8();
+      RecItems[I].CRC=Raw.Get4();
+    }
+  }
+
+  RecItems[RecNum].CRC=RevCRC; // Assign it here, after allocating RecItems.
+
+  return RecNum;
+}
+
+
+void RecVolumes5::Test(RAROptions *Cmd,const wchar *Name)
+{
+  wchar VolName[NM];
+  wcsncpyz(VolName,Name,ASIZE(VolName));
+
+  uint FoundRecVolumes=0;
+  while (FileExist(VolName))
+  {
+    File CurFile;
+    if (!CurFile.Open(VolName))
+    {
+      ErrHandler.OpenErrorMsg(VolName); // It also sets RARX_OPEN.
+      continue;
+    }
+    if (!uiStartFileExtract(VolName,false,true,false))
+      return;
+    mprintf(St(MExtrTestFile),VolName);
+    mprintf(L"     ");
+    bool Valid=false;
+    uint RecNum=ReadHeader(&CurFile,FoundRecVolumes==0);
+    if (RecNum!=0)
+    {
+      FoundRecVolumes++;
+
+      uint RevCRC;
+      CalcFileSum(&CurFile,&RevCRC,NULL,1,INT64NDF,CALCFSUM_CURPOS|(Cmd->DisablePercentage ? 0 : CALCFSUM_SHOWPROGRESS));
+      Valid=RevCRC==RecItems[RecNum].CRC;
+    }
+
+    if (Valid)
+    {
+      mprintf(L"%s%s ",L"\b\b\b\b\b ",St(MOk));
+    }
+    else
+    {
+      uiMsg(UIERROR_CHECKSUM,VolName,VolName);
+      ErrHandler.SetErrorCode(RARX_CRC);
+    }
+
+    NextVolumeName(VolName,ASIZE(VolName),false);
+  }
+}
diff --git a/third_party/unrar/src/resource.cpp b/third_party/unrar/src/resource.cpp
new file mode 100644
index 0000000..20a8575
--- /dev/null
+++ b/third_party/unrar/src/resource.cpp
@@ -0,0 +1,11 @@
+#include "rar.hpp"
+
+
+
+#ifndef RARDLL
+const wchar *St(MSGID StringId)
+{
+  return StringId;
+}
+#endif
+
diff --git a/third_party/unrar/src/resource.hpp b/third_party/unrar/src/resource.hpp
new file mode 100644
index 0000000..98a6c6b
--- /dev/null
+++ b/third_party/unrar/src/resource.hpp
@@ -0,0 +1,11 @@
+#ifndef _RAR_RESOURCE_
+#define _RAR_RESOURCE_
+
+#ifdef RARDLL
+#define St(x) (L"")
+#else
+const wchar *St(MSGID StringId);
+#endif
+
+
+#endif
diff --git a/third_party/unrar/src/rijndael.cpp b/third_party/unrar/src/rijndael.cpp
new file mode 100644
index 0000000..a091423
--- /dev/null
+++ b/third_party/unrar/src/rijndael.cpp
@@ -0,0 +1,506 @@
+/***************************************************************************
+ * This code is based on public domain Szymon Stefanek AES implementation: *
+ * http://www.pragmaware.net/software/rijndael/index.php                   *
+ *                                                                         *
+ * Dynamic tables generation is based on the Brian Gladman work:           *
+ * http://fp.gladman.plus.com/cryptography_technology/rijndael             *
+ ***************************************************************************/
+#include "rar.hpp"
+
+#ifdef USE_SSE
+#include <wmmintrin.h>
+#endif
+
+static byte S[256],S5[256],rcon[30];
+static byte T1[256][4],T2[256][4],T3[256][4],T4[256][4];
+static byte T5[256][4],T6[256][4],T7[256][4],T8[256][4];
+static byte U1[256][4],U2[256][4],U3[256][4],U4[256][4];
+
+
+inline void Xor128(void *dest,const void *arg1,const void *arg2)
+{
+#ifdef ALLOW_MISALIGNED
+  ((uint32*)dest)[0]=((uint32*)arg1)[0]^((uint32*)arg2)[0];
+  ((uint32*)dest)[1]=((uint32*)arg1)[1]^((uint32*)arg2)[1];
+  ((uint32*)dest)[2]=((uint32*)arg1)[2]^((uint32*)arg2)[2];
+  ((uint32*)dest)[3]=((uint32*)arg1)[3]^((uint32*)arg2)[3];
+#else
+  for (int I=0;I<16;I++)
+    ((byte*)dest)[I]=((byte*)arg1)[I]^((byte*)arg2)[I];
+#endif
+}
+
+
+inline void Xor128(byte *dest,const byte *arg1,const byte *arg2,
+                   const byte *arg3,const byte *arg4)
+{
+#ifdef ALLOW_MISALIGNED
+  (*(uint32*)dest)=(*(uint32*)arg1)^(*(uint32*)arg2)^(*(uint32*)arg3)^(*(uint32*)arg4);
+#else
+  for (int I=0;I<4;I++)
+    dest[I]=arg1[I]^arg2[I]^arg3[I]^arg4[I];
+#endif
+}
+
+
+inline void Copy128(byte *dest,const byte *src)
+{
+#ifdef ALLOW_MISALIGNED
+  ((uint32*)dest)[0]=((uint32*)src)[0];
+  ((uint32*)dest)[1]=((uint32*)src)[1];
+  ((uint32*)dest)[2]=((uint32*)src)[2];
+  ((uint32*)dest)[3]=((uint32*)src)[3];
+#else
+  for (int I=0;I<16;I++)
+    dest[I]=src[I];
+#endif
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// API
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+Rijndael::Rijndael()
+{
+  if (S[0]==0)
+    GenerateTables();
+  CBCMode = true; // Always true for RAR.
+}
+
+
+void Rijndael::Init(bool Encrypt,const byte *key,uint keyLen,const byte * initVector)
+{
+#ifdef USE_SSE
+  // Check SSE here instead of constructor, so if object is a part of some
+  // structure memset'ed before use, this variable is not lost.
+  int CPUInfo[4];
+  __cpuid(CPUInfo, 1);
+  AES_NI=(CPUInfo[2] & 0x2000000)!=0;
+#endif
+
+  uint uKeyLenInBytes;
+  switch(keyLen)
+  {
+    case 128:
+      uKeyLenInBytes = 16;
+      m_uRounds = 10;
+      break;
+    case 192:
+      uKeyLenInBytes = 24;
+      m_uRounds = 12;
+      break;
+    case 256:
+      uKeyLenInBytes = 32;
+      m_uRounds = 14;
+      break;
+  }
+
+  byte keyMatrix[_MAX_KEY_COLUMNS][4];
+
+  for(uint i = 0; i < uKeyLenInBytes; i++)
+    keyMatrix[i >> 2][i & 3] = key[i]; 
+
+  if (initVector==NULL)
+    memset(m_initVector, 0, sizeof(m_initVector));
+  else
+    for(int i = 0; i < MAX_IV_SIZE; i++)
+      m_initVector[i] = initVector[i];
+
+  keySched(keyMatrix);
+
+  if(!Encrypt)
+    keyEncToDec();
+}
+
+void Rijndael::blockEncrypt(const byte *input,size_t inputLen,byte *outBuffer)
+{
+  if (inputLen <= 0)
+    return;
+
+  size_t numBlocks = inputLen/16;
+#ifdef USE_SSE
+  if (AES_NI)
+  {
+    blockEncryptSSE(input,numBlocks,outBuffer);
+    return;
+  }
+#endif
+  
+  byte *prevBlock = m_initVector;
+  for(size_t i = numBlocks;i > 0;i--)
+  {
+    byte block[16];
+    if (CBCMode)
+      Xor128(block,prevBlock,input);
+    else
+      Copy128(block,input);
+
+    byte temp[4][4];
+
+    Xor128(temp,block,m_expandedKey[0]);
+    Xor128(outBuffer,   T1[temp[0][0]],T2[temp[1][1]],T3[temp[2][2]],T4[temp[3][3]]);
+    Xor128(outBuffer+4, T1[temp[1][0]],T2[temp[2][1]],T3[temp[3][2]],T4[temp[0][3]]);
+    Xor128(outBuffer+8, T1[temp[2][0]],T2[temp[3][1]],T3[temp[0][2]],T4[temp[1][3]]);
+    Xor128(outBuffer+12,T1[temp[3][0]],T2[temp[0][1]],T3[temp[1][2]],T4[temp[2][3]]);
+
+    for(int r = 1; r < m_uRounds-1; r++)
+    {
+      Xor128(temp,outBuffer,m_expandedKey[r]);
+      Xor128(outBuffer,   T1[temp[0][0]],T2[temp[1][1]],T3[temp[2][2]],T4[temp[3][3]]);
+      Xor128(outBuffer+4, T1[temp[1][0]],T2[temp[2][1]],T3[temp[3][2]],T4[temp[0][3]]);
+      Xor128(outBuffer+8, T1[temp[2][0]],T2[temp[3][1]],T3[temp[0][2]],T4[temp[1][3]]);
+      Xor128(outBuffer+12,T1[temp[3][0]],T2[temp[0][1]],T3[temp[1][2]],T4[temp[2][3]]);
+    }
+    Xor128(temp,outBuffer,m_expandedKey[m_uRounds-1]);
+    outBuffer[ 0] = T1[temp[0][0]][1];
+    outBuffer[ 1] = T1[temp[1][1]][1];
+    outBuffer[ 2] = T1[temp[2][2]][1];
+    outBuffer[ 3] = T1[temp[3][3]][1];
+    outBuffer[ 4] = T1[temp[1][0]][1];
+    outBuffer[ 5] = T1[temp[2][1]][1];
+    outBuffer[ 6] = T1[temp[3][2]][1];
+    outBuffer[ 7] = T1[temp[0][3]][1];
+    outBuffer[ 8] = T1[temp[2][0]][1];
+    outBuffer[ 9] = T1[temp[3][1]][1];
+    outBuffer[10] = T1[temp[0][2]][1];
+    outBuffer[11] = T1[temp[1][3]][1];
+    outBuffer[12] = T1[temp[3][0]][1];
+    outBuffer[13] = T1[temp[0][1]][1];
+    outBuffer[14] = T1[temp[1][2]][1];
+    outBuffer[15] = T1[temp[2][3]][1];
+    Xor128(outBuffer,outBuffer,m_expandedKey[m_uRounds]);
+    prevBlock=outBuffer;
+
+    outBuffer += 16;
+    input += 16;
+  }
+  Copy128(m_initVector,prevBlock);
+}
+
+
+#ifdef USE_SSE
+void Rijndael::blockEncryptSSE(const byte *input,size_t numBlocks,byte *outBuffer)
+{
+  __m128i v = _mm_loadu_si128((__m128i*)m_initVector);
+  __m128i *src=(__m128i*)input;
+  __m128i *dest=(__m128i*)outBuffer;
+  __m128i *rkey=(__m128i*)m_expandedKey;
+  while (numBlocks > 0)
+  {
+    __m128i d = _mm_loadu_si128(src++);
+    if (CBCMode)
+      v = _mm_xor_si128(v, d);
+    else
+      v = d;
+    __m128i r0 = _mm_loadu_si128(rkey);
+    v = _mm_xor_si128(v, r0);
+    
+    for (int i=1; i<m_uRounds; i++)
+    {
+      __m128i ri = _mm_loadu_si128(rkey + i);
+      v = _mm_aesenc_si128(v, ri);
+    }
+
+    __m128i rl = _mm_loadu_si128(rkey + m_uRounds);
+    v = _mm_aesenclast_si128(v, rl);
+    _mm_storeu_si128(dest++,v);
+    numBlocks--;
+  }
+  _mm_storeu_si128((__m128i*)m_initVector,v);
+}
+#endif
+
+  
+void Rijndael::blockDecrypt(const byte *input, size_t inputLen, byte *outBuffer)
+{
+  if (inputLen <= 0)
+    return;
+
+  size_t numBlocks=inputLen/16;
+#ifdef USE_SSE
+  if (AES_NI)
+  {
+    blockDecryptSSE(input,numBlocks,outBuffer);
+    return;
+  }
+#endif
+
+  byte block[16], iv[4][4];
+  memcpy(iv,m_initVector,16); 
+
+  for (size_t i = numBlocks; i > 0; i--)
+  {
+    byte temp[4][4];
+    
+    Xor128(temp,input,m_expandedKey[m_uRounds]);
+
+    Xor128(block,   T5[temp[0][0]],T6[temp[3][1]],T7[temp[2][2]],T8[temp[1][3]]);
+    Xor128(block+4, T5[temp[1][0]],T6[temp[0][1]],T7[temp[3][2]],T8[temp[2][3]]);
+    Xor128(block+8, T5[temp[2][0]],T6[temp[1][1]],T7[temp[0][2]],T8[temp[3][3]]);
+    Xor128(block+12,T5[temp[3][0]],T6[temp[2][1]],T7[temp[1][2]],T8[temp[0][3]]);
+
+    for(int r = m_uRounds-1; r > 1; r--)
+    {
+      Xor128(temp,block,m_expandedKey[r]);
+      Xor128(block,   T5[temp[0][0]],T6[temp[3][1]],T7[temp[2][2]],T8[temp[1][3]]);
+      Xor128(block+4, T5[temp[1][0]],T6[temp[0][1]],T7[temp[3][2]],T8[temp[2][3]]);
+      Xor128(block+8, T5[temp[2][0]],T6[temp[1][1]],T7[temp[0][2]],T8[temp[3][3]]);
+      Xor128(block+12,T5[temp[3][0]],T6[temp[2][1]],T7[temp[1][2]],T8[temp[0][3]]);
+    }
+   
+    Xor128(temp,block,m_expandedKey[1]);
+    block[ 0] = S5[temp[0][0]];
+    block[ 1] = S5[temp[3][1]];
+    block[ 2] = S5[temp[2][2]];
+    block[ 3] = S5[temp[1][3]];
+    block[ 4] = S5[temp[1][0]];
+    block[ 5] = S5[temp[0][1]];
+    block[ 6] = S5[temp[3][2]];
+    block[ 7] = S5[temp[2][3]];
+    block[ 8] = S5[temp[2][0]];
+    block[ 9] = S5[temp[1][1]];
+    block[10] = S5[temp[0][2]];
+    block[11] = S5[temp[3][3]];
+    block[12] = S5[temp[3][0]];
+    block[13] = S5[temp[2][1]];
+    block[14] = S5[temp[1][2]];
+    block[15] = S5[temp[0][3]];
+    Xor128(block,block,m_expandedKey[0]);
+
+    if (CBCMode)
+      Xor128(block,block,iv);
+
+    Copy128((byte*)iv,input);
+    Copy128(outBuffer,block);
+
+    input += 16;
+    outBuffer += 16;
+  }
+
+  memcpy(m_initVector,iv,16);
+}
+
+
+#ifdef USE_SSE
+void Rijndael::blockDecryptSSE(const byte *input, size_t numBlocks, byte *outBuffer)
+{
+  __m128i initVector = _mm_loadu_si128((__m128i*)m_initVector);
+  __m128i *src=(__m128i*)input;
+  __m128i *dest=(__m128i*)outBuffer;
+  __m128i *rkey=(__m128i*)m_expandedKey;
+  while (numBlocks > 0)
+  {
+    __m128i rl = _mm_loadu_si128(rkey + m_uRounds);
+    __m128i d = _mm_loadu_si128(src++);
+    __m128i v = _mm_xor_si128(rl, d);
+
+    for (int i=m_uRounds-1; i>0; i--)
+    {
+      __m128i ri = _mm_loadu_si128(rkey + i);
+      v = _mm_aesdec_si128(v, ri);
+    }
+    
+    __m128i r0 = _mm_loadu_si128(rkey);
+    v = _mm_aesdeclast_si128(v, r0);
+
+    if (CBCMode)
+      v = _mm_xor_si128(v, initVector);
+    initVector = d;
+    _mm_storeu_si128(dest++,v);
+    numBlocks--;
+  }
+  _mm_storeu_si128((__m128i*)m_initVector,initVector);
+}
+#endif
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// ALGORITHM
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+void Rijndael::keySched(byte key[_MAX_KEY_COLUMNS][4])
+{
+  int j,rconpointer = 0;
+
+  // Calculate the necessary round keys
+  // The number of calculations depends on keyBits and blockBits
+  int uKeyColumns = m_uRounds - 6;
+
+  byte tempKey[_MAX_KEY_COLUMNS][4];
+
+  // Copy the input key to the temporary key matrix
+
+  memcpy(tempKey,key,sizeof(tempKey));
+
+  int r = 0;
+  int t = 0;
+
+  // copy values into round key array
+  for(j = 0;(j < uKeyColumns) && (r <= m_uRounds); )
+  {
+    for(;(j < uKeyColumns) && (t < 4); j++, t++)
+      for (int k=0;k<4;k++)
+        m_expandedKey[r][t][k]=tempKey[j][k];
+
+    if(t == 4)
+    {
+      r++;
+      t = 0;
+    }
+  }
+    
+  while(r <= m_uRounds)
+  {
+    tempKey[0][0] ^= S[tempKey[uKeyColumns-1][1]];
+    tempKey[0][1] ^= S[tempKey[uKeyColumns-1][2]];
+    tempKey[0][2] ^= S[tempKey[uKeyColumns-1][3]];
+    tempKey[0][3] ^= S[tempKey[uKeyColumns-1][0]];
+    tempKey[0][0] ^= rcon[rconpointer++];
+
+    if (uKeyColumns != 8)
+      for(j = 1; j < uKeyColumns; j++)
+        for (int k=0;k<4;k++)
+          tempKey[j][k] ^= tempKey[j-1][k];
+    else
+    {
+      for(j = 1; j < uKeyColumns/2; j++)
+        for (int k=0;k<4;k++)
+          tempKey[j][k] ^= tempKey[j-1][k];
+
+      tempKey[uKeyColumns/2][0] ^= S[tempKey[uKeyColumns/2 - 1][0]];
+      tempKey[uKeyColumns/2][1] ^= S[tempKey[uKeyColumns/2 - 1][1]];
+      tempKey[uKeyColumns/2][2] ^= S[tempKey[uKeyColumns/2 - 1][2]];
+      tempKey[uKeyColumns/2][3] ^= S[tempKey[uKeyColumns/2 - 1][3]];
+      for(j = uKeyColumns/2 + 1; j < uKeyColumns; j++)
+        for (int k=0;k<4;k++)
+          tempKey[j][k] ^= tempKey[j-1][k];
+    }
+    for(j = 0; (j < uKeyColumns) && (r <= m_uRounds); )
+    {
+      for(; (j < uKeyColumns) && (t < 4); j++, t++)
+        for (int k=0;k<4;k++)
+          m_expandedKey[r][t][k] = tempKey[j][k];
+      if(t == 4)
+      {
+        r++;
+        t = 0;
+      }
+    }
+  }   
+}
+
+void Rijndael::keyEncToDec()
+{
+  for(int r = 1; r < m_uRounds; r++)
+  {
+    byte n_expandedKey[4][4];
+    for (int i = 0; i < 4; i++)
+      for (int j = 0; j < 4; j++)
+      {
+        byte *w=m_expandedKey[r][j];
+        n_expandedKey[j][i]=U1[w[0]][i]^U2[w[1]][i]^U3[w[2]][i]^U4[w[3]][i];
+      }
+    memcpy(m_expandedKey[r],n_expandedKey,sizeof(m_expandedKey[0]));
+  }
+} 
+
+
+#define ff_poly 0x011b
+#define ff_hi   0x80
+
+#define FFinv(x)    ((x) ? pow[255 - log[x]]: 0)
+
+#define FFmul02(x) (x ? pow[log[x] + 0x19] : 0)
+#define FFmul03(x) (x ? pow[log[x] + 0x01] : 0)
+#define FFmul09(x) (x ? pow[log[x] + 0xc7] : 0)
+#define FFmul0b(x) (x ? pow[log[x] + 0x68] : 0)
+#define FFmul0d(x) (x ? pow[log[x] + 0xee] : 0)
+#define FFmul0e(x) (x ? pow[log[x] + 0xdf] : 0)
+#define fwd_affine(x) \
+    (w = (uint)x, w ^= (w<<1)^(w<<2)^(w<<3)^(w<<4), (byte)(0x63^(w^(w>>8))))
+
+#define inv_affine(x) \
+    (w = (uint)x, w = (w<<1)^(w<<3)^(w<<6), (byte)(0x05^(w^(w>>8))))
+
+void Rijndael::GenerateTables()
+{
+  unsigned char pow[512],log[256];
+  int i = 0, w = 1; 
+  do
+  {   
+    pow[i] = (byte)w;
+    pow[i + 255] = (byte)w;
+    log[w] = (byte)i++;
+    w ^=  (w << 1) ^ (w & ff_hi ? ff_poly : 0);
+  } while (w != 1);
+ 
+  for (int i = 0,w = 1; i < sizeof(rcon)/sizeof(rcon[0]); i++)
+  {
+    rcon[i] = w;
+    w = (w << 1) ^ (w & ff_hi ? ff_poly : 0);
+  }
+  for(int i = 0; i < 256; ++i)
+  {   
+    unsigned char b=S[i]=fwd_affine(FFinv((byte)i));
+    T1[i][1]=T1[i][2]=T2[i][2]=T2[i][3]=T3[i][0]=T3[i][3]=T4[i][0]=T4[i][1]=b;
+    T1[i][0]=T2[i][1]=T3[i][2]=T4[i][3]=FFmul02(b);
+    T1[i][3]=T2[i][0]=T3[i][1]=T4[i][2]=FFmul03(b);
+    S5[i] = b = FFinv(inv_affine((byte)i));
+    U1[b][3]=U2[b][0]=U3[b][1]=U4[b][2]=T5[i][3]=T6[i][0]=T7[i][1]=T8[i][2]=FFmul0b(b);
+    U1[b][1]=U2[b][2]=U3[b][3]=U4[b][0]=T5[i][1]=T6[i][2]=T7[i][3]=T8[i][0]=FFmul09(b);
+    U1[b][2]=U2[b][3]=U3[b][0]=U4[b][1]=T5[i][2]=T6[i][3]=T7[i][0]=T8[i][1]=FFmul0d(b);
+    U1[b][0]=U2[b][1]=U3[b][2]=U4[b][3]=T5[i][0]=T6[i][1]=T7[i][2]=T8[i][3]=FFmul0e(b);
+  }
+}
+
+
+#if 0
+static void TestRijndael();
+struct TestRij {TestRij() {TestRijndael();exit(0);}} GlobalTestRij;
+
+// Test CBC encryption according to NIST 800-38A.
+void TestRijndael()
+{
+  byte IV[16]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
+  byte PT[64]={
+    0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a,
+    0xae,0x2d,0x8a,0x57,0x1e,0x03,0xac,0x9c,0x9e,0xb7,0x6f,0xac,0x45,0xaf,0x8e,0x51,
+    0x30,0xc8,0x1c,0x46,0xa3,0x5c,0xe4,0x11,0xe5,0xfb,0xc1,0x19,0x1a,0x0a,0x52,0xef,
+    0xf6,0x9f,0x24,0x45,0xdf,0x4f,0x9b,0x17,0xad,0x2b,0x41,0x7b,0xe6,0x6c,0x37,0x10,
+  };
+
+  byte Key128[16]={0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6,0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c};
+  byte Chk128[16]={0x3f,0xf1,0xca,0xa1,0x68,0x1f,0xac,0x09,0x12,0x0e,0xca,0x30,0x75,0x86,0xe1,0xa7};
+  byte Key192[24]={0x8e,0x73,0xb0,0xf7,0xda,0x0e,0x64,0x52,0xc8,0x10,0xf3,0x2b,0x80,0x90,0x79,0xe5,0x62,0xf8,0xea,0xd2,0x52,0x2c,0x6b,0x7b};
+  byte Chk192[16]={0x08,0xb0,0xe2,0x79,0x88,0x59,0x88,0x81,0xd9,0x20,0xa9,0xe6,0x4f,0x56,0x15,0xcd};
+  byte Key256[32]={0x60,0x3d,0xeb,0x10,0x15,0xca,0x71,0xbe,0x2b,0x73,0xae,0xf0,0x85,0x7d,0x77,0x81,0x1f,0x35,0x2c,0x07,0x3b,0x61,0x08,0xd7,0x2d,0x98,0x10,0xa3,0x09,0x14,0xdf,0xf4};
+  byte Chk256[16]={0xb2,0xeb,0x05,0xe2,0xc3,0x9b,0xe9,0xfc,0xda,0x6c,0x19,0x07,0x8c,0x6a,0x9d,0x1b};
+  byte *Key[3]={Key128,Key192,Key256};
+  byte *Chk[3]={Chk128,Chk192,Chk256};
+
+  Rijndael rij; // Declare outside of loop to test re-initialization.
+  for (uint L=0;L<3;L++)
+  {
+    byte Out[16];
+    wchar Str[sizeof(Out)*2+1];
+
+    uint KeyLength=128+L*64;
+    rij.Init(true,Key[L],KeyLength,IV);
+    for (uint I=0;I<sizeof(PT);I+=16)
+      rij.blockEncrypt(PT+I,16,Out);
+    BinToHex(Chk[L],16,NULL,Str,ASIZE(Str));
+    mprintf(L"\nAES-%d expected: %s",KeyLength,Str);
+    BinToHex(Out,sizeof(Out),NULL,Str,ASIZE(Str));
+    mprintf(L"\nAES-%d result:   %s",KeyLength,Str);
+    if (memcmp(Out,Chk[L],16)==0)
+      mprintf(L" OK");
+    else
+    {
+      mprintf(L" FAILED");
+      getchar();
+    }
+  }
+}
+#endif
diff --git a/third_party/unrar/src/rijndael.hpp b/third_party/unrar/src/rijndael.hpp
new file mode 100644
index 0000000..2144e02
--- /dev/null
+++ b/third_party/unrar/src/rijndael.hpp
@@ -0,0 +1,44 @@
+#ifndef _RIJNDAEL_H_
+#define _RIJNDAEL_H_
+
+/**************************************************************************
+ * This code is based on Szymon Stefanek AES implementation:              *
+ * http://www.esat.kuleuven.ac.be/~rijmen/rijndael/rijndael-cpplib.tar.gz *
+ *                                                                        *
+ * Dynamic tables generation is based on the Brian Gladman's work:        *
+ * http://fp.gladman.plus.com/cryptography_technology/rijndael            *
+ **************************************************************************/
+
+#define _MAX_KEY_COLUMNS (256/32)
+#define _MAX_ROUNDS      14
+#define MAX_IV_SIZE      16
+
+class Rijndael
+{ 
+  private:
+#ifdef USE_SSE
+    void blockEncryptSSE(const byte *input,size_t numBlocks,byte *outBuffer);
+    void blockDecryptSSE(const byte *input, size_t numBlocks, byte *outBuffer);
+
+    bool AES_NI;
+#endif
+    void keySched(byte key[_MAX_KEY_COLUMNS][4]);
+    void keyEncToDec();
+    void GenerateTables();
+
+    // RAR always uses CBC, but we may need to turn it off when calling
+    // this code from other archive formats with CTR and other modes.
+    bool     CBCMode;
+    
+    int      m_uRounds;
+    byte     m_initVector[MAX_IV_SIZE];
+    byte     m_expandedKey[_MAX_ROUNDS+1][4][4];
+  public:
+    Rijndael();
+    void Init(bool Encrypt,const byte *key,uint keyLen,const byte *initVector);
+    void blockEncrypt(const byte *input, size_t inputLen, byte *outBuffer);
+    void blockDecrypt(const byte *input, size_t inputLen, byte *outBuffer);
+    void SetCBCMode(bool Mode) {CBCMode=Mode;}
+};
+  
+#endif // _RIJNDAEL_H_
diff --git a/third_party/unrar/src/rs.cpp b/third_party/unrar/src/rs.cpp
new file mode 100644
index 0000000..10ccc6d
--- /dev/null
+++ b/third_party/unrar/src/rs.cpp
@@ -0,0 +1,160 @@
+#include "rar.hpp"
+
+#define Clean(D,S)  {for (int I=0;I<(S);I++) (D)[I]=0;}
+
+void RSCoder::Init(int ParSize)
+{
+  RSCoder::ParSize=ParSize; // Store the number of recovery volumes.
+  FirstBlockDone=false;
+  gfInit();
+  pnInit();
+}
+
+
+// Initialize logarithms and exponents Galois field tables.
+void RSCoder::gfInit()
+{
+  for (int I=0,J=1;I<MAXPAR;I++)
+  {
+    gfLog[J]=I;
+    gfExp[I]=J;
+    J<<=1;
+    if (J > MAXPAR)
+      J^=0x11D; // 0x11D field-generator polynomial (x^8+x^4+x^3+x^2+1).
+  }
+  for (int I=MAXPAR;I<MAXPOL;I++) // Avoid gfExp overflow check.
+    gfExp[I]=gfExp[I-MAXPAR];
+}
+
+
+// Multiplication over Galois field. 
+inline int RSCoder::gfMult(int a,int b)
+{
+  return(a==0 || b == 0 ? 0:gfExp[gfLog[a]+gfLog[b]]);
+}
+
+
+// Create the generator polynomial g(x).
+// g(x)=(x-a)(x-a^2)(x-a^3)..(x-a^N)
+void RSCoder::pnInit()
+{
+  int p2[MAXPAR+1]; // Currently calculated part of g(x).
+
+  Clean(p2,ParSize);
+  p2[0]=1; // Set p2 polynomial to 1.
+
+  for (int I=1;I<=ParSize;I++)
+  {
+    int p1[MAXPAR+1]; // We use p1 as current (x+a^i) expression.
+    Clean(p1,ParSize);
+    p1[0]=gfExp[I];
+    p1[1]=1; // Set p1 polynomial to x+a^i.
+
+    // Multiply the already calucated part of g(x) to next (x+a^i).
+    pnMult(p1,p2,GXPol);
+
+    // p2=g(x).
+    for (int J=0;J<ParSize;J++)
+      p2[J]=GXPol[J];
+  }
+}
+
+
+// Multiply polynomial 'p1' to 'p2' and store the result in 'r'.
+void RSCoder::pnMult(int *p1,int *p2,int *r)
+{
+  Clean(r,ParSize);
+  for (int I=0;I<ParSize;I++)
+    if (p1[I]!=0)
+      for(int J=0;J<ParSize-I;J++)
+        r[I+J]^=gfMult(p1[I],p2[J]);
+}
+
+
+void RSCoder::Encode(byte *Data,int DataSize,byte *DestData)
+{
+  int ShiftReg[MAXPAR+1]; // Linear Feedback Shift Register.
+
+  Clean(ShiftReg,ParSize+1);
+  for (int I=0;I<DataSize;I++)
+  {
+    int D=Data[I]^ShiftReg[ParSize-1];
+
+    // Use g(x) to define feedback taps.
+    for (int J=ParSize-1;J>0;J--)
+      ShiftReg[J]=ShiftReg[J-1]^gfMult(GXPol[J],D);
+    ShiftReg[0]=gfMult(GXPol[0],D);
+  }
+  for (int I=0;I<ParSize;I++)
+    DestData[I]=ShiftReg[ParSize-I-1];
+}
+
+
+bool RSCoder::Decode(byte *Data,int DataSize,int *EraLoc,int EraSize)
+{
+  int SynData[MAXPOL]; // Syndrome data.
+
+  bool AllZeroes=true;
+  for (int I=0;I<ParSize;I++)
+  {
+    int Sum=0;
+    for (int J=0;J<DataSize;J++)
+      Sum=Data[J]^gfMult(gfExp[I+1],Sum);
+    if ((SynData[I]=Sum)!=0)
+      AllZeroes=false;
+  }
+
+  // If all syndrome numbers are zero, message does not have errors.
+  if (AllZeroes)
+    return(true);
+
+  if (!FirstBlockDone) // Do things which we need to do once for all data.
+  {
+    FirstBlockDone=true;
+
+    // Calculate the error locator polynomial.
+    Clean(ELPol,ParSize+1);
+    ELPol[0]=1;
+
+    for (int EraPos=0;EraPos<EraSize;EraPos++)
+      for (int I=ParSize,M=gfExp[DataSize-EraLoc[EraPos]-1];I>0;I--)
+        ELPol[I]^=gfMult(M,ELPol[I-1]);
+
+    ErrCount=0;
+
+    // Find roots of error locator polynomial.
+    for (int Root=MAXPAR-DataSize;Root<MAXPAR+1;Root++)
+    {
+      int Sum=0;
+      for (int B=0;B<ParSize+1;B++)
+        Sum^=gfMult(gfExp[(B*Root)%MAXPAR],ELPol[B]);
+      if (Sum==0) // Root found.
+      {
+        ErrorLocs[ErrCount]=MAXPAR-Root; // Location of error.
+
+        // Calculate the denominator for every error location.
+        Dnm[ErrCount]=0;
+        for (int I=1;I<ParSize+1;I+=2)
+          Dnm[ErrCount]^= gfMult(ELPol[I],gfExp[Root*(I-1)%MAXPAR]);
+
+        ErrCount++;
+      }
+    }
+  }
+
+  int EEPol[MAXPOL]; // Error Evaluator Polynomial.
+  pnMult(ELPol,SynData,EEPol);
+  // If errors are present and their number is correctable.
+  if ((ErrCount<=ParSize) && ErrCount>0)
+    for (int I=0;I<ErrCount;I++)
+    {
+      int Loc=ErrorLocs[I],DLoc=MAXPAR-Loc,N=0;
+      for (int J=0;J<ParSize;J++) 
+        N^=gfMult(EEPol[J],gfExp[DLoc*J%MAXPAR]);
+      int DataPos=DataSize-Loc-1;
+      // Perform bounds check and correct the data error.
+      if (DataPos>=0 && DataPos<DataSize)
+        Data[DataPos]^=gfMult(N,gfExp[MAXPAR-gfLog[Dnm[I]]]);
+    }
+  return(ErrCount<=ParSize); // Return true if success.
+}
diff --git a/third_party/unrar/src/rs.hpp b/third_party/unrar/src/rs.hpp
new file mode 100644
index 0000000..6ac8094
--- /dev/null
+++ b/third_party/unrar/src/rs.hpp
@@ -0,0 +1,32 @@
+#ifndef _RAR_RS_
+#define _RAR_RS_
+
+#define MAXPAR 255 // Maximum parity data size.
+#define MAXPOL 512 // Maximum polynomial degree.
+
+class RSCoder
+{
+  private:
+    void gfInit();
+    int gfMult(int a,int b);
+    void pnInit();
+    void pnMult(int *p1,int *p2,int *r);
+
+    int gfExp[MAXPOL];   // Galois field exponents.
+    int gfLog[MAXPAR+1]; // Galois field logarithms.
+
+    int GXPol[MAXPOL*2]; // Generator polynomial g(x).
+
+    int ErrorLocs[MAXPAR+1],ErrCount;
+    int Dnm[MAXPAR+1];
+
+    int ParSize; // Parity bytes size and so the number of recovery volumes.
+    int ELPol[MAXPOL]; // Error locator polynomial.
+    bool FirstBlockDone;
+  public:
+    void Init(int ParSize);
+    void Encode(byte *Data,int DataSize,byte *DestData);
+    bool Decode(byte *Data,int DataSize,int *EraLoc,int EraSize);
+};
+
+#endif
diff --git a/third_party/unrar/src/rs16.cpp b/third_party/unrar/src/rs16.cpp
new file mode 100644
index 0000000..f23cff8
--- /dev/null
+++ b/third_party/unrar/src/rs16.cpp
@@ -0,0 +1,419 @@
+#include "rar.hpp"
+
+// We used "Screaming Fast Galois Field Arithmetic Using Intel SIMD
+// Instructions" paper by James S. Plank, Kevin M. Greenan
+// and Ethan L. Miller for fast SSE based multiplication.
+// Also we are grateful to Artem Drobanov and Bulat Ziganshin
+// for samples and ideas allowed to make Reed-Solomon codec more efficient.
+
+RSCoder16::RSCoder16()
+{
+  Decoding=false;
+  ND=NR=NE=0;
+  ValidFlags=NULL;
+  MX=NULL;
+  DataLog=NULL;
+  DataLogSize=0;
+
+  gfInit();
+}
+
+
+RSCoder16::~RSCoder16()
+{
+  delete[] gfExp;
+  delete[] gfLog;
+  delete[] DataLog;
+  delete[] MX;
+  delete[] ValidFlags;
+}
+    
+
+// Initialize logarithms and exponents Galois field tables.
+void RSCoder16::gfInit()
+{
+  gfExp=new uint[4*gfSize+1];
+  gfLog=new uint[gfSize+1];
+
+  for (uint L=0,E=1;L<gfSize;L++)
+  {
+    gfLog[E]=L;
+    gfExp[L]=E;
+    gfExp[L+gfSize]=E;  // Duplicate the table to avoid gfExp overflow check.
+    E<<=1;
+    if (E>gfSize) 
+      E^=0x1100B; // Irreducible field-generator polynomial.
+  }
+
+  // log(0)+log(x) must be outside of usual log table, so we can set it
+  // to 0 and avoid check for 0 in multiplication parameters.
+  gfLog[0]= 2*gfSize;
+  for (uint I=2*gfSize;I<=4*gfSize;I++) // Results for log(0)+log(x).
+    gfExp[I]=0;
+}
+
+
+uint RSCoder16::gfAdd(uint a,uint b) // Addition in Galois field.
+{
+  return a^b;
+}
+
+
+uint RSCoder16::gfMul(uint a,uint b) // Multiplication in Galois field. 
+{
+  return gfExp[gfLog[a]+gfLog[b]];
+}
+
+
+uint RSCoder16::gfInv(uint a) // Inverse element in Galois field.
+{
+  return a==0 ? 0:gfExp[gfSize-gfLog[a]];
+}
+
+
+bool RSCoder16::Init(uint DataCount, uint RecCount, bool *ValidityFlags)
+{
+  ND = DataCount;
+  NR = RecCount;
+  NE = 0;
+
+  Decoding=ValidityFlags!=NULL;
+  if (Decoding)
+  {
+    delete[] ValidFlags;
+    ValidFlags=new bool[ND + NR];
+
+    for (uint I = 0; I < ND + NR; I++)
+      ValidFlags[I]=ValidityFlags[I];
+    for (uint I = 0; I < ND; I++)
+      if (!ValidFlags[I])
+        NE++;
+    uint ValidECC=0;
+    for (uint I = ND; I < ND + NR; I++)
+      if (ValidFlags[I])
+        ValidECC++;
+    if (NE > ValidECC || NE == 0 || ValidECC == 0)
+      return false;
+  }
+  if (ND + NR > gfSize || NR > ND || ND == 0 || NR == 0)
+    return false;
+
+  delete[] MX;
+  if (Decoding)
+  {
+    MX=new uint[NE * ND];
+    MakeDecoderMatrix();
+    InvertDecoderMatrix();
+  }
+  else
+  {
+    MX=new uint[NR * ND];
+    MakeEncoderMatrix();
+  }
+  return true;
+}
+
+
+void RSCoder16::MakeEncoderMatrix()
+{
+  // Create Cauchy encoder generator matrix. Skip trivial "1" diagonal rows,
+  // which would just copy source data to destination.
+  for (uint I = 0; I < NR; I++)
+    for (uint J = 0; J < ND; J++)
+      MX[I * ND + J] = gfInv( gfAdd( (I+ND), J) );
+}
+
+
+void RSCoder16::MakeDecoderMatrix()
+{
+  // Create Cauchy decoder matrix. Skip trivial rows matching valid data
+  // units and containing "1" on main diagonal. Such rows would just copy
+  // source data to destination and they have no real value for us.
+  // Include rows only for broken data units and replace them by first
+  // available valid recovery code rows.
+  for (uint Flag=0, R=ND, Dest=0; Flag < ND; Flag++)
+    if (!ValidFlags[Flag]) // For every broken data unit.
+    {
+      while (!ValidFlags[R]) // Find a valid recovery unit.
+        R++;
+      for (uint J = 0; J < ND; J++) // And place its row to matrix.
+        MX[Dest*ND + J] = gfInv( gfAdd(R,J) );
+      Dest++;
+      R++;
+    }
+}
+
+
+// Apply Gauss–Jordan elimination to find inverse of decoder matrix.
+// We have the square NDxND matrix, but we do not store its trivial
+// diagonal "1" rows matching valid data, so we work with NExND matrix.
+// Our original Cauchy matrix does not contain 0, so we skip search
+// for non-zero pivot.
+void RSCoder16::InvertDecoderMatrix()
+{
+  uint *MI=new uint[NE * ND]; // We'll create inverse matrix here.
+  memset(MI, 0, ND * NE * sizeof(*MI)); // Initialize to identity matrix.
+  for (uint Kr = 0, Kf = 0; Kr < NE; Kr++, Kf++)
+  {
+    while (ValidFlags[Kf]) // Skip trivial rows.
+      Kf++;                 
+    MI[Kr * ND + Kf] = 1;  // Set diagonal 1.
+  }
+
+  // Kr is the number of row in our actual reduced NE x ND matrix,
+  // which does not contain trivial diagonal 1 rows.
+  // Kf is the number of row in full ND x ND matrix with all trivial rows
+  // included.
+  for (uint Kr = 0, Kf = 0; Kf < ND; Kr++, Kf++) // Select pivot row.
+  {
+    while (ValidFlags[Kf] && Kf < ND)
+    {
+      // Here we process trivial diagonal 1 rows matching valid data units.
+      // Their processing can be simplified comparing to usual rows.
+      // In full version of elimination we would set MX[I * ND + Kf] to zero
+      // after MI[..]^=, but we do not need it for matrix inversion.
+      for (uint I = 0; I < NE; I++)
+        MI[I * ND + Kf] ^= MX[I * ND + Kf];
+      Kf++;                 
+    }
+
+    if (Kf == ND)
+      break;
+
+    uint *MXk = MX + Kr * ND; // k-th row of main matrix.
+    uint *MIk = MI + Kr * ND; // k-th row of inversion matrix.
+
+    uint PInv = gfInv( MXk[Kf] ); // Pivot inverse.
+    // Divide the pivot row by pivot, so pivot cell contains 1.
+    for (uint I = 0; I < ND; I++)
+    { 
+      MXk[I] = gfMul( MXk[I], PInv );
+      MIk[I] = gfMul( MIk[I], PInv );
+    }
+
+    for (uint I = 0; I < NE; I++)
+      if (I != Kr) // For all rows except containing the pivot cell.
+      { 
+        // Apply Gaussian elimination Mij -= Mkj * Mik / pivot.
+        // Since pivot is already 1, it is reduced to Mij -= Mkj * Mik.
+        uint *MXi = MX + I * ND; // i-th row of main matrix.
+        uint *MIi = MI + I * ND; // i-th row of inversion matrix.
+        uint Mik = MXi[Kf]; // Cell in pivot position.
+        for (uint J = 0; J < ND; J++)
+        {
+          MXi[J] ^= gfMul(MXk[J] , Mik);
+          MIi[J] ^= gfMul(MIk[J] , Mik);
+        }
+      }
+  }
+
+  // Copy data to main matrix.
+  for (uint I = 0; I < NE * ND; I++)
+    MX[I] = MI[I];
+
+  delete[] MI;
+}
+
+
+#if 0
+// Multiply matrix to data vector. When encoding, it contains data in Data
+// and stores error correction codes in Out. When decoding it contains
+// broken data followed by ECC in Data and stores recovered data to Out.
+// We do not use this function now, everything is moved to UpdateECC.
+void RSCoder16::Process(const uint *Data, uint *Out)
+{
+  uint ProcData[gfSize];
+
+  for (uint I = 0; I < ND; I++)
+    ProcData[I]=Data[I];
+
+  if (Decoding)
+  {
+    // Replace broken data units with first available valid recovery codes.
+    // 'Data' array must contain recovery codes after data.
+    for (uint I=0, R=ND, Dest=0; I < ND; I++)
+      if (!ValidFlags[I]) // For every broken data unit.
+      {
+        while (!ValidFlags[R]) // Find a valid recovery unit.
+          R++;
+        ProcData[I]=Data[R];
+        R++;
+      }
+  }
+
+  uint H=Decoding ? NE : NR;
+  for (uint I = 0; I < H; I++)
+  {
+    uint R = 0; // Result of matrix row multiplication to data.
+
+    uint *MXi=MX + I * ND;
+    for (uint J = 0; J < ND; J++)
+      R ^= gfMul(MXi[J], ProcData[J]);
+
+    Out[I] = R;
+  }
+}
+#endif
+
+
+// We update ECC in blocks by applying every data block to all ECC blocks.
+// This function applies one data block to one ECC block.
+void RSCoder16::UpdateECC(uint DataNum, uint ECCNum, const byte *Data, byte *ECC, size_t BlockSize)
+{
+  if (DataNum==0) // Init ECC data.
+    memset(ECC, 0, BlockSize);
+
+  bool DirectAccess;
+#ifdef LITTLE_ENDIAN
+  // We can access data and ECC directly if we have little endian 16 bit uint.
+  DirectAccess=sizeof(ushort)==2;
+#else
+  DirectAccess=false;
+#endif
+
+#ifdef USE_SSE
+  if (DirectAccess && SSE_UpdateECC(DataNum,ECCNum,Data,ECC,BlockSize))
+    return;
+#endif
+
+  if (ECCNum==0)
+  {
+    if (DataLogSize!=BlockSize)
+    {
+      delete[] DataLog;
+      DataLog=new uint[BlockSize];
+      DataLogSize=BlockSize;
+
+    }
+    if (DirectAccess)
+      for (size_t I=0; I<BlockSize; I+=2)
+        DataLog[I] = gfLog[ *(ushort*)(Data+I) ];
+    else
+      for (size_t I=0; I<BlockSize; I+=2)
+      {
+        uint D=Data[I]+Data[I+1]*256;
+        DataLog[I] = gfLog[ D ];
+      }
+  }
+
+  uint ML = gfLog[ MX[ECCNum * ND + DataNum] ];
+
+  if (DirectAccess)
+    for (size_t I=0; I<BlockSize; I+=2)
+      *(ushort*)(ECC+I) ^= gfExp[ ML + DataLog[I] ];
+  else
+    for (size_t I=0; I<BlockSize; I+=2)
+    {
+      uint R=gfExp[ ML + DataLog[I] ];
+      ECC[I]^=byte(R);
+      ECC[I+1]^=byte(R/256);
+    }
+}
+
+
+#ifdef USE_SSE
+// Data and ECC addresses must be properly aligned for SSE.
+// AVX2 did not provide a noticeable speed gain on i7-6700K here.
+bool RSCoder16::SSE_UpdateECC(uint DataNum, uint ECCNum, const byte *Data, byte *ECC, size_t BlockSize)
+{
+  // Check data alignment and SSSE3 support.
+  if ((size_t(Data) & (SSE_ALIGNMENT-1))!=0 || (size_t(ECC) & (SSE_ALIGNMENT-1))!=0 ||
+      _SSE_Version<SSE_SSSE3)
+    return false;
+
+  uint M=MX[ECCNum * ND + DataNum];
+
+  // Prepare tables containing products of M and 4, 8, 12, 16 bit length
+  // numbers, which have 4 high bits in 0..15 range and other bits set to 0.
+  // Store high and low bytes of resulting 16 bit product in separate tables.
+  __m128i T0L,T1L,T2L,T3L; // Low byte tables.
+  __m128i T0H,T1H,T2H,T3H; // High byte tables.
+
+  for (uint I=0;I<16;I++)
+  {
+    ((byte *)&T0L)[I]=gfMul(I,M);
+    ((byte *)&T0H)[I]=gfMul(I,M)>>8;
+    ((byte *)&T1L)[I]=gfMul(I<<4,M);
+    ((byte *)&T1H)[I]=gfMul(I<<4,M)>>8;
+    ((byte *)&T2L)[I]=gfMul(I<<8,M);
+    ((byte *)&T2H)[I]=gfMul(I<<8,M)>>8;
+    ((byte *)&T3L)[I]=gfMul(I<<12,M);
+    ((byte *)&T3H)[I]=gfMul(I<<12,M)>>8;
+  }
+
+  size_t Pos=0;
+
+  __m128i LowByteMask=_mm_set1_epi16(0xff);     // 00ff00ff...00ff
+  __m128i Low4Mask=_mm_set1_epi8(0xf);          // 0f0f0f0f...0f0f
+  __m128i High4Mask=_mm_slli_epi16(Low4Mask,4); // f0f0f0f0...f0f0
+
+  for (; Pos+2*sizeof(__m128i)<=BlockSize; Pos+=2*sizeof(__m128i))
+  {
+    // We process two 128 bit chunks of source data at once.
+    __m128i *D=(__m128i *)(Data+Pos);
+
+    // Place high bytes of both chunks to one variable and low bytes to
+    // another, so we can use the table lookup multiplication for 16 values
+    // 4 bit length each at once.
+    __m128i HighBytes0=_mm_srli_epi16(D[0],8);
+    __m128i LowBytes0=_mm_and_si128(D[0],LowByteMask);
+    __m128i HighBytes1=_mm_srli_epi16(D[1],8);
+    __m128i LowBytes1=_mm_and_si128(D[1],LowByteMask);
+    __m128i HighBytes=_mm_packus_epi16(HighBytes0,HighBytes1);
+    __m128i LowBytes=_mm_packus_epi16(LowBytes0,LowBytes1);
+    
+    // Multiply bits 0..3 of low bytes. Store low and high product bytes
+    // separately in cumulative sum variables.
+    __m128i LowBytesLow4=_mm_and_si128(LowBytes,Low4Mask);
+    __m128i LowBytesMultSum=_mm_shuffle_epi8(T0L,LowBytesLow4);
+    __m128i HighBytesMultSum=_mm_shuffle_epi8(T0H,LowBytesLow4);
+
+    // Multiply bits 4..7 of low bytes. Store low and high product bytes separately.
+    __m128i LowBytesHigh4=_mm_and_si128(LowBytes,High4Mask);
+            LowBytesHigh4=_mm_srli_epi16(LowBytesHigh4,4);
+    __m128i LowBytesHigh4MultLow=_mm_shuffle_epi8(T1L,LowBytesHigh4);
+    __m128i LowBytesHigh4MultHigh=_mm_shuffle_epi8(T1H,LowBytesHigh4);
+
+    // Add new product to existing sum, low and high bytes separately.
+    LowBytesMultSum=_mm_xor_si128(LowBytesMultSum,LowBytesHigh4MultLow);
+    HighBytesMultSum=_mm_xor_si128(HighBytesMultSum,LowBytesHigh4MultHigh);
+    
+    // Multiply bits 0..3 of high bytes. Store low and high product bytes separately.
+    __m128i HighBytesLow4=_mm_and_si128(HighBytes,Low4Mask);
+    __m128i HighBytesLow4MultLow=_mm_shuffle_epi8(T2L,HighBytesLow4);
+    __m128i HighBytesLow4MultHigh=_mm_shuffle_epi8(T2H,HighBytesLow4);
+
+    // Add new product to existing sum, low and high bytes separately.
+    LowBytesMultSum=_mm_xor_si128(LowBytesMultSum,HighBytesLow4MultLow);
+    HighBytesMultSum=_mm_xor_si128(HighBytesMultSum,HighBytesLow4MultHigh);
+
+    // Multiply bits 4..7 of high bytes. Store low and high product bytes separately.
+    __m128i HighBytesHigh4=_mm_and_si128(HighBytes,High4Mask);
+            HighBytesHigh4=_mm_srli_epi16(HighBytesHigh4,4);
+    __m128i HighBytesHigh4MultLow=_mm_shuffle_epi8(T3L,HighBytesHigh4);
+    __m128i HighBytesHigh4MultHigh=_mm_shuffle_epi8(T3H,HighBytesHigh4);
+
+    // Add new product to existing sum, low and high bytes separately.
+    LowBytesMultSum=_mm_xor_si128(LowBytesMultSum,HighBytesHigh4MultLow);
+    HighBytesMultSum=_mm_xor_si128(HighBytesMultSum,HighBytesHigh4MultHigh);
+
+    // Combine separate low and high cumulative sum bytes to 16-bit words.
+    __m128i HighBytesHigh4Mult0=_mm_unpacklo_epi8(LowBytesMultSum,HighBytesMultSum);
+    __m128i HighBytesHigh4Mult1=_mm_unpackhi_epi8(LowBytesMultSum,HighBytesMultSum);
+
+    // Add result to ECC.
+    __m128i *StoreECC=(__m128i *)(ECC+Pos);
+
+    StoreECC[0]=_mm_xor_si128(StoreECC[0],HighBytesHigh4Mult0);
+    StoreECC[1]=_mm_xor_si128(StoreECC[1],HighBytesHigh4Mult1);
+  }
+
+  // If we have non 128 bit aligned data in the end of block, process them
+  // in a usual way. We cannot do the same in the beginning of block,
+  // because Data and ECC can have different alignment offsets.
+  for (; Pos<BlockSize; Pos+=2)
+    *(ushort*)(ECC+Pos) ^= gfMul( M, *(ushort*)(Data+Pos) );
+  
+  return true;
+}
+#endif
diff --git a/third_party/unrar/src/rs16.hpp b/third_party/unrar/src/rs16.hpp
new file mode 100644
index 0000000..b67a7ca
--- /dev/null
+++ b/third_party/unrar/src/rs16.hpp
@@ -0,0 +1,44 @@
+#ifndef _RAR_RS16_
+#define _RAR_RS16_
+
+class RSCoder16
+{
+  private:
+    static const uint gfSize=65535;   // Galois field size.
+    void gfInit();                    // Galois field inititalization.
+    inline uint gfAdd(uint a,uint b); // Addition in Galois field. 
+    inline uint gfMul(uint a,uint b); // Multiplication in Galois field. 
+    inline uint gfInv(uint a);        // Inverse element in Galois field.
+    uint *gfExp;                      // Galois field exponents.
+    uint *gfLog;                      // Galois field logarithms.
+
+    void MakeEncoderMatrix();
+    void MakeDecoderMatrix();
+    void InvertDecoderMatrix();
+
+#ifdef USE_SSE
+    bool SSE_UpdateECC(uint DataNum, uint ECCNum, const byte *Data, byte *ECC, size_t BlockSize);
+#endif
+
+    bool Decoding;    // If we are decoding or encoding data.
+    uint ND;          // Number of data units.
+    uint NR;          // Number of Reed-Solomon code units.
+    uint NE;          // Number of erasures.
+    bool *ValidFlags; // Validity flags for data and ECC units.
+    uint *MX;         // Cauchy based coding or decoding matrix.
+
+    uint *DataLog; // Buffer to store data logarithms for UpdateECC.
+    size_t DataLogSize;
+
+  public:
+    RSCoder16();
+    ~RSCoder16();
+
+    bool Init(uint DataCount, uint RecCount, bool *ValidityFlags);
+#if 0 // We use only UpdateECC now.
+    void Process(const uint *Data, uint *Out);
+#endif
+    void UpdateECC(uint DataNum, uint ECCNum, const byte *Data, byte *ECC, size_t BlockSize);
+};
+
+#endif
diff --git a/third_party/unrar/src/savepos.hpp b/third_party/unrar/src/savepos.hpp
new file mode 100644
index 0000000..df61710
--- /dev/null
+++ b/third_party/unrar/src/savepos.hpp
@@ -0,0 +1,21 @@
+#ifndef _RAR_SAVEPOS_
+#define _RAR_SAVEPOS_
+
+class SaveFilePos
+{
+  private:
+    File *SaveFile;
+    int64 SavePos;
+  public:
+    SaveFilePos(File &Src)
+    {
+      SaveFile=&Src;
+      SavePos=Src.Tell();
+    }
+    ~SaveFilePos()
+    {
+      SaveFile->Seek(SavePos,SEEK_SET);
+    }
+};
+
+#endif
diff --git a/third_party/unrar/src/scantree.cpp b/third_party/unrar/src/scantree.cpp
new file mode 100644
index 0000000..841a1e9
--- /dev/null
+++ b/third_party/unrar/src/scantree.cpp
@@ -0,0 +1,486 @@
+#include "rar.hpp"
+
+ScanTree::ScanTree(StringList *FileMasks,RECURSE_MODE Recurse,bool GetLinks,SCAN_DIRS GetDirs)
+{
+  ScanTree::FileMasks=FileMasks;
+  ScanTree::Recurse=Recurse;
+  ScanTree::GetLinks=GetLinks;
+  ScanTree::GetDirs=GetDirs;
+
+  ScanEntireDisk=false;
+  FolderWildcards=false;
+
+  SetAllMaskDepth=0;
+  *CurMask=0;
+  memset(FindStack,0,sizeof(FindStack));
+  Depth=0;
+  Errors=0;
+  *ErrArcName=0;
+  Cmd=NULL;
+  ErrDirList=NULL;
+  ErrDirSpecPathLength=NULL;
+}
+
+
+ScanTree::~ScanTree()
+{
+  for (int I=Depth;I>=0;I--)
+    if (FindStack[I]!=NULL)
+      delete FindStack[I];
+}
+
+
+SCAN_CODE ScanTree::GetNext(FindData *FD)
+{
+  if (Depth<0)
+    return SCAN_DONE;
+
+#ifndef SILENT
+  uint LoopCount=0;
+#endif
+
+  SCAN_CODE FindCode;
+  while (1)
+  {
+    if (*CurMask==0 && !GetNextMask())
+      return SCAN_DONE;
+
+#ifndef SILENT
+    // Let's return some ticks to system or WinRAR can become irresponsible
+    // while scanning files in command like "winrar a -r arc c:\file.ext".
+    // Also we reset system sleep timer here.
+    if ((++LoopCount & 0x3ff)==0)
+      Wait();
+#endif
+
+    FindCode=FindProc(FD);
+    if (FindCode==SCAN_ERROR)
+    {
+      Errors++;
+      continue;
+    }
+    if (FindCode==SCAN_NEXT)
+      continue;
+    if (FindCode==SCAN_SUCCESS && FD->IsDir && GetDirs==SCAN_SKIPDIRS)
+      continue;
+    if (FindCode==SCAN_DONE && GetNextMask())
+      continue;
+    if (FilterList.ItemsCount()>0 && FindCode==SCAN_SUCCESS)
+      if (!CommandData::CheckArgs(&FilterList,FD->IsDir,FD->Name,false,MATCH_WILDSUBPATH))
+        continue;
+    break;
+  }
+  return FindCode;
+}
+
+
+// For masks like dir1\dir2*\*.ext in non-recursive mode.
+bool ScanTree::ExpandFolderMask()
+{
+  bool WildcardFound=false;
+  uint SlashPos=0;
+  for (int I=0;CurMask[I]!=0;I++)
+  {
+    if (CurMask[I]=='?' || CurMask[I]=='*')
+      WildcardFound=true;
+    if (WildcardFound && IsPathDiv(CurMask[I]))
+    {
+      // First path separator position after folder wildcard mask.
+      // In case of dir1\dir2*\dir3\name.ext mask it may point not to file
+      // name, so we cannot use PointToName() here.
+      SlashPos=I; 
+      break;
+    }
+  }
+
+  wchar Mask[NM];
+  wcsncpyz(Mask,CurMask,ASIZE(Mask));
+  Mask[SlashPos]=0;
+
+  // Prepare the list of all folders matching the wildcard mask.
+  ExpandedFolderList.Reset();
+  FindFile Find;
+  Find.SetMask(Mask);
+  FindData FD;
+  while (Find.Next(&FD))
+    if (FD.IsDir)
+    {
+      wcsncatz(FD.Name,CurMask+SlashPos,ASIZE(FD.Name));
+
+      // Treat dir*\* or dir*\*.* as dir, so empty 'dir' is also matched
+      // by such mask. Skipping empty dir with dir*\*.* confused some users.
+      wchar *LastMask=PointToName(FD.Name);
+      if (wcscmp(LastMask,L"*")==0 || wcscmp(LastMask,L"*.*")==0)
+        RemoveNameFromPath(FD.Name);
+
+      ExpandedFolderList.AddString(FD.Name);
+    }
+  if (ExpandedFolderList.ItemsCount()==0)
+    return false;
+  // Return the first matching folder name now.
+  ExpandedFolderList.GetString(CurMask,ASIZE(CurMask));
+  return true;
+}
+
+
+// For masks like dir1\dir2*\file.ext this function sets 'dir1' recursive mask
+// and '*\dir2*\file.ext' filter. Masks without folder wildcards are
+// returned as is.
+bool ScanTree::GetFilteredMask()
+{
+  // If we have some matching folders left for non-recursive folder wildcard
+  // mask, we return it here.
+  if (ExpandedFolderList.ItemsCount()>0 && ExpandedFolderList.GetString(CurMask,ASIZE(CurMask)))
+    return true;
+
+  FolderWildcards=false;
+  FilterList.Reset();
+  if (!FileMasks->GetString(CurMask,ASIZE(CurMask)))
+    return false;
+
+  // Check if folder wildcards present.
+  bool WildcardFound=false;
+  uint FolderWildcardCount=0;
+  uint SlashPos=0;
+  for (int I=0;CurMask[I]!=0;I++)
+  {
+    if (CurMask[I]=='?' || CurMask[I]=='*')
+      WildcardFound=true;
+    if (IsPathDiv(CurMask[I]) || IsDriveDiv(CurMask[I]))
+    {
+      if (WildcardFound)
+      {
+        // Calculate a number of folder wildcards in current mask.
+        FolderWildcardCount++;
+        WildcardFound=false;
+      }
+      if (FolderWildcardCount==0)
+        SlashPos=I; // Slash position before first folder wildcard mask.
+    }
+  }
+  if (FolderWildcardCount==0)
+    return true;
+  FolderWildcards=true; // Global folder wildcards flag.
+
+  // If we have only one folder wildcard component and -r is missing or -r-
+  // is specified, prepare matching folders in non-recursive mode.
+  // We assume -r for masks like dir1*\dir2*\file*, because it is complicated
+  // to fast find them using OS file find API call.
+  if ((Recurse==RECURSE_NONE || Recurse==RECURSE_DISABLE) && FolderWildcardCount==1)
+    return ExpandFolderMask();
+
+  wchar Filter[NM];
+  // Convert path\dir*\ to *\dir filter to search for 'dir' in all 'path' subfolders.
+  wcscpy(Filter,L"*");
+  AddEndSlash(Filter,ASIZE(Filter));
+  // SlashPos might point or not point to path separator for masks like 'dir*', '\dir*' or 'd:dir*'
+  wchar *WildName=IsPathDiv(CurMask[SlashPos]) || IsDriveDiv(CurMask[SlashPos]) ? CurMask+SlashPos+1 : CurMask+SlashPos;
+  wcsncatz(Filter,WildName,ASIZE(Filter));
+
+  // Treat dir*\* or dir*\*.* as dir\, so empty 'dir' is also matched
+  // by such mask. Skipping empty dir with dir*\*.* confused some users.
+  wchar *LastMask=PointToName(Filter);
+  if (wcscmp(LastMask,L"*")==0 || wcscmp(LastMask,L"*.*")==0)
+    *LastMask=0;
+
+  FilterList.AddString(Filter);
+
+  bool RelativeDrive=IsDriveDiv(CurMask[SlashPos]);
+  if (RelativeDrive)
+    SlashPos++; // Use "d:" instead of "d" for d:* mask.
+
+  CurMask[SlashPos]=0;
+
+  if (!RelativeDrive) // Keep d: mask as is, not convert to d:\*
+  {
+    // We need to append "\*" both for -ep1 to work correctly and to
+    // convert d:\* masks previously truncated to d: back to original form.
+    AddEndSlash(CurMask,ASIZE(CurMask));
+    wcsncatz(CurMask,MASKALL,ASIZE(CurMask));
+  }
+  return true;
+}
+
+
+bool ScanTree::GetNextMask()
+{
+  if (!GetFilteredMask())
+    return false;
+#ifdef _WIN_ALL
+  UnixSlashToDos(CurMask,CurMask,ASIZE(CurMask));
+#endif
+
+  // We wish to scan entire disk if mask like c:\ is specified
+  // regardless of recursion mode. Use c:\*.* mask when need to scan only 
+  // the root directory.
+  ScanEntireDisk=IsDriveLetter(CurMask) && IsPathDiv(CurMask[2]) && CurMask[3]==0;
+
+  wchar *Name=PointToName(CurMask);
+  if (*Name==0)
+    wcsncatz(CurMask,MASKALL,ASIZE(CurMask));
+  if (Name[0]=='.' && (Name[1]==0 || Name[1]=='.' && Name[2]==0))
+  {
+    AddEndSlash(CurMask,ASIZE(CurMask));
+    wcsncatz(CurMask,MASKALL,ASIZE(CurMask));
+  }
+  SpecPathLength=Name-CurMask;
+  Depth=0;
+
+  wcscpy(OrigCurMask,CurMask);
+
+  return true;
+}
+
+
+SCAN_CODE ScanTree::FindProc(FindData *FD)
+{
+  if (*CurMask==0)
+    return SCAN_NEXT;
+  bool FastFindFile=false;
+  
+  if (FindStack[Depth]==NULL) // No FindFile object for this depth yet.
+  {
+    bool Wildcards=IsWildcard(CurMask);
+
+    // If we have a file name without wildcards, we can try to use
+    // FastFind to optimize speed. For example, in Unix it results in
+    // stat call instead of opendir/readdir/closedir.
+    bool FindCode=!Wildcards && FindFile::FastFind(CurMask,FD,GetLinks);
+
+    // Link check is important for NTFS, where links can have "Directory"
+    // attribute, but we do not want to recurse to them in "get links" mode.
+    bool IsDir=FindCode && FD->IsDir && (!GetLinks || !FD->IsLink);
+
+    // SearchAll means that we'll use "*" mask for search, so we'll find
+    // subdirectories and will be able to recurse into them.
+    // We do not use "*" for directories at any level or for files
+    // at top level in recursion mode. We always comrpess the entire directory
+    // if folder wildcard is specified.
+    bool SearchAll=!IsDir && (Depth>0 || Recurse==RECURSE_ALWAYS ||
+                   FolderWildcards && Recurse!=RECURSE_DISABLE || 
+                   Wildcards && Recurse==RECURSE_WILDCARDS || 
+                   ScanEntireDisk && Recurse!=RECURSE_DISABLE);
+    if (Depth==0)
+      SearchAllInRoot=SearchAll;
+    if (SearchAll || Wildcards)
+    {
+      // Create the new FindFile object for wildcard based search.
+      FindStack[Depth]=new FindFile;
+
+      wchar SearchMask[NM];
+      wcsncpyz(SearchMask,CurMask,ASIZE(SearchMask));
+      if (SearchAll)
+        SetName(SearchMask,MASKALL,ASIZE(SearchMask));
+      FindStack[Depth]->SetMask(SearchMask);
+    }
+    else
+    {
+      // Either we failed to fast find or we found a file or we found
+      // a directory in RECURSE_DISABLE mode, so we do not need to scan it.
+      // We can return here and do not need to process further.
+      // We need to process further only if we fast found a directory.
+      if (!FindCode || !IsDir || Recurse==RECURSE_DISABLE)
+      {
+         // Return SCAN_SUCCESS if we found a file.
+        SCAN_CODE RetCode=SCAN_SUCCESS;
+
+        if (!FindCode)
+        {
+          // Return SCAN_ERROR if problem is more serious than just
+          // "file not found".
+          RetCode=FD->Error ? SCAN_ERROR:SCAN_NEXT;
+
+          // If we failed to find an object, but our current mask is excluded,
+          // we skip this object and avoid indicating an error.
+          if (Cmd!=NULL && Cmd->ExclCheck(CurMask,false,true,true))
+            RetCode=SCAN_NEXT;
+          else
+          {
+            ErrHandler.OpenErrorMsg(ErrArcName,CurMask);
+            // User asked to return RARX_NOFILES and not RARX_OPEN here.
+            ErrHandler.SetErrorCode(RARX_NOFILES);
+          }
+        }
+
+        // If we searched only for one file or directory in "fast find" 
+        // (without a wildcard) mode, let's set masks to zero, 
+        // so calling function will know that current mask is used 
+        // and next one must be read from mask list for next call.
+        // It is not necessary for directories, because even in "fast find"
+        // mode, directory recursing will quit by (Depth < 0) condition,
+        // which returns SCAN_DONE to calling function.
+        *CurMask=0;
+
+        return RetCode;
+      }
+
+      // We found a directory using only FindFile::FastFind function.
+      FastFindFile=true;
+    }
+  }
+
+  if (!FastFindFile && !FindStack[Depth]->Next(FD,GetLinks))
+  {
+    // We cannot find anything more in directory either because of
+    // some error or just as result of all directory entries already read.
+
+    bool Error=FD->Error;
+    if (Error)
+      ScanError(Error);
+
+    wchar DirName[NM];
+    *DirName=0;
+
+    // Going to at least one directory level higher.
+    delete FindStack[Depth];
+    FindStack[Depth--]=NULL;
+    while (Depth>=0 && FindStack[Depth]==NULL)
+      Depth--;
+    if (Depth < 0)
+    {
+      // Directories scanned both in normal and FastFindFile mode,
+      // finally exit from scan here, by (Depth < 0) condition.
+
+      if (Error)
+        Errors++;
+      return SCAN_DONE;
+    }
+
+    wchar *Slash=wcsrchr(CurMask,CPATHDIVIDER);
+    if (Slash!=NULL)
+    {
+      wchar Mask[NM];
+      wcscpy(Mask,Slash);
+      if (Depth<SetAllMaskDepth)
+        wcscpy(Mask+1,PointToName(OrigCurMask));
+      *Slash=0;
+      wcscpy(DirName,CurMask);
+      wchar *PrevSlash=wcsrchr(CurMask,CPATHDIVIDER);
+      if (PrevSlash==NULL)
+        wcscpy(CurMask,Mask+1);
+      else
+        wcscpy(PrevSlash,Mask);
+    }
+    if (GetDirs==SCAN_GETDIRSTWICE &&
+        FindFile::FastFind(DirName,FD,GetLinks) && FD->IsDir)
+    {
+      FD->Flags|=FDDF_SECONDDIR;
+      return Error ? SCAN_ERROR:SCAN_SUCCESS;
+    }
+    return Error ? SCAN_ERROR:SCAN_NEXT;
+  }
+
+  // Link check is required for NTFS links, not for Unix.
+  if (FD->IsDir && (!GetLinks || !FD->IsLink))
+  {
+    // If we found the directory in top (Depth==0) directory
+    // and if we are not in "fast find" (directory name only as argument)
+    // or in recurse (SearchAll was set when opening the top directory) mode,
+    // we do not recurse into this directory. We either return it by itself
+    // or skip it.
+    if (!FastFindFile && Depth==0 && !SearchAllInRoot)
+      return GetDirs==SCAN_GETCURDIRS ? SCAN_SUCCESS:SCAN_NEXT;
+
+    // Let's check if directory name is excluded, so we do not waste
+    // time searching in directory, which will be excluded anyway.
+    if (Cmd!=NULL && (Cmd->ExclCheck(FD->Name,true,false,false) ||
+        Cmd->ExclDirByAttr(FD->FileAttr)))
+    {
+      // If we are here in "fast find" mode, it means that entire directory
+      // specified in command line is excluded. Then we need to return
+      // SCAN_DONE to go to next mask and avoid the infinite loop
+      // in GetNext() function. Such loop would be possible in case of
+      // SCAN_NEXT code and "rar a arc dir -xdir" command.
+
+      return FastFindFile ? SCAN_DONE:SCAN_NEXT;
+    }
+    
+    wchar Mask[NM];
+
+    wcscpy(Mask,FastFindFile ? MASKALL:PointToName(CurMask));
+    wcscpy(CurMask,FD->Name);
+
+    if (wcslen(CurMask)+wcslen(Mask)+1>=NM || Depth>=MAXSCANDEPTH-1)
+    {
+      uiMsg(UIERROR_PATHTOOLONG,CurMask,SPATHDIVIDER,Mask);
+      return SCAN_ERROR;
+    }
+
+    AddEndSlash(CurMask,ASIZE(CurMask));
+    wcsncatz(CurMask,Mask,ASIZE(CurMask));
+
+    Depth++;
+
+    // We need to use OrigCurMask for depths less than SetAllMaskDepth
+    // and "*" for depths equal or larger than SetAllMaskDepth.
+    // It is important when "fast finding" directories at Depth > 0.
+    // For example, if current directory is RootFolder and we compress
+    // the following directories structure:
+    //   RootFolder
+    //     +--Folder1
+    //     |  +--Folder2
+    //     |  +--Folder3
+    //     +--Folder4
+    // with 'rar a -r arcname Folder2' command, rar could add not only
+    // Folder1\Folder2 contents, but also Folder1\Folder3 if we were using
+    // "*" mask at all levels. We need to use "*" mask inside of Folder2,
+    // but return to "Folder2" mask when completing scanning Folder2.
+    // We can rewrite SearchAll expression above to avoid fast finding
+    // directories at Depth > 0, but then 'rar a -r arcname Folder2'
+    // will add the empty Folder2 and do not add its contents.
+
+    if (FastFindFile)
+      SetAllMaskDepth=Depth;
+  }
+  if (!FastFindFile && !CmpName(CurMask,FD->Name,MATCH_NAMES))
+    return SCAN_NEXT;
+
+  return SCAN_SUCCESS;
+}
+
+
+void ScanTree::ScanError(bool &Error)
+{
+#ifdef _WIN_ALL
+  if (Error)
+  {
+    // Get attributes of parent folder and do not display an error
+    // if it is reparse point. We cannot scan contents of standard
+    // Windows reparse points like "C:\Documents and Settings"
+    // and we do not want to issue numerous useless errors for them.
+    // We cannot just check FD->FileAttr here, it can be undefined
+    // if we process "folder\*" mask or if we process "folder" mask,
+    // but "folder" is inaccessible.
+    wchar *Slash=PointToName(CurMask);
+    if (Slash>CurMask)
+    {
+      *(Slash-1)=0;
+      DWORD Attr=GetFileAttributes(CurMask);
+      *(Slash-1)=CPATHDIVIDER;
+      if (Attr!=0xffffffff && (Attr & FILE_ATTRIBUTE_REPARSE_POINT)!=0)
+        Error=false;
+    }
+
+    // Do not display an error if we cannot scan contents of
+    // "System Volume Information" folder. Normally it is not accessible.
+    if (wcsstr(CurMask,L"System Volume Information\\")!=NULL)
+      Error=false;
+  }
+#endif
+
+  if (Error && Cmd!=NULL && Cmd->ExclCheck(CurMask,false,true,true))
+    Error=false;
+
+  if (Error)
+  {
+    if (ErrDirList!=NULL)
+      ErrDirList->AddString(CurMask);
+    if (ErrDirSpecPathLength!=NULL)
+      ErrDirSpecPathLength->Push((uint)SpecPathLength);
+    wchar FullName[NM];
+    // This conversion works for wildcard masks too.
+    ConvertNameToFull(CurMask,FullName,ASIZE(FullName));
+    uiMsg(UIERROR_DIRSCAN,FullName);
+    ErrHandler.SysErrMsg();
+  }
+}
diff --git a/third_party/unrar/src/scantree.hpp b/third_party/unrar/src/scantree.hpp
new file mode 100644
index 0000000..40a6d84
--- /dev/null
+++ b/third_party/unrar/src/scantree.hpp
@@ -0,0 +1,78 @@
+#ifndef _RAR_SCANTREE_
+#define _RAR_SCANTREE_
+
+enum SCAN_DIRS 
+{ 
+  SCAN_SKIPDIRS,     // Skip directories, but recurse for files if recursion mode is enabled.
+  SCAN_GETDIRS,      // Get subdirectories in recurse mode.
+  SCAN_GETDIRSTWICE, // Get the directory name both before and after the list of files it contains.
+  SCAN_GETCURDIRS    // Get subdirectories in current directory even in RECURSE_NONE mode.
+};
+
+enum SCAN_CODE { SCAN_SUCCESS,SCAN_DONE,SCAN_ERROR,SCAN_NEXT };
+
+#define MAXSCANDEPTH    (NM/2)
+
+class CommandData;
+
+class ScanTree
+{
+  private:
+    bool ExpandFolderMask();
+    bool GetFilteredMask();
+    bool GetNextMask();
+    SCAN_CODE FindProc(FindData *FD);
+    void ScanError(bool &Error);
+
+    FindFile *FindStack[MAXSCANDEPTH];
+    int Depth;
+
+    int SetAllMaskDepth;
+
+    StringList *FileMasks;
+    RECURSE_MODE Recurse;
+    bool GetLinks;
+    SCAN_DIRS GetDirs;
+    int Errors;
+
+    // Set when processing paths like c:\ (root directory without wildcards).
+    bool ScanEntireDisk;
+
+    wchar CurMask[NM];
+    wchar OrigCurMask[NM];
+
+    // Store all folder masks generated from folder wildcard mask in non-recursive mode.
+    StringList ExpandedFolderList;
+
+    // Store a filter string for folder wildcard in recursive mode.
+    StringList FilterList;
+
+    // Save the list of unreadable dirs here.
+    StringList *ErrDirList;
+    Array<uint> *ErrDirSpecPathLength;
+
+    // Set if processing a folder wildcard mask.
+    bool FolderWildcards;
+
+    bool SearchAllInRoot;
+    size_t SpecPathLength;
+
+    wchar ErrArcName[NM];
+
+    CommandData *Cmd;
+  public:
+    ScanTree(StringList *FileMasks,RECURSE_MODE Recurse,bool GetLinks,SCAN_DIRS GetDirs);
+    ~ScanTree();
+    SCAN_CODE GetNext(FindData *FindData);
+    size_t GetSpecPathLength() {return SpecPathLength;};
+    int GetErrors() {return Errors;};
+    void SetErrArcName(const wchar *Name) {wcsncpyz(ErrArcName,Name,ASIZE(ErrArcName));}
+    void SetCommandData(CommandData *Cmd) {ScanTree::Cmd=Cmd;}
+    void SetErrDirList(StringList *List,Array<uint> *Lengths)
+    {
+      ErrDirList=List;
+      ErrDirSpecPathLength=Lengths;
+    }
+};
+
+#endif
diff --git a/third_party/unrar/src/secpassword.cpp b/third_party/unrar/src/secpassword.cpp
new file mode 100644
index 0000000..4865b3fd
--- /dev/null
+++ b/third_party/unrar/src/secpassword.cpp
@@ -0,0 +1,216 @@
+#include "rar.hpp"
+
+#if defined(_WIN_ALL)
+typedef BOOL (WINAPI *CRYPTPROTECTMEMORY)(LPVOID pData,DWORD cbData,DWORD dwFlags);
+typedef BOOL (WINAPI *CRYPTUNPROTECTMEMORY)(LPVOID pData,DWORD cbData,DWORD dwFlags);
+
+#ifndef CRYPTPROTECTMEMORY_BLOCK_SIZE
+#define CRYPTPROTECTMEMORY_BLOCK_SIZE           16
+#define CRYPTPROTECTMEMORY_SAME_PROCESS         0x00
+#define CRYPTPROTECTMEMORY_CROSS_PROCESS        0x01
+#endif
+
+class CryptLoader
+{
+  private:
+    HMODULE hCrypt;
+    bool LoadCalled;
+  public:
+    CryptLoader() 
+    {
+      hCrypt=NULL;
+      pCryptProtectMemory=NULL;
+      pCryptUnprotectMemory=NULL;
+      LoadCalled=false;
+    }
+    ~CryptLoader()
+    {
+      if (hCrypt!=NULL)
+        FreeLibrary(hCrypt);
+      hCrypt=NULL;
+      pCryptProtectMemory=NULL;
+      pCryptUnprotectMemory=NULL;
+    };
+    void Load()
+    {
+      if (!LoadCalled)
+      {
+        hCrypt = LoadSysLibrary(L"Crypt32.dll");
+        if (hCrypt != NULL)
+        {
+          // Available since Vista.
+          pCryptProtectMemory = (CRYPTPROTECTMEMORY)GetProcAddress(hCrypt, "CryptProtectMemory");
+          pCryptUnprotectMemory = (CRYPTUNPROTECTMEMORY)GetProcAddress(hCrypt, "CryptUnprotectMemory");
+        }
+        LoadCalled=true;
+      }
+    }
+
+    CRYPTPROTECTMEMORY pCryptProtectMemory;
+    CRYPTUNPROTECTMEMORY pCryptUnprotectMemory;
+};
+
+// We need to call FreeLibrary when RAR is exiting.
+CryptLoader GlobalCryptLoader;
+#endif
+
+SecPassword::SecPassword()
+{
+  CrossProcess=false;
+  Set(L"");
+}
+
+
+SecPassword::~SecPassword()
+{
+  Clean();
+}
+
+
+void SecPassword::Clean()
+{
+  PasswordSet=false;
+  cleandata(Password,sizeof(Password));
+}
+ 
+
+// When we call memset in end of function to clean local variables
+// for security reason, compiler optimizer can remove such call.
+// So we use our own function for this purpose.
+void cleandata(void *data,size_t size)
+{
+  if (data==NULL || size==0)
+    return;
+#if defined(_WIN_ALL) && defined(_MSC_VER)
+  SecureZeroMemory(data,size);
+#else
+  // 'volatile' is required. Otherwise optimizers can remove this function
+  // if cleaning local variables, which are not used after that.
+  volatile byte *d = (volatile byte *)data;
+  for (size_t i=0;i<size;i++)
+    d[i]=0;
+#endif
+}
+
+
+// We got a complain from user that it is possible to create WinRAR dump
+// with "Create dump file" command in Windows Task Manager and then easily
+// locate Unicode password string in the dump. It is unsecure if several
+// people share the same computer and somebody left WinRAR copy with entered
+// password. So we decided to obfuscate the password to make it more difficult
+// to find it in dump.
+void SecPassword::Process(const wchar *Src,size_t SrcSize,wchar *Dst,size_t DstSize,bool Encode)
+{
+  // Source string can be shorter than destination as in case when we process
+  // -p<pwd> parameter, so we need to take into account both sizes.
+  memcpy(Dst,Src,Min(SrcSize,DstSize)*sizeof(*Dst));
+  SecHideData(Dst,DstSize*sizeof(*Dst),Encode,CrossProcess);
+}
+
+
+void SecPassword::Get(wchar *Psw,size_t MaxSize)
+{
+  if (PasswordSet)
+  {
+    Process(Password,ASIZE(Password),Psw,MaxSize,false);
+    Psw[MaxSize-1]=0;
+  }
+  else
+    *Psw=0;
+}
+
+
+
+
+void SecPassword::Set(const wchar *Psw)
+{
+  if (*Psw==0)
+  {
+    PasswordSet=false;
+    memset(Password,0,sizeof(Password));
+  }
+  else
+  {
+    PasswordSet=true;
+    Process(Psw,wcslen(Psw)+1,Password,ASIZE(Password),true);
+  }
+}
+
+
+size_t SecPassword::Length()
+{
+  wchar Plain[MAXPASSWORD];
+  Get(Plain,ASIZE(Plain));
+  size_t Length=wcslen(Plain);
+  cleandata(Plain,ASIZE(Plain));
+  return Length;
+}
+
+
+bool SecPassword::operator == (SecPassword &psw)
+{
+  // We cannot compare encoded data directly, because there is no guarantee
+  // than encryption function will always produce the same result for same
+  // data (salt?) and because we do not clean the rest of password buffer
+  // after trailing zero before encoding password. So we decode first.
+  wchar Plain1[MAXPASSWORD],Plain2[MAXPASSWORD];
+  Get(Plain1,ASIZE(Plain1));
+  psw.Get(Plain2,ASIZE(Plain2));
+  bool Result=wcscmp(Plain1,Plain2)==0;
+  cleandata(Plain1,ASIZE(Plain1));
+  cleandata(Plain2,ASIZE(Plain2));
+  return Result;
+}
+
+
+void SecHideData(void *Data,size_t DataSize,bool Encode,bool CrossProcess)
+{
+  // CryptProtectMemory is not available in UWP and CryptProtectData
+  // increases data size not allowing in place conversion.
+#if defined(_WIN_ALL)
+  // Try to utilize the secure Crypt[Un]ProtectMemory if possible.
+  if (GlobalCryptLoader.pCryptProtectMemory==NULL)
+    GlobalCryptLoader.Load();
+  size_t Aligned=DataSize-DataSize%CRYPTPROTECTMEMORY_BLOCK_SIZE;
+  DWORD Flags=CrossProcess ? CRYPTPROTECTMEMORY_CROSS_PROCESS : CRYPTPROTECTMEMORY_SAME_PROCESS;
+  if (Encode)
+  {
+    if (GlobalCryptLoader.pCryptProtectMemory!=NULL)
+    {
+      if (!GlobalCryptLoader.pCryptProtectMemory(Data,DWORD(Aligned),Flags))
+      {
+        ErrHandler.GeneralErrMsg(L"CryptProtectMemory failed");
+        ErrHandler.SysErrMsg();
+        ErrHandler.Exit(RARX_FATAL);
+      }
+      return;
+    }
+  }
+  else
+  {
+    if (GlobalCryptLoader.pCryptUnprotectMemory!=NULL)
+    {
+      if (!GlobalCryptLoader.pCryptUnprotectMemory(Data,DWORD(Aligned),Flags))
+      {
+        ErrHandler.GeneralErrMsg(L"CryptUnprotectMemory failed");
+        ErrHandler.SysErrMsg();
+        ErrHandler.Exit(RARX_FATAL);
+      }
+      return;
+    }
+  }
+#endif
+  
+  // CryptProtectMemory is not available, so only slightly obfuscate data.
+  uint Key;
+#ifdef _WIN_ALL
+  Key=GetCurrentProcessId();
+#elif defined(_UNIX)
+  Key=getpid();
+#else
+  Key=0; // Just an arbitrary value.
+#endif
+
+  for (size_t I=0;I<DataSize;I++)
+    *((byte *)Data+I)^=Key+I+75;
+}
diff --git a/third_party/unrar/src/secpassword.hpp b/third_party/unrar/src/secpassword.hpp
new file mode 100644
index 0000000..375d388
--- /dev/null
+++ b/third_party/unrar/src/secpassword.hpp
@@ -0,0 +1,35 @@
+#ifndef _RAR_SECURE_PASSWORD_
+#define _RAR_SECURE_PASSWORD_
+
+// Store a password securely (if data encryption is provided by OS)
+// or obfuscated to make search for password in memory dump less trivial.
+class SecPassword
+{
+  private:
+    void Process(const wchar *Src,size_t SrcSize,wchar *Dst,size_t DstSize,bool Encode);
+
+    wchar Password[MAXPASSWORD];
+
+    // It is important to have this 'bool' value, so if our object is cleaned
+    // with memset as a part of larger structure, it is handled correctly.
+    bool PasswordSet;
+  public:
+    SecPassword();
+    ~SecPassword();
+    void Clean();
+    void Get(wchar *Psw,size_t MaxSize);
+    void Set(const wchar *Psw);
+    bool IsSet() {return PasswordSet;}
+    size_t Length();
+    bool operator == (SecPassword &psw);
+
+    // Set to true if we need to pass a password to another process.
+    // We use it when transferring parameters to UAC elevated WinRAR.
+    bool CrossProcess;
+};
+
+
+void cleandata(void *data,size_t size);
+void SecHideData(void *Data,size_t DataSize,bool Encode,bool CrossProcess);
+
+#endif
diff --git a/third_party/unrar/src/sha1.cpp b/third_party/unrar/src/sha1.cpp
new file mode 100644
index 0000000..562aadd0
--- /dev/null
+++ b/third_party/unrar/src/sha1.cpp
@@ -0,0 +1,204 @@
+#include "rar.hpp"
+
+/*
+SHA-1 in C
+By Steve Reid <steve@edmweb.com>
+100% Public Domain
+*/
+
+#ifndef SFX_MODULE
+#define SHA1_UNROLL
+#endif
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+#ifdef LITTLE_ENDIAN
+#define blk0(i) (block->l[i] = ByteSwap32(block->l[i]))
+#else
+#define blk0(i) block->l[i]
+#endif
+#define blk(i) (block->l[i&15] = rotl32(block->l[(i+13)&15]^block->l[(i+8)&15] \
+    ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) {z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rotl32(v,5);w=rotl32(w,30);}
+#define R1(v,w,x,y,z,i) {z+=((w&(x^y))^y)+blk(i)+0x5A827999+rotl32(v,5);w=rotl32(w,30);}
+#define R2(v,w,x,y,z,i) {z+=(w^x^y)+blk(i)+0x6ED9EBA1+rotl32(v,5);w=rotl32(w,30);}
+#define R3(v,w,x,y,z,i) {z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rotl32(v,5);w=rotl32(w,30);}
+#define R4(v,w,x,y,z,i) {z+=(w^x^y)+blk(i)+0xCA62C1D6+rotl32(v,5);w=rotl32(w,30);}
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+void SHA1Transform(uint32 state[5], uint32 workspace[16], const byte buffer[64], bool inplace)
+{
+  uint32 a, b, c, d, e;
+
+  union CHAR64LONG16
+  {
+    unsigned char c[64];
+    uint32 l[16];
+  } *block;
+
+  if (inplace)
+    block = (CHAR64LONG16*)buffer;
+  else
+  {
+    block = (CHAR64LONG16*)workspace;
+    memcpy(block, buffer, 64);
+  }
+
+  /* Copy context->state[] to working vars */
+  a = state[0];
+  b = state[1];
+  c = state[2];
+  d = state[3];
+  e = state[4];
+
+#ifdef SHA1_UNROLL
+  /* 4 rounds of 20 operations each. Loop unrolled. */
+  R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+  R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+  R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+  R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+  R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+  R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+  R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+  R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+  R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+  R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+  R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+  R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+  R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+  R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+  R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+  R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+  R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+  R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+  R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+  R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+#else
+  for (uint I=0;;I+=5)
+  {
+    R0(a,b,c,d,e, I+0); if (I==15) break;
+    R0(e,a,b,c,d, I+1); R0(d,e,a,b,c, I+2);
+    R0(c,d,e,a,b, I+3); R0(b,c,d,e,a, I+4);
+  }
+  R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+  for (uint I=20;I<=35;I+=5)
+  {
+    R2(a,b,c,d,e,I+0); R2(e,a,b,c,d,I+1); R2(d,e,a,b,c,I+2);
+    R2(c,d,e,a,b,I+3); R2(b,c,d,e,a,I+4);
+  }
+  for (uint I=40;I<=55;I+=5)
+  {
+    R3(a,b,c,d,e,I+0); R3(e,a,b,c,d,I+1); R3(d,e,a,b,c,I+2);
+    R3(c,d,e,a,b,I+3); R3(b,c,d,e,a,I+4);
+  }
+  for (uint I=60;I<=75;I+=5)
+  {
+    R4(a,b,c,d,e,I+0); R4(e,a,b,c,d,I+1); R4(d,e,a,b,c,I+2);
+    R4(c,d,e,a,b,I+3); R4(b,c,d,e,a,I+4);
+  }
+#endif
+  /* Add the working vars back into context.state[] */
+  state[0] += a;
+  state[1] += b;
+  state[2] += c;
+  state[3] += d;
+  state[4] += e;
+}
+
+
+/* Initialize new context */
+void sha1_init(sha1_context* context)
+{
+  context->count = 0;
+  /* SHA1 initialization constants */
+  context->state[0] = 0x67452301;
+  context->state[1] = 0xEFCDAB89;
+  context->state[2] = 0x98BADCFE;
+  context->state[3] = 0x10325476;
+  context->state[4] = 0xC3D2E1F0;
+}
+
+
+/* Run your data through this. */
+void sha1_process( sha1_context * context, const unsigned char * data, size_t len)
+{
+  size_t i, j = (size_t)(context->count & 63);
+  context->count += len;
+
+  if ((j + len) > 63)
+  {
+    memcpy(context->buffer+j, data, (i = 64-j));
+    uint32 workspace[16];
+    SHA1Transform(context->state, workspace, context->buffer, true);
+    for ( ; i + 63 < len; i += 64)
+      SHA1Transform(context->state, workspace, data+i, false);
+    j = 0;
+  }
+  else
+    i = 0;
+  if (len > i)
+    memcpy(context->buffer+j, data+i, len - i);
+}
+
+
+void sha1_process_rar29(sha1_context *context, const unsigned char *data, size_t len)
+{
+  size_t i, j = (size_t)(context->count & 63);
+  context->count += len;
+
+  if ((j + len) > 63)
+  {
+    memcpy(context->buffer+j, data, (i = 64-j));
+    uint32 workspace[16];
+    SHA1Transform(context->state, workspace, context->buffer, true);
+    for ( ; i + 63 < len; i += 64)
+    {
+      SHA1Transform(context->state, workspace, data+i, false);
+      for (uint k = 0; k < 16; k++)
+        RawPut4(workspace[k],(void*)(data+i+k*4));
+    }
+    j = 0;
+  }
+  else
+    i = 0;
+  if (len > i)
+    memcpy(context->buffer+j, data+i, len - i);
+}
+
+
+/* Add padding and return the message digest. */
+void sha1_done( sha1_context* context, uint32 digest[5])
+{
+  uint32 workspace[16];
+  uint64 BitLength = context->count * 8;
+  uint BufPos = (uint)context->count & 0x3f;
+  context->buffer[BufPos++] = 0x80; // Padding the message with "1" bit.
+
+  if (BufPos!=56) // We need 56 bytes block followed by 8 byte length.
+  {
+    if (BufPos>56)
+    {
+      while (BufPos<64)
+        context->buffer[BufPos++] = 0;
+      BufPos=0;
+    }
+    if (BufPos==0)
+      SHA1Transform(context->state, workspace, context->buffer, true);
+    memset(context->buffer+BufPos,0,56-BufPos);
+  }
+
+  RawPutBE4((uint32)(BitLength>>32), context->buffer + 56);
+  RawPutBE4((uint32)(BitLength), context->buffer + 60);
+
+  SHA1Transform(context->state, workspace, context->buffer, true);
+
+  for (uint i = 0; i < 5; i++)
+    digest[i] = context->state[i];
+
+  /* Wipe variables */
+  sha1_init(context);
+}
+
+
diff --git a/third_party/unrar/src/sha1.hpp b/third_party/unrar/src/sha1.hpp
new file mode 100644
index 0000000..7c0b7fb
--- /dev/null
+++ b/third_party/unrar/src/sha1.hpp
@@ -0,0 +1,15 @@
+#ifndef _RAR_SHA1_
+#define _RAR_SHA1_
+
+typedef struct {
+    uint32 state[5];
+    uint64 count;
+    unsigned char buffer[64];
+} sha1_context;
+
+void sha1_init( sha1_context * c );
+void sha1_process(sha1_context * c, const byte *data, size_t len);
+void sha1_process_rar29(sha1_context *context, const unsigned char *data, size_t len);
+void sha1_done( sha1_context * c, uint32 digest[5] );
+
+#endif
diff --git a/third_party/unrar/src/sha256.cpp b/third_party/unrar/src/sha256.cpp
new file mode 100644
index 0000000..f90d2c0
--- /dev/null
+++ b/third_party/unrar/src/sha256.cpp
@@ -0,0 +1,148 @@
+#include "rar.hpp"
+#include "sha256.hpp"
+
+static const uint32 K[64] = 
+{
+  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+  0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+  0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+  0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+  0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+  0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+  0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+  0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+  0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+  0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+  0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+};
+
+// SHA-256 functions. We could optimize Ch and Maj a little,
+// but with no visible speed benefit.
+#define Ch(x, y, z)  ((x & y) ^ (~x & z))
+#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
+
+// Sigma functions.
+#define Sg0(x) (rotr32(x, 2) ^ rotr32(x,13) ^ rotr32(x, 22))
+#define Sg1(x) (rotr32(x, 6) ^ rotr32(x,11) ^ rotr32(x, 25))
+#define sg0(x) (rotr32(x, 7) ^ rotr32(x,18) ^ (x >> 3))
+#define sg1(x) (rotr32(x,17) ^ rotr32(x,19) ^ (x >> 10))
+
+void sha256_init(sha256_context *ctx)
+{
+  ctx->H[0] = 0x6a09e667; // Set the initial hash value.
+  ctx->H[1] = 0xbb67ae85;
+  ctx->H[2] = 0x3c6ef372;
+  ctx->H[3] = 0xa54ff53a;
+  ctx->H[4] = 0x510e527f;
+  ctx->H[5] = 0x9b05688c;
+  ctx->H[6] = 0x1f83d9ab;
+  ctx->H[7] = 0x5be0cd19;
+  ctx->Count    = 0;      // Processed data counter.
+}
+
+
+static void sha256_transform(sha256_context *ctx)
+{
+  uint32 W[64]; // Words of message schedule.
+  uint32 v[8];  // FIPS a, b, c, d, e, f, g, h working variables.
+
+  // Prepare message schedule.
+  for (uint I = 0; I < 16; I++)
+    W[I] = RawGetBE4(ctx->Buffer + I * 4);
+  for (uint I = 16; I < 64; I++)
+    W[I] = sg1(W[I-2]) + W[I-7] + sg0(W[I-15]) + W[I-16];
+
+  uint32 *H=ctx->H;
+  v[0]=H[0]; v[1]=H[1]; v[2]=H[2]; v[3]=H[3];
+  v[4]=H[4]; v[5]=H[5]; v[6]=H[6]; v[7]=H[7];
+
+  for (uint I = 0; I < 64; I++)
+  {
+    uint T1 = v[7] + Sg1(v[4]) + Ch(v[4], v[5], v[6]) + K[I] + W[I];
+
+    // It is possible to eliminate variable copying if we unroll loop
+    // and rename variables every time. But my test did not show any speed
+    // gain on i7 for such full or partial unrolling.
+    v[7] = v[6];
+    v[6] = v[5];
+    v[5] = v[4];
+    v[4] = v[3] + T1;
+
+    // It works a little faster when moved here from beginning of loop.
+    uint T2 = Sg0(v[0]) + Maj(v[0], v[1], v[2]);
+
+    v[3] = v[2];
+    v[2] = v[1];
+    v[1] = v[0];
+    v[0] = T1 + T2;
+  }
+
+  H[0]+=v[0]; H[1]+=v[1]; H[2]+=v[2]; H[3]+=v[3];
+  H[4]+=v[4]; H[5]+=v[5]; H[6]+=v[6]; H[7]+=v[7];
+}
+
+
+void sha256_process(sha256_context *ctx, const void *Data, size_t Size)
+{
+  const byte *Src=(const byte *)Data;
+  size_t BufPos = (uint)ctx->Count & 0x3f;
+  ctx->Count+=Size;
+  while (Size > 0)
+  {
+    size_t BufSpace=sizeof(ctx->Buffer)-BufPos;
+    size_t CopySize=Size>BufSpace ? BufSpace:Size;
+
+    memcpy(ctx->Buffer+BufPos,Src,CopySize);
+
+    Src+=CopySize;
+    BufPos+=CopySize;
+    Size-=CopySize;
+    if (BufPos == 64)
+    {
+      BufPos = 0;
+      sha256_transform(ctx);
+    }
+  }
+}
+
+
+void sha256_done(sha256_context *ctx, byte *Digest)
+{
+  uint64 BitLength = ctx->Count * 8;
+  uint BufPos = (uint)ctx->Count & 0x3f;
+  ctx->Buffer[BufPos++] = 0x80; // Padding the message with "1" bit.
+
+  if (BufPos!=56) // We need 56 bytes block followed by 8 byte length.
+  {
+    if (BufPos>56)
+    {
+      while (BufPos<64)
+        ctx->Buffer[BufPos++] = 0;
+      BufPos=0;
+    }
+    if (BufPos==0)
+      sha256_transform(ctx);
+    memset(ctx->Buffer+BufPos,0,56-BufPos);
+  }
+
+  RawPutBE4((uint32)(BitLength>>32), ctx->Buffer + 56);
+  RawPutBE4((uint32)(BitLength), ctx->Buffer + 60);
+
+  sha256_transform(ctx);
+
+  RawPutBE4(ctx->H[0], Digest +  0);
+  RawPutBE4(ctx->H[1], Digest +  4);
+  RawPutBE4(ctx->H[2], Digest +  8);
+  RawPutBE4(ctx->H[3], Digest + 12);
+  RawPutBE4(ctx->H[4], Digest + 16);
+  RawPutBE4(ctx->H[5], Digest + 20);
+  RawPutBE4(ctx->H[6], Digest + 24);
+  RawPutBE4(ctx->H[7], Digest + 28);
+
+  sha256_init(ctx);
+}
diff --git a/third_party/unrar/src/sha256.hpp b/third_party/unrar/src/sha256.hpp
new file mode 100644
index 0000000..b6837e76
--- /dev/null
+++ b/third_party/unrar/src/sha256.hpp
@@ -0,0 +1,17 @@
+#ifndef _RAR_SHA256_
+#define _RAR_SHA256_
+
+#define SHA256_DIGEST_SIZE 32
+
+typedef struct
+{
+  uint32 H[8];
+  uint64 Count;
+  byte Buffer[64];
+} sha256_context;
+
+void sha256_init(sha256_context *ctx);
+void sha256_process(sha256_context *ctx, const void *Data, size_t Size);
+void sha256_done(sha256_context *ctx, byte *Digest);
+
+#endif
diff --git a/third_party/unrar/src/smallfn.cpp b/third_party/unrar/src/smallfn.cpp
new file mode 100644
index 0000000..81259d02
--- /dev/null
+++ b/third_party/unrar/src/smallfn.cpp
@@ -0,0 +1,19 @@
+#include "rar.hpp"
+
+int ToPercent(int64 N1,int64 N2)
+{
+  if (N2<N1)
+    return 100;
+  return ToPercentUnlim(N1,N2);
+}
+
+
+// Allows the percent larger than 100.
+int ToPercentUnlim(int64 N1,int64 N2)
+{
+  if (N2==0)
+    return 0;
+  return (int)(N1*100/N2);
+}
+
+
diff --git a/third_party/unrar/src/smallfn.hpp b/third_party/unrar/src/smallfn.hpp
new file mode 100644
index 0000000..f53daa8
--- /dev/null
+++ b/third_party/unrar/src/smallfn.hpp
@@ -0,0 +1,8 @@
+#ifndef _RAR_SMALLFN_
+#define _RAR_SMALLFN_
+
+int ToPercent(int64 N1,int64 N2);
+int ToPercentUnlim(int64 N1,int64 N2);
+void RARInitData();
+
+#endif
diff --git a/third_party/unrar/src/strfn.cpp b/third_party/unrar/src/strfn.cpp
new file mode 100644
index 0000000..283c67b4
--- /dev/null
+++ b/third_party/unrar/src/strfn.cpp
@@ -0,0 +1,464 @@
+#include "rar.hpp"
+
+const char *NullToEmpty(const char *Str)
+{
+  return Str==NULL ? "":Str;
+}
+
+
+const wchar *NullToEmpty(const wchar *Str)
+{
+  return Str==NULL ? L"":Str;
+}
+
+
+void IntToExt(const char *Src,char *Dest,size_t DestSize)
+{
+#ifdef _WIN_ALL
+  // OemToCharBuff does not stop at 0, so let's check source length.
+  size_t SrcLength=strlen(Src)+1;
+  if (DestSize>SrcLength)
+    DestSize=SrcLength;
+  OemToCharBuffA(Src,Dest,(DWORD)DestSize);
+  Dest[DestSize-1]=0;
+#else
+  if (Dest!=Src)
+    strncpyz(Dest,Src,DestSize);
+#endif
+}
+
+
+// Convert archived names to Unicode. Allow user to select a code page in GUI.
+void ArcCharToWide(const char *Src,wchar *Dest,size_t DestSize,ACTW_ENCODING Encoding)
+{
+#if defined(_WIN_ALL) // Console Windows RAR.
+  if (Encoding==ACTW_UTF8)
+    UtfToWide(Src,Dest,DestSize);
+  else
+  {
+    char NameA[NM];
+    if (Encoding==ACTW_OEM)
+    {
+      IntToExt(Src,NameA,ASIZE(NameA));
+      Src=NameA;
+    }
+    CharToWide(Src,Dest,DestSize);
+  }
+#else // RAR for Unix.
+  if (Encoding==ACTW_UTF8)
+    UtfToWide(Src,Dest,DestSize);
+  else
+    CharToWide(Src,Dest,DestSize);
+#endif
+  // Ensure that we return a zero terminate string for security reason.
+  // While [Jni]CharToWide might already do it, be protected in case of future
+  // changes in these functions.
+  if (DestSize>0)
+    Dest[DestSize-1]=0;
+}
+
+
+int stricomp(const char *s1,const char *s2)
+{
+#ifdef _WIN_ALL
+  return CompareStringA(LOCALE_USER_DEFAULT,NORM_IGNORECASE|SORT_STRINGSORT,s1,-1,s2,-1)-2;
+#else
+  while (toupper(*s1)==toupper(*s2))
+  {
+    if (*s1==0)
+      return 0;
+    s1++;
+    s2++;
+  }
+  return s1 < s2 ? -1 : 1;
+#endif
+}
+
+
+int strnicomp(const char *s1,const char *s2,size_t n)
+{
+#ifdef _WIN_ALL
+  // If we specify 'n' exceeding the actual string length, CompareString goes
+  // beyond the trailing zero and compares garbage. So we need to limit 'n'
+  // to real string length.
+  // It is important to use strnlen (or memchr(...,0)) instead of strlen,
+  // because data can be not zero terminated.
+  size_t l1=Min(strnlen(s1,n),n);
+  size_t l2=Min(strnlen(s2,n),n);
+  return CompareStringA(LOCALE_USER_DEFAULT,NORM_IGNORECASE|SORT_STRINGSORT,s1,(int)l1,s2,(int)l2)-2;
+#else
+  if (n==0)
+    return 0;
+  while (toupper(*s1)==toupper(*s2))
+  {
+    if (*s1==0 || --n==0)
+      return 0;
+    s1++;
+    s2++;
+  }
+  return s1 < s2 ? -1 : 1;
+#endif
+}
+
+
+wchar* RemoveEOL(wchar *Str)
+{
+  for (int I=(int)wcslen(Str)-1;I>=0 && (Str[I]=='\r' || Str[I]=='\n' || Str[I]==' ' || Str[I]=='\t');I--)
+    Str[I]=0;
+  return Str;
+}
+
+
+wchar* RemoveLF(wchar *Str)
+{
+  for (int I=(int)wcslen(Str)-1;I>=0 && (Str[I]=='\r' || Str[I]=='\n');I--)
+    Str[I]=0;
+  return Str;
+}
+
+
+unsigned char loctolower(unsigned char ch)
+{
+#if defined(_WIN_ALL)
+  // Convert to LPARAM first to avoid a warning in 64 bit mode.
+  return (int)(LPARAM)CharLowerA((LPSTR)ch);
+#else
+  return tolower(ch);
+#endif
+}
+
+
+unsigned char loctoupper(unsigned char ch)
+{
+#if defined(_WIN_ALL)
+  // Convert to LPARAM first to avoid a warning in 64 bit mode.
+  return (int)(LPARAM)CharUpperA((LPSTR)ch);
+#else
+  return toupper(ch);
+#endif
+}
+
+
+// toupper with English only results if English input is provided.
+// It avoids Turkish (small i) -> (big I with dot) conversion problem.
+// We do not define 'ch' as 'int' to avoid necessity to cast all
+// signed chars passed to this function to unsigned char.
+unsigned char etoupper(unsigned char ch)
+{
+  if (ch=='i')
+    return 'I';
+  return toupper(ch);
+}
+
+
+// Unicode version of etoupper.
+wchar etoupperw(wchar ch)
+{
+  if (ch=='i')
+    return 'I';
+  return toupperw(ch);
+}
+
+
+// We do not want to cast every signed char to unsigned when passing to
+// isdigit, so we implement the replacement. Shall work for Unicode too.
+// If chars are signed, conversion from char to int could generate negative
+// values, resulting in undefined behavior in standard isdigit.
+bool IsDigit(int ch)
+{
+  return ch>='0' && ch<='9';
+}
+
+
+// We do not want to cast every signed char to unsigned when passing to
+// isspace, so we implement the replacement. Shall work for Unicode too.
+// If chars are signed, conversion from char to int could generate negative
+// values, resulting in undefined behavior in standard isspace.
+bool IsSpace(int ch)
+{
+  return ch==' ' || ch=='\t';
+}
+
+
+// We do not want to cast every signed char to unsigned when passing to
+// isalpha, so we implement the replacement. Shall work for Unicode too.
+// If chars are signed, conversion from char to int could generate negative
+// values, resulting in undefined behavior in standard function.
+bool IsAlpha(int ch)
+{
+  return ch>='A' && ch<='Z' || ch>='a' && ch<='z';
+}
+
+
+
+
+void BinToHex(const byte *Bin,size_t BinSize,char *HexA,wchar *HexW,size_t HexSize)
+{
+  uint A=0,W=0; // ASCII and Unicode hex output positions.
+  for (uint I=0;I<BinSize;I++)
+  {
+    uint High=Bin[I] >> 4;
+    uint Low=Bin[I] & 0xf;
+    uint HighHex=High>9 ? 'a'+High-10:'0'+High;
+    uint LowHex=Low>9 ? 'a'+Low-10:'0'+Low;
+    if (HexA!=NULL && A<HexSize-2) // Need space for 2 chars and final zero.
+    {
+      HexA[A++]=(char)HighHex;
+      HexA[A++]=(char)LowHex;
+    }
+    if (HexW!=NULL && W<HexSize-2) // Need space for 2 chars and final zero.
+    {
+      HexW[W++]=HighHex;
+      HexW[W++]=LowHex;
+    }
+  }
+  if (HexA!=NULL && HexSize>0)
+    HexA[A]=0;
+  if (HexW!=NULL && HexSize>0)
+    HexW[W]=0;
+}
+
+
+#ifndef SFX_MODULE
+uint GetDigits(uint Number)
+{
+  uint Digits=1;
+  while (Number>=10)
+  {
+    Number/=10;
+    Digits++;
+  }
+  return Digits;
+}
+#endif
+
+
+bool LowAscii(const char *Str)
+{
+  for (int I=0;Str[I]!=0;I++)
+    if ((byte)Str[I]<32 || (byte)Str[I]>127)
+      return false;
+  return true;
+}
+
+
+bool LowAscii(const wchar *Str)
+{
+  for (int I=0;Str[I]!=0;I++)
+  {
+    // We convert wchar_t to uint just in case if some compiler
+    // uses signed wchar_t.
+    if ((uint)Str[I]<32 || (uint)Str[I]>127)
+      return false;
+  }
+  return true;
+}
+
+
+int wcsicompc(const wchar *s1,const wchar *s2) // For path comparison.
+{
+#if defined(_UNIX)
+  return wcscmp(s1,s2);
+#else
+  return wcsicomp(s1,s2);
+#endif
+}
+
+
+int wcsnicompc(const wchar *s1,const wchar *s2,size_t n)
+{
+#if defined(_UNIX)
+  return wcsncmp(s1,s2,n);
+#else
+  return wcsnicomp(s1,s2,n);
+#endif
+}
+
+
+// Safe strncpy: copies maxlen-1 max and always returns zero terminated dest.
+char* strncpyz(char *dest, const char *src, size_t maxlen)
+{
+  if (maxlen>0)
+  {
+    strncpy(dest,src,maxlen-1);
+    dest[maxlen-1]=0;
+  }
+  return dest;
+}
+
+
+// Safe wcsncpy: copies maxlen-1 max and always returns zero terminated dest.
+wchar* wcsncpyz(wchar *dest, const wchar *src, size_t maxlen)
+{
+  if (maxlen>0)
+  {
+    wcsncpy(dest,src,maxlen-1);
+    dest[maxlen-1]=0;
+  }
+  return dest;
+}
+
+
+// Safe strncat: resulting dest length cannot exceed maxlen and dest 
+// is always zero terminated. Note that 'maxlen' parameter defines the entire
+// dest buffer size and is not compatible with standard strncat.
+char* strncatz(char* dest, const char* src, size_t maxlen)
+{
+  size_t Length = strlen(dest);
+  int avail=int(maxlen - Length - 1);
+  if (avail > 0)
+    strncat(dest, src, avail);
+  return dest;
+}
+
+
+// Safe wcsncat: resulting dest length cannot exceed maxlen and dest 
+// is always zero terminated. Note that 'maxlen' parameter defines the entire
+// dest buffer size and is not compatible with standard wcsncat.
+wchar* wcsncatz(wchar* dest, const wchar* src, size_t maxlen)
+{
+  size_t Length = wcslen(dest);
+  int avail=int(maxlen - Length - 1);
+  if (avail > 0)
+    wcsncat(dest, src, avail);
+  return dest;
+}
+
+
+void itoa(int64 n,char *Str,size_t MaxSize)
+{
+  char NumStr[50];
+  size_t Pos=0;
+
+  int Neg=n < 0 ? 1 : 0;
+  if (Neg)
+    n=-n;
+
+  do
+  {
+    if (Pos+1>=MaxSize-Neg)
+      break;
+    NumStr[Pos++]=char(n%10)+'0';
+    n=n/10;
+  } while (n!=0);
+
+  if (Neg)
+    NumStr[Pos++]='-';
+
+  for (size_t I=0;I<Pos;I++)
+    Str[I]=NumStr[Pos-I-1];
+  Str[Pos]=0;
+}
+
+
+void itoa(int64 n,wchar *Str,size_t MaxSize)
+{
+  wchar NumStr[50];
+  size_t Pos=0;
+
+  int Neg=n < 0 ? 1 : 0;
+  if (Neg)
+    n=-n;
+
+  do
+  {
+    if (Pos+1>=MaxSize-Neg)
+      break;
+    NumStr[Pos++]=wchar(n%10)+'0';
+    n=n/10;
+  } while (n!=0);
+
+  if (Neg)
+    NumStr[Pos++]='-';
+
+  for (size_t I=0;I<Pos;I++)
+    Str[I]=NumStr[Pos-I-1];
+  Str[Pos]=0;
+}
+
+
+const wchar* GetWide(const char *Src)
+{
+  const size_t MaxLength=NM;
+  static wchar StrTable[4][MaxLength];
+  static uint StrNum=0;
+  if (++StrNum >= ASIZE(StrTable))
+    StrNum=0;
+  wchar *Str=StrTable[StrNum];
+  CharToWide(Src,Str,MaxLength);
+  Str[MaxLength-1]=0;
+  return Str;
+}
+
+
+// Parse string containing parameters separated with spaces.
+// Support quote marks. Param can be NULL to return the pointer to next
+// parameter, which can be used to estimate the buffer size for Param.
+const wchar* GetCmdParam(const wchar *CmdLine,wchar *Param,size_t MaxSize)
+{
+  while (IsSpace(*CmdLine))
+    CmdLine++;
+  if (*CmdLine==0)
+    return NULL;
+
+  size_t ParamSize=0;
+  bool Quote=false;
+  while (*CmdLine!=0 && (Quote || !IsSpace(*CmdLine)))
+  {
+    if (*CmdLine=='\"')
+    {
+      if (CmdLine[1]=='\"')
+      {
+        // Insert the quote character instead of two adjoining quote characters.
+        if (Param!=NULL && ParamSize<MaxSize-1)
+          Param[ParamSize++]='\"';
+        CmdLine++;
+      }
+      else
+        Quote=!Quote;
+    }
+    else
+      if (Param!=NULL && ParamSize<MaxSize-1)
+        Param[ParamSize++]=*CmdLine;
+    CmdLine++;
+  }
+  if (Param!=NULL)
+    Param[ParamSize]=0;
+  return CmdLine;
+}
+
+
+#ifndef SILENT
+// For compatibility with existing translations we use %s to print Unicode
+// strings in format strings and convert them to %ls here. %s could work
+// without such conversion in Windows, but not in Unix wprintf.
+void PrintfPrepareFmt(const wchar *Org,wchar *Cvt,size_t MaxSize)
+{
+  uint Src=0,Dest=0;
+  while (Org[Src]!=0 && Dest<MaxSize-1)
+  {
+    if (Org[Src]=='%' && (Src==0 || Org[Src-1]!='%'))
+    {
+      uint SPos=Src+1;
+      // Skipping a possible width specifier like %-50s.
+      while (IsDigit(Org[SPos]) || Org[SPos]=='-')
+        SPos++;
+      if (Org[SPos]=='s' && Dest<MaxSize-(SPos-Src+1))
+      {
+        while (Src<SPos)
+          Cvt[Dest++]=Org[Src++];
+        Cvt[Dest++]='l';
+      }
+    }
+#ifdef _WIN_ALL
+    // Convert \n to \r\n in Windows. Important when writing to log,
+    // so other tools like Notebook can view resulting log properly.
+    if (Org[Src]=='\n' && (Src==0 || Org[Src-1]!='\r'))
+      Cvt[Dest++]='\r';
+#endif
+
+    Cvt[Dest++]=Org[Src++];
+  }
+  Cvt[Dest]=0;
+}
+#endif
diff --git a/third_party/unrar/src/strfn.hpp b/third_party/unrar/src/strfn.hpp
new file mode 100644
index 0000000..7faac42
--- /dev/null
+++ b/third_party/unrar/src/strfn.hpp
@@ -0,0 +1,50 @@
+#ifndef _RAR_STRFN_
+#define _RAR_STRFN_
+
+const char* NullToEmpty(const char *Str);
+const wchar* NullToEmpty(const wchar *Str);
+void IntToExt(const char *Src,char *Dest,size_t DestSize);
+
+enum ACTW_ENCODING { ACTW_DEFAULT, ACTW_OEM, ACTW_UTF8};
+void ArcCharToWide(const char *Src,wchar *Dest,size_t DestSize,ACTW_ENCODING Encoding);
+
+int stricomp(const char *s1,const char *s2);
+int strnicomp(const char *s1,const char *s2,size_t n);
+wchar* RemoveEOL(wchar *Str);
+wchar* RemoveLF(wchar *Str);
+unsigned char loctolower(unsigned char ch);
+unsigned char loctoupper(unsigned char ch);
+
+char* strncpyz(char *dest, const char *src, size_t maxlen);
+wchar* wcsncpyz(wchar *dest, const wchar *src, size_t maxlen);
+char* strncatz(char* dest, const char* src, size_t maxlen);
+wchar* wcsncatz(wchar* dest, const wchar* src, size_t maxlen);
+
+unsigned char etoupper(unsigned char ch);
+wchar etoupperw(wchar ch);
+
+bool IsDigit(int ch);
+bool IsSpace(int ch);
+bool IsAlpha(int ch);
+
+void BinToHex(const byte *Bin,size_t BinSize,char *Hex,wchar *HexW,size_t HexSize);
+
+#ifndef SFX_MODULE
+uint GetDigits(uint Number);
+#endif
+
+bool LowAscii(const char *Str);
+bool LowAscii(const wchar *Str);
+
+int wcsicompc(const wchar *s1,const wchar *s2);
+int wcsnicompc(const wchar *s1,const wchar *s2,size_t n);
+
+void itoa(int64 n,char *Str,size_t MaxSize);
+void itoa(int64 n,wchar *Str,size_t MaxSize);
+const wchar* GetWide(const char *Src);
+const wchar* GetCmdParam(const wchar *CmdLine,wchar *Param,size_t MaxSize);
+#ifndef SILENT
+void PrintfPrepareFmt(const wchar *Org,wchar *Cvt,size_t MaxSize);
+#endif
+
+#endif
diff --git a/third_party/unrar/src/strlist.cpp b/third_party/unrar/src/strlist.cpp
new file mode 100644
index 0000000..50d69c7
--- /dev/null
+++ b/third_party/unrar/src/strlist.cpp
@@ -0,0 +1,151 @@
+#include "rar.hpp"
+
+StringList::StringList()
+{
+  Reset();
+}
+
+
+void StringList::Reset()
+{
+  Rewind();
+  StringData.Reset();
+  StringsCount=0;
+  SavePosNumber=0;
+}
+
+
+void StringList::AddStringA(const char *Str)
+{
+  Array<wchar> StrW(strlen(Str));
+  CharToWide(Str,&StrW[0],StrW.Size());
+  AddString(&StrW[0]);
+}
+
+
+void StringList::AddString(const wchar *Str)
+{
+  if (Str==NULL)
+    Str=L"";
+
+  size_t PrevSize=StringData.Size();
+  StringData.Add(wcslen(Str)+1);
+  wcscpy(&StringData[PrevSize],Str);
+
+  StringsCount++;
+}
+
+
+bool StringList::GetStringA(char *Str,size_t MaxLength)
+{
+  Array<wchar> StrW(MaxLength);
+  if (!GetString(&StrW[0],StrW.Size()))
+    return false;
+  WideToChar(&StrW[0],Str,MaxLength);
+  return true;
+}
+
+
+bool StringList::GetString(wchar *Str,size_t MaxLength)
+{
+  wchar *StrPtr;
+  if (!GetString(&StrPtr))
+    return false;
+  wcsncpyz(Str,StrPtr,MaxLength);
+  return true;
+}
+
+
+#ifndef SFX_MODULE
+bool StringList::GetString(wchar *Str,size_t MaxLength,int StringNum)
+{
+  SavePosition();
+  Rewind();
+  bool RetCode=true;
+  while (StringNum-- >=0)
+    if (!GetString(Str,MaxLength))
+    {
+      RetCode=false;
+      break;
+    }
+  RestorePosition();
+  return RetCode;
+}
+#endif
+
+
+wchar* StringList::GetString()
+{
+  wchar *Str;
+  GetString(&Str);
+  return Str;
+}
+
+
+bool StringList::GetString(wchar **Str)
+{
+  if (CurPos>=StringData.Size()) // No more strings left unprocessed.
+  {
+    if (Str!=NULL)
+      *Str=NULL;
+    return false;
+  }
+
+  wchar *CurStr=&StringData[CurPos];
+  CurPos+=wcslen(CurStr)+1;
+  if (Str!=NULL)
+    *Str=CurStr;
+
+  return true;
+}
+
+
+void StringList::Rewind()
+{
+  CurPos=0;
+}
+
+
+#ifndef SFX_MODULE
+bool StringList::Search(const wchar *Str,bool CaseSensitive)
+{
+  SavePosition();
+  Rewind();
+  bool Found=false;
+  wchar *CurStr;
+  while (GetString(&CurStr))
+  {
+    if (Str!=NULL && CurStr!=NULL)
+      if ((CaseSensitive ? wcscmp(Str,CurStr):wcsicomp(Str,CurStr))!=0)
+        continue;
+    Found=true;
+    break;
+  }
+  RestorePosition();
+  return Found;
+}
+#endif
+
+
+#ifndef SFX_MODULE
+void StringList::SavePosition()
+{
+  if (SavePosNumber<ASIZE(SaveCurPos))
+  {
+    SaveCurPos[SavePosNumber]=CurPos;
+    SavePosNumber++;
+  }
+}
+#endif
+
+
+#ifndef SFX_MODULE
+void StringList::RestorePosition()
+{
+  if (SavePosNumber>0)
+  {
+    SavePosNumber--;
+    CurPos=SaveCurPos[SavePosNumber];
+  }
+}
+#endif
diff --git a/third_party/unrar/src/strlist.hpp b/third_party/unrar/src/strlist.hpp
new file mode 100644
index 0000000..cc6bc5d4
--- /dev/null
+++ b/third_party/unrar/src/strlist.hpp
@@ -0,0 +1,31 @@
+#ifndef _RAR_STRLIST_
+#define _RAR_STRLIST_
+
+class StringList
+{
+  private:
+    Array<wchar> StringData;
+    size_t CurPos;
+
+    uint StringsCount;
+
+    size_t SaveCurPos[16],SavePosNumber;
+  public:
+    StringList();
+    void Reset();
+    void AddStringA(const char *Str);
+    void AddString(const wchar *Str);
+    bool GetStringA(char *Str,size_t MaxLength);
+    bool GetString(wchar *Str,size_t MaxLength);
+    bool GetString(wchar *Str,size_t MaxLength,int StringNum);
+    wchar* GetString();
+    bool GetString(wchar **Str);
+    void Rewind();
+    uint ItemsCount() {return StringsCount;};
+    size_t GetCharCount() {return StringData.Size();}
+    bool Search(const wchar *Str,bool CaseSensitive);
+    void SavePosition();
+    void RestorePosition();
+};
+
+#endif
diff --git a/third_party/unrar/src/suballoc.cpp b/third_party/unrar/src/suballoc.cpp
new file mode 100644
index 0000000..f1704e2
--- /dev/null
+++ b/third_party/unrar/src/suballoc.cpp
@@ -0,0 +1,295 @@
+/****************************************************************************
+ *  This file is part of PPMd project                                       *
+ *  Written and distributed to public domain by Dmitry Shkarin 1997,        *
+ *  1999-2000                                                               *
+ *  Contents: memory allocation routines                                    *
+ ****************************************************************************/
+
+static const uint UNIT_SIZE=Max(sizeof(RARPPM_CONTEXT),sizeof(RARPPM_MEM_BLK));
+static const uint FIXED_UNIT_SIZE=12;
+
+SubAllocator::SubAllocator()
+{
+  Clean();
+}
+
+
+void SubAllocator::Clean()
+{
+  SubAllocatorSize=0;
+}
+
+
+inline void SubAllocator::InsertNode(void* p,int indx) 
+{
+  ((RAR_NODE*) p)->next=FreeList[indx].next;
+  FreeList[indx].next=(RAR_NODE*) p;
+}
+
+
+inline void* SubAllocator::RemoveNode(int indx) 
+{
+  RAR_NODE* RetVal=FreeList[indx].next;
+  FreeList[indx].next=RetVal->next;
+  return RetVal;
+}
+
+
+inline uint SubAllocator::U2B(int NU) 
+{ 
+  // We calculate the size of units in bytes based on real UNIT_SIZE.
+  // In original implementation it was 8*NU+4*NU.
+  return UNIT_SIZE*NU;
+}
+
+
+
+// Calculate RARPPM_MEM_BLK+Items address. Real RARPPM_MEM_BLK size must be
+// equal to UNIT_SIZE, so we cannot just add Items to RARPPM_MEM_BLK address.
+inline RARPPM_MEM_BLK* SubAllocator::MBPtr(RARPPM_MEM_BLK *BasePtr,int Items)
+{
+  return((RARPPM_MEM_BLK*)( ((byte *)(BasePtr))+U2B(Items) ));
+}
+
+
+inline void SubAllocator::SplitBlock(void* pv,int OldIndx,int NewIndx)
+{
+  int i, UDiff=Indx2Units[OldIndx]-Indx2Units[NewIndx];
+  byte* p=((byte*) pv)+U2B(Indx2Units[NewIndx]);
+  if (Indx2Units[i=Units2Indx[UDiff-1]] != UDiff) 
+  {
+    InsertNode(p,--i);
+    p += U2B(i=Indx2Units[i]);
+    UDiff -= i;
+  }
+  InsertNode(p,Units2Indx[UDiff-1]);
+}
+
+
+void SubAllocator::StopSubAllocator()
+{
+  if ( SubAllocatorSize ) 
+  {
+    SubAllocatorSize=0;
+    free(HeapStart);
+  }
+}
+
+
+bool SubAllocator::StartSubAllocator(int SASize)
+{
+  uint t=SASize << 20;
+  if (SubAllocatorSize == t)
+    return TRUE;
+  StopSubAllocator();
+
+  // Original algorithm expects FIXED_UNIT_SIZE, but actual structure size
+  // can be larger. So let's recalculate the allocated size and add two more
+  // units: one as reserve for HeapEnd overflow checks and another
+  // to provide the space to correctly align UnitsStart.
+  uint AllocSize=t/FIXED_UNIT_SIZE*UNIT_SIZE+2*UNIT_SIZE;
+  if ((HeapStart=(byte *)malloc(AllocSize)) == NULL)
+  {
+    ErrHandler.MemoryError();
+    return FALSE;
+  }
+
+  // HeapEnd did not present in original algorithm. We added it to control
+  // invalid memory access attempts when processing corrupt archived data.
+  HeapEnd=HeapStart+AllocSize-UNIT_SIZE;
+
+  SubAllocatorSize=t;
+  return TRUE;
+}
+
+
+void SubAllocator::InitSubAllocator()
+{
+  int i, k;
+  memset(FreeList,0,sizeof(FreeList));
+  pText=HeapStart;
+
+  // Original algorithm operates with 12 byte FIXED_UNIT_SIZE, but actual
+  // size of RARPPM_MEM_BLK and RARPPM_CONTEXT structures can exceed this value
+  // because of alignment and larger pointer fields size.
+  // So we define UNIT_SIZE for this larger size and adjust memory
+  // pointers accordingly.
+
+  // Size2 is (HiUnit-LoUnit) memory area size to allocate as originally
+  // supposed by compression algorithm. It is 7/8 of total allocated size.
+  uint Size2=FIXED_UNIT_SIZE*(SubAllocatorSize/8/FIXED_UNIT_SIZE*7);
+
+  // RealSize2 is the real adjusted size of (HiUnit-LoUnit) memory taking
+  // into account that our UNIT_SIZE can be larger than FIXED_UNIT_SIZE.
+  uint RealSize2=Size2/FIXED_UNIT_SIZE*UNIT_SIZE;
+
+  // Size1 is the size of memory area from HeapStart to FakeUnitsStart
+  // as originally supposed by compression algorithm. This area can contain
+  // different data types, both single symbols and structures.
+  uint Size1=SubAllocatorSize-Size2;
+
+  // Real size of this area. We correct it according to UNIT_SIZE vs
+  // FIXED_UNIT_SIZE difference. Also we add one more UNIT_SIZE
+  // to compensate a possible reminder from Size1/FIXED_UNIT_SIZE,
+  // which would be lost otherwise. We add UNIT_SIZE instead of 
+  // this Size1%FIXED_UNIT_SIZE reminder, because it allows to align
+  // UnitsStart easily and adding more than reminder is ok for algorithm.
+  uint RealSize1=Size1/FIXED_UNIT_SIZE*UNIT_SIZE+UNIT_SIZE;
+
+  // RealSize1 must be divided by UNIT_SIZE without a reminder, so UnitsStart
+  // is aligned to UNIT_SIZE. It is important for those architectures,
+  // where a proper memory alignment is mandatory. Since we produce RealSize1
+  // multiplying by UNIT_SIZE, this condition is always true. So LoUnit,
+  // UnitsStart, HeapStart are properly aligned,
+  LoUnit=UnitsStart=HeapStart+RealSize1;
+
+  // When we reach FakeUnitsStart, we restart the model. It is where
+  // the original algorithm expected to see UnitsStart. Real UnitsStart
+  // can have a larger value.
+  FakeUnitsStart=HeapStart+Size1;
+
+  HiUnit=LoUnit+RealSize2;
+  for (i=0,k=1;i < N1     ;i++,k += 1)
+    Indx2Units[i]=k;
+  for (k++;i < N1+N2      ;i++,k += 2)
+    Indx2Units[i]=k;
+  for (k++;i < N1+N2+N3   ;i++,k += 3)
+    Indx2Units[i]=k;
+  for (k++;i < N1+N2+N3+N4;i++,k += 4)
+    Indx2Units[i]=k;
+  for (GlueCount=k=i=0;k < 128;k++)
+  {
+    i += (Indx2Units[i] < k+1);
+    Units2Indx[k]=i;
+  }
+}
+
+
+inline void SubAllocator::GlueFreeBlocks()
+{
+  RARPPM_MEM_BLK s0, * p, * p1;
+  int i, k, sz;
+  if (LoUnit != HiUnit)
+    *LoUnit=0;
+  for (i=0, s0.next=s0.prev=&s0;i < N_INDEXES;i++)
+    while ( FreeList[i].next )
+    {
+      p=(RARPPM_MEM_BLK*)RemoveNode(i);
+      p->insertAt(&s0);
+      p->Stamp=0xFFFF;
+      p->NU=Indx2Units[i];
+    }
+  for (p=s0.next;p != &s0;p=p->next)
+    while ((p1=MBPtr(p,p->NU))->Stamp == 0xFFFF && int(p->NU)+p1->NU < 0x10000)
+    {
+      p1->remove();
+      p->NU += p1->NU;
+    }
+  while ((p=s0.next) != &s0)
+  {
+    for (p->remove(), sz=p->NU;sz > 128;sz -= 128, p=MBPtr(p,128))
+      InsertNode(p,N_INDEXES-1);
+    if (Indx2Units[i=Units2Indx[sz-1]] != sz)
+    {
+      k=sz-Indx2Units[--i];
+      InsertNode(MBPtr(p,sz-k),k-1);
+    }
+    InsertNode(p,i);
+  }
+}
+
+void* SubAllocator::AllocUnitsRare(int indx)
+{
+  if ( !GlueCount )
+  {
+    GlueCount = 255;
+    GlueFreeBlocks();
+    if ( FreeList[indx].next )
+      return RemoveNode(indx);
+  }
+  int i=indx;
+  do
+  {
+    if (++i == N_INDEXES)
+    {
+      GlueCount--;
+      i=U2B(Indx2Units[indx]);
+      int j=FIXED_UNIT_SIZE*Indx2Units[indx];
+      if (FakeUnitsStart - pText > j)
+      {
+        FakeUnitsStart -= j;
+        UnitsStart -= i;
+        return UnitsStart;
+      }
+      return NULL;
+    }
+  } while ( !FreeList[i].next );
+  void* RetVal=RemoveNode(i);
+  SplitBlock(RetVal,i,indx);
+  return RetVal;
+}
+
+
+inline void* SubAllocator::AllocUnits(int NU)
+{
+  int indx=Units2Indx[NU-1];
+  if ( FreeList[indx].next )
+    return RemoveNode(indx);
+  void* RetVal=LoUnit;
+  LoUnit += U2B(Indx2Units[indx]);
+  if (LoUnit <= HiUnit)
+    return RetVal;
+  LoUnit -= U2B(Indx2Units[indx]);
+  return AllocUnitsRare(indx);
+}
+
+
+void* SubAllocator::AllocContext()
+{
+  if (HiUnit != LoUnit)
+    return (HiUnit -= UNIT_SIZE);
+  if ( FreeList->next )
+    return RemoveNode(0);
+  return AllocUnitsRare(0);
+}
+
+
+void* SubAllocator::ExpandUnits(void* OldPtr,int OldNU)
+{
+  int i0=Units2Indx[OldNU-1], i1=Units2Indx[OldNU-1+1];
+  if (i0 == i1)
+    return OldPtr;
+  void* ptr=AllocUnits(OldNU+1);
+  if ( ptr ) 
+  {
+    memcpy(ptr,OldPtr,U2B(OldNU));
+    InsertNode(OldPtr,i0);
+  }
+  return ptr;
+}
+
+
+void* SubAllocator::ShrinkUnits(void* OldPtr,int OldNU,int NewNU)
+{
+  int i0=Units2Indx[OldNU-1], i1=Units2Indx[NewNU-1];
+  if (i0 == i1)
+    return OldPtr;
+  if ( FreeList[i1].next )
+  {
+    void* ptr=RemoveNode(i1);
+    memcpy(ptr,OldPtr,U2B(NewNU));
+    InsertNode(OldPtr,i0);
+    return ptr;
+  } 
+  else 
+  {
+    SplitBlock(OldPtr,i0,i1);
+    return OldPtr;
+  }
+}
+
+
+void SubAllocator::FreeUnits(void* ptr,int OldNU)
+{
+  InsertNode(ptr,Units2Indx[OldNU-1]);
+}
diff --git a/third_party/unrar/src/suballoc.hpp b/third_party/unrar/src/suballoc.hpp
new file mode 100644
index 0000000..5989e82e
--- /dev/null
+++ b/third_party/unrar/src/suballoc.hpp
@@ -0,0 +1,86 @@
+/****************************************************************************
+ *  This file is part of PPMd project                                       *
+ *  Written and distributed to public domain by Dmitry Shkarin 1997,        *
+ *  1999-2000                                                               *
+ *  Contents: interface to memory allocation routines                       *
+ ****************************************************************************/
+#if !defined(_SUBALLOC_H_)
+#define _SUBALLOC_H_
+
+#if defined(__GNUC__) && defined(ALLOW_MISALIGNED)
+#define RARPPM_PACK_ATTR __attribute__ ((packed))
+#else
+#define RARPPM_PACK_ATTR
+#endif /* defined(__GNUC__) */
+
+#ifdef ALLOW_MISALIGNED
+#pragma pack(1)
+#endif
+
+struct RARPPM_MEM_BLK 
+{
+  ushort Stamp, NU;
+  RARPPM_MEM_BLK* next, * prev;
+  void insertAt(RARPPM_MEM_BLK* p) 
+  {
+    next=(prev=p)->next;
+    p->next=next->prev=this;
+  }
+  void remove() 
+  {
+    prev->next=next;
+    next->prev=prev;
+  }
+} RARPPM_PACK_ATTR;
+
+#ifdef ALLOW_MISALIGNED
+#ifdef _AIX
+#pragma pack(pop)
+#else
+#pragma pack()
+#endif
+#endif
+
+
+class SubAllocator
+{
+  private:
+    static const int N1=4, N2=4, N3=4, N4=(128+3-1*N1-2*N2-3*N3)/4;
+    static const int N_INDEXES=N1+N2+N3+N4;
+
+    struct RAR_NODE
+    {
+      RAR_NODE* next;
+    };
+
+    inline void InsertNode(void* p,int indx);
+    inline void* RemoveNode(int indx);
+    inline uint U2B(int NU);
+    inline void SplitBlock(void* pv,int OldIndx,int NewIndx);
+    inline void GlueFreeBlocks();
+    void* AllocUnitsRare(int indx);
+    inline RARPPM_MEM_BLK* MBPtr(RARPPM_MEM_BLK *BasePtr,int Items);
+
+    long SubAllocatorSize;
+    byte Indx2Units[N_INDEXES], Units2Indx[128], GlueCount;
+    byte *HeapStart,*LoUnit, *HiUnit;
+    struct RAR_NODE FreeList[N_INDEXES];
+  public:
+    SubAllocator();
+    ~SubAllocator() {StopSubAllocator();}
+    void Clean();
+    bool StartSubAllocator(int SASize);
+    void StopSubAllocator();
+    void  InitSubAllocator();
+    inline void* AllocContext();
+    inline void* AllocUnits(int NU);
+    inline void* ExpandUnits(void* ptr,int OldNU);
+    inline void* ShrinkUnits(void* ptr,int OldNU,int NewNU);
+    inline void  FreeUnits(void* ptr,int OldNU);
+    long GetAllocatedMemory() {return(SubAllocatorSize);};
+
+    byte *pText, *UnitsStart,*HeapEnd,*FakeUnitsStart;
+};
+
+
+#endif /* !defined(_SUBALLOC_H_) */
diff --git a/third_party/unrar/src/system.cpp b/third_party/unrar/src/system.cpp
new file mode 100644
index 0000000..450b2bb3
--- /dev/null
+++ b/third_party/unrar/src/system.cpp
@@ -0,0 +1,180 @@
+#include "rar.hpp"
+
+static int SleepTime=0;
+
+void InitSystemOptions(int SleepTime)
+{
+  ::SleepTime=SleepTime;
+}
+
+
+#if !defined(SFX_MODULE)
+void SetPriority(int Priority)
+{
+#ifdef _WIN_ALL
+  uint PriorityClass;
+  int PriorityLevel;
+  if (Priority<1 || Priority>15)
+    return;
+
+  if (Priority==1)
+  {
+    PriorityClass=IDLE_PRIORITY_CLASS;
+    PriorityLevel=THREAD_PRIORITY_IDLE;
+
+//  Background mode for Vista, can be slow for many small files.
+//    if (WinNT()>=WNT_VISTA)
+//      SetPriorityClass(GetCurrentProcess(),PROCESS_MODE_BACKGROUND_BEGIN);
+  }
+  else
+    if (Priority<7)
+    {
+      PriorityClass=IDLE_PRIORITY_CLASS;
+      PriorityLevel=Priority-4;
+    }
+    else
+      if (Priority==7)
+      {
+        PriorityClass=BELOW_NORMAL_PRIORITY_CLASS;
+        PriorityLevel=THREAD_PRIORITY_ABOVE_NORMAL;
+      }
+      else
+        if (Priority<10)
+        {
+          PriorityClass=NORMAL_PRIORITY_CLASS;
+          PriorityLevel=Priority-7;
+        }
+        else
+          if (Priority==10)
+          {
+            PriorityClass=ABOVE_NORMAL_PRIORITY_CLASS;
+            PriorityLevel=THREAD_PRIORITY_NORMAL;
+          }
+          else
+          {
+            PriorityClass=HIGH_PRIORITY_CLASS;
+            PriorityLevel=Priority-13;
+          }
+  SetPriorityClass(GetCurrentProcess(),PriorityClass);
+  SetThreadPriority(GetCurrentThread(),PriorityLevel);
+
+#ifdef RAR_SMP
+  ThreadPool::SetPriority(PriorityLevel);
+#endif
+
+#endif
+}
+#endif
+
+
+// Monotonic clock. Like clock(), returns time passed in CLOCKS_PER_SEC items.
+// In Android 5+ and Unix usual clock() returns time spent by all threads
+// together, so we cannot use it to measure time intervals anymore.
+clock_t MonoClock()
+{
+  return clock();
+}
+
+
+
+void Wait()
+{
+  if (ErrHandler.UserBreak)
+    ErrHandler.Exit(RARX_USERBREAK);
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+  if (SleepTime!=0)
+  {
+    static clock_t LastTime=MonoClock();
+    if (MonoClock()-LastTime>10*CLOCKS_PER_SEC/1000)
+    {
+      Sleep(SleepTime);
+      LastTime=MonoClock();
+    }
+  }
+#endif
+#if defined(_WIN_ALL)
+  // Reset system sleep timer to prevent system going sleep.
+  SetThreadExecutionState(ES_SYSTEM_REQUIRED);
+#endif
+}
+
+
+
+
+#if defined(_WIN_ALL) && !defined(SFX_MODULE)
+void Shutdown(POWER_MODE Mode)
+{
+  HANDLE hToken;
+  TOKEN_PRIVILEGES tkp;
+  if (OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken))
+  {
+    LookupPrivilegeValue(NULL,SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid);
+    tkp.PrivilegeCount = 1;
+    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+    AdjustTokenPrivileges(hToken,FALSE,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);
+  }
+  if (Mode==POWERMODE_OFF)
+    ExitWindowsEx(EWX_SHUTDOWN|EWX_FORCE|EWX_POWEROFF,SHTDN_REASON_FLAG_PLANNED);
+  if (Mode==POWERMODE_SLEEP)
+    SetSuspendState(FALSE,FALSE,FALSE);
+  if (Mode==POWERMODE_HIBERNATE)
+    SetSuspendState(TRUE,FALSE,FALSE);
+}
+#endif
+
+
+
+
+#if defined(_WIN_ALL)
+// Load library from Windows System32 folder. Use this function to prevent
+// loading a malicious code from current folder or same folder as exe.
+HMODULE WINAPI LoadSysLibrary(const wchar *Name)
+{
+  wchar SysDir[NM];
+  if (GetSystemDirectory(SysDir,ASIZE(SysDir))==0)
+    return NULL;
+  MakeName(SysDir,Name,SysDir,ASIZE(SysDir));
+  return LoadLibrary(SysDir);
+}
+
+
+bool IsUserAdmin()
+{
+  SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+  PSID AdministratorsGroup; 
+  BOOL b = AllocateAndInitializeSid(&NtAuthority,2,SECURITY_BUILTIN_DOMAIN_RID,
+           DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup); 
+  if (b) 
+  {
+    if (!CheckTokenMembership( NULL, AdministratorsGroup, &b)) 
+      b = FALSE;
+    FreeSid(AdministratorsGroup); 
+  }
+  return b!=FALSE;
+}
+
+#endif
+
+
+#ifdef USE_SSE
+SSE_VERSION _SSE_Version=GetSSEVersion();
+
+SSE_VERSION GetSSEVersion()
+{
+  int CPUInfo[4];
+  __cpuid(CPUInfo, 7);
+  if ((CPUInfo[1] & 0x20)!=0)
+    return SSE_AVX2;
+  __cpuid(CPUInfo, 1);
+  if ((CPUInfo[2] & 0x80000)!=0)
+    return SSE_SSE41;
+  if ((CPUInfo[2] & 0x200)!=0)
+    return SSE_SSSE3;
+  if ((CPUInfo[3] & 0x4000000)!=0)
+    return SSE_SSE2;
+  if ((CPUInfo[3] & 0x2000000)!=0)
+    return SSE_SSE;
+  return SSE_NONE;
+}
+#endif
diff --git a/third_party/unrar/src/system.hpp b/third_party/unrar/src/system.hpp
new file mode 100644
index 0000000..bacc4bd
--- /dev/null
+++ b/third_party/unrar/src/system.hpp
@@ -0,0 +1,39 @@
+#ifndef _RAR_SYSTEM_
+#define _RAR_SYSTEM_
+
+#ifdef _WIN_ALL
+#ifndef BELOW_NORMAL_PRIORITY_CLASS
+#define BELOW_NORMAL_PRIORITY_CLASS     0x00004000
+#define ABOVE_NORMAL_PRIORITY_CLASS     0x00008000
+#endif
+#ifndef PROCESS_MODE_BACKGROUND_BEGIN
+#define PROCESS_MODE_BACKGROUND_BEGIN   0x00100000
+#define PROCESS_MODE_BACKGROUND_END     0x00200000
+#endif
+#ifndef SHTDN_REASON_MAJOR_APPLICATION
+#define SHTDN_REASON_MAJOR_APPLICATION  0x00040000
+#define SHTDN_REASON_FLAG_PLANNED       0x80000000
+#define SHTDN_REASON_MINOR_MAINTENANCE  0x00000001
+#endif
+#endif
+
+void InitSystemOptions(int SleepTime);
+void SetPriority(int Priority);
+clock_t MonoClock();
+void Wait();
+bool EmailFile(const wchar *FileName,const wchar *MailToW);
+void Shutdown(POWER_MODE Mode);
+
+#ifdef _WIN_ALL
+HMODULE WINAPI LoadSysLibrary(const wchar *Name);
+bool IsUserAdmin();
+#endif
+
+
+#ifdef USE_SSE
+enum SSE_VERSION {SSE_NONE,SSE_SSE,SSE_SSE2,SSE_SSSE3,SSE_SSE41,SSE_AVX2};
+SSE_VERSION GetSSEVersion();
+extern SSE_VERSION _SSE_Version;
+#endif
+
+#endif
diff --git a/third_party/unrar/src/threadmisc.cpp b/third_party/unrar/src/threadmisc.cpp
new file mode 100644
index 0000000..4ad5af2
--- /dev/null
+++ b/third_party/unrar/src/threadmisc.cpp
@@ -0,0 +1,206 @@
+// Typically we use the same global thread pool for all RAR modules.
+static ThreadPool *GlobalPool=NULL;
+static uint GlobalPoolUseCount=0;
+
+static inline bool CriticalSectionCreate(CRITSECT_HANDLE *CritSection)
+{
+#ifdef _WIN_ALL
+  InitializeCriticalSection(CritSection);
+  return true;
+#elif defined(_UNIX)
+  return pthread_mutex_init(CritSection,NULL)==0;
+#endif
+}
+
+
+static inline void CriticalSectionDelete(CRITSECT_HANDLE *CritSection)
+{
+#ifdef _WIN_ALL
+  DeleteCriticalSection(CritSection);
+#elif defined(_UNIX)
+  pthread_mutex_destroy(CritSection);
+#endif
+}
+
+
+static inline void CriticalSectionStart(CRITSECT_HANDLE *CritSection)
+{
+#ifdef _WIN_ALL
+  EnterCriticalSection(CritSection);
+#elif defined(_UNIX)
+  pthread_mutex_lock(CritSection);
+#endif
+}
+
+
+static inline void CriticalSectionEnd(CRITSECT_HANDLE *CritSection)
+{
+#ifdef _WIN_ALL
+  LeaveCriticalSection(CritSection);
+#elif defined(_UNIX)
+  pthread_mutex_unlock(CritSection);
+#endif
+}
+
+
+static struct GlobalPoolCreateSync
+{
+  CRITSECT_HANDLE CritSection;
+  GlobalPoolCreateSync()  { CriticalSectionCreate(&CritSection); }
+  ~GlobalPoolCreateSync() { CriticalSectionDelete(&CritSection); }
+} PoolCreateSync;
+
+
+ThreadPool* CreateThreadPool()
+{
+  CriticalSectionStart(&PoolCreateSync.CritSection); 
+
+  if (GlobalPoolUseCount++ == 0)
+    GlobalPool=new ThreadPool(MaxPoolThreads);
+
+  // We use a simple thread pool, which does not allow to add tasks from
+  // different functions and threads in the same time. It is ok for RAR,
+  // but UnRAR.dll can be used in multithreaded environment. So if one of
+  // threads requests a copy of global pool and another copy is already
+  // in use, we create and return a new pool instead of existing global.
+  if (GlobalPoolUseCount > 1)
+  {
+    ThreadPool *Pool = new ThreadPool(MaxPoolThreads);
+    CriticalSectionEnd(&PoolCreateSync.CritSection); 
+    return Pool;
+  }
+
+  CriticalSectionEnd(&PoolCreateSync.CritSection); 
+  return GlobalPool;
+}
+
+
+void DestroyThreadPool(ThreadPool *Pool)
+{
+  if (Pool!=NULL)
+  {
+    CriticalSectionStart(&PoolCreateSync.CritSection); 
+
+    if (Pool==GlobalPool && GlobalPoolUseCount > 0 && --GlobalPoolUseCount == 0)
+      delete GlobalPool;
+
+    // To correctly work in multithreaded environment UnRAR.dll creates
+    // new pools if global pool is already in use. We delete such pools here.
+    if (Pool!=GlobalPool)
+      delete Pool;
+
+    CriticalSectionEnd(&PoolCreateSync.CritSection); 
+  }
+}
+
+
+static THREAD_HANDLE ThreadCreate(NATIVE_THREAD_PTR Proc,void *Data)
+{
+#ifdef _UNIX
+/*
+  pthread_attr_t attr;
+  pthread_attr_init(&attr);
+  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+*/
+  pthread_t pt;
+  int Code=pthread_create(&pt,NULL/*&attr*/,Proc,Data);
+  if (Code!=0)
+  {
+    wchar Msg[100];
+    swprintf(Msg,ASIZE(Msg),L"\npthread_create failed, code %d\n",Code);
+    ErrHandler.GeneralErrMsg(Msg);
+    ErrHandler.SysErrMsg();
+    ErrHandler.Exit(RARX_FATAL);
+  }
+  return pt;
+#else
+  DWORD ThreadId;
+  HANDLE hThread=CreateThread(NULL,0x10000,Proc,Data,0,&ThreadId);
+  if (hThread==NULL)
+  {
+    ErrHandler.GeneralErrMsg(L"CreateThread failed");
+    ErrHandler.SysErrMsg();
+    ErrHandler.Exit(RARX_FATAL);
+  }
+  return hThread;
+#endif
+}
+
+
+static void ThreadClose(THREAD_HANDLE hThread)
+{
+#ifdef _UNIX
+  pthread_join(hThread,NULL);
+#else
+  CloseHandle(hThread);
+#endif
+}
+
+
+#ifdef _WIN_ALL
+static void CWaitForSingleObject(HANDLE hHandle)
+{
+  DWORD rc=WaitForSingleObject(hHandle,INFINITE);
+  if (rc==WAIT_FAILED)
+  {
+    ErrHandler.GeneralErrMsg(L"\nWaitForMultipleObjects error %d, GetLastError %d",rc,GetLastError());
+    ErrHandler.Exit(RARX_FATAL);
+  }
+}
+#endif
+
+
+#ifdef _UNIX
+static void cpthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
+{
+  int rc=pthread_cond_wait(cond,mutex);
+  if (rc!=0)
+  {
+    ErrHandler.GeneralErrMsg(L"\npthread_cond_wait error %d",rc);
+    ErrHandler.Exit(RARX_FATAL);
+  }
+}
+#endif
+
+
+uint GetNumberOfCPU()
+{
+#ifndef RAR_SMP
+  return 1;
+#else
+#ifdef _UNIX
+#ifdef _SC_NPROCESSORS_ONLN
+  uint Count=(uint)sysconf(_SC_NPROCESSORS_ONLN);
+  return Count<1 ? 1:Count;
+#elif defined(_APPLE)
+  uint Count;
+  size_t Size=sizeof(Count);
+  return sysctlbyname("hw.ncpu",&Count,&Size,NULL,0)==0 ? Count:1;
+#endif
+#else // !_UNIX
+  DWORD_PTR ProcessMask;
+  DWORD_PTR SystemMask;
+
+  if (!GetProcessAffinityMask(GetCurrentProcess(),&ProcessMask,&SystemMask))
+    return 1;
+  uint Count=0;
+  for (DWORD_PTR Mask=1;Mask!=0;Mask<<=1)
+    if ((ProcessMask & Mask)!=0)
+      Count++;
+  return Count<1 ? 1:Count;
+#endif
+
+#endif // RAR_SMP
+}
+
+
+uint GetNumberOfThreads()
+{
+  uint NumCPU=GetNumberOfCPU();
+  if (NumCPU<1)
+    return 1;
+  if (NumCPU>MaxPoolThreads)
+    return MaxPoolThreads;
+  return NumCPU;
+}
+
diff --git a/third_party/unrar/src/threadpool.cpp b/third_party/unrar/src/threadpool.cpp
new file mode 100644
index 0000000..732dd75
--- /dev/null
+++ b/third_party/unrar/src/threadpool.cpp
@@ -0,0 +1,214 @@
+#include "rar.hpp"
+
+#ifdef RAR_SMP
+#include "threadmisc.cpp"
+
+#ifdef _WIN_ALL
+int ThreadPool::ThreadPriority=THREAD_PRIORITY_NORMAL;
+#endif
+
+ThreadPool::ThreadPool(uint MaxThreads)
+{
+  MaxAllowedThreads = MaxThreads;
+  if (MaxAllowedThreads>MaxPoolThreads)
+    MaxAllowedThreads=MaxPoolThreads;
+  if (MaxAllowedThreads==0)
+    MaxAllowedThreads=1;
+
+  ThreadsCreatedCount=0;
+
+  // If we have more threads than queue size, we'll hang on pool destroying,
+  // not releasing all waiting threads.
+  if (MaxAllowedThreads>ASIZE(TaskQueue))
+    MaxAllowedThreads=ASIZE(TaskQueue);
+
+  Closing=false;
+
+  bool Success = CriticalSectionCreate(&CritSection);
+#ifdef _WIN_ALL
+  QueuedTasksCnt=CreateSemaphore(NULL,0,ASIZE(TaskQueue),NULL);
+  NoneActive=CreateEvent(NULL,TRUE,TRUE,NULL);
+  Success=Success && QueuedTasksCnt!=NULL && NoneActive!=NULL;
+#elif defined(_UNIX)
+  AnyActive = false;
+  QueuedTasksCnt = 0;
+  Success=Success && pthread_cond_init(&AnyActiveCond,NULL)==0 &&
+          pthread_mutex_init(&AnyActiveMutex,NULL)==0 &&
+          pthread_cond_init(&QueuedTasksCntCond,NULL)==0 &&
+          pthread_mutex_init(&QueuedTasksCntMutex,NULL)==0;
+#endif
+  if (!Success)
+  {
+    ErrHandler.GeneralErrMsg(L"\nThread pool initialization failed.");
+    ErrHandler.Exit(RARX_FATAL);
+  }
+
+  QueueTop = 0;
+  QueueBottom = 0;
+  ActiveThreads = 0;
+}
+
+
+ThreadPool::~ThreadPool()
+{
+  WaitDone();
+  Closing=true;
+
+#ifdef _WIN_ALL
+  ReleaseSemaphore(QueuedTasksCnt,ASIZE(TaskQueue),NULL);
+#elif defined(_UNIX)
+  // Threads still can access QueuedTasksCnt for a short time after WaitDone(),
+  // so lock is required. We would occassionally hang without it.
+  pthread_mutex_lock(&QueuedTasksCntMutex);
+  QueuedTasksCnt+=ASIZE(TaskQueue);
+  pthread_mutex_unlock(&QueuedTasksCntMutex);
+
+  pthread_cond_broadcast(&QueuedTasksCntCond);
+#endif
+
+  for(uint I=0;I<ThreadsCreatedCount;I++)
+  {
+#ifdef _WIN_ALL
+    // Waiting until the thread terminates.
+    CWaitForSingleObject(ThreadHandles[I]);
+#endif
+    // Close the thread handle. In Unix it results in pthread_join call,
+    // which also waits for thread termination.
+    ThreadClose(ThreadHandles[I]);
+  }
+
+  CriticalSectionDelete(&CritSection);
+#ifdef _WIN_ALL
+  CloseHandle(QueuedTasksCnt);
+  CloseHandle(NoneActive);
+#elif defined(_UNIX)
+  pthread_cond_destroy(&AnyActiveCond);
+  pthread_mutex_destroy(&AnyActiveMutex);
+  pthread_cond_destroy(&QueuedTasksCntCond);
+  pthread_mutex_destroy(&QueuedTasksCntMutex);
+#endif
+}
+
+
+void ThreadPool::CreateThreads()
+{
+  for(uint I=0;I<MaxAllowedThreads;I++)
+  {
+    ThreadHandles[I] = ThreadCreate(PoolThread, this);
+    ThreadsCreatedCount++;
+#ifdef _WIN_ALL
+    if (ThreadPool::ThreadPriority!=THREAD_PRIORITY_NORMAL)
+      SetThreadPriority(ThreadHandles[I],ThreadPool::ThreadPriority);
+#endif
+  }
+}
+
+
+NATIVE_THREAD_TYPE ThreadPool::PoolThread(void *Param)
+{
+  ((ThreadPool*)Param)->PoolThreadLoop();
+  return 0;
+}
+
+
+void ThreadPool::PoolThreadLoop()
+{
+  QueueEntry Task;
+  while (GetQueuedTask(&Task))
+  {
+    Task.Proc(Task.Param);
+    
+    CriticalSectionStart(&CritSection); 
+    if (--ActiveThreads == 0)
+    {
+#ifdef _WIN_ALL
+      SetEvent(NoneActive);
+#elif defined(_UNIX)
+      pthread_mutex_lock(&AnyActiveMutex);
+      AnyActive=false;
+      pthread_cond_signal(&AnyActiveCond);
+      pthread_mutex_unlock(&AnyActiveMutex);
+#endif
+    }
+    CriticalSectionEnd(&CritSection); 
+  }
+}
+
+
+bool ThreadPool::GetQueuedTask(QueueEntry *Task)
+{
+#ifdef _WIN_ALL
+  CWaitForSingleObject(QueuedTasksCnt);
+#elif defined(_UNIX)
+  pthread_mutex_lock(&QueuedTasksCntMutex);
+  while (QueuedTasksCnt==0)
+    cpthread_cond_wait(&QueuedTasksCntCond,&QueuedTasksCntMutex);
+  QueuedTasksCnt--;
+  pthread_mutex_unlock(&QueuedTasksCntMutex);
+#endif
+
+  if (Closing)
+    return false;
+
+  CriticalSectionStart(&CritSection); 
+
+  *Task = TaskQueue[QueueBottom];
+  QueueBottom = (QueueBottom + 1) % ASIZE(TaskQueue);
+
+  CriticalSectionEnd(&CritSection); 
+
+  return true;
+}
+
+
+// Add task to queue. We assume that it is always called from main thread,
+// it allows to avoid any locks here. We process collected tasks only
+// when WaitDone is called.
+void ThreadPool::AddTask(PTHREAD_PROC Proc,void *Data)
+{
+  if (ThreadsCreatedCount == 0)
+    CreateThreads();
+  
+  // If queue is full, wait until it is empty.
+  if ((QueueTop + 1) % ASIZE(TaskQueue) == QueueBottom)
+    WaitDone();
+
+  TaskQueue[QueueTop].Proc = Proc;
+  TaskQueue[QueueTop].Param = Data;
+  QueueTop = (QueueTop + 1) % ASIZE(TaskQueue);
+}
+
+
+// Start queued tasks and wait until all threads are inactive.
+// We assume that it is always called from main thread, when pool threads
+// are sleeping yet.
+void ThreadPool::WaitDone()
+{
+  // We add ASIZE(TaskQueue) for case if TaskQueue array size is not
+  // a power of two. Negative numbers would not suit our purpose here.
+  ActiveThreads=(QueueTop+ASIZE(TaskQueue)-QueueBottom) % ASIZE(TaskQueue);
+  if (ActiveThreads==0)
+    return;
+#ifdef _WIN_ALL
+  ResetEvent(NoneActive);
+  ReleaseSemaphore(QueuedTasksCnt,ActiveThreads,NULL);
+  CWaitForSingleObject(NoneActive);
+#elif defined(_UNIX)
+  AnyActive=true;
+
+  // Threads reset AnyActive before accessing QueuedTasksCnt and even
+  // preceding WaitDone() call does not guarantee that some slow thread
+  // is not accessing QueuedTasksCnt now. So lock is necessary.
+  pthread_mutex_lock(&QueuedTasksCntMutex);
+  QueuedTasksCnt+=ActiveThreads;
+  pthread_mutex_unlock(&QueuedTasksCntMutex);
+
+  pthread_cond_broadcast(&QueuedTasksCntCond);
+
+  pthread_mutex_lock(&AnyActiveMutex);
+  while (AnyActive)
+    cpthread_cond_wait(&AnyActiveCond,&AnyActiveMutex);
+  pthread_mutex_unlock(&AnyActiveMutex);
+#endif
+}
+#endif // RAR_SMP
diff --git a/third_party/unrar/src/threadpool.hpp b/third_party/unrar/src/threadpool.hpp
new file mode 100644
index 0000000..dc45ca0
--- /dev/null
+++ b/third_party/unrar/src/threadpool.hpp
@@ -0,0 +1,107 @@
+#ifndef _RAR_THREADPOOL_
+#define _RAR_THREADPOOL_
+
+#ifndef RAR_SMP
+const uint MaxPoolThreads=1; // For single threaded version.
+#else
+const uint MaxPoolThreads=32;
+
+
+#ifdef _UNIX
+  #include <pthread.h>
+  #include <semaphore.h>
+#endif
+
+// Undefine for debugging.
+#define     USE_THREADS
+
+#ifdef _UNIX
+  #define NATIVE_THREAD_TYPE void*
+  typedef void* (*NATIVE_THREAD_PTR)(void *Data);
+  typedef pthread_t THREAD_HANDLE;
+  typedef pthread_mutex_t CRITSECT_HANDLE;
+#else
+  #define NATIVE_THREAD_TYPE DWORD WINAPI
+  typedef DWORD (WINAPI *NATIVE_THREAD_PTR)(void *Data);
+  typedef HANDLE THREAD_HANDLE;
+  typedef CRITICAL_SECTION CRITSECT_HANDLE;
+#endif
+
+typedef void (*PTHREAD_PROC)(void *Data);
+#define THREAD_PROC(fn) void fn(void *Data)
+
+uint GetNumberOfCPU();
+uint GetNumberOfThreads();
+
+
+class ThreadPool
+{
+  private:
+    struct QueueEntry
+    {
+    	PTHREAD_PROC Proc;
+      void *Param;
+    };
+
+    void CreateThreads();
+    static NATIVE_THREAD_TYPE PoolThread(void *Param);
+  	void PoolThreadLoop();
+  	bool GetQueuedTask(QueueEntry *Task);
+
+    // Number of threads in the pool. Must not exceed MaxPoolThreads.
+    uint MaxAllowedThreads;
+  	THREAD_HANDLE ThreadHandles[MaxPoolThreads];
+
+    // Number of actually created threads.
+    uint ThreadsCreatedCount;
+
+    uint ActiveThreads;
+
+  	QueueEntry TaskQueue[MaxPoolThreads];
+  	uint QueueTop;
+  	uint QueueBottom;
+
+    bool Closing; // Set true to quit all threads.
+  	
+#ifdef _WIN_ALL
+  	// Semaphore counting number of tasks stored in queue.
+  	HANDLE QueuedTasksCnt;
+
+    // Event signalling if no active tasks are performing now.
+    HANDLE NoneActive;
+
+#elif defined(_UNIX)
+    // Semaphores seem to be slower than conditional variables in pthreads,
+    // so we use the conditional variable to count tasks stored in queue.
+    uint QueuedTasksCnt;
+    pthread_cond_t QueuedTasksCntCond;
+    pthread_mutex_t QueuedTasksCntMutex;
+
+    bool AnyActive; // Active tasks present flag.
+    pthread_cond_t AnyActiveCond;
+    pthread_mutex_t AnyActiveMutex;
+#endif
+
+    // Pool critical section. We use the single section for all branches
+    // to avoid deadlocks, when thread1 has section1 and wants section2
+    // and thread2 has section2 and wants section1.
+  	CRITSECT_HANDLE CritSection;
+  public:
+    ThreadPool(uint MaxThreads);
+    ~ThreadPool();
+    void AddTask(PTHREAD_PROC Proc,void *Data);
+    void WaitDone();
+
+#ifdef _WIN_ALL
+    static int ThreadPriority;
+    static void SetPriority(int Priority) {ThreadPriority=Priority;}
+#endif
+};
+
+ThreadPool* CreateThreadPool();
+void DestroyThreadPool(ThreadPool *Pool);
+
+#endif // RAR_SMP
+
+#endif // _RAR_THREADPOOL_
+
diff --git a/third_party/unrar/src/timefn.cpp b/third_party/unrar/src/timefn.cpp
new file mode 100644
index 0000000..63f46608
--- /dev/null
+++ b/third_party/unrar/src/timefn.cpp
@@ -0,0 +1,340 @@
+#include "rar.hpp"
+
+void RarTime::GetLocal(RarLocalTime *lt)
+{
+#ifdef _WIN_ALL
+  FILETIME ft;
+  GetWinFT(&ft);
+  FILETIME lft;
+
+  if (WinNT() < WNT_VISTA)
+  {
+    // SystemTimeToTzSpecificLocalTime based code produces 1 hour error on XP.
+    FileTimeToLocalFileTime(&ft,&lft);
+  }
+  else
+  {
+    // We use these functions instead of FileTimeToLocalFileTime according to
+    // MSDN recommendation: "To account for daylight saving time
+    // when converting a file time to a local time ..."
+    SYSTEMTIME st1,st2;
+    FileTimeToSystemTime(&ft,&st1);
+    SystemTimeToTzSpecificLocalTime(NULL,&st1,&st2);
+    SystemTimeToFileTime(&st2,&lft);
+
+    // Correct precision loss (low 4 decimal digits) in FileTimeToSystemTime.
+    FILETIME rft;
+    SystemTimeToFileTime(&st1,&rft);
+    uint64 Corrected=INT32TO64(ft.dwHighDateTime,ft.dwLowDateTime)-
+                     INT32TO64(rft.dwHighDateTime,rft.dwLowDateTime)+
+                     INT32TO64(lft.dwHighDateTime,lft.dwLowDateTime);
+    lft.dwLowDateTime=(DWORD)Corrected;
+    lft.dwHighDateTime=(DWORD)(Corrected>>32);
+  }
+
+  SYSTEMTIME st;
+  FileTimeToSystemTime(&lft,&st);
+  lt->Year=st.wYear;
+  lt->Month=st.wMonth;
+  lt->Day=st.wDay;
+  lt->Hour=st.wHour;
+  lt->Minute=st.wMinute;
+  lt->Second=st.wSecond;
+  lt->wDay=st.wDayOfWeek;
+  lt->yDay=lt->Day-1;
+
+  static int mdays[12]={31,28,31,30,31,30,31,31,30,31,30,31};
+  for (uint I=1;I<lt->Month && I<=ASIZE(mdays);I++)
+    lt->yDay+=mdays[I-1];
+
+  if (lt->Month>2 && IsLeapYear(lt->Year))
+    lt->yDay++;
+#else
+  time_t ut=GetUnix();
+  struct tm *t;
+  t=localtime(&ut);
+
+  lt->Year=t->tm_year+1900;
+  lt->Month=t->tm_mon+1;
+  lt->Day=t->tm_mday;
+  lt->Hour=t->tm_hour;
+  lt->Minute=t->tm_min;
+  lt->Second=t->tm_sec;
+  lt->wDay=t->tm_wday;
+  lt->yDay=t->tm_yday;
+#endif
+  lt->Reminder=(itime % TICKS_PER_SECOND);
+}
+
+
+void RarTime::SetLocal(RarLocalTime *lt)
+{
+#ifdef _WIN_ALL
+  SYSTEMTIME st;
+  st.wYear=lt->Year;
+  st.wMonth=lt->Month;
+  st.wDay=lt->Day;
+  st.wHour=lt->Hour;
+  st.wMinute=lt->Minute;
+  st.wSecond=lt->Second;
+  st.wMilliseconds=0;
+  st.wDayOfWeek=0;
+  FILETIME lft;
+  if (SystemTimeToFileTime(&st,&lft))
+  {
+    FILETIME ft;
+
+    if (WinNT() < WNT_VISTA)
+    {
+      // TzSpecificLocalTimeToSystemTime based code produces 1 hour error on XP.
+      LocalFileTimeToFileTime(&lft,&ft);
+    }
+    else
+    {
+      // Reverse procedure which we do in GetLocal.
+      SYSTEMTIME st1,st2;
+      FileTimeToSystemTime(&lft,&st2); // st2 might be unequal to st, because we added lt->Reminder to lft.
+      TzSpecificLocalTimeToSystemTime(NULL,&st2,&st1);
+      SystemTimeToFileTime(&st1,&ft);
+
+      // Correct precision loss (low 4 decimal digits) in FileTimeToSystemTime.
+      FILETIME rft;
+      SystemTimeToFileTime(&st2,&rft);
+      uint64 Corrected=INT32TO64(lft.dwHighDateTime,lft.dwLowDateTime)-
+                       INT32TO64(rft.dwHighDateTime,rft.dwLowDateTime)+
+                       INT32TO64(ft.dwHighDateTime,ft.dwLowDateTime);
+      ft.dwLowDateTime=(DWORD)Corrected;
+      ft.dwHighDateTime=(DWORD)(Corrected>>32);
+    }
+
+    SetWinFT(&ft);
+  }
+  else
+    Reset();
+#else
+  struct tm t;
+
+  t.tm_sec=lt->Second;
+  t.tm_min=lt->Minute;
+  t.tm_hour=lt->Hour;
+  t.tm_mday=lt->Day;
+  t.tm_mon=lt->Month-1;
+  t.tm_year=lt->Year-1900;
+  t.tm_isdst=-1;
+  SetUnix(mktime(&t));
+#endif
+  itime+=lt->Reminder;
+}
+
+
+
+
+#ifdef _WIN_ALL
+void RarTime::GetWinFT(FILETIME *ft)
+{
+  _ULARGE_INTEGER ul;
+  ul.QuadPart=GetWin();
+  ft->dwLowDateTime=ul.LowPart;
+  ft->dwHighDateTime=ul.HighPart;
+}
+
+
+void RarTime::SetWinFT(FILETIME *ft)
+{
+  _ULARGE_INTEGER ul = {ft->dwLowDateTime, ft->dwHighDateTime};
+  SetWin(ul.QuadPart);
+}
+#endif
+
+
+// Get 64-bit representation of Windows FILETIME (100ns since 01.01.1601).
+uint64 RarTime::GetWin()
+{
+  return itime/(TICKS_PER_SECOND/10000000);
+}
+
+
+// Set 64-bit representation of Windows FILETIME (100ns since 01.01.1601).
+void RarTime::SetWin(uint64 WinTime)
+{
+  itime=WinTime*(TICKS_PER_SECOND/10000000);
+}
+
+
+time_t RarTime::GetUnix()
+{
+  return time_t(GetUnixNS()/1000000000);
+}
+
+
+void RarTime::SetUnix(time_t ut)
+{
+  if (sizeof(ut)>4)
+    SetUnixNS(uint64(ut)*1000000000);
+  else
+  {
+    // Convert 32-bit and possibly signed time_t to uint32 first,
+    // uint64 cast is not enough. Otherwise sign can expand to 64 bits.
+    SetUnixNS(uint64(uint32(ut))*1000000000);
+  }
+}
+
+
+// Get the high precision Unix time in nanoseconds since 01-01-1970.
+uint64 RarTime::GetUnixNS()
+{
+  // 11644473600000000000 - number of ns between 01-01-1601 and 01-01-1970.
+  uint64 ushift=INT32TO64(0xA1997B0B,0x4C6A0000);
+  return itime*(1000000000/TICKS_PER_SECOND)-ushift;
+}
+
+
+// Set the high precision Unix time in nanoseconds since 01-01-1970.
+void RarTime::SetUnixNS(uint64 ns)
+{
+  // 11644473600000000000 - number of ns between 01-01-1601 and 01-01-1970.
+  uint64 ushift=INT32TO64(0xA1997B0B,0x4C6A0000);
+  itime=(ns+ushift)/(1000000000/TICKS_PER_SECOND);
+}
+
+
+uint RarTime::GetDos()
+{
+  RarLocalTime lt;
+  GetLocal(&lt);
+  uint DosTime=(lt.Second/2)|(lt.Minute<<5)|(lt.Hour<<11)|
+               (lt.Day<<16)|(lt.Month<<21)|((lt.Year-1980)<<25);
+  return DosTime;
+}
+
+
+void RarTime::SetDos(uint DosTime)
+{
+  RarLocalTime lt;
+  lt.Second=(DosTime & 0x1f)*2;
+  lt.Minute=(DosTime>>5) & 0x3f;
+  lt.Hour=(DosTime>>11) & 0x1f;
+  lt.Day=(DosTime>>16) & 0x1f;
+  lt.Month=(DosTime>>21) & 0x0f;
+  lt.Year=(DosTime>>25)+1980;
+  lt.Reminder=0;
+  SetLocal(&lt);
+}
+
+
+void RarTime::GetText(wchar *DateStr,size_t MaxSize,bool FullMS)
+{
+  if (IsSet())
+  {
+    RarLocalTime lt;
+    GetLocal(&lt);
+    if (FullMS)
+      swprintf(DateStr,MaxSize,L"%u-%02u-%02u %02u:%02u:%02u,%09u",lt.Year,lt.Month,lt.Day,lt.Hour,lt.Minute,lt.Second,lt.Reminder*(1000000000/TICKS_PER_SECOND));
+    else
+      swprintf(DateStr,MaxSize,L"%u-%02u-%02u %02u:%02u",lt.Year,lt.Month,lt.Day,lt.Hour,lt.Minute);
+  }
+  else
+  {
+    // We use escape before '?' to avoid weird C trigraph characters.
+    wcscpy(DateStr,L"\?\?\?\?-\?\?-\?\? \?\?:\?\?");
+  }
+}
+
+
+#ifndef SFX_MODULE
+void RarTime::SetIsoText(const wchar *TimeText)
+{
+  int Field[6];
+  memset(Field,0,sizeof(Field));
+  for (uint DigitCount=0;*TimeText!=0;TimeText++)
+    if (IsDigit(*TimeText))
+    {
+      int FieldPos=DigitCount<4 ? 0:(DigitCount-4)/2+1;
+      if (FieldPos<ASIZE(Field))
+        Field[FieldPos]=Field[FieldPos]*10+*TimeText-'0';
+      DigitCount++;
+    }
+  RarLocalTime lt;
+  lt.Second=Field[5];
+  lt.Minute=Field[4];
+  lt.Hour=Field[3];
+  lt.Day=Field[2]==0 ? 1:Field[2];
+  lt.Month=Field[1]==0 ? 1:Field[1];
+  lt.Year=Field[0];
+  lt.Reminder=0;
+  SetLocal(&lt);
+}
+#endif
+
+
+#ifndef SFX_MODULE
+void RarTime::SetAgeText(const wchar *TimeText)
+{
+  uint Seconds=0,Value=0;
+  for (int I=0;TimeText[I]!=0;I++)
+  {
+    int Ch=TimeText[I];
+    if (IsDigit(Ch))
+      Value=Value*10+Ch-'0';
+    else
+    {
+      switch(etoupper(Ch))
+      {
+        case 'D':
+          Seconds+=Value*24*3600;
+          break;
+        case 'H':
+          Seconds+=Value*3600;
+          break;
+        case 'M':
+          Seconds+=Value*60;
+          break;
+        case 'S':
+          Seconds+=Value;
+          break;
+      }
+      Value=0;
+    }
+  }
+  SetCurrentTime();
+  itime-=uint64(Seconds)*TICKS_PER_SECOND;
+}
+#endif
+
+
+void RarTime::SetCurrentTime()
+{
+#ifdef _WIN_ALL
+  FILETIME ft;
+  SYSTEMTIME st;
+  GetSystemTime(&st);
+  SystemTimeToFileTime(&st,&ft);
+  SetWinFT(&ft);
+#else
+  time_t st;
+  time(&st);
+  SetUnix(st);
+#endif
+}
+
+
+// Add the specified signed number of nanoseconds.
+void RarTime::Adjust(int64 ns)
+{
+  ns/=1000000000/TICKS_PER_SECOND; // Convert ns to internal ticks.
+  itime+=(uint64)ns;
+}
+
+
+#ifndef SFX_MODULE
+const wchar *GetMonthName(int Month)
+{
+  return uiGetMonthName(Month);
+}
+#endif
+
+
+bool IsLeapYear(int Year)
+{
+  return (Year&3)==0 && (Year%100!=0 || Year%400==0);
+}
diff --git a/third_party/unrar/src/timefn.hpp b/third_party/unrar/src/timefn.hpp
new file mode 100644
index 0000000..5271361
--- /dev/null
+++ b/third_party/unrar/src/timefn.hpp
@@ -0,0 +1,65 @@
+#ifndef _RAR_TIMEFN_
+#define _RAR_TIMEFN_
+
+struct RarLocalTime
+{
+  uint Year;
+  uint Month;
+  uint Day;
+  uint Hour;
+  uint Minute;
+  uint Second;
+  uint Reminder; // Part of time smaller than 1 second, represented in 1/REMINDER_PRECISION intervals.
+  uint wDay;
+  uint yDay;
+};
+
+
+class RarTime
+{
+  private:
+    static const uint TICKS_PER_SECOND = 1000000000; // Internal precision.
+
+    // Internal time representation in 1/TICKS_PER_SECOND since 01.01.1601.
+    // We use nanoseconds here to handle the high precision Unix time.
+    uint64 itime;
+  public:
+    // RarLocalTime::Reminder precision. Must be equal to TICKS_PER_SECOND.
+    // Unlike TICKS_PER_SECOND, it is a public field.
+    static const uint REMINDER_PRECISION = TICKS_PER_SECOND;
+  public:
+    RarTime() {Reset();}
+    bool operator == (RarTime &rt) {return itime==rt.itime;}
+    bool operator != (RarTime &rt) {return itime!=rt.itime;}
+    bool operator < (RarTime &rt)  {return itime<rt.itime;}
+    bool operator <= (RarTime &rt) {return itime<rt.itime || itime==rt.itime;}
+    bool operator > (RarTime &rt)  {return itime>rt.itime;}
+    bool operator >= (RarTime &rt) {return itime>rt.itime || itime==rt.itime;}
+
+    void GetLocal(RarLocalTime *lt);
+    void SetLocal(RarLocalTime *lt);
+#ifdef _WIN_ALL
+    void GetWinFT(FILETIME *ft);
+    void SetWinFT(FILETIME *ft);
+#endif
+    uint64 GetWin();
+    void SetWin(uint64 WinTime);
+    time_t GetUnix();
+    void SetUnix(time_t ut);
+    uint64 GetUnixNS();
+    void SetUnixNS(uint64 ns);
+    uint GetDos();
+    void SetDos(uint DosTime);
+    void GetText(wchar *DateStr,size_t MaxSize,bool FullMS);
+    void SetIsoText(const wchar *TimeText);
+    void SetAgeText(const wchar *TimeText);
+    void SetCurrentTime();
+    void Reset() {itime=0;}
+    bool IsSet() {return itime!=0;}
+    void Adjust(int64 ns);
+};
+
+const wchar *GetMonthName(int Month);
+bool IsLeapYear(int Year);
+
+#endif
diff --git a/third_party/unrar/src/ui.cpp b/third_party/unrar/src/ui.cpp
new file mode 100644
index 0000000..9713a88
--- /dev/null
+++ b/third_party/unrar/src/ui.cpp
@@ -0,0 +1,14 @@
+#include "rar.hpp"
+
+#include "uicommon.cpp"
+
+#ifdef SILENT
+#include "uisilent.cpp"
+#else
+
+
+
+
+#include "uiconsole.cpp"
+
+#endif
diff --git a/third_party/unrar/src/ui.hpp b/third_party/unrar/src/ui.hpp
new file mode 100644
index 0000000..396484d
--- /dev/null
+++ b/third_party/unrar/src/ui.hpp
@@ -0,0 +1,163 @@
+#ifndef _RAR_UI_
+#define _RAR_UI_
+
+// UIERROR_ - error message;
+// UIMSG_   - informational message;
+// UIWAIT_  - message waiting for user confirmation;
+// UIEVENT_ - if simple message is not enough;
+
+enum UIMESSAGE_CODE {
+  UIERROR_SYSERRMSG, UIERROR_GENERALERRMSG, UIERROR_INCERRCOUNT,
+  UIERROR_CHECKSUM, UIERROR_CHECKSUMENC, UIERROR_CHECKSUMPACKED,
+  UIERROR_BADPSW, UIERROR_MEMORY, UIERROR_FILEOPEN, UIERROR_FILECREATE,
+  UIERROR_FILECLOSE, UIERROR_FILESEEK, UIERROR_FILEREAD,
+  UIERROR_FILEWRITE, UIERROR_FILEDELETE, UIERROR_FILERENAME,
+  UIERROR_FILEATTR, UIERROR_FILECOPY, UIERROR_FILECOPYHINT,
+  UIERROR_DIRCREATE, UIERROR_SLINKCREATE, UIERROR_HLINKCREATE,
+  UIERROR_NEEDADMIN, UIERROR_ARCBROKEN, UIERROR_HEADERBROKEN,
+  UIERROR_MHEADERBROKEN, UIERROR_FHEADERBROKEN, UIERROR_SUBHEADERBROKEN,
+  UIERROR_SUBHEADERUNKNOWN, UIERROR_SUBHEADERDATABROKEN, UIERROR_RRDAMAGED,
+  UIERROR_UNKNOWNMETHOD, UIERROR_UNKNOWNENCMETHOD, UIERROR_RENAMING,
+  UIERROR_NEWERRAR, UIERROR_NOTSFX, UIERROR_OLDTOSFX,
+  UIERROR_WRONGSFXVER, UIERROR_ALREADYENC, UIERROR_DICTOUTMEM,
+  UIERROR_USESMALLERDICT, UIERROR_MODIFYUNKNOWN, UIERROR_MODIFYOLD,
+  UIERROR_MODIFYLOCKED, UIERROR_MODIFYVOLUME, UIERROR_NOTVOLUME,
+  UIERROR_NOTFIRSTVOLUME, UIERROR_RECVOLLIMIT, UIERROR_RECVOLDIFFSETS,
+  UIERROR_RECVOLALLEXIST, UIERROR_RECVOLFOUND, UIERROR_RECONSTRUCTING,
+  UIERROR_RECVOLCANNOTFIX, UIERROR_OPFAILED, UIERROR_UNEXPEOF,
+  UIERROR_BADARCHIVE, UIERROR_CMTBROKEN, UIERROR_INVALIDNAME,
+  UIERROR_NEWRARFORMAT, UIERROR_NOTSUPPORTED, UIERROR_ENCRNOTSUPPORTED,
+  UIERROR_RARZIPONLY, UIERROR_REPAIROLDFORMAT, UIERROR_NOFILESREPAIRED,
+  UIERROR_NOFILESTOADD, UIERROR_NOFILESTODELETE, UIERROR_NOFILESTOEXTRACT,
+  UIERROR_MISSINGVOL, UIERROR_NEEDPREVVOL, UIERROR_UNKNOWNEXTRA,
+  UIERROR_CORRUPTEXTRA, UIERROR_NTFSREQUIRED, UIERROR_ZIPVOLSFX,
+  UIERROR_FILERO, UIERROR_TOOLARGESFX, UIERROR_EMAIL, UIERROR_ACLGET,
+  UIERROR_ACLBROKEN, UIERROR_ACLUNKNOWN, UIERROR_ACLSET, UIERROR_STREAMBROKEN,
+  UIERROR_STREAMUNKNOWN, UIERROR_INCOMPATSWITCH, UIERROR_PATHTOOLONG,
+  UIERROR_DIRSCAN, UIERROR_UOWNERGET, UIERROR_UOWNERBROKEN,
+  UIERROR_UOWNERGETOWNERID, UIERROR_UOWNERGETGROUPID, UIERROR_UOWNERSET,
+  UIERROR_ULINKREAD, UIERROR_ULINKEXIST,
+
+  UIMSG_FIRST,
+  UIMSG_STRING, UIMSG_BUILD, UIMSG_RRSEARCH, UIMSG_RRFOUND,
+  UIMSG_RRNOTFOUND, UIMSG_RRDAMAGED, UIMSG_BLOCKSRECOVERED,
+  UIMSG_COPYINGDATA, UIMSG_AREADAMAGED, UIMSG_SECTORDAMAGED,
+  UIMSG_SECTORRECOVERED, UIMSG_SECTORNOTRECOVERED, UIMSG_FOUND,
+  UIMSG_CORRECTINGNAME, UIMSG_BADARCHIVE, UIMSG_CREATING, UIMSG_RENAMING,
+  UIMSG_RECVOLCALCCHECKSUM, UIMSG_RECVOLFOUND, UIMSG_RECVOLMISSING,
+  UIMSG_MISSINGVOL, UIMSG_RECONSTRUCTING, UIMSG_CHECKSUM, UIMSG_FAT32SIZE,
+
+  UIWAIT_FIRST,
+  UIWAIT_DISKFULLNEXT, UIWAIT_FCREATEERROR, UIWAIT_BADPSW,
+
+  UIEVENT_FIRST,
+  UIEVENT_SEARCHDUPFILESSTART, UIEVENT_SEARCHDUPFILESEND,
+  UIEVENT_CLEARATTRSTART, UIEVENT_CLEARATTRFILE,
+  UIEVENT_DELADDEDSTART, UIEVENT_DELADDEDFILE, UIEVENT_FILESFOUND,
+  UIEVENT_ERASEDISK, UIEVENT_FILESUMSTART, UIEVENT_FILESUMPROGRESS,
+  UIEVENT_FILESUMEND, UIEVENT_PROTECTSTART, UIEVENT_PROTECTEND,
+  UIEVENT_TESTADDEDSTART, UIEVENT_TESTADDEDEND, UIEVENT_RRTESTINGSTART,
+  UIEVENT_RRTESTINGEND, UIEVENT_NEWARCHIVE, UIEVENT_NEWREVFILE
+};
+
+// Flags for uiAskReplace function.
+enum UIASKREP_FLAGS {
+  UIASKREP_F_NORENAME=1,UIASKREP_F_EXCHSRCDEST=2,UIASKREP_F_SHOWNAMEONLY=4
+};
+
+// Codes returned by uiAskReplace. Note that uiAskReplaceEx returns only
+// UIASKREP_R_REPLACE, UIASKREP_R_SKIP and UIASKREP_R_CANCEL codes.
+enum UIASKREP_RESULT {
+  UIASKREP_R_REPLACE,UIASKREP_R_SKIP,UIASKREP_R_REPLACEALL,UIASKREP_R_SKIPALL,
+  UIASKREP_R_RENAME,UIASKREP_R_RENAMEAUTO,UIASKREP_R_CANCEL,UIASKREP_R_UNUSED
+};
+
+UIASKREP_RESULT uiAskReplace(wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags);
+UIASKREP_RESULT uiAskReplaceEx(RAROptions *Cmd,wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags);
+
+void uiInit(bool Sound);
+
+
+void uiStartArchiveExtract(bool Extract,const wchar *ArcName);
+bool uiStartFileExtract(const wchar *FileName,bool Extract,bool Test,bool Skip);
+void uiExtractProgress(int64 CurFileSize,int64 TotalFileSize,int64 CurSize,int64 TotalSize);
+void uiProcessProgress(const char *Command,int64 CurSize,int64 TotalSize);
+
+enum UIPASSWORD_TYPE {UIPASSWORD_GLOBAL,UIPASSWORD_FILE,UIPASSWORD_ARCHIVE};
+bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password);
+
+enum UIALARM_TYPE {UIALARM_ERROR, UIALARM_INFO, UIALARM_QUESTION};
+void uiAlarm(UIALARM_TYPE Type);
+
+
+bool uiAskNextVolume(wchar *VolName,size_t MaxSize);
+bool uiAskRepeatRead(const wchar *FileName);
+bool uiAskRepeatWrite(const wchar *FileName,bool DiskFull);
+
+#ifndef SFX_MODULE
+const wchar *uiGetMonthName(int Month);
+#endif
+
+
+class uiMsgStore
+{
+  private:
+    static const size_t MAX_MSG = 8;
+    const wchar *Str[MAX_MSG];
+    uint Num[MAX_MSG];
+    uint StrSize,NumSize;
+    UIMESSAGE_CODE Code;
+  public:
+    uiMsgStore(UIMESSAGE_CODE Code)
+    {
+      NumSize=StrSize=0;
+      this->Code=Code;
+    }
+    uiMsgStore& operator << (const wchar *s)
+    {
+      if (StrSize<MAX_MSG)
+        Str[StrSize++]=s;
+      return *this;
+    }
+    uiMsgStore& operator << (uint n)
+    {
+      if (NumSize<MAX_MSG)
+        Num[NumSize++]=n;
+      return *this;
+    }
+
+    void Msg();
+};
+
+
+// Templates recognize usual NULL as integer, not wchar*.
+#define UINULL ((wchar *)NULL)
+
+inline void uiMsg(UIMESSAGE_CODE Code)
+{
+  uiMsgStore Store(Code);
+  Store.Msg();
+}
+
+template<class T1> void uiMsg(UIMESSAGE_CODE Code,T1 a1)
+{
+  uiMsgStore Store(Code);
+  Store<<a1;
+  Store.Msg();
+}
+
+template<class T1,class T2> void uiMsg(UIMESSAGE_CODE Code,T1 a1,T2 a2)
+{
+  uiMsgStore Store(Code);
+  Store<<a1<<a2;
+  Store.Msg();
+}
+
+template<class T1,class T2,class T3> void uiMsg(UIMESSAGE_CODE code,T1 a1,T2 a2,T3 a3)
+{
+  uiMsgStore Store(code);
+  Store<<a1<<a2<<a3;
+  Store.Msg();
+}
+
+#endif
diff --git a/third_party/unrar/src/uicommon.cpp b/third_party/unrar/src/uicommon.cpp
new file mode 100644
index 0000000..5be551a4
--- /dev/null
+++ b/third_party/unrar/src/uicommon.cpp
@@ -0,0 +1,65 @@
+static bool uiSoundEnabled;
+
+void uiInit(bool Sound)
+{
+  uiSoundEnabled = Sound;
+}
+
+
+// Additionally to handling user input, it analyzes and sets command options.
+// Returns only 'replace', 'skip' and 'cancel' codes.
+UIASKREP_RESULT uiAskReplaceEx(RAROptions *Cmd,wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags)
+{
+  if (Cmd->Overwrite==OVERWRITE_NONE)
+    return UIASKREP_R_SKIP;
+
+#if !defined(SFX_MODULE) && !defined(SILENT)
+  // Must be before Cmd->AllYes check or -y switch would override -or.
+  if (Cmd->Overwrite==OVERWRITE_AUTORENAME && GetAutoRenamedName(Name,MaxNameSize))
+    return UIASKREP_R_REPLACE;
+#endif
+
+  // This check must be after OVERWRITE_AUTORENAME processing or -y switch
+  // would override -or.
+  if (Cmd->AllYes || Cmd->Overwrite==OVERWRITE_ALL)
+  {
+    PrepareToDelete(Name);
+    return UIASKREP_R_REPLACE;
+  }
+
+  wchar NewName[NM];
+  wcsncpyz(NewName,Name,ASIZE(NewName));
+  UIASKREP_RESULT Choice=uiAskReplace(NewName,ASIZE(NewName),FileSize,FileTime,Flags);
+
+  if (Choice==UIASKREP_R_REPLACE || Choice==UIASKREP_R_REPLACEALL)
+    PrepareToDelete(Name);
+
+  if (Choice==UIASKREP_R_REPLACEALL)
+  {
+    Cmd->Overwrite=OVERWRITE_ALL;
+    return UIASKREP_R_REPLACE;
+  }
+  if (Choice==UIASKREP_R_SKIPALL)
+  {
+    Cmd->Overwrite=OVERWRITE_NONE;
+    return UIASKREP_R_SKIP;
+  }
+  if (Choice==UIASKREP_R_RENAME)
+  {
+    if (PointToName(NewName)==NewName)
+      SetName(Name,NewName,MaxNameSize);
+    else
+      wcsncpyz(Name,NewName,MaxNameSize);
+    if (FileExist(Name))
+      return uiAskReplaceEx(Cmd,Name,MaxNameSize,FileSize,FileTime,Flags);
+    return UIASKREP_R_REPLACE;
+  }
+#if !defined(SFX_MODULE) && !defined(SILENT)
+  if (Choice==UIASKREP_R_RENAMEAUTO && GetAutoRenamedName(Name,MaxNameSize))
+  {
+    Cmd->Overwrite=OVERWRITE_AUTORENAME;
+    return UIASKREP_R_REPLACE;
+  }
+#endif
+  return Choice;
+}
diff --git a/third_party/unrar/src/uiconsole.cpp b/third_party/unrar/src/uiconsole.cpp
new file mode 100644
index 0000000..281eade0
--- /dev/null
+++ b/third_party/unrar/src/uiconsole.cpp
@@ -0,0 +1,398 @@
+// Purely user interface function. Gets and returns user input.
+UIASKREP_RESULT uiAskReplace(wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags)
+{
+  wchar SizeText1[20],DateStr1[50],SizeText2[20],DateStr2[50];
+
+  FindData ExistingFD;
+  memset(&ExistingFD,0,sizeof(ExistingFD)); // In case find fails.
+  FindFile::FastFind(Name,&ExistingFD);
+  itoa(ExistingFD.Size,SizeText1,ASIZE(SizeText1));
+  ExistingFD.mtime.GetText(DateStr1,ASIZE(DateStr1),false);
+
+  if (FileSize==INT64NDF || FileTime==NULL)
+  {
+    eprintf(L"\n");
+    eprintf(St(MAskOverwrite),Name);
+  }
+  else
+  {
+    itoa(FileSize,SizeText2,ASIZE(SizeText2));
+    FileTime->GetText(DateStr2,ASIZE(DateStr2),false);
+    eprintf(St(MAskReplace),Name,SizeText1,DateStr1,SizeText2,DateStr2);
+  }
+
+  bool AllowRename=(Flags & UIASKREP_F_NORENAME)==0;
+  int Choice=0;
+  do
+  {
+    Choice=Ask(St(AllowRename ? MYesNoAllRenQ : MYesNoAllQ));
+  } while (Choice==0); // 0 means invalid input.
+  switch(Choice)
+  {
+    case 1:
+      return UIASKREP_R_REPLACE;
+    case 2:
+      return UIASKREP_R_SKIP;
+    case 3:
+      return UIASKREP_R_REPLACEALL;
+    case 4:
+      return UIASKREP_R_SKIPALL;
+  }
+  if (AllowRename && Choice==5)
+  {
+    mprintf(St(MAskNewName));
+    if (getwstr(Name,MaxNameSize))
+      return UIASKREP_R_RENAME;
+    else
+      return UIASKREP_R_SKIP; // Process fwgets failure as if user answered 'No'.
+  }
+  return UIASKREP_R_CANCEL;
+}
+
+
+
+
+void uiStartArchiveExtract(bool Extract,const wchar *ArcName)
+{
+  mprintf(St(Extract ? MExtracting : MExtrTest), ArcName);
+}
+
+
+bool uiStartFileExtract(const wchar *FileName,bool Extract,bool Test,bool Skip)
+{
+  return true;
+}
+
+
+void uiExtractProgress(int64 CurFileSize,int64 TotalFileSize,int64 CurSize,int64 TotalSize)
+{
+  int CurPercent=ToPercent(CurSize,TotalSize);
+  mprintf(L"\b\b\b\b%3d%%",CurPercent);
+}
+
+
+void uiProcessProgress(const char *Command,int64 CurSize,int64 TotalSize)
+{
+  int CurPercent=ToPercent(CurSize,TotalSize);
+  mprintf(L"\b\b\b\b%3d%%",CurPercent);
+}
+
+
+void uiMsgStore::Msg()
+{
+  switch(Code)
+  {
+    case UIERROR_SYSERRMSG:
+    case UIERROR_GENERALERRMSG:
+      Log(NULL,L"\n%ls",Str[0]);
+      break;
+    case UIERROR_CHECKSUM:
+      Log(Str[0],St(MCRCFailed),Str[1]);
+      break;
+    case UIERROR_CHECKSUMENC:
+      Log(Str[0],St(MEncrBadCRC),Str[1]);
+      break;
+    case UIERROR_CHECKSUMPACKED:
+      Log(Str[0],St(MDataBadCRC),Str[1],Str[0]);
+      break;
+    case UIERROR_BADPSW:
+    case UIWAIT_BADPSW:
+      Log(Str[0],St(MWrongPassword));
+      break;
+    case UIERROR_MEMORY:
+      Log(NULL,St(MErrOutMem));
+      break;
+    case UIERROR_FILEOPEN:
+      Log(Str[0],St(MCannotOpen),Str[1]);
+      break;
+    case UIERROR_FILECREATE:
+      Log(Str[0],St(MCannotCreate),Str[1]);
+      break;
+    case UIERROR_FILECLOSE:
+      Log(NULL,St(MErrFClose),Str[0]);
+      break;
+    case UIERROR_FILESEEK:
+      Log(NULL,St(MErrSeek),Str[0]);
+      break;
+    case UIERROR_FILEREAD:
+      Log(Str[0],St(MErrRead),Str[1]);
+      break;
+    case UIERROR_FILEWRITE:
+      Log(Str[0],St(MErrWrite),Str[1]);
+      break;
+#ifndef SFX_MODULE
+    case UIERROR_FILEDELETE:
+      Log(Str[0],St(MCannotDelete),Str[1]);
+      break;
+    case UIERROR_FILERENAME:
+      Log(Str[0],St(MErrRename),Str[1],Str[2]);
+      break;
+#endif
+    case UIERROR_FILEATTR:
+      Log(Str[0],St(MErrChangeAttr),Str[1]);
+      break;
+    case UIERROR_FILECOPY:
+      Log(Str[0],St(MCopyError),Str[1],Str[2]);
+      break;
+    case UIERROR_FILECOPYHINT:
+      Log(Str[0],St(MCopyErrorHint));
+      mprintf(L"     "); // For progress percent.
+      break;
+    case UIERROR_DIRCREATE:
+      Log(Str[0],St(MExtrErrMkDir),Str[1]);
+      break;
+    case UIERROR_SLINKCREATE:
+      Log(Str[0],St(MErrCreateLnkS),Str[1]);
+      break;
+    case UIERROR_HLINKCREATE:
+      Log(NULL,St(MErrCreateLnkH),Str[0]);
+      break;
+    case UIERROR_NEEDADMIN:
+      Log(NULL,St(MNeedAdmin));
+      break;
+    case UIERROR_ARCBROKEN:
+      Log(Str[0],St(MErrBrokenArc));
+      break;
+    case UIERROR_HEADERBROKEN:
+      Log(Str[0],St(MHeaderBroken));
+      break;
+    case UIERROR_MHEADERBROKEN:
+      Log(Str[0],St(MMainHeaderBroken));
+      break;
+    case UIERROR_FHEADERBROKEN:
+      Log(Str[0],St(MLogFileHead),Str[1]);
+      break;
+    case UIERROR_SUBHEADERBROKEN:
+      Log(Str[0],St(MSubHeadCorrupt));
+      break;
+    case UIERROR_SUBHEADERUNKNOWN:
+      Log(Str[0],St(MSubHeadUnknown));
+      break;
+    case UIERROR_SUBHEADERDATABROKEN:
+      Log(Str[0],St(MSubHeadDataCRC),Str[1]);
+      break;
+    case UIERROR_RRDAMAGED:
+      Log(Str[0],St(MRRDamaged));
+      break;
+    case UIERROR_UNKNOWNMETHOD:
+      Log(Str[0],St(MUnknownMeth),Str[1]);
+      break;
+    case UIERROR_UNKNOWNENCMETHOD:
+      Log(Str[0],St(MUnkEncMethod),Str[1]);
+      break;
+#ifndef SFX_MODULE
+   case UIERROR_RENAMING:
+      Log(Str[0],St(MRenaming),Str[1],Str[2]);
+      break;
+    case UIERROR_NEWERRAR:
+      Log(Str[0],St(MNewerRAR));
+      break;
+#endif
+    case UIERROR_RECVOLDIFFSETS:
+      Log(NULL,St(MRecVolDiffSets),Str[0],Str[1]);
+      break;
+    case UIERROR_RECVOLALLEXIST:
+      mprintf(St(MRecVolAllExist));
+      break;
+    case UIERROR_RECONSTRUCTING:
+      mprintf(St(MReconstructing));
+      break;
+    case UIERROR_RECVOLCANNOTFIX:
+      mprintf(St(MRecVolCannotFix));
+      break;
+    case UIERROR_UNEXPEOF:
+      Log(Str[0],St(MLogUnexpEOF));
+      break;
+    case UIERROR_BADARCHIVE:
+      Log(Str[0],St(MBadArc),Str[0]);
+      break;
+    case UIERROR_CMTBROKEN:
+      Log(Str[0],St(MLogCommBrk));
+      break;
+    case UIERROR_INVALIDNAME:
+      Log(Str[0],St(MInvalidName),Str[1]);
+      break;
+#ifndef SFX_MODULE
+    case UIERROR_NEWRARFORMAT:
+      Log(Str[0],St(MNewRarFormat));
+      break;
+#endif
+    case UIERROR_NOFILESTOEXTRACT:
+      mprintf(St(MExtrNoFiles));
+      break;
+    case UIERROR_MISSINGVOL:
+      Log(Str[0],St(MAbsNextVol),Str[0]);
+      break;
+#ifndef SFX_MODULE
+    case UIERROR_NEEDPREVVOL:
+      Log(Str[0],St(MUnpCannotMerge),Str[1]);
+      break;
+    case UIERROR_UNKNOWNEXTRA:
+      Log(Str[0],St(MUnknownExtra),Str[1]);
+      break;
+    case UIERROR_CORRUPTEXTRA:
+      Log(Str[0],St(MCorruptExtra),Str[1],Str[2]);
+      break;
+#endif
+#if !defined(SFX_MODULE) && defined(_WIN_ALL)
+    case UIERROR_NTFSREQUIRED:
+      Log(NULL,St(MNTFSRequired),Str[0]);
+      break;
+#endif
+#if !defined(SFX_MODULE) && defined(_WIN_ALL)
+    case UIERROR_ACLBROKEN:
+      Log(Str[0],St(MACLBroken),Str[1]);
+      break;
+    case UIERROR_ACLUNKNOWN:
+      Log(Str[0],St(MACLUnknown),Str[1]);
+      break;
+    case UIERROR_ACLSET:
+      Log(Str[0],St(MACLSetError),Str[1]);
+      break;
+    case UIERROR_STREAMBROKEN:
+      Log(Str[0],St(MStreamBroken),Str[1]);
+      break;
+    case UIERROR_STREAMUNKNOWN:
+      Log(Str[0],St(MStreamUnknown),Str[1]);
+      break;
+#endif
+    case UIERROR_INCOMPATSWITCH:
+      mprintf(St(MIncompatSwitch),Str[0],Num[0]);
+      break;
+    case UIERROR_PATHTOOLONG:
+      Log(NULL,L"\n%ls%ls%ls",Str[0],Str[1],Str[2]);
+      Log(NULL,St(MPathTooLong));
+      break;
+#ifndef SFX_MODULE
+    case UIERROR_DIRSCAN:
+      Log(NULL,St(MScanError),Str[0]);
+      break;
+#endif
+    case UIERROR_UOWNERBROKEN:
+      Log(Str[0],St(MOwnersBroken),Str[1]);
+      break;
+    case UIERROR_UOWNERGETOWNERID:
+      Log(Str[0],St(MErrGetOwnerID),Str[1]);
+      break;
+    case UIERROR_UOWNERGETGROUPID:
+      Log(Str[0],St(MErrGetGroupID),Str[1]);
+      break;
+    case UIERROR_UOWNERSET:
+      Log(Str[0],St(MSetOwnersError),Str[1]);
+      break;
+    case UIERROR_ULINKREAD:
+      Log(NULL,St(MErrLnkRead),Str[0]);
+      break;
+    case UIERROR_ULINKEXIST:
+      Log(NULL,St(MSymLinkExists),Str[0]);
+      break;
+
+
+#ifndef SFX_MODULE
+    case UIMSG_STRING:
+      mprintf(L"\n%s",Str[0]);
+      break;
+#endif
+    case UIMSG_CORRECTINGNAME:
+      Log(Str[0],St(MCorrectingName));
+      break;
+    case UIMSG_BADARCHIVE:
+      mprintf(St(MBadArc),Str[0]);
+      break;
+    case UIMSG_CREATING:
+      mprintf(St(MCreating),Str[0]);
+      break;
+    case UIMSG_RENAMING:
+      mprintf(St(MRenaming),Str[0],Str[1]);
+      break;
+    case UIMSG_RECVOLCALCCHECKSUM:
+      mprintf(St(MCalcCRCAllVol));
+      break;
+    case UIMSG_RECVOLFOUND:
+      mprintf(St(MRecVolFound),Num[0]);
+      break;
+    case UIMSG_RECVOLMISSING:
+      mprintf(St(MRecVolMissing),Num[0]);
+      break;
+    case UIMSG_MISSINGVOL:
+      mprintf(St(MAbsNextVol),Str[0]);
+      break;
+    case UIMSG_RECONSTRUCTING:
+      mprintf(St(MReconstructing));
+      break;
+    case UIMSG_CHECKSUM:
+      mprintf(St(MCRCFailed),Str[0]);
+      break;
+    case UIMSG_FAT32SIZE:
+      mprintf(St(MFAT32Size));
+      mprintf(L"     "); // For progress percent.
+      break;
+
+
+
+    case UIEVENT_RRTESTINGSTART:
+      mprintf(L"%s      ",St(MTestingRR));
+      break;
+  }
+}
+
+
+bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password)
+{
+  return GetConsolePassword(Type,FileName,Password);
+}
+
+
+void uiAlarm(UIALARM_TYPE Type)
+{
+  if (uiSoundEnabled)
+  {
+    static clock_t LastTime=-10; // Negative to always beep first time.
+    if ((MonoClock()-LastTime)/CLOCKS_PER_SEC>5)
+    {
+#ifdef _WIN_ALL
+      MessageBeep(-1);
+#else
+      putwchar('\007');
+#endif
+      LastTime=MonoClock();
+    }
+  }
+}
+
+
+
+
+bool uiAskNextVolume(wchar *VolName,size_t MaxSize)
+{
+  eprintf(St(MAskNextVol),VolName);
+  return Ask(St(MContinueQuit))!=2;
+}
+
+
+bool uiAskRepeatRead(const wchar *FileName)
+{
+  mprintf(L"\n");
+  Log(NULL,St(MErrRead),FileName);
+  return Ask(St(MRetryAbort))==1;
+}
+
+
+bool uiAskRepeatWrite(const wchar *FileName,bool DiskFull)
+{
+  mprintf(L"\n");
+  Log(NULL,St(DiskFull ? MNotEnoughDisk:MErrWrite),FileName);
+  return Ask(St(MRetryAbort))==1;
+}
+
+
+#ifndef SFX_MODULE
+const wchar *uiGetMonthName(int Month)
+{
+  static MSGID MonthID[12]={
+         MMonthJan,MMonthFeb,MMonthMar,MMonthApr,MMonthMay,MMonthJun,
+         MMonthJul,MMonthAug,MMonthSep,MMonthOct,MMonthNov,MMonthDec
+  };
+  return St(MonthID[Month]);
+}
+#endif
diff --git a/third_party/unrar/src/uisilent.cpp b/third_party/unrar/src/uisilent.cpp
new file mode 100644
index 0000000..87e5638
--- /dev/null
+++ b/third_party/unrar/src/uisilent.cpp
@@ -0,0 +1,63 @@
+// Purely user interface function. Gets and returns user input.
+UIASKREP_RESULT uiAskReplace(wchar *Name,size_t MaxNameSize,int64 FileSize,RarTime *FileTime,uint Flags)
+{
+  return UIASKREP_R_REPLACE;
+}
+
+
+
+
+void uiStartArchiveExtract(bool Extract,const wchar *ArcName)
+{
+}
+
+
+bool uiStartFileExtract(const wchar *FileName,bool Extract,bool Test,bool Skip)
+{
+  return true;
+}
+
+
+void uiExtractProgress(int64 CurFileSize,int64 TotalFileSize,int64 CurSize,int64 TotalSize)
+{
+}
+
+
+void uiProcessProgress(const char *Command,int64 CurSize,int64 TotalSize)
+{
+}
+
+
+void uiMsgStore::Msg()
+{
+}
+
+
+bool uiGetPassword(UIPASSWORD_TYPE Type,const wchar *FileName,SecPassword *Password)
+{
+  return false;
+}
+
+
+void uiAlarm(UIALARM_TYPE Type)
+{
+}
+
+
+bool uiIsAborted()
+{
+  return false;
+}
+
+
+void uiGiveTick()
+{
+}
+
+
+#ifndef SFX_MODULE
+const wchar *uiGetMonthName(int Month)
+{
+  return L"";
+}
+#endif
diff --git a/third_party/unrar/src/ulinks.cpp b/third_party/unrar/src/ulinks.cpp
new file mode 100644
index 0000000..1656824f
--- /dev/null
+++ b/third_party/unrar/src/ulinks.cpp
@@ -0,0 +1,105 @@
+
+
+static bool UnixSymlink(const char *Target,const wchar *LinkName,RarTime *ftm,RarTime *fta)
+{
+  CreatePath(LinkName,true);
+  DelFile(LinkName);
+  char LinkNameA[NM];
+  WideToChar(LinkName,LinkNameA,ASIZE(LinkNameA));
+  if (symlink(Target,LinkNameA)==-1) // Error.
+  {
+    if (errno==EEXIST)
+      uiMsg(UIERROR_ULINKEXIST,LinkName);
+    else
+    {
+      uiMsg(UIERROR_SLINKCREATE,UINULL,LinkName);
+      ErrHandler.SetErrorCode(RARX_WARNING);
+    }
+    return false;
+  }
+#ifdef USE_LUTIMES
+#ifdef UNIX_TIME_NS
+  timespec times[2];
+  times[0].tv_sec=fta->GetUnix();
+  times[0].tv_nsec=fta->IsSet() ? long(fta->GetUnixNS()%1000000000) : UTIME_NOW;
+  times[1].tv_sec=ftm->GetUnix();
+  times[1].tv_nsec=ftm->IsSet() ? long(ftm->GetUnixNS()%1000000000) : UTIME_NOW;
+  utimensat(AT_FDCWD,LinkNameA,times,AT_SYMLINK_NOFOLLOW);
+#else
+  struct timeval tv[2];
+  tv[0].tv_sec=fta->GetUnix();
+  tv[0].tv_usec=long(fta->GetUnixNS()%1000000000/1000);
+  tv[1].tv_sec=ftm->GetUnix();
+  tv[1].tv_usec=long(ftm->GetUnixNS()%1000000000/1000);
+  lutimes(LinkNameA,tv);
+#endif
+#endif
+
+  return true;
+}
+
+
+static bool IsFullPath(const char *PathA) // Unix ASCII version.
+{
+  return *PathA==CPATHDIVIDER;
+}
+
+
+bool ExtractUnixLink30(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName)
+{
+  char Target[NM];
+  if (IsLink(Arc.FileHead.FileAttr))
+  {
+    size_t DataSize=(size_t)Arc.FileHead.PackSize;
+    if (DataSize>ASIZE(Target)-1)
+      return false;
+    if ((size_t)DataIO.UnpRead((byte *)Target,DataSize)!=DataSize)
+      return false;
+    Target[DataSize]=0;
+
+    DataIO.UnpHash.Init(Arc.FileHead.FileHash.Type,1);
+    DataIO.UnpHash.Update(Target,strlen(Target));
+    DataIO.UnpHash.Result(&Arc.FileHead.FileHash);
+
+    // Return true in case of bad checksum, so link will be processed further
+    // and extraction routine will report the checksum error.
+    if (!DataIO.UnpHash.Cmp(&Arc.FileHead.FileHash,Arc.FileHead.UseHashKey ? Arc.FileHead.HashKey:NULL))
+      return true;
+
+    wchar TargetW[NM];
+    CharToWide(Target,TargetW,ASIZE(TargetW));
+    // Check for *TargetW==0 to catch CharToWide failure.
+    // Use Arc.FileHead.FileName instead of LinkName, since LinkName
+    // can include the destination path as a prefix, which can
+    // confuse IsRelativeSymlinkSafe algorithm.
+    if (!Cmd->AbsoluteLinks && (*TargetW==0 || IsFullPath(TargetW) ||
+        !IsRelativeSymlinkSafe(Cmd,Arc.FileHead.FileName,LinkName,TargetW)))
+      return false;
+    return UnixSymlink(Target,LinkName,&Arc.FileHead.mtime,&Arc.FileHead.atime);
+  }
+  return false;
+}
+
+
+bool ExtractUnixLink50(CommandData *Cmd,const wchar *Name,FileHeader *hd)
+{
+  char Target[NM];
+  WideToChar(hd->RedirName,Target,ASIZE(Target));
+  if (hd->RedirType==FSREDIR_WINSYMLINK || hd->RedirType==FSREDIR_JUNCTION)
+  {
+    // Cannot create Windows absolute path symlinks in Unix. Only relative path
+    // Windows symlinks can be created here. RAR 5.0 used \??\ prefix
+    // for Windows absolute symlinks, since RAR 5.1 /??/ is used.
+    // We escape ? as \? to avoid "trigraph" warning
+    if (strncmp(Target,"\\??\\",4)==0 || strncmp(Target,"/\?\?/",4)==0)
+      return false;
+    DosSlashToUnix(Target,Target,ASIZE(Target));
+  }
+  // Use hd->FileName instead of LinkName, since LinkName can include
+  // the destination path as a prefix, which can confuse
+  // IsRelativeSymlinkSafe algorithm.
+  if (!Cmd->AbsoluteLinks && (IsFullPath(Target) ||
+      !IsRelativeSymlinkSafe(Cmd,hd->FileName,Name,hd->RedirName)))
+    return false;
+  return UnixSymlink(Target,Name,&hd->mtime,&hd->atime);
+}
diff --git a/third_party/unrar/src/unicode.cpp b/third_party/unrar/src/unicode.cpp
new file mode 100644
index 0000000..9f002ac
--- /dev/null
+++ b/third_party/unrar/src/unicode.cpp
@@ -0,0 +1,645 @@
+#include "rar.hpp"
+#define MBFUNCTIONS
+
+#if defined(_UNIX) && defined(MBFUNCTIONS)
+
+static bool WideToCharMap(const wchar *Src,char *Dest,size_t DestSize,bool &Success);
+static void CharToWideMap(const char *Src,wchar *Dest,size_t DestSize,bool &Success);
+
+// In Unix we map high ASCII characters which cannot be converted to Unicode
+// to 0xE000 - 0xE0FF private use Unicode area.
+static const uint MapAreaStart=0xE000;
+
+// Mapped string marker. Initially we used 0xFFFF for this purpose,
+// but it causes MSVC2008 swprintf to fail (it treats 0xFFFF as error marker).
+// While we could workaround it, it is safer to use another character.
+static const uint MappedStringMark=0xFFFE;
+
+#endif
+
+bool WideToChar(const wchar *Src,char *Dest,size_t DestSize)
+{
+  bool RetCode=true;
+  *Dest=0; // Set 'Dest' to zero just in case the conversion will fail.
+
+#ifdef _WIN_ALL
+  if (WideCharToMultiByte(CP_ACP,0,Src,-1,Dest,(int)DestSize,NULL,NULL)==0)
+    RetCode=false;
+
+// wcstombs is broken in Android NDK r9.
+#elif defined(_APPLE)
+  WideToUtf(Src,Dest,DestSize);
+
+#elif defined(MBFUNCTIONS)
+  if (!WideToCharMap(Src,Dest,DestSize,RetCode))
+  {
+    mbstate_t ps; // Use thread safe external state based functions.
+    memset (&ps, 0, sizeof(ps));
+    const wchar *SrcParam=Src; // wcsrtombs can change the pointer.
+
+    // Some implementations of wcsrtombs can cause memory analyzing tools
+    // like valgrind to report uninitialized data access. It happens because
+    // internally these implementations call SSE4 based wcslen function,
+    // which reads 16 bytes at once including those beyond of trailing 0.
+    size_t ResultingSize=wcsrtombs(Dest,&SrcParam,DestSize,&ps);
+
+    if (ResultingSize==(size_t)-1 && errno==EILSEQ)
+    {
+      // Aborted on inconvertible character not zero terminating the result.
+      // EILSEQ helps to distinguish it from small output buffer abort.
+      // We want to convert as much as we can, so we clean the output buffer
+      // and repeat conversion.
+      memset (&ps, 0, sizeof(ps));
+      SrcParam=Src; // wcsrtombs can change the pointer.
+      memset(Dest,0,DestSize);
+      ResultingSize=wcsrtombs(Dest,&SrcParam,DestSize,&ps);
+    }
+
+    if (ResultingSize==(size_t)-1)
+      RetCode=false;
+    if (ResultingSize==0 && *Src!=0)
+      RetCode=false;
+  }
+#else
+  for (int I=0;I<DestSize;I++)
+  {
+    Dest[I]=(char)Src[I];
+    if (Src[I]==0)
+      break;
+  }
+#endif
+  if (DestSize>0)
+    Dest[DestSize-1]=0;
+  
+  // We tried to return the empty string if conversion is failed,
+  // but it does not work well. WideCharToMultiByte returns 'failed' code
+  // and partially converted string even if we wanted to convert only a part
+  // of string and passed DestSize smaller than required for fully converted
+  // string. Such call is the valid behavior in RAR code and we do not expect
+  // the empty string in this case.
+
+  return RetCode;
+}
+
+
+bool CharToWide(const char *Src,wchar *Dest,size_t DestSize)
+{
+  bool RetCode=true;
+  *Dest=0; // Set 'Dest' to zero just in case the conversion will fail.
+
+#ifdef _WIN_ALL
+  if (MultiByteToWideChar(CP_ACP,0,Src,-1,Dest,(int)DestSize)==0)
+    RetCode=false;
+
+// mbstowcs is broken in Android NDK r9.
+#elif defined(_APPLE)
+  UtfToWide(Src,Dest,DestSize);
+
+#elif defined(MBFUNCTIONS)
+  mbstate_t ps;
+  memset (&ps, 0, sizeof(ps));
+  const char *SrcParam=Src; // mbsrtowcs can change the pointer.
+  size_t ResultingSize=mbsrtowcs(Dest,&SrcParam,DestSize,&ps);
+  if (ResultingSize==(size_t)-1)
+    RetCode=false;
+  if (ResultingSize==0 && *Src!=0)
+    RetCode=false;
+
+  if (RetCode==false && DestSize>1)
+    CharToWideMap(Src,Dest,DestSize,RetCode);
+#else
+  for (int I=0;I<DestSize;I++)
+  {
+    Dest[I]=(wchar_t)Src[I];
+    if (Src[I]==0)
+      break;
+  }
+#endif
+  if (DestSize>0)
+    Dest[DestSize-1]=0;
+
+  // We tried to return the empty string if conversion is failed,
+  // but it does not work well. MultiByteToWideChar returns 'failed' code
+  // even if we wanted to convert only a part of string and passed DestSize
+  // smaller than required for fully converted string. Such call is the valid
+  // behavior in RAR code and we do not expect the empty string in this case.
+
+  return RetCode;
+}
+
+
+#if defined(_UNIX) && defined(MBFUNCTIONS)
+// Convert and restore mapped inconvertible Unicode characters. 
+// We use it for extended ASCII names in Unix.
+bool WideToCharMap(const wchar *Src,char *Dest,size_t DestSize,bool &Success)
+{
+  // String with inconvertible characters mapped to private use Unicode area
+  // must have the mark code somewhere.
+  if (wcschr(Src,(wchar)MappedStringMark)==NULL)
+    return false;
+
+  Success=true;
+  uint SrcPos=0,DestPos=0;
+  while (Src[SrcPos]!=0 && DestPos<DestSize-MB_CUR_MAX)
+  {
+    if (uint(Src[SrcPos])==MappedStringMark)
+    {
+      SrcPos++;
+      continue;
+    }
+    // For security reasons do not restore low ASCII codes, so mapping cannot
+    // be used to hide control codes like path separators.
+    if (uint(Src[SrcPos])>=MapAreaStart+0x80 && uint(Src[SrcPos])<MapAreaStart+0x100)
+      Dest[DestPos++]=char(uint(Src[SrcPos++])-MapAreaStart);
+    else
+    {
+      mbstate_t ps;
+      memset(&ps,0,sizeof(ps));
+      if (wcrtomb(Dest+DestPos,Src[SrcPos],&ps)==(size_t)-1)
+      {
+        Dest[DestPos]='_';
+        Success=false;
+      }
+      SrcPos++;
+      memset(&ps,0,sizeof(ps));
+      int Length=mbrlen(Dest+DestPos,MB_CUR_MAX,&ps);
+      DestPos+=Max(Length,1);
+    }
+  }
+  Dest[Min(DestPos,DestSize-1)]=0;
+  return true;
+}
+#endif
+
+
+#if defined(_UNIX) && defined(MBFUNCTIONS)
+// Convert and map inconvertible Unicode characters. 
+// We use it for extended ASCII names in Unix.
+void CharToWideMap(const char *Src,wchar *Dest,size_t DestSize,bool &Success)
+{
+  // Map inconvertible characters to private use Unicode area 0xE000.
+  // Mark such string by placing special non-character code before
+  // first inconvertible character.
+  Success=false;
+  bool MarkAdded=false;
+  uint SrcPos=0,DestPos=0;
+  while (DestPos<DestSize)
+  {
+    if (Src[SrcPos]==0)
+    {
+      Success=true;
+      break;
+    }
+    mbstate_t ps;
+    memset(&ps,0,sizeof(ps));
+    size_t res=mbrtowc(Dest+DestPos,Src+SrcPos,MB_CUR_MAX,&ps);
+    if (res==(size_t)-1 || res==(size_t)-2)
+    {
+      // For security reasons we do not want to map low ASCII characters,
+      // so we do not have additional .. and path separator codes.
+      if (byte(Src[SrcPos])>=0x80)
+      {
+        if (!MarkAdded)
+        {
+          Dest[DestPos++]=MappedStringMark;
+          MarkAdded=true;
+          if (DestPos>=DestSize)
+            break;
+        }
+        Dest[DestPos++]=byte(Src[SrcPos++])+MapAreaStart;
+      }
+      else
+        break;
+    }
+    else
+    {
+      memset(&ps,0,sizeof(ps));
+      int Length=mbrlen(Src+SrcPos,MB_CUR_MAX,&ps);
+      SrcPos+=Max(Length,1);
+      DestPos++;
+    }
+  }
+  Dest[Min(DestPos,DestSize-1)]=0;
+}
+#endif
+
+
+// SrcSize is in wide characters, not in bytes.
+byte* WideToRaw(const wchar *Src,byte *Dest,size_t SrcSize)
+{
+  for (size_t I=0;I<SrcSize;I++,Src++)
+  {
+    Dest[I*2]=(byte)*Src;
+    Dest[I*2+1]=(byte)(*Src>>8);
+    if (*Src==0)
+      break;
+  }
+  return Dest;
+}
+
+
+wchar* RawToWide(const byte *Src,wchar *Dest,size_t DestSize)
+{
+  for (size_t I=0;I<DestSize;I++)
+    if ((Dest[I]=Src[I*2]+(Src[I*2+1]<<8))==0)
+      break;
+  return Dest;
+}
+
+
+void WideToUtf(const wchar *Src,char *Dest,size_t DestSize)
+{
+  long dsize=(long)DestSize;
+  dsize--;
+  while (*Src!=0 && --dsize>=0)
+  {
+    uint c=*(Src++);
+    if (c<0x80)
+      *(Dest++)=c;
+    else
+      if (c<0x800 && --dsize>=0)
+      {
+        *(Dest++)=(0xc0|(c>>6));
+        *(Dest++)=(0x80|(c&0x3f));
+      }
+      else
+      {
+        if (c>=0xd800 && c<=0xdbff && *Src>=0xdc00 && *Src<=0xdfff) // Surrogate pair.
+        {
+          c=((c-0xd800)<<10)+(*Src-0xdc00)+0x10000;
+          Src++;
+        }
+        if (c<0x10000 && (dsize-=2)>=0)
+        {
+          *(Dest++)=(0xe0|(c>>12));
+          *(Dest++)=(0x80|((c>>6)&0x3f));
+          *(Dest++)=(0x80|(c&0x3f));
+        }
+        else
+          if (c < 0x200000 && (dsize-=3)>=0)
+          {
+            *(Dest++)=(0xf0|(c>>18));
+            *(Dest++)=(0x80|((c>>12)&0x3f));
+            *(Dest++)=(0x80|((c>>6)&0x3f));
+            *(Dest++)=(0x80|(c&0x3f));
+          }
+      }
+  }
+  *Dest=0;
+}
+
+
+size_t WideToUtfSize(const wchar *Src)
+{
+  size_t Size=0;
+  for (;*Src!=0;Src++)
+    if (*Src<0x80)
+      Size++;
+    else
+      if (*Src<0x800)
+        Size+=2;
+      else
+        if (*Src<0x10000)
+        {
+          if (Src[0]>=0xd800 && Src[0]<=0xdbff && Src[1]>=0xdc00 && Src[1]<=0xdfff)
+          {
+            Size+=4; // 4 output bytes for Unicode surrogate pair.
+            Src++;
+          }
+          else
+            Size+=3;
+        }
+        else
+          if (*Src<0x200000)
+            Size+=4;
+  return Size+1; // Include terminating zero.
+}
+
+
+bool UtfToWide(const char *Src,wchar *Dest,size_t DestSize)
+{
+  bool Success=true;
+  long dsize=(long)DestSize;
+  dsize--;
+  while (*Src!=0)
+  {
+    uint c=byte(*(Src++)),d;
+    if (c<0x80)
+      d=c;
+    else
+      if ((c>>5)==6)
+      {
+        if ((*Src&0xc0)!=0x80)
+        {
+          Success=false;
+          break;
+        }
+        d=((c&0x1f)<<6)|(*Src&0x3f);
+        Src++;
+      }
+      else
+        if ((c>>4)==14)
+        {
+          if ((Src[0]&0xc0)!=0x80 || (Src[1]&0xc0)!=0x80)
+          {
+            Success=false;
+            break;
+          }
+          d=((c&0xf)<<12)|((Src[0]&0x3f)<<6)|(Src[1]&0x3f);
+          Src+=2;
+        }
+        else
+          if ((c>>3)==30)
+          {
+            if ((Src[0]&0xc0)!=0x80 || (Src[1]&0xc0)!=0x80 || (Src[2]&0xc0)!=0x80)
+            {
+              Success=false;
+              break;
+            }
+            d=((c&7)<<18)|((Src[0]&0x3f)<<12)|((Src[1]&0x3f)<<6)|(Src[2]&0x3f);
+            Src+=3;
+          }
+          else
+          {
+            Success=false;
+            break;
+          }
+    if (--dsize<0)
+      break;
+    if (d>0xffff)
+    {
+      if (--dsize<0)
+        break;
+      if (d>0x10ffff) // UTF-8 must end at 0x10ffff according to RFC 3629.
+      {
+        Success=false;
+        continue;
+      }
+      if (sizeof(*Dest)==2) // Use the surrogate pair.
+      {
+        *(Dest++)=((d-0x10000)>>10)+0xd800;
+        *(Dest++)=(d&0x3ff)+0xdc00;
+      }
+      else
+        *(Dest++)=d;
+    }
+    else
+      *(Dest++)=d;
+  }
+  *Dest=0;
+  return Success;
+}
+
+
+// For zero terminated strings.
+bool IsTextUtf8(const byte *Src)
+{
+  return IsTextUtf8(Src,strlen((const char *)Src));
+}
+
+
+// Source data can be both with and without UTF-8 BOM.
+bool IsTextUtf8(const byte *Src,size_t SrcSize)
+{
+  while (SrcSize-- > 0)
+  {
+    byte C=*(Src++);
+    int HighOne=0; // Number of leftmost '1' bits.
+    for (byte Mask=0x80;Mask!=0 && (C & Mask)!=0;Mask>>=1)
+      HighOne++;
+    if (HighOne==1 || HighOne>6)
+      return false;
+    while (--HighOne > 0)
+      if (SrcSize-- <= 0 || (*(Src++) & 0xc0)!=0x80)
+        return false;
+  }
+  return true;
+}
+
+
+int wcsicomp(const wchar *s1,const wchar *s2)
+{
+#ifdef _WIN_ALL
+  return CompareStringW(LOCALE_USER_DEFAULT,NORM_IGNORECASE|SORT_STRINGSORT,s1,-1,s2,-1)-2;
+#else
+  while (true)
+  {
+    wchar u1 = towupper(*s1);
+    wchar u2 = towupper(*s2);
+    if (u1 != u2)
+      return u1 < u2 ? -1 : 1;
+    if (*s1==0)
+      break;
+    s1++;
+    s2++;
+  }
+  return 0;
+#endif
+}
+
+
+int wcsnicomp(const wchar *s1,const wchar *s2,size_t n)
+{
+#ifdef _WIN_ALL
+  // If we specify 'n' exceeding the actual string length, CompareString goes
+  // beyond the trailing zero and compares garbage. So we need to limit 'n'
+  // to real string length.
+  size_t l1=Min(wcslen(s1)+1,n);
+  size_t l2=Min(wcslen(s2)+1,n);
+  return CompareStringW(LOCALE_USER_DEFAULT,NORM_IGNORECASE|SORT_STRINGSORT,s1,(int)l1,s2,(int)l2)-2;
+#else
+  if (n==0)
+    return 0;
+  while (true)
+  {
+    wchar u1 = towupper(*s1);
+    wchar u2 = towupper(*s2);
+    if (u1 != u2)
+      return u1 < u2 ? -1 : 1;
+    if (*s1==0 || --n==0)
+      break;
+    s1++;
+    s2++;
+  }
+  return 0;
+#endif
+}
+
+
+const wchar_t* wcscasestr(const wchar_t *str, const wchar_t *search)
+{
+  for (size_t i=0;str[i]!=0;i++)
+    for (size_t j=0;;j++)
+    {
+      if (search[j]==0)
+        return str+i;
+      if (tolowerw(str[i+j])!=tolowerw(search[j]))
+        break;
+    }
+  return NULL;
+}
+
+
+#ifndef SFX_MODULE
+wchar* wcslower(wchar *s)
+{
+#ifdef _WIN_ALL
+  CharLower(s);
+#else
+  for (wchar *c=s;*c!=0;c++)
+    *c=towlower(*c);
+#endif
+  return s;
+}
+#endif
+
+
+#ifndef SFX_MODULE
+wchar* wcsupper(wchar *s)
+{
+#ifdef _WIN_ALL
+  CharUpper(s);
+#else
+  for (wchar *c=s;*c!=0;c++)
+    *c=towupper(*c);
+#endif
+  return s;
+}
+#endif
+
+
+
+
+int toupperw(int ch)
+{
+#if defined(_WIN_ALL)
+  // CharUpper is more reliable than towupper in Windows, which seems to be
+  // C locale dependent even in Unicode version. For example, towupper failed
+  // to convert lowercase Russian characters.
+  return (int)(INT_PTR)CharUpper((wchar *)(INT_PTR)ch);
+#else
+  return towupper(ch);
+#endif
+}
+
+
+int tolowerw(int ch)
+{
+#if defined(_WIN_ALL)
+  // CharLower is more reliable than towlower in Windows.
+  // See comment for towupper above.
+  return (int)(INT_PTR)CharLower((wchar *)(INT_PTR)ch);
+#else
+  return towlower(ch);
+#endif
+}
+
+
+int atoiw(const wchar *s)
+{
+  return (int)atoilw(s);
+}
+
+
+int64 atoilw(const wchar *s)
+{
+  bool sign=false;
+  if (*s=='-')
+  {
+    s++;
+    sign=true;
+  }
+  // Use unsigned type here, since long string can overflow the variable
+  // and signed integer overflow is undefined behavior in C++.
+  uint64 n=0;
+  while (*s>='0' && *s<='9')
+  {
+    n=n*10+(*s-'0');
+    s++;
+  }
+  // Check int64(n)>=0 to avoid the signed overflow with undefined behavior
+  // when negating 0x8000000000000000.
+  return sign && int64(n)>=0 ? -int64(n) : int64(n);
+}
+
+
+#ifdef DBCS_SUPPORTED
+SupportDBCS gdbcs;
+
+SupportDBCS::SupportDBCS()
+{
+  Init();
+}
+
+
+void SupportDBCS::Init()
+{
+  CPINFO CPInfo;
+  GetCPInfo(CP_ACP,&CPInfo);
+  DBCSMode=CPInfo.MaxCharSize > 1;
+  for (uint I=0;I<ASIZE(IsLeadByte);I++)
+    IsLeadByte[I]=IsDBCSLeadByte(I)!=0;
+}
+
+
+char* SupportDBCS::charnext(const char *s)
+{
+  // Zero cannot be the trail byte. So if next byte after the lead byte
+  // is 0, the string is corrupt and we'll better return the pointer to 0,
+  // to break string processing loops.
+  return (char *)(IsLeadByte[(byte)*s] && s[1]!=0 ? s+2:s+1);
+}
+
+
+size_t SupportDBCS::strlend(const char *s)
+{
+  size_t Length=0;
+  while (*s!=0)
+  {
+    if (IsLeadByte[(byte)*s])
+      s+=2;
+    else
+      s++;
+    Length++;
+  }
+  return(Length);
+}
+
+
+char* SupportDBCS::strchrd(const char *s, int c)
+{
+  while (*s!=0)
+    if (IsLeadByte[(byte)*s])
+      s+=2;
+    else
+      if (*s==c)
+        return((char *)s);
+      else
+        s++;
+  return(NULL);
+}
+
+
+void SupportDBCS::copychrd(char *dest,const char *src)
+{
+  dest[0]=src[0];
+  if (IsLeadByte[(byte)src[0]])
+    dest[1]=src[1];
+}
+
+
+char* SupportDBCS::strrchrd(const char *s, int c)
+{
+  const char *found=NULL;
+  while (*s!=0)
+    if (IsLeadByte[(byte)*s])
+      s+=2;
+    else
+    {
+      if (*s==c)
+        found=s;
+      s++;
+    }
+  return((char *)found);
+}
+#endif
diff --git a/third_party/unrar/src/unicode.hpp b/third_party/unrar/src/unicode.hpp
new file mode 100644
index 0000000..e38667d9
--- /dev/null
+++ b/third_party/unrar/src/unicode.hpp
@@ -0,0 +1,66 @@
+#ifndef _RAR_UNICODE_
+#define _RAR_UNICODE_
+
+#if defined( _WIN_ALL)
+#define DBCS_SUPPORTED
+#endif
+
+bool WideToChar(const wchar *Src,char *Dest,size_t DestSize);
+bool CharToWide(const char *Src,wchar *Dest,size_t DestSize);
+byte* WideToRaw(const wchar *Src,byte *Dest,size_t SrcSize);
+wchar* RawToWide(const byte *Src,wchar *Dest,size_t DestSize);
+void WideToUtf(const wchar *Src,char *Dest,size_t DestSize);
+size_t WideToUtfSize(const wchar *Src);
+bool UtfToWide(const char *Src,wchar *Dest,size_t DestSize);
+bool IsTextUtf8(const byte *Src);
+bool IsTextUtf8(const byte *Src,size_t SrcSize);
+
+int wcsicomp(const wchar *s1,const wchar *s2);
+int wcsnicomp(const wchar *s1,const wchar *s2,size_t n);
+const wchar_t* wcscasestr(const wchar_t *str, const wchar_t *search);
+#ifndef SFX_MODULE
+wchar* wcslower(wchar *s);
+wchar* wcsupper(wchar *s);
+#endif
+int toupperw(int ch);
+int tolowerw(int ch);
+int atoiw(const wchar *s);
+int64 atoilw(const wchar *s);
+
+#ifdef DBCS_SUPPORTED
+class SupportDBCS
+{
+  public:
+    SupportDBCS();
+    void Init();
+
+    char* charnext(const char *s);
+    size_t strlend(const char *s);
+    char *strchrd(const char *s, int c);
+    char *strrchrd(const char *s, int c);
+    void copychrd(char *dest,const char *src);
+
+    bool IsLeadByte[256];
+    bool DBCSMode;
+};
+
+extern SupportDBCS gdbcs;
+
+inline char* charnext(const char *s) {return (char *)(gdbcs.DBCSMode ? gdbcs.charnext(s):s+1);}
+inline size_t strlend(const char *s) {return (uint)(gdbcs.DBCSMode ? gdbcs.strlend(s):strlen(s));}
+inline char* strchrd(const char *s, int c) {return (char *)(gdbcs.DBCSMode ? gdbcs.strchrd(s,c):strchr(s,c));}
+inline char* strrchrd(const char *s, int c) {return (char *)(gdbcs.DBCSMode ? gdbcs.strrchrd(s,c):strrchr(s,c));}
+inline void copychrd(char *dest,const char *src) {if (gdbcs.DBCSMode) gdbcs.copychrd(dest,src); else *dest=*src;}
+inline bool IsDBCSMode() {return(gdbcs.DBCSMode);}
+inline void InitDBCS() {gdbcs.Init();}
+
+#else
+#define charnext(s) ((s)+1)
+#define strlend strlen
+#define strchrd strchr
+#define strrchrd strrchr
+#define IsDBCSMode() (true)
+inline void copychrd(char *dest,const char *src) {*dest=*src;}
+#endif
+
+#endif
diff --git a/third_party/unrar/src/unpack.cpp b/third_party/unrar/src/unpack.cpp
new file mode 100644
index 0000000..0163c49
--- /dev/null
+++ b/third_party/unrar/src/unpack.cpp
@@ -0,0 +1,354 @@
+#include "rar.hpp"
+
+#include "coder.cpp"
+#include "suballoc.cpp"
+#include "model.cpp"
+#include "unpackinline.cpp"
+#ifdef RAR_SMP
+#include "unpack50mt.cpp"
+#endif
+#ifndef SFX_MODULE
+#include "unpack15.cpp"
+#include "unpack20.cpp"
+#endif
+#include "unpack30.cpp"
+#include "unpack50.cpp"
+#include "unpack50frag.cpp"
+
+Unpack::Unpack(ComprDataIO *DataIO)
+:Inp(true),VMCodeInp(true)
+{
+  UnpIO=DataIO;
+  Window=NULL;
+  Fragmented=false;
+  Suspended=false;
+  UnpAllBuf=false;
+  UnpSomeRead=false;
+#ifdef RAR_SMP
+  MaxUserThreads=1;
+  UnpThreadPool=CreateThreadPool();
+  ReadBufMT=NULL;
+  UnpThreadData=NULL;
+#endif
+  MaxWinSize=0;
+  MaxWinMask=0;
+
+  // Perform initialization, which should be done only once for all files.
+  // It prevents crash if first DoUnpack call is later made with wrong
+  // (true) 'Solid' value.
+  UnpInitData(false);
+#ifndef SFX_MODULE
+  // RAR 1.5 decompression initialization
+  UnpInitData15(false);
+  InitHuff();
+#endif
+}
+
+
+Unpack::~Unpack()
+{
+  InitFilters30(false);
+
+  if (Window!=NULL)
+    free(Window);
+#ifdef RAR_SMP
+  DestroyThreadPool(UnpThreadPool);
+  delete[] ReadBufMT;
+  delete[] UnpThreadData;
+#endif
+}
+
+
+void Unpack::Init(size_t WinSize,bool Solid)
+{
+  // If 32-bit RAR unpacks an archive with 4 GB dictionary, the window size
+  // will be 0 because of size_t overflow. Let's issue the memory error.
+  if (WinSize==0)
+    ErrHandler.MemoryError();
+
+  // Minimum window size must be at least twice more than maximum possible
+  // size of filter block, which is 0x10000 in RAR now. If window size is
+  // smaller, we can have a block with never cleared flt->NextWindow flag
+  // in UnpWriteBuf(). Minimum window size 0x20000 would be enough, but let's
+  // use 0x40000 for extra safety and possible filter area size expansion.
+  const size_t MinAllocSize=0x40000;
+  if (WinSize<MinAllocSize)
+    WinSize=MinAllocSize;
+
+  if (WinSize<=MaxWinSize) // Use the already allocated window.
+    return;
+  if ((WinSize>>16)>0x10000) // Window size must not exceed 4 GB.
+    return;
+
+  // Archiving code guarantees that window size does not grow in the same
+  // solid stream. So if we are here, we are either creating a new window
+  // or increasing the size of non-solid window. So we could safely reject
+  // current window data without copying them to a new window, though being
+  // extra cautious, we still handle the solid window grow case below.
+  bool Grow=Solid && (Window!=NULL || Fragmented);
+
+  // We do not handle growth for existing fragmented window.
+  if (Grow && Fragmented)
+    throw std::bad_alloc();
+
+  byte *NewWindow=Fragmented ? NULL : (byte *)malloc(WinSize);
+
+  if (NewWindow==NULL)
+    if (Grow || WinSize<0x1000000)
+    {
+      // We do not support growth for new fragmented window.
+      // Also exclude RAR4 and small dictionaries.
+      throw std::bad_alloc();
+    }
+    else
+    {
+      if (Window!=NULL) // If allocated by preceding files.
+      {
+        free(Window);
+        Window=NULL;
+      }
+      FragWindow.Init(WinSize);
+      Fragmented=true;
+    }
+
+  if (!Fragmented)
+  {
+    // Clean the window to generate the same output when unpacking corrupt
+    // RAR files, which may access unused areas of sliding dictionary.
+    memset(NewWindow,0,WinSize);
+
+    // If Window is not NULL, it means that window size has grown.
+    // In solid streams we need to copy data to a new window in such case.
+    // RAR archiving code does not allow it in solid streams now,
+    // but let's implement it anyway just in case we'll change it sometimes.
+    if (Grow)
+      for (size_t I=1;I<=MaxWinSize;I++)
+        NewWindow[(UnpPtr-I)&(WinSize-1)]=Window[(UnpPtr-I)&(MaxWinSize-1)];
+
+    if (Window!=NULL)
+      free(Window);
+    Window=NewWindow;
+  }
+
+  MaxWinSize=WinSize;
+  MaxWinMask=MaxWinSize-1;
+}
+
+
+void Unpack::DoUnpack(uint Method,bool Solid)
+{
+  // Methods <50 will crash in Fragmented mode when accessing NULL Window.
+  // They cannot be called in such mode now, but we check it below anyway
+  // just for extra safety.
+  switch(Method)
+  {
+#ifndef SFX_MODULE
+    case 15: // rar 1.5 compression
+      if (!Fragmented)
+        Unpack15(Solid);
+      break;
+    case 20: // rar 2.x compression
+    case 26: // files larger than 2GB
+      if (!Fragmented)
+        Unpack20(Solid);
+      break;
+#endif
+    case 29: // rar 3.x compression
+      if (!Fragmented)
+        Unpack29(Solid);
+      break;
+    case 50: // RAR 5.0 compression algorithm.
+#ifdef RAR_SMP
+      if (MaxUserThreads>1)
+      {
+//      We do not use the multithreaded unpack routine to repack RAR archives
+//      in 'suspended' mode, because unlike the single threaded code it can
+//      write more than one dictionary for same loop pass. So we would need
+//      larger buffers of unknown size. Also we do not support multithreading
+//      in fragmented window mode.
+          if (!Fragmented)
+          {
+            Unpack5MT(Solid);
+            break;
+          }
+      }
+#endif
+      Unpack5(Solid);
+      break;
+  }
+}
+
+
+void Unpack::UnpInitData(bool Solid)
+{
+  if (!Solid)
+  {
+    memset(OldDist,0,sizeof(OldDist));
+    OldDistPtr=0;
+    LastDist=LastLength=0;
+//    memset(Window,0,MaxWinSize);
+    memset(&BlockTables,0,sizeof(BlockTables));
+    UnpPtr=WrPtr=0;
+    WriteBorder=Min(MaxWinSize,UNPACK_MAX_WRITE)&MaxWinMask;
+  }
+  // Filters never share several solid files, so we can safely reset them
+  // even in solid archive.
+  InitFilters();
+
+  Inp.InitBitInput();
+  WrittenFileSize=0;
+  ReadTop=0;
+  ReadBorder=0;
+
+  memset(&BlockHeader,0,sizeof(BlockHeader));
+  BlockHeader.BlockSize=-1;  // '-1' means not defined yet.
+#ifndef SFX_MODULE
+  UnpInitData20(Solid);
+#endif
+  UnpInitData30(Solid);
+  UnpInitData50(Solid);
+}
+
+
+// LengthTable contains the length in bits for every element of alphabet.
+// Dec is the structure to decode Huffman code/
+// Size is size of length table and DecodeNum field in Dec structure,
+void Unpack::MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size)
+{
+  // Size of alphabet and DecodePos array.
+  Dec->MaxNum=Size;
+
+  // Calculate how many entries for every bit length in LengthTable we have.
+  uint LengthCount[16];
+  memset(LengthCount,0,sizeof(LengthCount));
+  for (size_t I=0;I<Size;I++)
+    LengthCount[LengthTable[I] & 0xf]++;
+
+  // We must not calculate the number of zero length codes.
+  LengthCount[0]=0;
+
+  // Set the entire DecodeNum to zero.
+  memset(Dec->DecodeNum,0,Size*sizeof(*Dec->DecodeNum));
+
+  // Initialize not really used entry for zero length code.
+  Dec->DecodePos[0]=0;
+
+  // Start code for bit length 1 is 0.
+  Dec->DecodeLen[0]=0;
+
+  // Right aligned upper limit code for current bit length.
+  uint UpperLimit=0;
+
+  for (size_t I=1;I<16;I++)
+  {
+    // Adjust the upper limit code.
+    UpperLimit+=LengthCount[I];
+
+    // Left aligned upper limit code.
+    uint LeftAligned=UpperLimit<<(16-I);
+
+    // Prepare the upper limit code for next bit length.
+    UpperLimit*=2;
+
+    // Store the left aligned upper limit code.
+    Dec->DecodeLen[I]=(uint)LeftAligned;
+
+    // Every item of this array contains the sum of all preceding items.
+    // So it contains the start position in code list for every bit length. 
+    Dec->DecodePos[I]=Dec->DecodePos[I-1]+LengthCount[I-1];
+  }
+
+  // Prepare the copy of DecodePos. We'll modify this copy below,
+  // so we cannot use the original DecodePos.
+  uint CopyDecodePos[ASIZE(Dec->DecodePos)];
+  memcpy(CopyDecodePos,Dec->DecodePos,sizeof(CopyDecodePos));
+
+  // For every bit length in the bit length table and so for every item
+  // of alphabet.
+  for (uint I=0;I<Size;I++)
+  {
+    // Get the current bit length.
+    byte CurBitLength=LengthTable[I] & 0xf;
+
+    if (CurBitLength!=0)
+    {
+      // Last position in code list for current bit length.
+      uint LastPos=CopyDecodePos[CurBitLength];
+
+      // Prepare the decode table, so this position in code list will be
+      // decoded to current alphabet item number.
+      Dec->DecodeNum[LastPos]=(ushort)I;
+
+      // We'll use next position number for this bit length next time.
+      // So we pass through the entire range of positions available
+      // for every bit length.
+      CopyDecodePos[CurBitLength]++;
+    }
+  }
+
+  // Define the number of bits to process in quick mode. We use more bits
+  // for larger alphabets. More bits means that more codes will be processed
+  // in quick mode, but also that more time will be spent to preparation
+  // of tables for quick decode.
+  switch (Size)
+  {
+    case NC:
+    case NC20:
+    case NC30:
+      Dec->QuickBits=MAX_QUICK_DECODE_BITS;
+      break;
+    default:
+      Dec->QuickBits=MAX_QUICK_DECODE_BITS-3;
+      break;
+  }
+
+  // Size of tables for quick mode.
+  uint QuickDataSize=1<<Dec->QuickBits;
+
+  // Bit length for current code, start from 1 bit codes. It is important
+  // to use 1 bit instead of 0 for minimum code length, so we are moving
+  // forward even when processing a corrupt archive.
+  uint CurBitLength=1;
+
+  // For every right aligned bit string which supports the quick decoding.
+  for (uint Code=0;Code<QuickDataSize;Code++)
+  {
+    // Left align the current code, so it will be in usual bit field format.
+    uint BitField=Code<<(16-Dec->QuickBits);
+
+    // Prepare the table for quick decoding of bit lengths.
+  
+    // Find the upper limit for current bit field and adjust the bit length
+    // accordingly if necessary.
+    while (CurBitLength<ASIZE(Dec->DecodeLen) && BitField>=Dec->DecodeLen[CurBitLength])
+      CurBitLength++;
+
+    // Translation of right aligned bit string to bit length.
+    Dec->QuickLen[Code]=CurBitLength;
+
+    // Prepare the table for quick translation of position in code list
+    // to position in alphabet.
+
+    // Calculate the distance from the start code for current bit length.
+    uint Dist=BitField-Dec->DecodeLen[CurBitLength-1];
+
+    // Right align the distance.
+    Dist>>=(16-CurBitLength);
+
+    // Now we can calculate the position in the code list. It is the sum
+    // of first position for current bit length and right aligned distance
+    // between our bit field and start code for current bit length.
+    uint Pos;
+    if (CurBitLength<ASIZE(Dec->DecodePos) &&
+        (Pos=Dec->DecodePos[CurBitLength]+Dist)<Size)
+    {
+      // Define the code to alphabet number translation.
+      Dec->QuickNum[Code]=Dec->DecodeNum[Pos];
+    }
+    else
+    {
+      // Can be here for length table filled with zeroes only (empty).
+      Dec->QuickNum[Code]=0;
+    }
+  }
+}
diff --git a/third_party/unrar/src/unpack.hpp b/third_party/unrar/src/unpack.hpp
new file mode 100644
index 0000000..93d5b87
--- /dev/null
+++ b/third_party/unrar/src/unpack.hpp
@@ -0,0 +1,403 @@
+#ifndef _RAR_UNPACK_
+#define _RAR_UNPACK_
+
+// Maximum allowed number of compressed bits processed in quick mode.
+#define MAX_QUICK_DECODE_BITS      10
+
+// Maximum number of filters per entire data block. Must be at least
+// twice more than MAX_PACK_FILTERS to store filters from two data blocks.
+#define MAX_UNPACK_FILTERS       8192
+
+// Maximum number of filters per entire data block for RAR3 unpack.
+// Must be at least twice more than v3_MAX_PACK_FILTERS to store filters
+// from two data blocks.
+#define MAX3_UNPACK_FILTERS      8192
+
+// Limit maximum number of channels in RAR3 delta filter to some reasonable
+// value to prevent too slow processing of corrupt archives with invalid
+// channels number. Must be equal or larger than v3_MAX_FILTER_CHANNELS.
+// No need to provide it for RAR5, which uses only 5 bits to store channels.
+#define MAX3_UNPACK_CHANNELS      1024
+
+// Maximum size of single filter block. We restrict it to limit memory
+// allocation. Must be equal or larger than MAX_ANALYZE_SIZE.
+#define MAX_FILTER_BLOCK_SIZE 0x400000
+
+// Write data in 4 MB or smaller blocks. Must not exceed PACK_MAX_WRITE,
+// so we keep number of buffered filter in unpacker reasonable.
+#define UNPACK_MAX_WRITE      0x400000
+
+// Decode compressed bit fields to alphabet numbers.
+struct DecodeTable:PackDef
+{
+  // Real size of DecodeNum table.
+  uint MaxNum;
+
+  // Left aligned start and upper limit codes defining code space 
+  // ranges for bit lengths. DecodeLen[BitLength-1] defines the start of
+  // range for bit length and DecodeLen[BitLength] defines next code
+  // after the end of range or in other words the upper limit code
+  // for specified bit length.
+  uint DecodeLen[16]; 
+
+  // Every item of this array contains the sum of all preceding items.
+  // So it contains the start position in code list for every bit length. 
+  uint DecodePos[16];
+
+  // Number of compressed bits processed in quick mode.
+  // Must not exceed MAX_QUICK_DECODE_BITS.
+  uint QuickBits;
+
+  // Translates compressed bits (up to QuickBits length)
+  // to bit length in quick mode.
+  byte QuickLen[1<<MAX_QUICK_DECODE_BITS];
+
+  // Translates compressed bits (up to QuickBits length)
+  // to position in alphabet in quick mode.
+  // 'ushort' saves some memory and even provides a little speed gain
+  // comparting to 'uint' here.
+  ushort QuickNum[1<<MAX_QUICK_DECODE_BITS];
+
+  // Translate the position in code list to position in alphabet.
+  // We do not allocate it dynamically to avoid performance overhead
+  // introduced by pointer, so we use the largest possible table size
+  // as array dimension. Real size of this array is defined in MaxNum.
+  // We use this array if compressed bit field is too lengthy
+  // for QuickLen based translation.
+  // 'ushort' saves some memory and even provides a little speed gain
+  // comparting to 'uint' here.
+  ushort DecodeNum[LARGEST_TABLE_SIZE];
+};
+
+
+struct UnpackBlockHeader
+{
+  int BlockSize;
+  int BlockBitSize;
+  int BlockStart;
+  int HeaderSize;
+  bool LastBlockInFile;
+  bool TablePresent;
+};
+
+
+struct UnpackBlockTables
+{
+  DecodeTable LD;  // Decode literals.
+  DecodeTable DD;  // Decode distances.
+  DecodeTable LDD; // Decode lower bits of distances.
+  DecodeTable RD;  // Decode repeating distances.
+  DecodeTable BD;  // Decode bit lengths in Huffman table.
+};
+
+
+#ifdef RAR_SMP
+enum UNP_DEC_TYPE {
+  UNPDT_LITERAL,UNPDT_MATCH,UNPDT_FULLREP,UNPDT_REP,UNPDT_FILTER
+};
+
+struct UnpackDecodedItem
+{
+  UNP_DEC_TYPE Type;
+  ushort Length;
+  union
+  {
+    uint Distance;
+    byte Literal[4];
+  };
+};
+
+
+struct UnpackThreadData
+{
+  Unpack *UnpackPtr;
+  BitInput Inp;
+  bool HeaderRead;
+  UnpackBlockHeader BlockHeader;
+  bool TableRead;
+  UnpackBlockTables BlockTables;
+  int DataSize;    // Data left in buffer. Can be less than block size.
+  bool DamagedData;
+  bool LargeBlock;
+  bool NoDataLeft; // 'true' if file is read completely.
+  bool Incomplete; // Not entire block was processed, need to read more data.
+
+  UnpackDecodedItem *Decoded;
+  uint DecodedSize;
+  uint DecodedAllocated;
+  uint ThreadNumber; // For debugging.
+
+  UnpackThreadData()
+  :Inp(false)
+  {
+    Decoded=NULL;
+  }
+  ~UnpackThreadData()
+  {
+    if (Decoded!=NULL)
+      free(Decoded);
+  }
+};
+#endif
+
+
+struct UnpackFilter
+{
+  byte Type;
+  uint BlockStart;
+  uint BlockLength;
+  byte Channels;
+//  uint Width;
+//  byte PosR;
+  bool NextWindow;
+};
+
+
+struct UnpackFilter30
+{
+  unsigned int BlockStart;
+  unsigned int BlockLength;
+  bool NextWindow;
+
+  // Position of parent filter in Filters array used as prototype for filter
+  // in PrgStack array. Not defined for filters in Filters array.
+  unsigned int ParentFilter;
+
+  VM_PreparedProgram Prg;
+};
+
+
+struct AudioVariables // For RAR 2.0 archives only.
+{
+  int K1,K2,K3,K4,K5;
+  int D1,D2,D3,D4;
+  int LastDelta;
+  unsigned int Dif[11];
+  unsigned int ByteCount;
+  int LastChar;
+};
+
+
+// We can use the fragmented dictionary in case heap does not have the single
+// large enough memory block. It is slower than normal dictionary.
+class FragmentedWindow
+{
+  private:
+    enum {MAX_MEM_BLOCKS=32};
+
+    void Reset();
+    byte *Mem[MAX_MEM_BLOCKS];
+    size_t MemSize[MAX_MEM_BLOCKS];
+  public:
+    FragmentedWindow();
+    ~FragmentedWindow();
+    void Init(size_t WinSize);
+    byte& operator [](size_t Item);
+    void CopyString(uint Length,uint Distance,size_t &UnpPtr,size_t MaxWinMask);
+    void CopyData(byte *Dest,size_t WinPos,size_t Size);
+    size_t GetBlockSize(size_t StartPos,size_t RequiredSize);
+};
+
+
+class Unpack:PackDef
+{
+  private:
+
+    void Unpack5(bool Solid);
+    void Unpack5MT(bool Solid);
+    bool UnpReadBuf();
+    void UnpWriteBuf();
+    byte* ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt);
+    void UnpWriteArea(size_t StartPtr,size_t EndPtr);
+    void UnpWriteData(byte *Data,size_t Size);
+    _forceinline uint SlotToLength(BitInput &Inp,uint Slot);
+    void UnpInitData50(bool Solid);
+    bool ReadBlockHeader(BitInput &Inp,UnpackBlockHeader &Header);
+    bool ReadTables(BitInput &Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables);
+    void MakeDecodeTables(byte *LengthTable,DecodeTable *Dec,uint Size);
+    _forceinline uint DecodeNumber(BitInput &Inp,DecodeTable *Dec);
+    void CopyString();
+    inline void InsertOldDist(unsigned int Distance);
+    void UnpInitData(bool Solid);
+    _forceinline void CopyString(uint Length,uint Distance);
+    uint ReadFilterData(BitInput &Inp);
+    bool ReadFilter(BitInput &Inp,UnpackFilter &Filter);
+    bool AddFilter(UnpackFilter &Filter);
+    bool AddFilter();
+    void InitFilters();
+
+    ComprDataIO *UnpIO;
+    BitInput Inp;
+
+#ifdef RAR_SMP
+    void InitMT();
+    bool UnpackLargeBlock(UnpackThreadData &D);
+    bool ProcessDecoded(UnpackThreadData &D);
+
+    ThreadPool *UnpThreadPool;
+    UnpackThreadData *UnpThreadData;
+    uint MaxUserThreads;
+    byte *ReadBufMT;
+#endif
+
+    Array<byte> FilterSrcMemory;
+    Array<byte> FilterDstMemory;
+
+    // Filters code, one entry per filter.
+    Array<UnpackFilter> Filters;
+
+    uint OldDist[4],OldDistPtr;
+    uint LastLength;
+
+    // LastDist is necessary only for RAR2 and older with circular OldDist
+    // array. In RAR3 last distance is always stored in OldDist[0].
+    uint LastDist;
+
+    size_t UnpPtr,WrPtr;
+    
+    // Top border of read packed data.
+    int ReadTop; 
+
+    // Border to call UnpReadBuf. We use it instead of (ReadTop-C)
+    // for optimization reasons. Ensures that we have C bytes in buffer
+    // unless we are at the end of file.
+    int ReadBorder;
+
+    UnpackBlockHeader BlockHeader;
+    UnpackBlockTables BlockTables;
+
+    size_t WriteBorder;
+
+    byte *Window;
+
+    FragmentedWindow FragWindow;
+    bool Fragmented;
+
+
+    int64 DestUnpSize;
+
+    bool Suspended;
+    bool UnpAllBuf;
+    bool UnpSomeRead;
+    int64 WrittenFileSize;
+    bool FileExtracted;
+
+
+/***************************** Unpack v 1.5 *********************************/
+    void Unpack15(bool Solid);
+    void ShortLZ();
+    void LongLZ();
+    void HuffDecode();
+    void GetFlagsBuf();
+    void UnpInitData15(int Solid);
+    void InitHuff();
+    void CorrHuff(ushort *CharSet,byte *NumToPlace);
+    void CopyString15(uint Distance,uint Length);
+    uint DecodeNum(uint Num,uint StartPos,uint *DecTab,uint *PosTab);
+
+    ushort ChSet[256],ChSetA[256],ChSetB[256],ChSetC[256];
+    byte NToPl[256],NToPlB[256],NToPlC[256];
+    uint FlagBuf,AvrPlc,AvrPlcB,AvrLn1,AvrLn2,AvrLn3;
+    int Buf60,NumHuf,StMode,LCount,FlagsCnt;
+    uint Nhfb,Nlzb,MaxDist3;
+/***************************** Unpack v 1.5 *********************************/
+
+/***************************** Unpack v 2.0 *********************************/
+    void Unpack20(bool Solid);
+
+    DecodeTable MD[4]; // Decode multimedia data, up to 4 channels.
+
+    unsigned char UnpOldTable20[MC20*4];
+    bool UnpAudioBlock;
+    uint UnpChannels,UnpCurChannel;
+    int UnpChannelDelta;
+    void CopyString20(uint Length,uint Distance);
+    bool ReadTables20();
+    void UnpWriteBuf20();
+    void UnpInitData20(int Solid);
+    void ReadLastTables();
+    byte DecodeAudio(int Delta);
+    struct AudioVariables AudV[4];
+/***************************** Unpack v 2.0 *********************************/
+
+/***************************** Unpack v 3.0 *********************************/
+    enum BLOCK_TYPES {BLOCK_LZ,BLOCK_PPM};
+
+    void UnpInitData30(bool Solid);
+    void Unpack29(bool Solid);
+    void InitFilters30(bool Solid);
+    bool ReadEndOfBlock();
+    bool ReadVMCode();
+    bool ReadVMCodePPM();
+    bool AddVMCode(uint FirstByte,byte *Code,uint CodeSize);
+    int SafePPMDecodeChar();
+    bool ReadTables30();
+    bool UnpReadBuf30();
+    void UnpWriteBuf30();
+    void ExecuteCode(VM_PreparedProgram *Prg);
+
+    int PrevLowDist,LowDistRepCount;
+
+    ModelPPM PPM;
+    int PPMEscChar;
+
+    byte UnpOldTable[HUFF_TABLE_SIZE30];
+    int UnpBlockType;
+
+    // If we already read decoding tables for Unpack v2,v3,v5.
+    // We should not use a single variable for all algorithm versions,
+    // because we can have a corrupt archive with one algorithm file
+    // followed by another algorithm file with "solid" flag and we do not
+    // want to reuse tables from one algorithm in another.
+    bool TablesRead2,TablesRead3,TablesRead5;
+
+    // Virtual machine to execute filters code.
+    RarVM VM;
+  
+    // Buffer to read VM filters code. We moved it here from AddVMCode
+    // function to reduce time spent in BitInput constructor.
+    BitInput VMCodeInp;
+
+    // Filters code, one entry per filter.
+    Array<UnpackFilter30 *> Filters30;
+
+    // Filters stack, several entrances of same filter are possible.
+    Array<UnpackFilter30 *> PrgStack;
+
+    // Lengths of preceding data blocks, one length of one last block
+    // for every filter. Used to reduce the size required to write
+    // the data block length if lengths are repeating.
+    Array<int> OldFilterLengths;
+
+    int LastFilter;
+/***************************** Unpack v 3.0 *********************************/
+
+  public:
+    Unpack(ComprDataIO *DataIO);
+    ~Unpack();
+    void Init(size_t WinSize,bool Solid);
+    void DoUnpack(uint Method,bool Solid);
+    bool IsFileExtracted() {return(FileExtracted);}
+    void SetDestSize(int64 DestSize) {DestUnpSize=DestSize;FileExtracted=false;}
+    void SetSuspended(bool Suspended) {Unpack::Suspended=Suspended;}
+
+#ifdef RAR_SMP
+    // More than 8 threads are unlikely to provide a noticeable gain
+    // for unpacking, but would use the additional memory.
+    void SetThreads(uint Threads) {MaxUserThreads=Min(Threads,8);}
+
+    void UnpackDecode(UnpackThreadData &D);
+#endif
+
+    size_t MaxWinSize;
+    size_t MaxWinMask;
+
+    uint GetChar()
+    {
+      if (Inp.InAddr>BitInput::MAX_SIZE-30)
+        UnpReadBuf();
+      return(Inp.InBuf[Inp.InAddr++]);
+    }
+};
+
+#endif
diff --git a/third_party/unrar/src/unpack15.cpp b/third_party/unrar/src/unpack15.cpp
new file mode 100644
index 0000000..1e7cf76
--- /dev/null
+++ b/third_party/unrar/src/unpack15.cpp
@@ -0,0 +1,489 @@
+#define STARTL1  2
+static unsigned int DecL1[]={0x8000,0xa000,0xc000,0xd000,0xe000,0xea00,
+                             0xee00,0xf000,0xf200,0xf200,0xffff};
+static unsigned int PosL1[]={0,0,0,2,3,5,7,11,16,20,24,32,32};
+
+#define STARTL2  3
+static unsigned int DecL2[]={0xa000,0xc000,0xd000,0xe000,0xea00,0xee00,
+                             0xf000,0xf200,0xf240,0xffff};
+static unsigned int PosL2[]={0,0,0,0,5,7,9,13,18,22,26,34,36};
+
+#define STARTHF0  4
+static unsigned int DecHf0[]={0x8000,0xc000,0xe000,0xf200,0xf200,0xf200,
+                              0xf200,0xf200,0xffff};
+static unsigned int PosHf0[]={0,0,0,0,0,8,16,24,33,33,33,33,33};
+
+
+#define STARTHF1  5
+static unsigned int DecHf1[]={0x2000,0xc000,0xe000,0xf000,0xf200,0xf200,
+                              0xf7e0,0xffff};
+static unsigned int PosHf1[]={0,0,0,0,0,0,4,44,60,76,80,80,127};
+
+
+#define STARTHF2  5
+static unsigned int DecHf2[]={0x1000,0x2400,0x8000,0xc000,0xfa00,0xffff,
+                              0xffff,0xffff};
+static unsigned int PosHf2[]={0,0,0,0,0,0,2,7,53,117,233,0,0};
+
+
+#define STARTHF3  6
+static unsigned int DecHf3[]={0x800,0x2400,0xee00,0xfe80,0xffff,0xffff,
+                              0xffff};
+static unsigned int PosHf3[]={0,0,0,0,0,0,0,2,16,218,251,0,0};
+
+
+#define STARTHF4  8
+static unsigned int DecHf4[]={0xff00,0xffff,0xffff,0xffff,0xffff,0xffff};
+static unsigned int PosHf4[]={0,0,0,0,0,0,0,0,0,255,0,0,0};
+
+
+void Unpack::Unpack15(bool Solid)
+{
+  UnpInitData(Solid);
+  UnpInitData15(Solid);
+  UnpReadBuf();
+  if (!Solid)
+  {
+    InitHuff();
+    UnpPtr=0;
+  }
+  else
+    UnpPtr=WrPtr;
+  --DestUnpSize;
+  if (DestUnpSize>=0)
+  {
+    GetFlagsBuf();
+    FlagsCnt=8;
+  }
+
+  while (DestUnpSize>=0)
+  {
+    UnpPtr&=MaxWinMask;
+
+    if (Inp.InAddr>ReadTop-30 && !UnpReadBuf())
+      break;
+    if (((WrPtr-UnpPtr) & MaxWinMask)<270 && WrPtr!=UnpPtr)
+      UnpWriteBuf20();
+    if (StMode)
+    {
+      HuffDecode();
+      continue;
+    }
+
+    if (--FlagsCnt < 0)
+    {
+      GetFlagsBuf();
+      FlagsCnt=7;
+    }
+
+    if (FlagBuf & 0x80)
+    {
+      FlagBuf<<=1;
+      if (Nlzb > Nhfb)
+        LongLZ();
+      else
+        HuffDecode();
+    }
+    else
+    {
+      FlagBuf<<=1;
+      if (--FlagsCnt < 0)
+      {
+        GetFlagsBuf();
+        FlagsCnt=7;
+      }
+      if (FlagBuf & 0x80)
+      {
+        FlagBuf<<=1;
+        if (Nlzb > Nhfb)
+          HuffDecode();
+        else
+          LongLZ();
+      }
+      else
+      {
+        FlagBuf<<=1;
+        ShortLZ();
+      }
+    }
+  }
+  UnpWriteBuf20();
+}
+
+
+#define GetShortLen1(pos) ((pos)==1 ? Buf60+3:ShortLen1[pos])
+#define GetShortLen2(pos) ((pos)==3 ? Buf60+3:ShortLen2[pos])
+
+void Unpack::ShortLZ()
+{
+  static unsigned int ShortLen1[]={1,3,4,4,5,6,7,8,8,4,4,5,6,6,4,0};
+  static unsigned int ShortXor1[]={0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe,
+                                   0xff,0xc0,0x80,0x90,0x98,0x9c,0xb0};
+  static unsigned int ShortLen2[]={2,3,3,3,4,4,5,6,6,4,4,5,6,6,4,0};
+  static unsigned int ShortXor2[]={0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8,
+                                   0xfc,0xc0,0x80,0x90,0x98,0x9c,0xb0};
+
+
+  unsigned int Length,SaveLength;
+  unsigned int LastDistance;
+  unsigned int Distance;
+  int DistancePlace;
+  NumHuf=0;
+
+  unsigned int BitField=Inp.fgetbits();
+  if (LCount==2)
+  {
+    Inp.faddbits(1);
+    if (BitField >= 0x8000)
+    {
+      CopyString15((unsigned int)LastDist,LastLength);
+      return;
+    }
+    BitField <<= 1;
+    LCount=0;
+  }
+
+  BitField>>=8;
+
+//  not thread safe, replaced by GetShortLen1 and GetShortLen2 macro
+//  ShortLen1[1]=ShortLen2[3]=Buf60+3;
+
+  if (AvrLn1<37)
+  {
+    for (Length=0;;Length++)
+      if (((BitField^ShortXor1[Length]) & (~(0xff>>GetShortLen1(Length))))==0)
+        break;
+    Inp.faddbits(GetShortLen1(Length));
+  }
+  else
+  {
+    for (Length=0;;Length++)
+      if (((BitField^ShortXor2[Length]) & (~(0xff>>GetShortLen2(Length))))==0)
+        break;
+    Inp.faddbits(GetShortLen2(Length));
+  }
+
+  if (Length >= 9)
+  {
+    if (Length == 9)
+    {
+      LCount++;
+      CopyString15((unsigned int)LastDist,LastLength);
+      return;
+    }
+    if (Length == 14)
+    {
+      LCount=0;
+      Length=DecodeNum(Inp.fgetbits(),STARTL2,DecL2,PosL2)+5;
+      Distance=(Inp.fgetbits()>>1) | 0x8000;
+      Inp.faddbits(15);
+      LastLength=Length;
+      LastDist=Distance;
+      CopyString15(Distance,Length);
+      return;
+    }
+
+    LCount=0;
+    SaveLength=Length;
+    Distance=OldDist[(OldDistPtr-(Length-9)) & 3];
+    Length=DecodeNum(Inp.fgetbits(),STARTL1,DecL1,PosL1)+2;
+    if (Length==0x101 && SaveLength==10)
+    {
+      Buf60 ^= 1;
+      return;
+    }
+    if (Distance > 256)
+      Length++;
+    if (Distance >= MaxDist3)
+      Length++;
+
+    OldDist[OldDistPtr++]=Distance;
+    OldDistPtr = OldDistPtr & 3;
+    LastLength=Length;
+    LastDist=Distance;
+    CopyString15(Distance,Length);
+    return;
+  }
+
+  LCount=0;
+  AvrLn1 += Length;
+  AvrLn1 -= AvrLn1 >> 4;
+
+  DistancePlace=DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2) & 0xff;
+  Distance=ChSetA[DistancePlace];
+  if (--DistancePlace != -1)
+  {
+    LastDistance=ChSetA[DistancePlace];
+    ChSetA[DistancePlace+1]=LastDistance;
+    ChSetA[DistancePlace]=Distance;
+  }
+  Length+=2;
+  OldDist[OldDistPtr++] = ++Distance;
+  OldDistPtr = OldDistPtr & 3;
+  LastLength=Length;
+  LastDist=Distance;
+  CopyString15(Distance,Length);
+}
+
+
+void Unpack::LongLZ()
+{
+  unsigned int Length;
+  unsigned int Distance;
+  unsigned int DistancePlace,NewDistancePlace;
+  unsigned int OldAvr2,OldAvr3;
+
+  NumHuf=0;
+  Nlzb+=16;
+  if (Nlzb > 0xff)
+  {
+    Nlzb=0x90;
+    Nhfb >>= 1;
+  }
+  OldAvr2=AvrLn2;
+
+  unsigned int BitField=Inp.fgetbits();
+  if (AvrLn2 >= 122)
+    Length=DecodeNum(BitField,STARTL2,DecL2,PosL2);
+  else
+    if (AvrLn2 >= 64)
+      Length=DecodeNum(BitField,STARTL1,DecL1,PosL1);
+    else
+      if (BitField < 0x100)
+      {
+        Length=BitField;
+        Inp.faddbits(16);
+      }
+      else
+      {
+        for (Length=0;((BitField<<Length)&0x8000)==0;Length++)
+          ;
+        Inp.faddbits(Length+1);
+      }
+
+  AvrLn2 += Length;
+  AvrLn2 -= AvrLn2 >> 5;
+
+  BitField=Inp.fgetbits();
+  if (AvrPlcB > 0x28ff)
+    DistancePlace=DecodeNum(BitField,STARTHF2,DecHf2,PosHf2);
+  else
+    if (AvrPlcB > 0x6ff)
+      DistancePlace=DecodeNum(BitField,STARTHF1,DecHf1,PosHf1);
+    else
+      DistancePlace=DecodeNum(BitField,STARTHF0,DecHf0,PosHf0);
+
+  AvrPlcB += DistancePlace;
+  AvrPlcB -= AvrPlcB >> 8;
+  while (1)
+  {
+    Distance = ChSetB[DistancePlace & 0xff];
+    NewDistancePlace = NToPlB[Distance++ & 0xff]++;
+    if (!(Distance & 0xff))
+      CorrHuff(ChSetB,NToPlB);
+    else
+      break;
+  }
+
+  ChSetB[DistancePlace & 0xff]=ChSetB[NewDistancePlace];
+  ChSetB[NewDistancePlace]=Distance;
+
+  Distance=((Distance & 0xff00) | (Inp.fgetbits() >> 8)) >> 1;
+  Inp.faddbits(7);
+
+  OldAvr3=AvrLn3;
+  if (Length!=1 && Length!=4)
+    if (Length==0 && Distance <= MaxDist3)
+    {
+      AvrLn3++;
+      AvrLn3 -= AvrLn3 >> 8;
+    }
+    else
+      if (AvrLn3 > 0)
+        AvrLn3--;
+  Length+=3;
+  if (Distance >= MaxDist3)
+    Length++;
+  if (Distance <= 256)
+    Length+=8;
+  if (OldAvr3 > 0xb0 || AvrPlc >= 0x2a00 && OldAvr2 < 0x40)
+    MaxDist3=0x7f00;
+  else
+    MaxDist3=0x2001;
+  OldDist[OldDistPtr++]=Distance;
+  OldDistPtr = OldDistPtr & 3;
+  LastLength=Length;
+  LastDist=Distance;
+  CopyString15(Distance,Length);
+}
+
+
+void Unpack::HuffDecode()
+{
+  unsigned int CurByte,NewBytePlace;
+  unsigned int Length;
+  unsigned int Distance;
+  int BytePlace;
+
+  unsigned int BitField=Inp.fgetbits();
+
+  if (AvrPlc > 0x75ff)
+    BytePlace=DecodeNum(BitField,STARTHF4,DecHf4,PosHf4);
+  else
+    if (AvrPlc > 0x5dff)
+      BytePlace=DecodeNum(BitField,STARTHF3,DecHf3,PosHf3);
+    else
+      if (AvrPlc > 0x35ff)
+        BytePlace=DecodeNum(BitField,STARTHF2,DecHf2,PosHf2);
+      else
+        if (AvrPlc > 0x0dff)
+          BytePlace=DecodeNum(BitField,STARTHF1,DecHf1,PosHf1);
+        else
+          BytePlace=DecodeNum(BitField,STARTHF0,DecHf0,PosHf0);
+  BytePlace&=0xff;
+  if (StMode)
+  {
+    if (BytePlace==0 && BitField > 0xfff)
+      BytePlace=0x100;
+    if (--BytePlace==-1)
+    {
+      BitField=Inp.fgetbits();
+      Inp.faddbits(1);
+      if (BitField & 0x8000)
+      {
+        NumHuf=StMode=0;
+        return;
+      }
+      else
+      {
+        Length = (BitField & 0x4000) ? 4 : 3;
+        Inp.faddbits(1);
+        Distance=DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2);
+        Distance = (Distance << 5) | (Inp.fgetbits() >> 11);
+        Inp.faddbits(5);
+        CopyString15(Distance,Length);
+        return;
+      }
+    }
+  }
+  else
+    if (NumHuf++ >= 16 && FlagsCnt==0)
+      StMode=1;
+  AvrPlc += BytePlace;
+  AvrPlc -= AvrPlc >> 8;
+  Nhfb+=16;
+  if (Nhfb > 0xff)
+  {
+    Nhfb=0x90;
+    Nlzb >>= 1;
+  }
+
+  Window[UnpPtr++]=(byte)(ChSet[BytePlace]>>8);
+  --DestUnpSize;
+
+  while (1)
+  {
+    CurByte=ChSet[BytePlace];
+    NewBytePlace=NToPl[CurByte++ & 0xff]++;
+    if ((CurByte & 0xff) > 0xa1)
+      CorrHuff(ChSet,NToPl);
+    else
+      break;
+  }
+
+  ChSet[BytePlace]=ChSet[NewBytePlace];
+  ChSet[NewBytePlace]=CurByte;
+}
+
+
+void Unpack::GetFlagsBuf()
+{
+  unsigned int Flags,NewFlagsPlace;
+  unsigned int FlagsPlace=DecodeNum(Inp.fgetbits(),STARTHF2,DecHf2,PosHf2);
+
+  // Our Huffman table stores 257 items and needs all them in other parts
+  // of code such as when StMode is on, so the first item is control item.
+  // While normally we do not use the last item to code the flags byte here,
+  // we need to check for value 256 when unpacking in case we unpack
+  // a corrupt archive.
+  if (FlagsPlace>=sizeof(ChSetC)/sizeof(ChSetC[0]))
+    return;
+
+  while (1)
+  {
+    Flags=ChSetC[FlagsPlace];
+    FlagBuf=Flags>>8;
+    NewFlagsPlace=NToPlC[Flags++ & 0xff]++;
+    if ((Flags & 0xff) != 0)
+      break;
+    CorrHuff(ChSetC,NToPlC);
+  }
+
+  ChSetC[FlagsPlace]=ChSetC[NewFlagsPlace];
+  ChSetC[NewFlagsPlace]=Flags;
+}
+
+
+void Unpack::UnpInitData15(int Solid)
+{
+  if (!Solid)
+  {
+    AvrPlcB=AvrLn1=AvrLn2=AvrLn3=NumHuf=Buf60=0;
+    AvrPlc=0x3500;
+    MaxDist3=0x2001;
+    Nhfb=Nlzb=0x80;
+  }
+  FlagsCnt=0;
+  FlagBuf=0;
+  StMode=0;
+  LCount=0;
+  ReadTop=0;
+}
+
+
+void Unpack::InitHuff()
+{
+  for (unsigned int I=0;I<256;I++)
+  {
+    ChSet[I]=ChSetB[I]=I<<8;
+    ChSetA[I]=I;
+    ChSetC[I]=((~I+1) & 0xff)<<8;
+  }
+  memset(NToPl,0,sizeof(NToPl));
+  memset(NToPlB,0,sizeof(NToPlB));
+  memset(NToPlC,0,sizeof(NToPlC));
+  CorrHuff(ChSetB,NToPlB);
+}
+
+
+void Unpack::CorrHuff(ushort *CharSet,byte *NumToPlace)
+{
+  int I,J;
+  for (I=7;I>=0;I--)
+    for (J=0;J<32;J++,CharSet++)
+      *CharSet=(*CharSet & ~0xff) | I;
+  memset(NumToPlace,0,sizeof(NToPl));
+  for (I=6;I>=0;I--)
+    NumToPlace[I]=(7-I)*32;
+}
+
+
+void Unpack::CopyString15(uint Distance,uint Length)
+{
+  DestUnpSize-=Length;
+  while (Length--)
+  {
+    Window[UnpPtr]=Window[(UnpPtr-Distance) & MaxWinMask];
+    UnpPtr=(UnpPtr+1) & MaxWinMask;
+  }
+}
+
+
+uint Unpack::DecodeNum(uint Num,uint StartPos,uint *DecTab,uint *PosTab)
+{
+  int I;
+  for (Num&=0xfff0,I=0;DecTab[I]<=Num;I++)
+    StartPos++;
+  Inp.faddbits(StartPos);
+  return(((Num-(I ? DecTab[I-1]:0))>>(16-StartPos))+PosTab[StartPos]);
+}
diff --git a/third_party/unrar/src/unpack20.cpp b/third_party/unrar/src/unpack20.cpp
new file mode 100644
index 0000000..a7a41c34
--- /dev/null
+++ b/third_party/unrar/src/unpack20.cpp
@@ -0,0 +1,378 @@
+#include "rar.hpp"
+
+void Unpack::CopyString20(uint Length,uint Distance)
+{
+  LastDist=OldDist[OldDistPtr++ & 3]=Distance;
+  LastLength=Length;
+  DestUnpSize-=Length;
+  CopyString(Length,Distance);
+}
+
+
+void Unpack::Unpack20(bool Solid)
+{
+  static unsigned char LDecode[]={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224};
+  static unsigned char LBits[]=  {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,  4,  5,  5,  5,  5};
+  static uint DDecode[]={0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576,32768U,49152U,65536,98304,131072,196608,262144,327680,393216,458752,524288,589824,655360,720896,786432,851968,917504,983040};
+  static unsigned char DBits[]=  {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5,  6,  6,  7,  7,  8,  8,   9,   9,  10,  10,  11,  11,  12,   12,   13,   13,    14,    14,   15,   15,    16,    16,    16,    16,    16,    16,    16,    16,    16,    16,    16,    16,    16,    16};
+  static unsigned char SDDecode[]={0,4,8,16,32,64,128,192};
+  static unsigned char SDBits[]=  {2,2,3, 4, 5, 6,  6,  6};
+  uint Bits;
+
+  if (Suspended)
+    UnpPtr=WrPtr;
+  else
+  {
+    UnpInitData(Solid);
+    if (!UnpReadBuf())
+      return;
+    if ((!Solid || !TablesRead2) && !ReadTables20())
+      return;
+    --DestUnpSize;
+  }
+
+  while (DestUnpSize>=0)
+  {
+    UnpPtr&=MaxWinMask;
+
+    if (Inp.InAddr>ReadTop-30)
+      if (!UnpReadBuf())
+        break;
+    if (((WrPtr-UnpPtr) & MaxWinMask)<270 && WrPtr!=UnpPtr)
+    {
+      UnpWriteBuf20();
+      if (Suspended)
+        return;
+    }
+    if (UnpAudioBlock)
+    {
+      uint AudioNumber=DecodeNumber(Inp,&MD[UnpCurChannel]);
+
+      if (AudioNumber==256)
+      {
+        if (!ReadTables20())
+          break;
+        continue;
+      }
+      Window[UnpPtr++]=DecodeAudio((int)AudioNumber);
+      if (++UnpCurChannel==UnpChannels)
+        UnpCurChannel=0;
+      --DestUnpSize;
+      continue;
+    }
+
+    uint Number=DecodeNumber(Inp,&BlockTables.LD);
+    if (Number<256)
+    {
+      Window[UnpPtr++]=(byte)Number;
+      --DestUnpSize;
+      continue;
+    }
+    if (Number>269)
+    {
+      uint Length=LDecode[Number-=270]+3;
+      if ((Bits=LBits[Number])>0)
+      {
+        Length+=Inp.getbits()>>(16-Bits);
+        Inp.addbits(Bits);
+      }
+
+      uint DistNumber=DecodeNumber(Inp,&BlockTables.DD);
+      uint Distance=DDecode[DistNumber]+1;
+      if ((Bits=DBits[DistNumber])>0)
+      {
+        Distance+=Inp.getbits()>>(16-Bits);
+        Inp.addbits(Bits);
+      }
+
+      if (Distance>=0x2000)
+      {
+        Length++;
+        if (Distance>=0x40000L)
+          Length++;
+      }
+
+      CopyString20(Length,Distance);
+      continue;
+    }
+    if (Number==269)
+    {
+      if (!ReadTables20())
+        break;
+      continue;
+    }
+    if (Number==256)
+    {
+      CopyString20(LastLength,LastDist);
+      continue;
+    }
+    if (Number<261)
+    {
+      uint Distance=OldDist[(OldDistPtr-(Number-256)) & 3];
+      uint LengthNumber=DecodeNumber(Inp,&BlockTables.RD);
+      uint Length=LDecode[LengthNumber]+2;
+      if ((Bits=LBits[LengthNumber])>0)
+      {
+        Length+=Inp.getbits()>>(16-Bits);
+        Inp.addbits(Bits);
+      }
+      if (Distance>=0x101)
+      {
+        Length++;
+        if (Distance>=0x2000)
+        {
+          Length++;
+          if (Distance>=0x40000)
+            Length++;
+        }
+      }
+      CopyString20(Length,Distance);
+      continue;
+    }
+    if (Number<270)
+    {
+      uint Distance=SDDecode[Number-=261]+1;
+      if ((Bits=SDBits[Number])>0)
+      {
+        Distance+=Inp.getbits()>>(16-Bits);
+        Inp.addbits(Bits);
+      }
+      CopyString20(2,Distance);
+      continue;
+   }
+  }
+  ReadLastTables();
+  UnpWriteBuf20();
+}
+
+
+void Unpack::UnpWriteBuf20()
+{
+  if (UnpPtr!=WrPtr)
+    UnpSomeRead=true;
+  if (UnpPtr<WrPtr)
+  {
+    UnpIO->UnpWrite(&Window[WrPtr],-(int)WrPtr & MaxWinMask);
+    UnpIO->UnpWrite(Window,UnpPtr);
+    UnpAllBuf=true;
+  }
+  else
+    UnpIO->UnpWrite(&Window[WrPtr],UnpPtr-WrPtr);
+  WrPtr=UnpPtr;
+}
+
+
+bool Unpack::ReadTables20()
+{
+  byte BitLength[BC20];
+  byte Table[MC20*4];
+  if (Inp.InAddr>ReadTop-25)
+    if (!UnpReadBuf())
+      return false;
+  uint BitField=Inp.getbits();
+  UnpAudioBlock=(BitField & 0x8000)!=0;
+
+  if (!(BitField & 0x4000))
+    memset(UnpOldTable20,0,sizeof(UnpOldTable20));
+  Inp.addbits(2);
+
+  uint TableSize;
+  if (UnpAudioBlock)
+  {
+    UnpChannels=((BitField>>12) & 3)+1;
+    if (UnpCurChannel>=UnpChannels)
+      UnpCurChannel=0;
+    Inp.addbits(2);
+    TableSize=MC20*UnpChannels;
+  }
+  else
+    TableSize=NC20+DC20+RC20;
+
+  for (uint I=0;I<BC20;I++)
+  {
+    BitLength[I]=(byte)(Inp.getbits() >> 12);
+    Inp.addbits(4);
+  }
+  MakeDecodeTables(BitLength,&BlockTables.BD,BC20);
+  for (uint I=0;I<TableSize;)
+  {
+    if (Inp.InAddr>ReadTop-5)
+      if (!UnpReadBuf())
+        return false;
+    uint Number=DecodeNumber(Inp,&BlockTables.BD);
+    if (Number<16)
+    {
+      Table[I]=(Number+UnpOldTable20[I]) & 0xf;
+      I++;
+    }
+    else
+      if (Number==16)
+      {
+        uint N=(Inp.getbits() >> 14)+3;
+        Inp.addbits(2);
+        if (I==0)
+          return false; // We cannot have "repeat previous" code at the first position.
+        else
+          while (N-- > 0 && I<TableSize)
+          {
+            Table[I]=Table[I-1];
+            I++;
+          }
+      }
+      else
+      {
+        uint N;
+        if (Number==17)
+        {
+          N=(Inp.getbits() >> 13)+3;
+          Inp.addbits(3);
+        }
+        else
+        {
+          N=(Inp.getbits() >> 9)+11;
+          Inp.addbits(7);
+        }
+        while (N-- > 0 && I<TableSize)
+          Table[I++]=0;
+      }
+  }
+  TablesRead2=true;
+  if (Inp.InAddr>ReadTop)
+    return true;
+  if (UnpAudioBlock)
+    for (uint I=0;I<UnpChannels;I++)
+      MakeDecodeTables(&Table[I*MC20],&MD[I],MC20);
+  else
+  {
+    MakeDecodeTables(&Table[0],&BlockTables.LD,NC20);
+    MakeDecodeTables(&Table[NC20],&BlockTables.DD,DC20);
+    MakeDecodeTables(&Table[NC20+DC20],&BlockTables.RD,RC20);
+  }
+  memcpy(UnpOldTable20,Table,TableSize);
+  return true;
+}
+
+
+void Unpack::ReadLastTables()
+{
+  if (ReadTop>=Inp.InAddr+5)
+    if (UnpAudioBlock)
+    {
+      if (DecodeNumber(Inp,&MD[UnpCurChannel])==256)
+        ReadTables20();
+    }
+    else
+      if (DecodeNumber(Inp,&BlockTables.LD)==269)
+        ReadTables20();
+}
+
+
+void Unpack::UnpInitData20(int Solid)
+{
+  if (!Solid)
+  {
+    TablesRead2=false;
+    UnpAudioBlock=false;
+    UnpChannelDelta=0;
+    UnpCurChannel=0;
+    UnpChannels=1;
+
+    memset(AudV,0,sizeof(AudV));
+    memset(UnpOldTable20,0,sizeof(UnpOldTable20));
+    memset(MD,0,sizeof(MD));
+  }
+}
+
+
+byte Unpack::DecodeAudio(int Delta)
+{
+  struct AudioVariables *V=&AudV[UnpCurChannel];
+  V->ByteCount++;
+  V->D4=V->D3;
+  V->D3=V->D2;
+  V->D2=V->LastDelta-V->D1;
+  V->D1=V->LastDelta;
+  int PCh=8*V->LastChar+V->K1*V->D1+V->K2*V->D2+V->K3*V->D3+V->K4*V->D4+V->K5*UnpChannelDelta;
+  PCh=(PCh>>3) & 0xFF;
+
+  uint Ch=PCh-Delta;
+
+  int D=(signed char)Delta;
+  // Left shift of negative value is undefined behavior in C++,
+  // so we cast it to unsigned to follow the standard.
+  D=(uint)D<<3;
+
+  V->Dif[0]+=abs(D);
+  V->Dif[1]+=abs(D-V->D1);
+  V->Dif[2]+=abs(D+V->D1);
+  V->Dif[3]+=abs(D-V->D2);
+  V->Dif[4]+=abs(D+V->D2);
+  V->Dif[5]+=abs(D-V->D3);
+  V->Dif[6]+=abs(D+V->D3);
+  V->Dif[7]+=abs(D-V->D4);
+  V->Dif[8]+=abs(D+V->D4);
+  V->Dif[9]+=abs(D-UnpChannelDelta);
+  V->Dif[10]+=abs(D+UnpChannelDelta);
+
+  UnpChannelDelta=V->LastDelta=(signed char)(Ch-V->LastChar);
+  V->LastChar=Ch;
+
+  if ((V->ByteCount & 0x1F)==0)
+  {
+    uint MinDif=V->Dif[0],NumMinDif=0;
+    V->Dif[0]=0;
+    for (uint I=1;I<ASIZE(V->Dif);I++)
+    {
+      if (V->Dif[I]<MinDif)
+      {
+        MinDif=V->Dif[I];
+        NumMinDif=I;
+      }
+      V->Dif[I]=0;
+    }
+    switch(NumMinDif)
+    {
+      case 1:
+        if (V->K1>=-16)
+          V->K1--;
+        break;
+      case 2:
+        if (V->K1<16)
+          V->K1++;
+        break;
+      case 3:
+        if (V->K2>=-16)
+          V->K2--;
+        break;
+      case 4:
+        if (V->K2<16)
+          V->K2++;
+        break;
+      case 5:
+        if (V->K3>=-16)
+          V->K3--;
+        break;
+      case 6:
+        if (V->K3<16)
+          V->K3++;
+        break;
+      case 7:
+        if (V->K4>=-16)
+          V->K4--;
+        break;
+      case 8:
+        if (V->K4<16)
+          V->K4++;
+        break;
+      case 9:
+        if (V->K5>=-16)
+          V->K5--;
+        break;
+      case 10:
+        if (V->K5<16)
+          V->K5++;
+        break;
+    }
+  }
+  return (byte)Ch;
+}
diff --git a/third_party/unrar/src/unpack30.cpp b/third_party/unrar/src/unpack30.cpp
new file mode 100644
index 0000000..6a8efa23b
--- /dev/null
+++ b/third_party/unrar/src/unpack30.cpp
@@ -0,0 +1,765 @@
+// We use it instead of direct PPM.DecodeChar call to be sure that
+// we reset PPM structures in case of corrupt data. It is important,
+// because these structures can be invalid after PPM.DecodeChar returned -1.
+inline int Unpack::SafePPMDecodeChar()
+{
+  int Ch=PPM.DecodeChar();
+  if (Ch==-1)              // Corrupt PPM data found.
+  {
+    PPM.CleanUp();         // Reset possibly corrupt PPM data structures.
+    UnpBlockType=BLOCK_LZ; // Set faster and more fail proof LZ mode.
+  }
+  return(Ch);
+}
+
+
+void Unpack::Unpack29(bool Solid)
+{
+  static unsigned char LDecode[]={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224};
+  static unsigned char LBits[]=  {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,  4,  5,  5,  5,  5};
+  static int DDecode[DC];
+  static byte DBits[DC];
+  static int DBitLengthCounts[]= {4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12};
+  static unsigned char SDDecode[]={0,4,8,16,32,64,128,192};
+  static unsigned char SDBits[]=  {2,2,3, 4, 5, 6,  6,  6};
+  unsigned int Bits;
+
+  if (DDecode[1]==0)
+  {
+    int Dist=0,BitLength=0,Slot=0;
+    for (int I=0;I<ASIZE(DBitLengthCounts);I++,BitLength++)
+      for (int J=0;J<DBitLengthCounts[I];J++,Slot++,Dist+=(1<<BitLength))
+      {
+        DDecode[Slot]=Dist;
+        DBits[Slot]=BitLength;
+      }
+  }
+
+  FileExtracted=true;
+
+  if (!Suspended)
+  {
+    UnpInitData(Solid);
+    if (!UnpReadBuf30())
+      return;
+    if ((!Solid || !TablesRead3) && !ReadTables30())
+      return;
+  }
+
+  while (true)
+  {
+    UnpPtr&=MaxWinMask;
+
+    if (Inp.InAddr>ReadBorder)
+    {
+      if (!UnpReadBuf30())
+        break;
+    }
+    if (((WrPtr-UnpPtr) & MaxWinMask)<260 && WrPtr!=UnpPtr)
+    {
+      UnpWriteBuf30();
+      if (WrittenFileSize>DestUnpSize)
+        return;
+      if (Suspended)
+      {
+        FileExtracted=false;
+        return;
+      }
+    }
+    if (UnpBlockType==BLOCK_PPM)
+    {
+      // Here speed is critical, so we do not use SafePPMDecodeChar,
+      // because sometimes even the inline function can introduce
+      // some additional penalty.
+      int Ch=PPM.DecodeChar();
+      if (Ch==-1)              // Corrupt PPM data found.
+      {
+        PPM.CleanUp();         // Reset possibly corrupt PPM data structures.
+        UnpBlockType=BLOCK_LZ; // Set faster and more fail proof LZ mode.
+        break;
+      }
+      if (Ch==PPMEscChar)
+      {
+        int NextCh=SafePPMDecodeChar();
+        if (NextCh==0)  // End of PPM encoding.
+        {
+          if (!ReadTables30())
+            break;
+          continue;
+        }
+        if (NextCh==-1) // Corrupt PPM data found.
+          break;
+        if (NextCh==2)  // End of file in PPM mode.
+          break;
+        if (NextCh==3)  // Read VM code.
+        {
+          if (!ReadVMCodePPM())
+            break;
+          continue;
+        }
+        if (NextCh==4) // LZ inside of PPM.
+        {
+          unsigned int Distance=0,Length;
+          bool Failed=false;
+          for (int I=0;I<4 && !Failed;I++)
+          {
+            int Ch=SafePPMDecodeChar();
+            if (Ch==-1)
+              Failed=true;
+            else
+              if (I==3)
+                Length=(byte)Ch;
+              else
+                Distance=(Distance<<8)+(byte)Ch;
+          }
+          if (Failed)
+            break;
+
+          CopyString(Length+32,Distance+2);
+          continue;
+        }
+        if (NextCh==5) // One byte distance match (RLE) inside of PPM.
+        {
+          int Length=SafePPMDecodeChar();
+          if (Length==-1)
+            break;
+          CopyString(Length+4,1);
+          continue;
+        }
+        // If we are here, NextCh must be 1, what means that current byte
+        // is equal to our 'escape' byte, so we just store it to Window.
+      }
+      Window[UnpPtr++]=Ch;
+      continue;
+    }
+
+    uint Number=DecodeNumber(Inp,&BlockTables.LD);
+    if (Number<256)
+    {
+      Window[UnpPtr++]=(byte)Number;
+      continue;
+    }
+    if (Number>=271)
+    {
+      uint Length=LDecode[Number-=271]+3;
+      if ((Bits=LBits[Number])>0)
+      {
+        Length+=Inp.getbits()>>(16-Bits);
+        Inp.addbits(Bits);
+      }
+
+      uint DistNumber=DecodeNumber(Inp,&BlockTables.DD);
+      uint Distance=DDecode[DistNumber]+1;
+      if ((Bits=DBits[DistNumber])>0)
+      {
+        if (DistNumber>9)
+        {
+          if (Bits>4)
+          {
+            Distance+=((Inp.getbits()>>(20-Bits))<<4);
+            Inp.addbits(Bits-4);
+          }
+          if (LowDistRepCount>0)
+          {
+            LowDistRepCount--;
+            Distance+=PrevLowDist;
+          }
+          else
+          {
+            uint LowDist=DecodeNumber(Inp,&BlockTables.LDD);
+            if (LowDist==16)
+            {
+              LowDistRepCount=LOW_DIST_REP_COUNT-1;
+              Distance+=PrevLowDist;
+            }
+            else
+            {
+              Distance+=LowDist;
+              PrevLowDist=LowDist;
+            }
+          }
+        }
+        else
+        {
+          Distance+=Inp.getbits()>>(16-Bits);
+          Inp.addbits(Bits);
+        }
+      }
+
+      if (Distance>=0x2000)
+      {
+        Length++;
+        if (Distance>=0x40000)
+          Length++;
+      }
+
+      InsertOldDist(Distance);
+      LastLength=Length;
+      CopyString(Length,Distance);
+      continue;
+    }
+    if (Number==256)
+    {
+      if (!ReadEndOfBlock())
+        break;
+      continue;
+    }
+    if (Number==257)
+    {
+      if (!ReadVMCode())
+        break;
+      continue;
+    }
+    if (Number==258)
+    {
+      if (LastLength!=0)
+        CopyString(LastLength,OldDist[0]);
+      continue;
+    }
+    if (Number<263)
+    {
+      uint DistNum=Number-259;
+      uint Distance=OldDist[DistNum];
+      for (uint I=DistNum;I>0;I--)
+        OldDist[I]=OldDist[I-1];
+      OldDist[0]=Distance;
+
+      uint LengthNumber=DecodeNumber(Inp,&BlockTables.RD);
+      int Length=LDecode[LengthNumber]+2;
+      if ((Bits=LBits[LengthNumber])>0)
+      {
+        Length+=Inp.getbits()>>(16-Bits);
+        Inp.addbits(Bits);
+      }
+      LastLength=Length;
+      CopyString(Length,Distance);
+      continue;
+    }
+    if (Number<272)
+    {
+      uint Distance=SDDecode[Number-=263]+1;
+      if ((Bits=SDBits[Number])>0)
+      {
+        Distance+=Inp.getbits()>>(16-Bits);
+        Inp.addbits(Bits);
+      }
+      InsertOldDist(Distance);
+      LastLength=2;
+      CopyString(2,Distance);
+      continue;
+    }
+  }
+  UnpWriteBuf30();
+}
+
+
+// Return 'false' to quit unpacking the current file or 'true' to continue.
+bool Unpack::ReadEndOfBlock()
+{
+  uint BitField=Inp.getbits();
+  bool NewTable,NewFile=false;
+
+  // "1"  - no new file, new table just here.
+  // "00" - new file,    no new table.
+  // "01" - new file,    new table (in beginning of next file).
+  
+  if ((BitField & 0x8000)!=0)
+  {
+    NewTable=true;
+    Inp.addbits(1);
+  }
+  else
+  {
+    NewFile=true;
+    NewTable=(BitField & 0x4000)!=0;
+    Inp.addbits(2);
+  }
+  TablesRead3=!NewTable;
+
+  // Quit immediately if "new file" flag is set. If "new table" flag
+  // is present, we'll read the table in beginning of next file
+  // based on 'TablesRead3' 'false' value.
+  if (NewFile)
+    return false;
+  return ReadTables30(); // Quit only if we failed to read tables.
+}
+
+
+bool Unpack::ReadVMCode()
+{
+  // Entire VM code is guaranteed to fully present in block defined 
+  // by current Huffman table. Compressor checks that VM code does not cross
+  // Huffman block boundaries.
+  uint FirstByte=Inp.getbits()>>8;
+  Inp.addbits(8);
+  uint Length=(FirstByte & 7)+1;
+  if (Length==7)
+  {
+    Length=(Inp.getbits()>>8)+7;
+    Inp.addbits(8);
+  }
+  else
+    if (Length==8)
+    {
+      Length=Inp.getbits();
+      Inp.addbits(16);
+    }
+  if (Length==0)
+    return false;
+  Array<byte> VMCode(Length);
+  for (uint I=0;I<Length;I++)
+  {
+    // Try to read the new buffer if only one byte is left.
+    // But if we read all bytes except the last, one byte is enough.
+    if (Inp.InAddr>=ReadTop-1 && !UnpReadBuf30() && I<Length-1)
+      return false;
+    VMCode[I]=Inp.getbits()>>8;
+    Inp.addbits(8);
+  }
+  return AddVMCode(FirstByte,&VMCode[0],Length);
+}
+
+
+bool Unpack::ReadVMCodePPM()
+{
+  uint FirstByte=SafePPMDecodeChar();
+  if ((int)FirstByte==-1)
+    return false;
+  uint Length=(FirstByte & 7)+1;
+  if (Length==7)
+  {
+    int B1=SafePPMDecodeChar();
+    if (B1==-1)
+      return false;
+    Length=B1+7;
+  }
+  else
+    if (Length==8)
+    {
+      int B1=SafePPMDecodeChar();
+      if (B1==-1)
+        return false;
+      int B2=SafePPMDecodeChar();
+      if (B2==-1)
+        return false;
+      Length=B1*256+B2;
+    }
+  if (Length==0)
+    return false;
+  Array<byte> VMCode(Length);
+  for (uint I=0;I<Length;I++)
+  {
+    int Ch=SafePPMDecodeChar();
+    if (Ch==-1)
+      return false;
+    VMCode[I]=Ch;
+  }
+  return AddVMCode(FirstByte,&VMCode[0],Length);
+}
+
+
+bool Unpack::AddVMCode(uint FirstByte,byte *Code,uint CodeSize)
+{
+  VMCodeInp.InitBitInput();
+  memcpy(VMCodeInp.InBuf,Code,Min(BitInput::MAX_SIZE,CodeSize));
+  VM.Init();
+
+  uint FiltPos;
+  if ((FirstByte & 0x80)!=0)
+  {
+    FiltPos=RarVM::ReadData(VMCodeInp);
+    if (FiltPos==0)
+      InitFilters30(false);
+    else
+      FiltPos--;
+  }
+  else
+    FiltPos=LastFilter; // Use the same filter as last time.
+
+  if (FiltPos>Filters30.Size() || FiltPos>OldFilterLengths.Size())
+    return false;
+  LastFilter=FiltPos;
+  bool NewFilter=(FiltPos==Filters30.Size());
+
+  UnpackFilter30 *StackFilter=new UnpackFilter30; // New filter for PrgStack.
+
+  UnpackFilter30 *Filter;
+  if (NewFilter) // New filter code, never used before since VM reset.
+  {
+    if (FiltPos>MAX3_UNPACK_FILTERS)
+    {
+      // Too many different filters, corrupt archive.
+      delete StackFilter;
+      return false;
+    }
+
+    Filters30.Add(1);
+    Filters30[Filters30.Size()-1]=Filter=new UnpackFilter30;
+    StackFilter->ParentFilter=(uint)(Filters30.Size()-1);
+
+    // Reserve one item to store the data block length of our new filter 
+    // entry. We'll set it to real block length below, after reading it.
+    // But we need to initialize it now, because when processing corrupt
+    // data, we can access this item even before we set it to real value.
+    OldFilterLengths.Push(0);
+  }
+  else  // Filter was used in the past.
+  {
+    Filter=Filters30[FiltPos];
+    StackFilter->ParentFilter=FiltPos;
+  }
+
+  uint EmptyCount=0;
+  for (uint I=0;I<PrgStack.Size();I++)
+  {
+    PrgStack[I-EmptyCount]=PrgStack[I];
+    if (PrgStack[I]==NULL)
+      EmptyCount++;
+    if (EmptyCount>0)
+      PrgStack[I]=NULL;
+  }
+  if (EmptyCount==0)
+  {
+    if (PrgStack.Size()>MAX3_UNPACK_FILTERS)
+    {
+      delete StackFilter;
+      return false;
+    }
+    PrgStack.Add(1);
+    EmptyCount=1;
+  }
+  size_t StackPos=PrgStack.Size()-EmptyCount;
+  PrgStack[StackPos]=StackFilter;
+ 
+  uint BlockStart=RarVM::ReadData(VMCodeInp);
+  if ((FirstByte & 0x40)!=0)
+    BlockStart+=258;
+  StackFilter->BlockStart=(uint)((BlockStart+UnpPtr)&MaxWinMask);
+  if ((FirstByte & 0x20)!=0)
+  {
+    StackFilter->BlockLength=RarVM::ReadData(VMCodeInp);
+
+    // Store the last data block length for current filter.
+    OldFilterLengths[FiltPos]=StackFilter->BlockLength;
+  }
+  else
+  {
+    // Set the data block size to same value as the previous block size
+    // for same filter. It is possible for corrupt data to access a new 
+    // and not filled yet item of OldFilterLengths array here. This is why
+    // we set new OldFilterLengths items to zero above.
+    StackFilter->BlockLength=FiltPos<OldFilterLengths.Size() ? OldFilterLengths[FiltPos]:0;
+  }
+
+  StackFilter->NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=BlockStart;
+
+//  DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x BlockStart=%08x",UnpPtr,WrPtr,BlockStart);
+
+  memset(StackFilter->Prg.InitR,0,sizeof(StackFilter->Prg.InitR));
+  StackFilter->Prg.InitR[4]=StackFilter->BlockLength;
+
+  if ((FirstByte & 0x10)!=0) // Set registers to optional parameters if any.
+  {
+    uint InitMask=VMCodeInp.fgetbits()>>9;
+    VMCodeInp.faddbits(7);
+    for (uint I=0;I<7;I++)
+      if (InitMask & (1<<I))
+        StackFilter->Prg.InitR[I]=RarVM::ReadData(VMCodeInp);
+  }
+
+  if (NewFilter)
+  {
+    uint VMCodeSize=RarVM::ReadData(VMCodeInp);
+    if (VMCodeSize>=0x10000 || VMCodeSize==0 || VMCodeInp.InAddr+VMCodeSize>CodeSize)
+      return false;
+    Array<byte> VMCode(VMCodeSize);
+    for (uint I=0;I<VMCodeSize;I++)
+    {
+      if (VMCodeInp.Overflow(3))
+        return false;
+      VMCode[I]=VMCodeInp.fgetbits()>>8;
+      VMCodeInp.faddbits(8);
+    }
+    VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg);
+  }
+  StackFilter->Prg.Type=Filter->Prg.Type;
+
+  return true;
+}
+
+
+bool Unpack::UnpReadBuf30()
+{
+  int DataSize=ReadTop-Inp.InAddr; // Data left to process.
+  if (DataSize<0)
+    return false;
+  if (Inp.InAddr>BitInput::MAX_SIZE/2)
+  {
+    // If we already processed more than half of buffer, let's move
+    // remaining data into beginning to free more space for new data
+    // and ensure that calling function does not cross the buffer border
+    // even if we did not read anything here. Also it ensures that read size
+    // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk
+    // to make it zero.
+    if (DataSize>0)
+      memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize);
+    Inp.InAddr=0;
+    ReadTop=DataSize;
+  }
+  else
+    DataSize=ReadTop;
+  int ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput::MAX_SIZE-DataSize);
+  if (ReadCode>0)
+    ReadTop+=ReadCode;
+  ReadBorder=ReadTop-30;
+  return ReadCode!=-1;
+}
+
+
+void Unpack::UnpWriteBuf30()
+{
+  uint WrittenBorder=(uint)WrPtr;
+  uint WriteSize=(uint)((UnpPtr-WrittenBorder)&MaxWinMask);
+  for (size_t I=0;I<PrgStack.Size();I++)
+  {
+    // Here we apply filters to data which we need to write.
+    // We always copy data to virtual machine memory before processing.
+    // We cannot process them just in place in Window buffer, because
+    // these data can be used for future string matches, so we must
+    // preserve them in original form.
+
+    UnpackFilter30 *flt=PrgStack[I];
+    if (flt==NULL)
+      continue;
+    if (flt->NextWindow)
+    {
+      flt->NextWindow=false;
+      continue;
+    }
+    unsigned int BlockStart=flt->BlockStart;
+    unsigned int BlockLength=flt->BlockLength;
+    if (((BlockStart-WrittenBorder)&MaxWinMask)<WriteSize)
+    {
+      if (WrittenBorder!=BlockStart)
+      {
+        UnpWriteArea(WrittenBorder,BlockStart);
+        WrittenBorder=BlockStart;
+        WriteSize=(uint)((UnpPtr-WrittenBorder)&MaxWinMask);
+      }
+      if (BlockLength<=WriteSize)
+      {
+        uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask;
+        if (BlockStart<BlockEnd || BlockEnd==0)
+          VM.SetMemory(0,Window+BlockStart,BlockLength);
+        else
+        {
+          uint FirstPartLength=uint(MaxWinSize-BlockStart);
+          VM.SetMemory(0,Window+BlockStart,FirstPartLength);
+          VM.SetMemory(FirstPartLength,Window,BlockEnd);
+        }
+
+        VM_PreparedProgram *ParentPrg=&Filters30[flt->ParentFilter]->Prg;
+        VM_PreparedProgram *Prg=&flt->Prg;
+
+        ExecuteCode(Prg);
+
+        byte *FilteredData=Prg->FilteredData;
+        unsigned int FilteredDataSize=Prg->FilteredDataSize;
+
+        delete PrgStack[I];
+        PrgStack[I]=NULL;
+        while (I+1<PrgStack.Size())
+        {
+          UnpackFilter30 *NextFilter=PrgStack[I+1];
+          // It is required to check NextWindow here.
+          if (NextFilter==NULL || NextFilter->BlockStart!=BlockStart ||
+              NextFilter->BlockLength!=FilteredDataSize || NextFilter->NextWindow)
+            break;
+
+          // Apply several filters to same data block.
+
+          VM.SetMemory(0,FilteredData,FilteredDataSize);
+
+          VM_PreparedProgram *ParentPrg=&Filters30[NextFilter->ParentFilter]->Prg;
+          VM_PreparedProgram *NextPrg=&NextFilter->Prg;
+
+          ExecuteCode(NextPrg);
+
+          FilteredData=NextPrg->FilteredData;
+          FilteredDataSize=NextPrg->FilteredDataSize;
+          I++;
+          delete PrgStack[I];
+          PrgStack[I]=NULL;
+        }
+        UnpIO->UnpWrite(FilteredData,FilteredDataSize);
+        UnpSomeRead=true;
+        WrittenFileSize+=FilteredDataSize;
+        WrittenBorder=BlockEnd;
+        WriteSize=uint((UnpPtr-WrittenBorder)&MaxWinMask);
+      }
+      else
+      {
+        // Current filter intersects the window write border, so we adjust
+        // the window border to process this filter next time, not now.
+        for (size_t J=I;J<PrgStack.Size();J++)
+        {
+          UnpackFilter30 *flt=PrgStack[J];
+          if (flt!=NULL && flt->NextWindow)
+            flt->NextWindow=false;
+        }
+        WrPtr=WrittenBorder;
+        return;
+      }
+    }
+  }
+      
+  UnpWriteArea(WrittenBorder,UnpPtr);
+  WrPtr=UnpPtr;
+}
+
+
+void Unpack::ExecuteCode(VM_PreparedProgram *Prg)
+{
+  Prg->InitR[6]=(uint)WrittenFileSize;
+  VM.Execute(Prg);
+}
+
+
+bool Unpack::ReadTables30()
+{
+  byte BitLength[BC];
+  byte Table[HUFF_TABLE_SIZE30];
+  if (Inp.InAddr>ReadTop-25)
+    if (!UnpReadBuf30())
+      return(false);
+  Inp.faddbits((8-Inp.InBit)&7);
+  uint BitField=Inp.fgetbits();
+  if (BitField & 0x8000)
+  {
+    UnpBlockType=BLOCK_PPM;
+    return(PPM.DecodeInit(this,PPMEscChar));
+  }
+  UnpBlockType=BLOCK_LZ;
+  
+  PrevLowDist=0;
+  LowDistRepCount=0;
+
+  if (!(BitField & 0x4000))
+    memset(UnpOldTable,0,sizeof(UnpOldTable));
+  Inp.faddbits(2);
+
+  for (uint I=0;I<BC;I++)
+  {
+    uint Length=(byte)(Inp.fgetbits() >> 12);
+    Inp.faddbits(4);
+    if (Length==15)
+    {
+      uint ZeroCount=(byte)(Inp.fgetbits() >> 12);
+      Inp.faddbits(4);
+      if (ZeroCount==0)
+        BitLength[I]=15;
+      else
+      {
+        ZeroCount+=2;
+        while (ZeroCount-- > 0 && I<ASIZE(BitLength))
+          BitLength[I++]=0;
+        I--;
+      }
+    }
+    else
+      BitLength[I]=Length;
+  }
+  MakeDecodeTables(BitLength,&BlockTables.BD,BC30);
+
+  const uint TableSize=HUFF_TABLE_SIZE30;
+  for (uint I=0;I<TableSize;)
+  {
+    if (Inp.InAddr>ReadTop-5)
+      if (!UnpReadBuf30())
+        return(false);
+    uint Number=DecodeNumber(Inp,&BlockTables.BD);
+    if (Number<16)
+    {
+      Table[I]=(Number+UnpOldTable[I]) & 0xf;
+      I++;
+    }
+    else
+      if (Number<18)
+      {
+        uint N;
+        if (Number==16)
+        {
+          N=(Inp.fgetbits() >> 13)+3;
+          Inp.faddbits(3);
+        }
+        else
+        {
+          N=(Inp.fgetbits() >> 9)+11;
+          Inp.faddbits(7);
+        }
+        if (I==0)
+          return false; // We cannot have "repeat previous" code at the first position.
+        else
+          while (N-- > 0 && I<TableSize)
+          {
+            Table[I]=Table[I-1];
+            I++;
+          }
+      }
+      else
+      {
+        uint N;
+        if (Number==18)
+        {
+          N=(Inp.fgetbits() >> 13)+3;
+          Inp.faddbits(3);
+        }
+        else
+        {
+          N=(Inp.fgetbits() >> 9)+11;
+          Inp.faddbits(7);
+        }
+        while (N-- > 0 && I<TableSize)
+          Table[I++]=0;
+      }
+  }
+  TablesRead3=true;
+  if (Inp.InAddr>ReadTop)
+    return false;
+  MakeDecodeTables(&Table[0],&BlockTables.LD,NC30);
+  MakeDecodeTables(&Table[NC30],&BlockTables.DD,DC30);
+  MakeDecodeTables(&Table[NC30+DC30],&BlockTables.LDD,LDC30);
+  MakeDecodeTables(&Table[NC30+DC30+LDC30],&BlockTables.RD,RC30);
+  memcpy(UnpOldTable,Table,sizeof(UnpOldTable));
+  return true;
+}
+
+
+void Unpack::UnpInitData30(bool Solid)
+{
+  if (!Solid)
+  {
+    TablesRead3=false;
+    memset(UnpOldTable,0,sizeof(UnpOldTable));
+    PPMEscChar=2;
+    UnpBlockType=BLOCK_LZ;
+  }
+  InitFilters30(Solid);
+}
+
+
+void Unpack::InitFilters30(bool Solid)
+{
+  if (!Solid)
+  {
+    OldFilterLengths.SoftReset();
+    LastFilter=0;
+
+    for (size_t I=0;I<Filters30.Size();I++)
+      delete Filters30[I];
+    Filters30.SoftReset();
+  }
+  for (size_t I=0;I<PrgStack.Size();I++)
+    delete PrgStack[I];
+  PrgStack.SoftReset();
+}
diff --git a/third_party/unrar/src/unpack50.cpp b/third_party/unrar/src/unpack50.cpp
new file mode 100644
index 0000000..dac1f6f
--- /dev/null
+++ b/third_party/unrar/src/unpack50.cpp
@@ -0,0 +1,683 @@
+void Unpack::Unpack5(bool Solid)
+{
+  FileExtracted=true;
+
+  if (!Suspended)
+  {
+    UnpInitData(Solid);
+    if (!UnpReadBuf())
+      return;
+
+    // Check TablesRead5 to be sure that we read tables at least once
+    // regardless of current block header TablePresent flag.
+    // So we can safefly use these tables below.
+    if (!ReadBlockHeader(Inp,BlockHeader) || 
+        !ReadTables(Inp,BlockHeader,BlockTables) || !TablesRead5)
+      return;
+  }
+
+  while (true)
+  {
+    UnpPtr&=MaxWinMask;
+
+    if (Inp.InAddr>=ReadBorder)
+    {
+      bool FileDone=false;
+
+      // We use 'while', because for empty block containing only Huffman table,
+      // we'll be on the block border once again just after reading the table.
+      while (Inp.InAddr>BlockHeader.BlockStart+BlockHeader.BlockSize-1 || 
+             Inp.InAddr==BlockHeader.BlockStart+BlockHeader.BlockSize-1 && 
+             Inp.InBit>=BlockHeader.BlockBitSize)
+      {
+        if (BlockHeader.LastBlockInFile)
+        {
+          FileDone=true;
+          break;
+        }
+        if (!ReadBlockHeader(Inp,BlockHeader) || !ReadTables(Inp,BlockHeader,BlockTables))
+          return;
+      }
+      if (FileDone || !UnpReadBuf())
+        break;
+    }
+
+    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
+    {
+      UnpWriteBuf();
+      if (WrittenFileSize>DestUnpSize)
+        return;
+      if (Suspended)
+      {
+        FileExtracted=false;
+        return;
+      }
+    }
+
+    uint MainSlot=DecodeNumber(Inp,&BlockTables.LD);
+    if (MainSlot<256)
+    {
+      if (Fragmented)
+        FragWindow[UnpPtr++]=(byte)MainSlot;
+      else
+        Window[UnpPtr++]=(byte)MainSlot;
+      continue;
+    }
+    if (MainSlot>=262)
+    {
+      uint Length=SlotToLength(Inp,MainSlot-262);
+
+      uint DBits,Distance=1,DistSlot=DecodeNumber(Inp,&BlockTables.DD);
+      if (DistSlot<4)
+      {
+        DBits=0;
+        Distance+=DistSlot;
+      }
+      else
+      {
+        DBits=DistSlot/2 - 1;
+        Distance+=(2 | (DistSlot & 1)) << DBits;
+      }
+
+      if (DBits>0)
+      {
+        if (DBits>=4)
+        {
+          if (DBits>4)
+          {
+            Distance+=((Inp.getbits32()>>(36-DBits))<<4);
+            Inp.addbits(DBits-4);
+          }
+          uint LowDist=DecodeNumber(Inp,&BlockTables.LDD);
+          Distance+=LowDist;
+        }
+        else
+        {
+          Distance+=Inp.getbits32()>>(32-DBits);
+          Inp.addbits(DBits);
+        }
+      }
+
+      if (Distance>0x100)
+      {
+        Length++;
+        if (Distance>0x2000)
+        {
+          Length++;
+          if (Distance>0x40000)
+            Length++;
+        }
+      }
+
+      InsertOldDist(Distance);
+      LastLength=Length;
+      if (Fragmented)
+        FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask);
+      else
+        CopyString(Length,Distance);
+      continue;
+    }
+    if (MainSlot==256)
+    {
+      UnpackFilter Filter;
+      if (!ReadFilter(Inp,Filter) || !AddFilter(Filter))
+        break;
+      continue;
+    }
+    if (MainSlot==257)
+    {
+      if (LastLength!=0)
+        if (Fragmented)
+          FragWindow.CopyString(LastLength,OldDist[0],UnpPtr,MaxWinMask);
+        else
+          CopyString(LastLength,OldDist[0]);
+      continue;
+    }
+    if (MainSlot<262)
+    {
+      uint DistNum=MainSlot-258;
+      uint Distance=OldDist[DistNum];
+      for (uint I=DistNum;I>0;I--)
+        OldDist[I]=OldDist[I-1];
+      OldDist[0]=Distance;
+
+      uint LengthSlot=DecodeNumber(Inp,&BlockTables.RD);
+      uint Length=SlotToLength(Inp,LengthSlot);
+      LastLength=Length;
+      if (Fragmented)
+        FragWindow.CopyString(Length,Distance,UnpPtr,MaxWinMask);
+      else
+        CopyString(Length,Distance);
+      continue;
+    }
+  }
+  UnpWriteBuf();
+}
+
+
+uint Unpack::ReadFilterData(BitInput &Inp)
+{
+  uint ByteCount=(Inp.fgetbits()>>14)+1;
+  Inp.addbits(2);
+
+  uint Data=0;
+  for (uint I=0;I<ByteCount;I++)
+  {
+    Data+=(Inp.fgetbits()>>8)<<(I*8);
+    Inp.addbits(8);
+  }
+  return Data;
+}
+
+
+bool Unpack::ReadFilter(BitInput &Inp,UnpackFilter &Filter)
+{
+  if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-16)
+    if (!UnpReadBuf())
+      return false;
+
+  Filter.BlockStart=ReadFilterData(Inp);
+  Filter.BlockLength=ReadFilterData(Inp);
+  if (Filter.BlockLength>MAX_FILTER_BLOCK_SIZE)
+    Filter.BlockLength=0;
+
+  Filter.Type=Inp.fgetbits()>>13;
+  Inp.faddbits(3);
+
+  if (Filter.Type==FILTER_DELTA)
+  {
+    Filter.Channels=(Inp.fgetbits()>>11)+1;
+    Inp.faddbits(5);
+  }
+
+  return true;
+}
+
+
+bool Unpack::AddFilter(UnpackFilter &Filter)
+{
+  if (Filters.Size()>=MAX_UNPACK_FILTERS)
+  {
+    UnpWriteBuf(); // Write data, apply and flush filters.
+    if (Filters.Size()>=MAX_UNPACK_FILTERS)
+      InitFilters(); // Still too many filters, prevent excessive memory use.
+  }
+
+  // If distance to filter start is that large that due to circular dictionary
+  // mode now it points to old not written yet data, then we set 'NextWindow'
+  // flag and process this filter only after processing that older data.
+  Filter.NextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<=Filter.BlockStart;
+
+  Filter.BlockStart=uint((Filter.BlockStart+UnpPtr)&MaxWinMask);
+  Filters.Push(Filter);
+  return true;
+}
+
+
+bool Unpack::UnpReadBuf()
+{
+  int DataSize=ReadTop-Inp.InAddr; // Data left to process.
+  if (DataSize<0)
+    return false;
+  BlockHeader.BlockSize-=Inp.InAddr-BlockHeader.BlockStart;
+  if (Inp.InAddr>BitInput::MAX_SIZE/2)
+  {
+    // If we already processed more than half of buffer, let's move
+    // remaining data into beginning to free more space for new data
+    // and ensure that calling function does not cross the buffer border
+    // even if we did not read anything here. Also it ensures that read size
+    // is not less than CRYPT_BLOCK_SIZE, so we can align it without risk
+    // to make it zero.
+    if (DataSize>0)
+      memmove(Inp.InBuf,Inp.InBuf+Inp.InAddr,DataSize);
+    Inp.InAddr=0;
+    ReadTop=DataSize;
+  }
+  else
+    DataSize=ReadTop;
+  int ReadCode=0;
+  if (BitInput::MAX_SIZE!=DataSize)
+    ReadCode=UnpIO->UnpRead(Inp.InBuf+DataSize,BitInput::MAX_SIZE-DataSize);
+  if (ReadCode>0) // Can be also -1.
+    ReadTop+=ReadCode;
+  ReadBorder=ReadTop-30;
+  BlockHeader.BlockStart=Inp.InAddr;
+  if (BlockHeader.BlockSize!=-1) // '-1' means not defined yet.
+  {
+    // We may need to quit from main extraction loop and read new block header
+    // and trees earlier than data in input buffer ends.
+    ReadBorder=Min(ReadBorder,BlockHeader.BlockStart+BlockHeader.BlockSize-1);
+  }
+  return ReadCode!=-1;
+}
+
+
+void Unpack::UnpWriteBuf()
+{
+  size_t WrittenBorder=WrPtr;
+  size_t FullWriteSize=(UnpPtr-WrittenBorder)&MaxWinMask;
+  size_t WriteSizeLeft=FullWriteSize;
+  bool NotAllFiltersProcessed=false;
+  for (size_t I=0;I<Filters.Size();I++)
+  {
+    // Here we apply filters to data which we need to write.
+    // We always copy data to another memory block before processing.
+    // We cannot process them just in place in Window buffer, because
+    // these data can be used for future string matches, so we must
+    // preserve them in original form.
+
+    UnpackFilter *flt=&Filters[I];
+    if (flt->Type==FILTER_NONE)
+      continue;
+    if (flt->NextWindow)
+    {
+      // Here we skip filters which have block start in current data range
+      // due to address wrap around in circular dictionary, but actually
+      // belong to next dictionary block. If such filter start position
+      // is included to current write range, then we reset 'NextWindow' flag.
+      // In fact we can reset it even without such check, because current
+      // implementation seems to guarantee 'NextWindow' flag reset after
+      // buffer writing for all existing filters. But let's keep this check
+      // just in case. Compressor guarantees that distance between
+      // filter block start and filter storing position cannot exceed
+      // the dictionary size. So if we covered the filter block start with
+      // our write here, we can safely assume that filter is applicable
+      // to next block on no further wrap arounds is possible.
+      if (((flt->BlockStart-WrPtr)&MaxWinMask)<=FullWriteSize)
+        flt->NextWindow=false;
+      continue;
+    }
+    uint BlockStart=flt->BlockStart;
+    uint BlockLength=flt->BlockLength;
+    if (((BlockStart-WrittenBorder)&MaxWinMask)<WriteSizeLeft)
+    {
+      if (WrittenBorder!=BlockStart)
+      {
+        UnpWriteArea(WrittenBorder,BlockStart);
+        WrittenBorder=BlockStart;
+        WriteSizeLeft=(UnpPtr-WrittenBorder)&MaxWinMask;
+      }
+      if (BlockLength<=WriteSizeLeft)
+      {
+        if (BlockLength>0) // We set it to 0 also for invalid filters.
+        {
+          uint BlockEnd=(BlockStart+BlockLength)&MaxWinMask;
+
+          FilterSrcMemory.Alloc(BlockLength);
+          byte *Mem=&FilterSrcMemory[0];
+          if (BlockStart<BlockEnd || BlockEnd==0)
+          {
+            if (Fragmented)
+              FragWindow.CopyData(Mem,BlockStart,BlockLength);
+            else
+              memcpy(Mem,Window+BlockStart,BlockLength);
+          }
+          else
+          {
+            size_t FirstPartLength=size_t(MaxWinSize-BlockStart);
+            if (Fragmented)
+            {
+              FragWindow.CopyData(Mem,BlockStart,FirstPartLength);
+              FragWindow.CopyData(Mem+FirstPartLength,0,BlockEnd);
+            }
+            else
+            {
+              memcpy(Mem,Window+BlockStart,FirstPartLength);
+              memcpy(Mem+FirstPartLength,Window,BlockEnd);
+            }
+          }
+
+          byte *OutMem=ApplyFilter(Mem,BlockLength,flt);
+
+          Filters[I].Type=FILTER_NONE;
+
+          if (OutMem!=NULL)
+            UnpIO->UnpWrite(OutMem,BlockLength);
+
+          UnpSomeRead=true;
+          WrittenFileSize+=BlockLength;
+          WrittenBorder=BlockEnd;
+          WriteSizeLeft=(UnpPtr-WrittenBorder)&MaxWinMask;
+        }
+      }
+      else
+      {
+        // Current filter intersects the window write border, so we adjust
+        // the window border to process this filter next time, not now.
+        WrPtr=WrittenBorder;
+
+        // Since Filter start position can only increase, we quit processing
+        // all following filters for this data block and reset 'NextWindow'
+        // flag for them.
+        for (size_t J=I;J<Filters.Size();J++)
+        {
+          UnpackFilter *flt=&Filters[J];
+          if (flt->Type!=FILTER_NONE)
+            flt->NextWindow=false;
+        }
+
+        // Do not write data left after current filter now.
+        NotAllFiltersProcessed=true;
+        break;
+      }
+    }
+  }
+
+  // Remove processed filters from queue.
+  size_t EmptyCount=0;
+  for (size_t I=0;I<Filters.Size();I++)
+  {
+    if (EmptyCount>0)
+      Filters[I-EmptyCount]=Filters[I];
+    if (Filters[I].Type==FILTER_NONE)
+      EmptyCount++;
+  }
+  if (EmptyCount>0)
+    Filters.Alloc(Filters.Size()-EmptyCount);
+
+  if (!NotAllFiltersProcessed) // Only if all filters are processed.
+  {
+    // Write data left after last filter.
+    UnpWriteArea(WrittenBorder,UnpPtr);
+    WrPtr=UnpPtr;
+  }
+
+  // We prefer to write data in blocks not exceeding UNPACK_MAX_WRITE
+  // instead of potentially huge MaxWinSize blocks. It also allows us
+  // to keep the size of Filters array reasonable.
+  WriteBorder=(UnpPtr+Min(MaxWinSize,UNPACK_MAX_WRITE))&MaxWinMask;
+
+  // Choose the nearest among WriteBorder and WrPtr actual written border.
+  // If border is equal to UnpPtr, it means that we have MaxWinSize data ahead.
+  if (WriteBorder==UnpPtr || 
+      WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MaxWinMask)<((WriteBorder-UnpPtr)&MaxWinMask))
+    WriteBorder=WrPtr;
+}
+
+
+byte* Unpack::ApplyFilter(byte *Data,uint DataSize,UnpackFilter *Flt)
+{
+  byte *SrcData=Data;
+  switch(Flt->Type)
+  {
+    case FILTER_E8:
+    case FILTER_E8E9:
+      {
+        uint FileOffset=(uint)WrittenFileSize;
+
+        const uint FileSize=0x1000000;
+        byte CmpByte2=Flt->Type==FILTER_E8E9 ? 0xe9:0xe8;
+        // DataSize is unsigned, so we use "CurPos+4" and not "DataSize-4"
+        // to avoid overflow for DataSize<4.
+        for (uint CurPos=0;CurPos+4<DataSize;)
+        {
+          byte CurByte=*(Data++);
+          CurPos++;
+          if (CurByte==0xe8 || CurByte==CmpByte2)
+          {
+            uint Offset=(CurPos+FileOffset)%FileSize;
+            uint Addr=RawGet4(Data);
+
+            // We check 0x80000000 bit instead of '< 0' comparison
+            // not assuming int32 presence or uint size and endianness.
+            if ((Addr & 0x80000000)!=0)              // Addr<0
+            {
+              if (((Addr+Offset) & 0x80000000)==0)   // Addr+Offset>=0
+                RawPut4(Addr+FileSize,Data);
+            }
+            else
+              if (((Addr-FileSize) & 0x80000000)!=0) // Addr<FileSize
+                RawPut4(Addr-Offset,Data);
+
+            Data+=4;
+            CurPos+=4;
+          }
+        }
+      }
+      return SrcData;
+    case FILTER_ARM:
+      {
+        uint FileOffset=(uint)WrittenFileSize;
+        // DataSize is unsigned, so we use "CurPos+3" and not "DataSize-3"
+        // to avoid overflow for DataSize<3.
+        for (uint CurPos=0;CurPos+3<DataSize;CurPos+=4)
+        {
+          byte *D=Data+CurPos;
+          if (D[3]==0xeb) // BL command with '1110' (Always) condition.
+          {
+            uint Offset=D[0]+uint(D[1])*0x100+uint(D[2])*0x10000;
+            Offset-=(FileOffset+CurPos)/4;
+            D[0]=(byte)Offset;
+            D[1]=(byte)(Offset>>8);
+            D[2]=(byte)(Offset>>16);
+          }
+        }
+      }
+      return SrcData;
+    case FILTER_DELTA:
+      {
+        // Unlike RAR3, we do not need to reject excessive channel
+        // values here, since RAR5 uses only 5 bits to store channel.
+        uint Channels=Flt->Channels,SrcPos=0;
+
+        FilterDstMemory.Alloc(DataSize);
+        byte *DstData=&FilterDstMemory[0];
+
+        // Bytes from same channels are grouped to continual data blocks,
+        // so we need to place them back to their interleaving positions.
+        for (uint CurChannel=0;CurChannel<Channels;CurChannel++)
+        {
+          byte PrevByte=0;
+          for (uint DestPos=CurChannel;DestPos<DataSize;DestPos+=Channels)
+            DstData[DestPos]=(PrevByte-=Data[SrcPos++]);
+        }
+        return DstData;
+      }
+
+  }
+  return NULL;
+}
+
+
+void Unpack::UnpWriteArea(size_t StartPtr,size_t EndPtr)
+{
+  if (EndPtr!=StartPtr)
+    UnpSomeRead=true;
+  if (EndPtr<StartPtr)
+    UnpAllBuf=true;
+
+  if (Fragmented)
+  {
+    size_t SizeToWrite=(EndPtr-StartPtr) & MaxWinMask;
+    while (SizeToWrite>0)
+    {
+      size_t BlockSize=FragWindow.GetBlockSize(StartPtr,SizeToWrite);
+      UnpWriteData(&FragWindow[StartPtr],BlockSize);
+      SizeToWrite-=BlockSize;
+      StartPtr=(StartPtr+BlockSize) & MaxWinMask;
+    }
+  }
+  else
+    if (EndPtr<StartPtr)
+    {
+      UnpWriteData(Window+StartPtr,MaxWinSize-StartPtr);
+      UnpWriteData(Window,EndPtr);
+    }
+    else
+      UnpWriteData(Window+StartPtr,EndPtr-StartPtr);
+}
+
+
+void Unpack::UnpWriteData(byte *Data,size_t Size)
+{
+  if (WrittenFileSize>=DestUnpSize)
+    return;
+  size_t WriteSize=Size;
+  int64 LeftToWrite=DestUnpSize-WrittenFileSize;
+  if ((int64)WriteSize>LeftToWrite)
+    WriteSize=(size_t)LeftToWrite;
+  UnpIO->UnpWrite(Data,WriteSize);
+  WrittenFileSize+=Size;
+}
+
+
+void Unpack::UnpInitData50(bool Solid)
+{
+  if (!Solid)
+    TablesRead5=false;
+}
+
+
+bool Unpack::ReadBlockHeader(BitInput &Inp,UnpackBlockHeader &Header)
+{
+  Header.HeaderSize=0;
+
+  if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-7)
+    if (!UnpReadBuf())
+      return false;
+  Inp.faddbits((8-Inp.InBit)&7);
+  
+  byte BlockFlags=Inp.fgetbits()>>8;
+  Inp.faddbits(8);
+  uint ByteCount=((BlockFlags>>3)&3)+1; // Block size byte count.
+  
+  if (ByteCount==4)
+    return false;
+
+  Header.HeaderSize=2+ByteCount;
+
+  Header.BlockBitSize=(BlockFlags&7)+1;
+
+  byte SavedCheckSum=Inp.fgetbits()>>8;
+  Inp.faddbits(8);
+
+  int BlockSize=0;
+  for (uint I=0;I<ByteCount;I++)
+  {
+    BlockSize+=(Inp.fgetbits()>>8)<<(I*8);
+    Inp.addbits(8);
+  }
+
+  Header.BlockSize=BlockSize;
+  byte CheckSum=byte(0x5a^BlockFlags^BlockSize^(BlockSize>>8)^(BlockSize>>16));
+  if (CheckSum!=SavedCheckSum)
+    return false;
+
+  Header.BlockStart=Inp.InAddr;
+  ReadBorder=Min(ReadBorder,Header.BlockStart+Header.BlockSize-1);
+
+  Header.LastBlockInFile=(BlockFlags & 0x40)!=0;
+  Header.TablePresent=(BlockFlags & 0x80)!=0;
+  return true;
+}
+
+
+bool Unpack::ReadTables(BitInput &Inp,UnpackBlockHeader &Header,UnpackBlockTables &Tables)
+{
+  if (!Header.TablePresent)
+    return true;
+
+  if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-25)
+    if (!UnpReadBuf())
+      return false;
+
+  byte BitLength[BC];
+  for (uint I=0;I<BC;I++)
+  {
+    uint Length=(byte)(Inp.fgetbits() >> 12);
+    Inp.faddbits(4);
+    if (Length==15)
+    {
+      uint ZeroCount=(byte)(Inp.fgetbits() >> 12);
+      Inp.faddbits(4);
+      if (ZeroCount==0)
+        BitLength[I]=15;
+      else
+      {
+        ZeroCount+=2;
+        while (ZeroCount-- > 0 && I<ASIZE(BitLength))
+          BitLength[I++]=0;
+        I--;
+      }
+    }
+    else
+      BitLength[I]=Length;
+  }
+
+  MakeDecodeTables(BitLength,&Tables.BD,BC);
+
+  byte Table[HUFF_TABLE_SIZE];
+  const uint TableSize=HUFF_TABLE_SIZE;
+  for (uint I=0;I<TableSize;)
+  {
+    if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop-5)
+      if (!UnpReadBuf())
+        return false;
+    uint Number=DecodeNumber(Inp,&Tables.BD);
+    if (Number<16)
+    {
+      Table[I]=Number;
+      I++;
+    }
+    else
+      if (Number<18)
+      {
+        uint N;
+        if (Number==16)
+        {
+          N=(Inp.fgetbits() >> 13)+3;
+          Inp.faddbits(3);
+        }
+        else
+        {
+          N=(Inp.fgetbits() >> 9)+11;
+          Inp.faddbits(7);
+        }
+        if (I==0)
+        {
+          // We cannot have "repeat previous" code at the first position.
+          // Multiple such codes would shift Inp position without changing I,
+          // which can lead to reading beyond of Inp boundary in mutithreading
+          // mode, where Inp.ExternalBuffer disables bounds check and we just
+          // reserve a lot of buffer space to not need such check normally.
+          return false;
+        }
+        else
+          while (N-- > 0 && I<TableSize)
+          {
+            Table[I]=Table[I-1];
+            I++;
+          }
+      }
+      else
+      {
+        uint N;
+        if (Number==18)
+        {
+          N=(Inp.fgetbits() >> 13)+3;
+          Inp.faddbits(3);
+        }
+        else
+        {
+          N=(Inp.fgetbits() >> 9)+11;
+          Inp.faddbits(7);
+        }
+        while (N-- > 0 && I<TableSize)
+          Table[I++]=0;
+      }
+  }
+  TablesRead5=true;
+  if (!Inp.ExternalBuffer && Inp.InAddr>ReadTop)
+    return false;
+  MakeDecodeTables(&Table[0],&Tables.LD,NC);
+  MakeDecodeTables(&Table[NC],&Tables.DD,DC);
+  MakeDecodeTables(&Table[NC+DC],&Tables.LDD,LDC);
+  MakeDecodeTables(&Table[NC+DC+LDC],&Tables.RD,RC);
+  return true;
+}
+
+
+void Unpack::InitFilters()
+{
+  Filters.SoftReset();
+}
diff --git a/third_party/unrar/src/unpack50frag.cpp b/third_party/unrar/src/unpack50frag.cpp
new file mode 100644
index 0000000..745b1b3
--- /dev/null
+++ b/third_party/unrar/src/unpack50frag.cpp
@@ -0,0 +1,103 @@
+FragmentedWindow::FragmentedWindow()
+{
+  memset(Mem,0,sizeof(Mem));
+  memset(MemSize,0,sizeof(MemSize));
+}
+
+
+FragmentedWindow::~FragmentedWindow()
+{
+  Reset();
+}
+
+
+void FragmentedWindow::Reset()
+{
+  for (uint I=0;I<ASIZE(Mem);I++)
+    if (Mem[I]!=NULL)
+    {
+      free(Mem[I]);
+      Mem[I]=NULL;
+    }
+}
+
+
+void FragmentedWindow::Init(size_t WinSize)
+{
+  Reset();
+
+  uint BlockNum=0;
+  size_t TotalSize=0; // Already allocated.
+  while (TotalSize<WinSize && BlockNum<ASIZE(Mem))
+  {
+    size_t Size=WinSize-TotalSize; // Size needed to allocate.
+
+    // Minimum still acceptable block size. Next allocations cannot be larger
+    // than current, so we do not need blocks if they are smaller than
+    // "size left / attempts left". Also we do not waste time to blocks
+    // smaller than some arbitrary constant.
+    size_t MinSize=Max(Size/(ASIZE(Mem)-BlockNum), 0x400000);
+
+    byte *NewMem=NULL;
+    while (Size>=MinSize)
+    {
+      NewMem=(byte *)malloc(Size);
+      if (NewMem!=NULL)
+        break;
+      Size-=Size/32;
+    }
+    if (NewMem==NULL)
+      throw std::bad_alloc();
+    
+    // Clean the window to generate the same output when unpacking corrupt
+    // RAR files, which may access to unused areas of sliding dictionary.
+    memset(NewMem,0,Size);
+
+    Mem[BlockNum]=NewMem;
+    TotalSize+=Size;
+    MemSize[BlockNum]=TotalSize;
+    BlockNum++;
+  }
+  if (TotalSize<WinSize) // Not found enough free blocks.
+    throw std::bad_alloc();
+}
+
+
+byte& FragmentedWindow::operator [](size_t Item)
+{
+  if (Item<MemSize[0])
+    return Mem[0][Item];
+  for (uint I=1;I<ASIZE(MemSize);I++)
+    if (Item<MemSize[I])
+      return Mem[I][Item-MemSize[I-1]];
+  return Mem[0][0]; // Must never happen;
+}
+
+
+void FragmentedWindow::CopyString(uint Length,uint Distance,size_t &UnpPtr,size_t MaxWinMask)
+{
+  size_t SrcPtr=UnpPtr-Distance;
+  while (Length-- > 0)
+  {
+    (*this)[UnpPtr]=(*this)[SrcPtr++ & MaxWinMask];
+    // We need to have masked UnpPtr after quit from loop, so it must not
+    // be replaced with '(*this)[UnpPtr++ & MaxWinMask]'
+    UnpPtr=(UnpPtr+1) & MaxWinMask;
+  }
+}
+
+
+void FragmentedWindow::CopyData(byte *Dest,size_t WinPos,size_t Size)
+{
+  for (size_t I=0;I<Size;I++)
+    Dest[I]=(*this)[WinPos+I];
+}
+
+
+size_t FragmentedWindow::GetBlockSize(size_t StartPos,size_t RequiredSize)
+{
+  for (uint I=0;I<ASIZE(MemSize);I++)
+    if (StartPos<MemSize[I])
+      return Min(MemSize[I]-StartPos,RequiredSize);
+  return 0; // Must never be here.
+}
diff --git a/third_party/unrar/src/unpack50mt.cpp b/third_party/unrar/src/unpack50mt.cpp
new file mode 100644
index 0000000..59e111b
--- /dev/null
+++ b/third_party/unrar/src/unpack50mt.cpp
@@ -0,0 +1,655 @@
+#define UNP_READ_SIZE_MT        0x400000
+#define UNP_BLOCKS_PER_THREAD          2
+
+
+struct UnpackThreadDataList
+{
+  UnpackThreadData *D;
+  uint BlockCount;
+};
+
+
+THREAD_PROC(UnpackDecodeThread)
+{
+  UnpackThreadDataList *DL=(UnpackThreadDataList *)Data;
+  for (uint I=0;I<DL->BlockCount;I++)
+    DL->D->UnpackPtr->UnpackDecode(DL->D[I]);
+}
+
+
+void Unpack::InitMT()
+{
+  if (ReadBufMT==NULL)
+  {
+    // Even getbits32 can read up to 3 additional bytes after current
+    // and our block header and table reading code can look much further.
+    // Let's allocate the additional space here, so we do not need to check
+    // bounds for every bit field access.
+    const size_t Overflow=1024;
+
+    ReadBufMT=new byte[UNP_READ_SIZE_MT+Overflow];
+    memset(ReadBufMT,0,UNP_READ_SIZE_MT+Overflow);
+  }
+  if (UnpThreadData==NULL)
+  {
+    uint MaxItems=MaxUserThreads*UNP_BLOCKS_PER_THREAD;
+    UnpThreadData=new UnpackThreadData[MaxItems];
+    memset(UnpThreadData,0,sizeof(UnpackThreadData)*MaxItems);
+
+    for (uint I=0;I<MaxItems;I++)
+    {
+      UnpackThreadData *CurData=UnpThreadData+I;
+      if (CurData->Decoded==NULL)
+      {
+        // Typical number of items in RAR blocks does not exceed 0x4000.
+        CurData->DecodedAllocated=0x4100;
+        // It will be freed in the object destructor, not in this file.
+        CurData->Decoded=(UnpackDecodedItem *)malloc(CurData->DecodedAllocated*sizeof(UnpackDecodedItem));
+        if (CurData->Decoded==NULL)
+          ErrHandler.MemoryError();
+      }
+    }
+  }
+}
+
+
+void Unpack::Unpack5MT(bool Solid)
+{
+  InitMT();
+  UnpInitData(Solid);
+
+  for (uint I=0;I<MaxUserThreads*UNP_BLOCKS_PER_THREAD;I++)
+  {
+    UnpackThreadData *CurData=UnpThreadData+I;
+    CurData->LargeBlock=false;
+    CurData->Incomplete=false;
+  }
+
+  UnpThreadData[0].BlockHeader=BlockHeader;
+  UnpThreadData[0].BlockTables=BlockTables;
+  uint LastBlockNum=0;
+
+  int DataSize=0;
+  int BlockStart=0;
+
+
+  // 'true' if we found a block too large for multithreaded extraction,
+  // so we switched to single threaded mode until the end of file.
+  // Large blocks could cause too high memory use in multithreaded mode.
+  bool LargeBlock=false;
+
+  bool Done=false;
+  while (!Done)
+  {
+    // Data amount, which is guaranteed to fit block header and tables,
+    // so we can safely read them without additional checks.
+    const int TooSmallToProcess=1024;
+
+    int ReadSize=UnpIO->UnpRead(ReadBufMT+DataSize,(UNP_READ_SIZE_MT-DataSize)&~0xf);
+    if (ReadSize<0)
+      break;
+    DataSize+=ReadSize;
+    if (DataSize==0)
+      break;
+
+    // First read chunk can be small if we are near the end of volume
+    // and we want it to fit block header and tables.
+    if (ReadSize>0 && DataSize<TooSmallToProcess)
+      continue;
+
+    while (BlockStart<DataSize && !Done)
+    {
+      uint BlockNumber=0,BlockNumberMT=0;
+      while (BlockNumber<MaxUserThreads*UNP_BLOCKS_PER_THREAD)
+      {
+        UnpackThreadData *CurData=UnpThreadData+BlockNumber;
+        LastBlockNum=BlockNumber;
+        CurData->UnpackPtr=this;
+
+        // 'Incomplete' thread is present. This is a thread processing block
+        // in the end of buffer, split between two read operations.
+        if (CurData->Incomplete)
+          CurData->DataSize=DataSize;
+        else
+        {
+          CurData->Inp.SetExternalBuffer(ReadBufMT+BlockStart);
+          CurData->Inp.InitBitInput();
+          CurData->DataSize=DataSize-BlockStart;
+          if (CurData->DataSize==0)
+            break;
+          CurData->DamagedData=false;
+          CurData->HeaderRead=false;
+          CurData->TableRead=false;
+        }
+
+        // We should not use 'last block in file' block flag here unless
+        // we'll check the block size, because even if block is last in file,
+        // it can exceed the current buffer and require more reading.
+        CurData->NoDataLeft=(ReadSize==0);
+
+        CurData->Incomplete=false;
+        CurData->ThreadNumber=BlockNumber;
+
+        if (!CurData->HeaderRead)
+        {
+          CurData->HeaderRead=true;
+          if (!ReadBlockHeader(CurData->Inp,CurData->BlockHeader) ||
+              !CurData->BlockHeader.TablePresent && !TablesRead5)
+          {
+            Done=true;
+            break;
+          }
+          TablesRead5=true;
+        }
+
+        // To prevent too high memory use we switch to single threaded mode
+        // if block exceeds this size. Typically RAR blocks do not exceed
+        // 64 KB, so this protection should not affect most of valid archives.
+        const int LargeBlockSize=0x20000;
+        if (LargeBlock || CurData->BlockHeader.BlockSize>LargeBlockSize)
+          LargeBlock=CurData->LargeBlock=true;
+        else
+          BlockNumberMT++; // Number of normal blocks processed in MT mode.
+
+        BlockStart+=CurData->BlockHeader.HeaderSize+CurData->BlockHeader.BlockSize;
+
+        BlockNumber++;
+
+        int DataLeft=DataSize-BlockStart;
+        if (DataLeft>=0 && CurData->BlockHeader.LastBlockInFile)
+          break;
+
+        // For second and following threads we move smaller blocks to buffer
+        // start to ensure that we have enough data to fit block header
+        // and tables.
+        if (DataLeft<TooSmallToProcess)
+          break;
+      }
+      
+//#undef USE_THREADS
+      UnpackThreadDataList UTDArray[MaxPoolThreads];
+      uint UTDArrayPos=0;
+
+      uint MaxBlockPerThread=BlockNumberMT/MaxUserThreads;
+      if (BlockNumberMT%MaxUserThreads!=0)
+        MaxBlockPerThread++;
+
+      // Decode all normal blocks until the first 'large' if any.
+      for (uint CurBlock=0;CurBlock<BlockNumberMT;CurBlock+=MaxBlockPerThread)
+      {
+        UnpackThreadDataList *UTD=UTDArray+UTDArrayPos++;
+        UTD->D=UnpThreadData+CurBlock;
+        UTD->BlockCount=Min(MaxBlockPerThread,BlockNumberMT-CurBlock);
+      
+#ifdef USE_THREADS
+        if (BlockNumber==1)
+          UnpackDecode(*UTD->D);
+        else
+          UnpThreadPool->AddTask(UnpackDecodeThread,(void*)UTD);
+#else
+        for (uint I=0;I<UTD->BlockCount;I++)
+          UnpackDecode(UTD->D[I]);
+#endif
+      }
+
+      if (BlockNumber==0)
+        break;
+
+#ifdef USE_THREADS
+      UnpThreadPool->WaitDone();
+#endif
+
+      bool IncompleteThread=false;
+      
+      for (uint Block=0;Block<BlockNumber;Block++)
+      {
+        UnpackThreadData *CurData=UnpThreadData+Block;
+        if (!CurData->LargeBlock && !ProcessDecoded(*CurData) ||
+            CurData->LargeBlock && !UnpackLargeBlock(*CurData) ||
+            CurData->DamagedData)
+        {
+          Done=true;
+          break;
+        }
+        if (CurData->Incomplete)
+        {
+          int BufPos=int(CurData->Inp.InBuf+CurData->Inp.InAddr-ReadBufMT);
+          if (DataSize<=BufPos) // Thread exceeded input buffer boundary.
+          {
+            Done=true;
+            break;
+          }
+          IncompleteThread=true;
+          memmove(ReadBufMT,ReadBufMT+BufPos,DataSize-BufPos);
+          CurData->BlockHeader.BlockSize-=CurData->Inp.InAddr-CurData->BlockHeader.BlockStart;
+          CurData->BlockHeader.HeaderSize=0;
+          CurData->BlockHeader.BlockStart=0;
+          CurData->Inp.InBuf=ReadBufMT;
+          CurData->Inp.InAddr=0;
+
+          if (Block!=0)
+          {
+            // Move the incomplete thread entry to the first position,
+            // so we'll start processing from it. Preserve the original
+            // buffer for decoded data.
+            UnpackDecodedItem *Decoded=UnpThreadData[0].Decoded;
+            uint DecodedAllocated=UnpThreadData[0].DecodedAllocated;
+            UnpThreadData[0]=*CurData;
+            UnpThreadData[0].Decoded=Decoded;
+            UnpThreadData[0].DecodedAllocated=DecodedAllocated;
+            CurData->Incomplete=false;
+          }
+
+          BlockStart=0;
+          DataSize-=BufPos;
+          break;
+        }
+        else
+          if (CurData->BlockHeader.LastBlockInFile)
+          {
+            Done=true;
+            break;
+          }
+      }
+      
+      if (IncompleteThread || Done)
+        break; // Current buffer is done, read more data or quit.
+      else
+      {
+        int DataLeft=DataSize-BlockStart;
+        if (DataLeft<TooSmallToProcess)
+        {
+          if (DataLeft<0) // Invalid data, must not happen in valid archive.
+          {
+            Done=true;
+            break;
+          }
+
+          // If we do not have incomplete thread and have some data
+          // in the end of buffer, too small for single thread,
+          // let's move it to beginning of next buffer.
+          if (DataLeft>0)
+            memmove(ReadBufMT,ReadBufMT+BlockStart,DataLeft);
+          DataSize=DataLeft;
+          BlockStart=0;
+          break; // Current buffer is done, try to read more data.
+        }
+      }
+    }
+  }
+  UnpPtr&=MaxWinMask; // ProcessDecoded and maybe others can leave UnpPtr > MaxWinMask here.
+  UnpWriteBuf();
+
+  BlockHeader=UnpThreadData[LastBlockNum].BlockHeader;
+  BlockTables=UnpThreadData[LastBlockNum].BlockTables;
+}
+
+
+// Decode Huffman block and save decoded data to memory.
+void Unpack::UnpackDecode(UnpackThreadData &D)
+{
+  if (!D.TableRead)
+  {
+    D.TableRead=true;
+    if (!ReadTables(D.Inp,D.BlockHeader,D.BlockTables))
+    {
+      D.DamagedData=true;
+      return;
+    }
+  }
+
+  if (D.Inp.InAddr>D.BlockHeader.HeaderSize+D.BlockHeader.BlockSize)
+  {
+    D.DamagedData=true;
+    return;
+  }
+  
+  D.DecodedSize=0;
+  int BlockBorder=D.BlockHeader.BlockStart+D.BlockHeader.BlockSize-1;
+
+  // Reserve enough space even for filter entry.
+  int DataBorder=D.DataSize-16;
+  int ReadBorder=Min(BlockBorder,DataBorder);
+
+  while (true)
+  {
+    if (D.Inp.InAddr>=ReadBorder)
+    {
+      if (D.Inp.InAddr>BlockBorder || D.Inp.InAddr==BlockBorder && 
+          D.Inp.InBit>=D.BlockHeader.BlockBitSize)
+        break;
+
+      // If we do not have any more data in file to read, we must process
+      // what we have until last byte. Otherwise we can return and append
+      // more data to unprocessed few bytes.
+      if ((D.Inp.InAddr>=DataBorder) && !D.NoDataLeft || D.Inp.InAddr>=D.DataSize)
+      {
+        D.Incomplete=true;
+        break;
+      }
+    }
+    if (D.DecodedSize>D.DecodedAllocated-8) // Filter can use several slots.
+    {
+      D.DecodedAllocated=D.DecodedAllocated*2;
+      void *Decoded=realloc(D.Decoded,D.DecodedAllocated*sizeof(UnpackDecodedItem));
+      if (Decoded==NULL)
+        ErrHandler.MemoryError(); // D.Decoded will be freed in the destructor.
+      D.Decoded=(UnpackDecodedItem *)Decoded;
+    }
+
+    UnpackDecodedItem *CurItem=D.Decoded+D.DecodedSize++;
+
+    uint MainSlot=DecodeNumber(D.Inp,&D.BlockTables.LD);
+    if (MainSlot<256)
+    {
+      if (D.DecodedSize>1)
+      {
+        UnpackDecodedItem *PrevItem=CurItem-1;
+        if (PrevItem->Type==UNPDT_LITERAL && PrevItem->Length<3)
+        {
+          PrevItem->Length++;
+          PrevItem->Literal[PrevItem->Length]=(byte)MainSlot;
+          D.DecodedSize--;
+          continue;
+        }
+      }
+      CurItem->Type=UNPDT_LITERAL;
+      CurItem->Literal[0]=(byte)MainSlot;
+      CurItem->Length=0;
+      continue;
+    }
+    if (MainSlot>=262)
+    {
+      uint Length=SlotToLength(D.Inp,MainSlot-262);
+
+      uint DBits,Distance=1,DistSlot=DecodeNumber(D.Inp,&D.BlockTables.DD);
+      if (DistSlot<4)
+      {
+        DBits=0;
+        Distance+=DistSlot;
+      }
+      else
+      {
+        DBits=DistSlot/2 - 1;
+        Distance+=(2 | (DistSlot & 1)) << DBits;
+      }
+
+      if (DBits>0)
+      {
+        if (DBits>=4)
+        {
+          if (DBits>4)
+          {
+            Distance+=((D.Inp.getbits32()>>(36-DBits))<<4);
+            D.Inp.addbits(DBits-4);
+          }
+          uint LowDist=DecodeNumber(D.Inp,&D.BlockTables.LDD);
+          Distance+=LowDist;
+        }
+        else
+        {
+          Distance+=D.Inp.getbits32()>>(32-DBits);
+          D.Inp.addbits(DBits);
+        }
+      }
+
+      if (Distance>0x100)
+      {
+        Length++;
+        if (Distance>0x2000)
+        {
+          Length++;
+          if (Distance>0x40000)
+            Length++;
+        }
+      }
+
+      CurItem->Type=UNPDT_MATCH;
+      CurItem->Length=(ushort)Length;
+      CurItem->Distance=Distance;
+      continue;
+    }
+    if (MainSlot==256)
+    {
+      UnpackFilter Filter;
+      ReadFilter(D.Inp,Filter);
+      
+      CurItem->Type=UNPDT_FILTER;
+      CurItem->Length=Filter.Type;
+      CurItem->Distance=Filter.BlockStart;
+
+      CurItem=D.Decoded+D.DecodedSize++;
+
+      CurItem->Type=UNPDT_FILTER;
+      CurItem->Length=Filter.Channels;
+      CurItem->Distance=Filter.BlockLength;
+
+      continue;
+    }
+    if (MainSlot==257)
+    {
+      CurItem->Type=UNPDT_FULLREP;
+      continue;
+    }
+    if (MainSlot<262)
+    {
+      CurItem->Type=UNPDT_REP;
+      CurItem->Distance=MainSlot-258;
+      uint LengthSlot=DecodeNumber(D.Inp,&D.BlockTables.RD);
+      uint Length=SlotToLength(D.Inp,LengthSlot);
+      CurItem->Length=(ushort)Length;
+      continue;
+    }
+  }
+}
+
+
+// Process decoded Huffman block data.
+bool Unpack::ProcessDecoded(UnpackThreadData &D)
+{
+  UnpackDecodedItem *Item=D.Decoded,*Border=D.Decoded+D.DecodedSize;
+  while (Item<Border)
+  {
+    UnpPtr&=MaxWinMask;
+    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
+    {
+      UnpWriteBuf();
+      if (WrittenFileSize>DestUnpSize)
+        return false;
+    }
+
+    if (Item->Type==UNPDT_LITERAL)
+    {
+#if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED)
+      if (Item->Length==3 && UnpPtr<MaxWinSize-4)
+      {
+        *(uint32 *)(Window+UnpPtr)=*(uint32 *)Item->Literal;
+        UnpPtr+=4;
+      }
+      else
+#endif
+        for (uint I=0;I<=Item->Length;I++)
+          Window[UnpPtr++ & MaxWinMask]=Item->Literal[I];
+    }
+    else
+      if (Item->Type==UNPDT_MATCH)
+      {
+        InsertOldDist(Item->Distance);
+        LastLength=Item->Length;
+        CopyString(Item->Length,Item->Distance);
+      }
+      else
+        if (Item->Type==UNPDT_REP)
+        {
+          uint Distance=OldDist[Item->Distance];
+          for (uint I=Item->Distance;I>0;I--)
+            OldDist[I]=OldDist[I-1];
+          OldDist[0]=Distance;
+          LastLength=Item->Length;
+          CopyString(Item->Length,Distance);
+        }
+        else
+          if (Item->Type==UNPDT_FULLREP)
+          {
+            if (LastLength!=0)
+              CopyString(LastLength,OldDist[0]);
+          }
+          else
+            if (Item->Type==UNPDT_FILTER)
+            {
+              UnpackFilter Filter;
+              
+              Filter.Type=(byte)Item->Length;
+              Filter.BlockStart=Item->Distance;
+
+              Item++;
+
+              Filter.Channels=(byte)Item->Length;
+              Filter.BlockLength=Item->Distance;
+
+              AddFilter(Filter);
+            }
+    Item++;
+  }
+  return true;
+}
+
+
+// For large blocks we decode and process in same function in single threaded
+// mode, so we do not need to store intermediate data in memory.
+bool Unpack::UnpackLargeBlock(UnpackThreadData &D)
+{
+  if (!D.TableRead)
+  {
+    D.TableRead=true;
+    if (!ReadTables(D.Inp,D.BlockHeader,D.BlockTables))
+    {
+      D.DamagedData=true;
+      return false;
+    }
+  }
+
+  if (D.Inp.InAddr>D.BlockHeader.HeaderSize+D.BlockHeader.BlockSize)
+  {
+    D.DamagedData=true;
+    return false;
+  }
+  
+  int BlockBorder=D.BlockHeader.BlockStart+D.BlockHeader.BlockSize-1;
+
+  // Reserve enough space even for filter entry.
+  int DataBorder=D.DataSize-16;
+  int ReadBorder=Min(BlockBorder,DataBorder);
+
+  while (true)
+  {
+    UnpPtr&=MaxWinMask;
+    if (D.Inp.InAddr>=ReadBorder)
+    {
+      if (D.Inp.InAddr>BlockBorder || D.Inp.InAddr==BlockBorder && 
+          D.Inp.InBit>=D.BlockHeader.BlockBitSize)
+        break;
+
+      // If we do not have any more data in file to read, we must process
+      // what we have until last byte. Otherwise we can return and append
+      // more data to unprocessed few bytes.
+      if ((D.Inp.InAddr>=DataBorder) && !D.NoDataLeft || D.Inp.InAddr>=D.DataSize)
+      {
+        D.Incomplete=true;
+        break;
+      }
+    }
+    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
+    {
+      UnpWriteBuf();
+      if (WrittenFileSize>DestUnpSize)
+        return false;
+    }
+
+    uint MainSlot=DecodeNumber(D.Inp,&D.BlockTables.LD);
+    if (MainSlot<256)
+    {
+      Window[UnpPtr++]=(byte)MainSlot;
+      continue;
+    }
+    if (MainSlot>=262)
+    {
+      uint Length=SlotToLength(D.Inp,MainSlot-262);
+
+      uint DBits,Distance=1,DistSlot=DecodeNumber(D.Inp,&D.BlockTables.DD);
+      if (DistSlot<4)
+      {
+        DBits=0;
+        Distance+=DistSlot;
+      }
+      else
+      {
+        DBits=DistSlot/2 - 1;
+        Distance+=(2 | (DistSlot & 1)) << DBits;
+      }
+
+      if (DBits>0)
+      {
+        if (DBits>=4)
+        {
+          if (DBits>4)
+          {
+            Distance+=((D.Inp.getbits32()>>(36-DBits))<<4);
+            D.Inp.addbits(DBits-4);
+          }
+          uint LowDist=DecodeNumber(D.Inp,&D.BlockTables.LDD);
+          Distance+=LowDist;
+        }
+        else
+        {
+          Distance+=D.Inp.getbits32()>>(32-DBits);
+          D.Inp.addbits(DBits);
+        }
+      }
+
+      if (Distance>0x100)
+      {
+        Length++;
+        if (Distance>0x2000)
+        {
+          Length++;
+          if (Distance>0x40000)
+            Length++;
+        }
+      }
+
+      InsertOldDist(Distance);
+      LastLength=Length;
+      CopyString(Length,Distance);
+      continue;
+    }
+    if (MainSlot==256)
+    {
+      UnpackFilter Filter;
+      if (!ReadFilter(D.Inp,Filter) || !AddFilter(Filter))
+        break;
+      continue;
+    }
+    if (MainSlot==257)
+    {
+      if (LastLength!=0)
+        CopyString(LastLength,OldDist[0]);
+      continue;
+    }
+    if (MainSlot<262)
+    {
+      uint DistNum=MainSlot-258;
+      uint Distance=OldDist[DistNum];
+      for (uint I=DistNum;I>0;I--)
+        OldDist[I]=OldDist[I-1];
+      OldDist[0]=Distance;
+
+      uint LengthSlot=DecodeNumber(D.Inp,&D.BlockTables.RD);
+      uint Length=SlotToLength(D.Inp,LengthSlot);
+      LastLength=Length;
+      CopyString(Length,Distance);
+      continue;
+    }
+  }
+  return true;
+}
diff --git a/third_party/unrar/src/unpackinline.cpp b/third_party/unrar/src/unpackinline.cpp
new file mode 100644
index 0000000..04c3d1f
--- /dev/null
+++ b/third_party/unrar/src/unpackinline.cpp
@@ -0,0 +1,147 @@
+_forceinline void Unpack::InsertOldDist(uint Distance)
+{
+  OldDist[3]=OldDist[2];
+  OldDist[2]=OldDist[1];
+  OldDist[1]=OldDist[0];
+  OldDist[0]=Distance;
+}
+
+#ifdef _MSC_VER
+#define FAST_MEMCPY
+#endif
+
+_forceinline void Unpack::CopyString(uint Length,uint Distance)
+{
+  size_t SrcPtr=UnpPtr-Distance;
+  if (SrcPtr<MaxWinSize-MAX_INC_LZ_MATCH && UnpPtr<MaxWinSize-MAX_INC_LZ_MATCH)
+  {
+    // If we are not close to end of window, we do not need to waste time
+    // to "& MaxWinMask" pointer protection.
+
+    byte *Src=Window+SrcPtr;
+    byte *Dest=Window+UnpPtr;
+    UnpPtr+=Length;
+
+#ifdef FAST_MEMCPY
+    if (Distance<Length) // Overlapping strings
+#endif
+      while (Length>=8)
+      {
+        Dest[0]=Src[0];
+        Dest[1]=Src[1];
+        Dest[2]=Src[2];
+        Dest[3]=Src[3];
+        Dest[4]=Src[4];
+        Dest[5]=Src[5];
+        Dest[6]=Src[6];
+        Dest[7]=Src[7];
+
+        Src+=8;
+        Dest+=8;
+        Length-=8;
+      }
+#ifdef FAST_MEMCPY
+    else
+      while (Length>=8)
+      {
+        // In theory we still could overlap here.
+        // Supposing Distance == MaxWinSize - 1 we have memcpy(Src, Src + 1, 8).
+        // But for real RAR archives Distance <= MaxWinSize - MAX_INC_LZ_MATCH
+        // always, so overlap here is impossible.
+
+        // This memcpy expanded inline by MSVC. We could also use uint64
+        // assignment, which seems to provide about the same speed.
+        memcpy(Dest,Src,8); 
+
+        Src+=8;
+        Dest+=8;
+        Length-=8;
+      }
+#endif
+
+    // Unroll the loop for 0 - 7 bytes left. Note that we use nested "if"s.
+    if (Length>0) { Dest[0]=Src[0];
+    if (Length>1) { Dest[1]=Src[1];
+    if (Length>2) { Dest[2]=Src[2];
+    if (Length>3) { Dest[3]=Src[3];
+    if (Length>4) { Dest[4]=Src[4];
+    if (Length>5) { Dest[5]=Src[5];
+    if (Length>6) { Dest[6]=Src[6]; } } } } } } } // Close all nested "if"s.
+  }
+  else
+    while (Length-- > 0) // Slow copying with all possible precautions.
+    {
+      Window[UnpPtr]=Window[SrcPtr++ & MaxWinMask];
+      // We need to have masked UnpPtr after quit from loop, so it must not
+      // be replaced with 'Window[UnpPtr++ & MaxWinMask]'
+      UnpPtr=(UnpPtr+1) & MaxWinMask;
+    }
+}
+
+
+_forceinline uint Unpack::DecodeNumber(BitInput &Inp,DecodeTable *Dec)
+{
+  // Left aligned 15 bit length raw bit field.
+  uint BitField=Inp.getbits() & 0xfffe;
+
+  if (BitField<Dec->DecodeLen[Dec->QuickBits])
+  {
+    uint Code=BitField>>(16-Dec->QuickBits);
+    Inp.addbits(Dec->QuickLen[Code]);
+    return Dec->QuickNum[Code];
+  }
+
+  // Detect the real bit length for current code.
+  uint Bits=15;
+  for (uint I=Dec->QuickBits+1;I<15;I++)
+    if (BitField<Dec->DecodeLen[I])
+    {
+      Bits=I;
+      break;
+    }
+
+  Inp.addbits(Bits);
+  
+  // Calculate the distance from the start code for current bit length.
+  uint Dist=BitField-Dec->DecodeLen[Bits-1];
+
+  // Start codes are left aligned, but we need the normal right aligned
+  // number. So we shift the distance to the right.
+  Dist>>=(16-Bits);
+
+  // Now we can calculate the position in the code list. It is the sum
+  // of first position for current bit length and right aligned distance
+  // between our bit field and start code for current bit length.
+  uint Pos=Dec->DecodePos[Bits]+Dist;
+
+  // Out of bounds safety check required for damaged archives.
+  if (Pos>=Dec->MaxNum)
+    Pos=0;
+
+  // Convert the position in the code list to position in alphabet
+  // and return it.
+  return Dec->DecodeNum[Pos];
+}
+
+
+_forceinline uint Unpack::SlotToLength(BitInput &Inp,uint Slot)
+{
+  uint LBits,Length=2;
+  if (Slot<8)
+  {
+    LBits=0;
+    Length+=Slot;
+  }
+  else
+  {
+    LBits=Slot/4-1;
+    Length+=(4 | (Slot & 3)) << LBits;
+  }
+
+  if (LBits>0)
+  {
+    Length+=Inp.getbits()>>(16-LBits);
+    Inp.addbits(LBits);
+  }
+  return Length;
+}
diff --git a/third_party/unrar/src/uowners.cpp b/third_party/unrar/src/uowners.cpp
new file mode 100644
index 0000000..9f463085
--- /dev/null
+++ b/third_party/unrar/src/uowners.cpp
@@ -0,0 +1,141 @@
+
+
+void ExtractUnixOwner20(Archive &Arc,const wchar *FileName)
+{
+  char NameA[NM];
+  WideToChar(FileName,NameA,ASIZE(NameA));
+
+  if (Arc.BrokenHeader)
+  {
+    uiMsg(UIERROR_UOWNERBROKEN,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_CRC);
+    return;
+  }
+
+  struct passwd *pw;
+  errno=0; // Required by getpwnam specification if we need to check errno.
+  if ((pw=getpwnam(Arc.UOHead.OwnerName))==NULL)
+  {
+    uiMsg(UIERROR_UOWNERGETOWNERID,Arc.FileName,GetWide(Arc.UOHead.OwnerName));
+    ErrHandler.SysErrMsg();
+    ErrHandler.SetErrorCode(RARX_WARNING);
+    return;
+  }
+  uid_t OwnerID=pw->pw_uid;
+
+  struct group *gr;
+  errno=0; // Required by getgrnam specification if we need to check errno.
+  if ((gr=getgrnam(Arc.UOHead.GroupName))==NULL)
+  {
+    uiMsg(UIERROR_UOWNERGETGROUPID,Arc.FileName,GetWide(Arc.UOHead.GroupName));
+    ErrHandler.SysErrMsg();
+    ErrHandler.SetErrorCode(RARX_CRC);
+    return;
+  }
+  uint Attr=GetFileAttr(FileName);
+  gid_t GroupID=gr->gr_gid;
+#if defined(SAVE_LINKS) && !defined(_APPLE)
+  if (lchown(NameA,OwnerID,GroupID)!=0)
+#else
+  if (chown(NameA,OwnerID,GroupID)!=0)
+#endif
+  {
+    uiMsg(UIERROR_UOWNERSET,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_CREATE);
+  }
+  SetFileAttr(FileName,Attr);
+}
+
+
+void ExtractUnixOwner30(Archive &Arc,const wchar *FileName)
+{
+  char NameA[NM];
+  WideToChar(FileName,NameA,ASIZE(NameA));
+
+  char *OwnerName=(char *)&Arc.SubHead.SubData[0];
+  int OwnerSize=strlen(OwnerName)+1;
+  int GroupSize=Arc.SubHead.SubData.Size()-OwnerSize;
+  char GroupName[NM];
+  strncpy(GroupName,(char *)&Arc.SubHead.SubData[OwnerSize],GroupSize);
+  GroupName[GroupSize]=0;
+
+  struct passwd *pw;
+  if ((pw=getpwnam(OwnerName))==NULL)
+  {
+    uiMsg(UIERROR_UOWNERGETOWNERID,Arc.FileName,GetWide(OwnerName));
+    ErrHandler.SetErrorCode(RARX_WARNING);
+    return;
+  }
+  uid_t OwnerID=pw->pw_uid;
+
+  struct group *gr;
+  if ((gr=getgrnam(GroupName))==NULL)
+  {
+    uiMsg(UIERROR_UOWNERGETGROUPID,Arc.FileName,GetWide(GroupName));
+    ErrHandler.SetErrorCode(RARX_WARNING);
+    return;
+  }
+  uint Attr=GetFileAttr(FileName);
+  gid_t GroupID=gr->gr_gid;
+#if defined(SAVE_LINKS) && !defined(_APPLE)
+  if (lchown(NameA,OwnerID,GroupID)!=0)
+#else
+  if (chown(NameA,OwnerID,GroupID)!=0)
+#endif
+  {
+    uiMsg(UIERROR_UOWNERSET,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_CREATE);
+  }
+  SetFileAttr(FileName,Attr);
+}
+
+
+void SetUnixOwner(Archive &Arc,const wchar *FileName)
+{
+  char NameA[NM];
+  WideToChar(FileName,NameA,ASIZE(NameA));
+
+  // First, we try to resolve symbolic names. If they are missing or cannot
+  // be resolved, we try to use numeric values if any. If numeric values
+  // are missing too, function fails.
+  FileHeader &hd=Arc.FileHead;
+  if (*hd.UnixOwnerName!=0)
+  {
+    struct passwd *pw;
+    if ((pw=getpwnam(hd.UnixOwnerName))==NULL)
+    {
+      if (!hd.UnixOwnerNumeric)
+      {
+        uiMsg(UIERROR_UOWNERGETOWNERID,Arc.FileName,GetWide(hd.UnixOwnerName));
+        ErrHandler.SetErrorCode(RARX_WARNING);
+        return;
+      }
+    }
+    else
+      hd.UnixOwnerID=pw->pw_uid;
+  }
+  if (*hd.UnixGroupName!=0)
+  {
+    struct group *gr;
+    if ((gr=getgrnam(hd.UnixGroupName))==NULL)
+    {
+      if (!hd.UnixGroupNumeric)
+      {
+        uiMsg(UIERROR_UOWNERGETGROUPID,Arc.FileName,GetWide(hd.UnixGroupName));
+        ErrHandler.SetErrorCode(RARX_WARNING);
+        return;
+      }
+    }
+    else
+      hd.UnixGroupID=gr->gr_gid;
+  }
+#if defined(SAVE_LINKS) && !defined(_APPLE)
+  if (lchown(NameA,hd.UnixOwnerID,hd.UnixGroupID)!=0)
+#else
+  if (chown(NameA,hd.UnixOwnerID,hd.UnixGroupID)!=0)
+#endif
+  {
+    uiMsg(UIERROR_UOWNERSET,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_CREATE);
+  }
+}
diff --git a/third_party/unrar/src/version.hpp b/third_party/unrar/src/version.hpp
new file mode 100644
index 0000000..6684b12c
--- /dev/null
+++ b/third_party/unrar/src/version.hpp
@@ -0,0 +1,6 @@
+#define RARVER_MAJOR     5
+#define RARVER_MINOR    60
+#define RARVER_BETA      1
+#define RARVER_DAY      13
+#define RARVER_MONTH    11
+#define RARVER_YEAR   2017
diff --git a/third_party/unrar/src/volume.cpp b/third_party/unrar/src/volume.cpp
new file mode 100644
index 0000000..5d9c4c50
--- /dev/null
+++ b/third_party/unrar/src/volume.cpp
@@ -0,0 +1,288 @@
+#include "rar.hpp"
+
+#ifdef RARDLL
+static bool DllVolChange(RAROptions *Cmd,wchar *NextName,size_t NameSize);
+static bool DllVolNotify(RAROptions *Cmd,wchar *NextName);
+#endif
+
+
+
+bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,wchar Command)
+{
+  RAROptions *Cmd=Arc.GetRAROptions();
+
+  HEADER_TYPE HeaderType=Arc.GetHeaderType();
+  FileHeader *hd=HeaderType==HEAD_SERVICE ? &Arc.SubHead:&Arc.FileHead;
+  bool SplitHeader=(HeaderType==HEAD_FILE || HeaderType==HEAD_SERVICE) &&
+                   hd->SplitAfter;
+
+  if (DataIO!=NULL && SplitHeader)
+  {
+    bool PackedHashPresent=Arc.Format==RARFMT50 || 
+         hd->UnpVer>=20 && hd->FileHash.CRC32!=0xffffffff;
+    if (PackedHashPresent && 
+        !DataIO->PackedDataHash.Cmp(&hd->FileHash,hd->UseHashKey ? hd->HashKey:NULL))
+      uiMsg(UIERROR_CHECKSUMPACKED, Arc.FileName, hd->FileName);
+  }
+
+  int64 PosBeforeClose=Arc.Tell();
+
+  if (DataIO!=NULL)
+    DataIO->ProcessedArcSize+=Arc.FileLength();
+
+
+  Arc.Close();
+
+  wchar NextName[NM];
+  wcscpy(NextName,Arc.FileName);
+  NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering);
+
+#if !defined(SFX_MODULE) && !defined(RARDLL)
+  bool RecoveryDone=false;
+#endif
+  bool FailedOpen=false,OldSchemeTested=false;
+
+#if !defined(SILENT)
+  // In -vp mode we force the pause before next volume even if it is present
+  // and even if we are on the hard disk. It is important when user does not
+  // want to process partially downloaded volumes preliminary.
+  if (Cmd->VolumePause && !uiAskNextVolume(NextName,ASIZE(NextName)))
+    FailedOpen=true;
+#endif
+
+  uint OpenMode = Cmd->OpenShared ? FMF_OPENSHARED : 0;
+
+  if (!FailedOpen)
+    while (!Arc.Open(NextName,OpenMode))
+    {
+      // We need to open a new volume which size was not calculated
+      // in total size before, so we cannot calculate the total progress
+      // anymore. Let's reset the total size to zero and stop 
+      // the total progress.
+      if (DataIO!=NULL)
+        DataIO->TotalArcSize=0;
+
+      if (!OldSchemeTested)
+      {
+        // Checking for new style volumes renamed by user to old style
+        // name format. Some users did it for unknown reason.
+        wchar AltNextName[NM];
+        wcscpy(AltNextName,Arc.FileName);
+        NextVolumeName(AltNextName,ASIZE(AltNextName),true);
+        OldSchemeTested=true;
+        if (Arc.Open(AltNextName,OpenMode))
+        {
+          wcscpy(NextName,AltNextName);
+          break;
+        }
+      }
+#ifdef RARDLL
+      if (!DllVolChange(Cmd,NextName,ASIZE(NextName)))
+      {
+        FailedOpen=true;
+        break;
+      }
+#else // !RARDLL
+
+#ifndef SFX_MODULE
+      if (!RecoveryDone)
+      {
+        RecVolumesRestore(Cmd,Arc.FileName,true);
+        RecoveryDone=true;
+        continue;
+      }
+#endif
+
+      if (!Cmd->VolumePause && !IsRemovable(NextName))
+      {
+        FailedOpen=true;
+        break;
+      }
+#ifndef SILENT
+      if (Cmd->AllYes || !uiAskNextVolume(NextName,ASIZE(NextName)))
+#endif
+      {
+        FailedOpen=true;
+        break;
+      }
+
+#endif // RARDLL
+    }
+  
+  if (FailedOpen)
+  {
+    uiMsg(UIERROR_MISSINGVOL,NextName);
+    Arc.Open(Arc.FileName,OpenMode);
+    Arc.Seek(PosBeforeClose,SEEK_SET);
+    return false;
+  }
+
+  if (Command=='T' || Command=='X' || Command=='E')
+    mprintf(St(Command=='T' ? MTestVol:MExtrVol),Arc.FileName);
+
+
+  Arc.CheckArc(true);
+#ifdef RARDLL
+  if (!DllVolNotify(Cmd,NextName))
+    return false;
+#endif
+
+  if (SplitHeader)
+    Arc.SearchBlock(HeaderType);
+  else
+    Arc.ReadHeader();
+  if (Arc.GetHeaderType()==HEAD_FILE)
+  {
+    Arc.ConvertAttributes();
+    Arc.Seek(Arc.NextBlockPos-Arc.FileHead.PackSize,SEEK_SET);
+  }
+  if (ShowFileName)
+  {
+    mprintf(St(MExtrPoints),Arc.FileHead.FileName);
+    if (!Cmd->DisablePercentage)
+      mprintf(L"     ");
+  }
+  if (DataIO!=NULL)
+  {
+    if (HeaderType==HEAD_ENDARC)
+      DataIO->UnpVolume=false;
+    else
+    {
+      DataIO->UnpVolume=hd->SplitAfter;
+      DataIO->SetPackedSizeToRead(hd->PackSize);
+    }
+#ifdef SFX_MODULE
+    DataIO->UnpArcSize=Arc.FileLength();
+#endif
+    
+    // Reset the size of packed data read from current volume. It is used
+    // to display the total progress and preceding volumes are already
+    // compensated with ProcessedArcSize, so we need to reset this variable.
+    DataIO->CurUnpRead=0;
+
+    DataIO->PackedDataHash.Init(hd->FileHash.Type,Cmd->Threads);
+  }
+  return true;
+}
+
+
+
+
+
+
+#ifdef RARDLL
+#if defined(RARDLL) && defined(_MSC_VER) && !defined(_WIN_64)
+// Disable the run time stack check for unrar.dll, so we can manipulate
+// with ChangeVolProc call type below. Run time check would intercept
+// a wrong ESP before we restore it.
+#pragma runtime_checks( "s", off )
+#endif
+
+bool DllVolChange(RAROptions *Cmd,wchar *NextName,size_t NameSize)
+{
+  bool DllVolChanged=false,DllVolAborted=false;
+
+  if (Cmd->Callback!=NULL)
+  {
+    wchar OrgNextName[NM];
+    wcscpy(OrgNextName,NextName);
+    if (Cmd->Callback(UCM_CHANGEVOLUMEW,Cmd->UserData,(LPARAM)NextName,RAR_VOL_ASK)==-1)
+      DllVolAborted=true;
+    else
+      if (wcscmp(OrgNextName,NextName)!=0)
+        DllVolChanged=true;
+      else
+      {
+        char NextNameA[NM],OrgNextNameA[NM];
+        WideToChar(NextName,NextNameA,ASIZE(NextNameA));
+        strcpy(OrgNextNameA,NextNameA);
+        if (Cmd->Callback(UCM_CHANGEVOLUME,Cmd->UserData,(LPARAM)NextNameA,RAR_VOL_ASK)==-1)
+          DllVolAborted=true;
+        else
+          if (strcmp(OrgNextNameA,NextNameA)!=0)
+          {
+            // We can damage some Unicode characters by U->A->U conversion,
+            // so set Unicode name only if we see that ANSI name is changed.
+            CharToWide(NextNameA,NextName,NameSize);
+            DllVolChanged=true;
+          }
+      }
+  }
+  if (!DllVolChanged && Cmd->ChangeVolProc!=NULL)
+  {
+    char NextNameA[NM];
+    WideToChar(NextName,NextNameA,ASIZE(NextNameA));
+    // Here we preserve ESP value. It is necessary for those developers,
+    // who still define ChangeVolProc callback as "C" type function,
+    // even though in year 2001 we announced in unrar.dll whatsnew.txt
+    // that it will be PASCAL type (for compatibility with Visual Basic).
+#if defined(_MSC_VER)
+#ifndef _WIN_64
+    __asm mov ebx,esp
+#endif
+#elif defined(_WIN_ALL) && defined(__BORLANDC__)
+    _EBX=_ESP;
+#endif
+    int RetCode=Cmd->ChangeVolProc(NextNameA,RAR_VOL_ASK);
+
+    // Restore ESP after ChangeVolProc with wrongly defined calling
+    // convention broken it.
+#if defined(_MSC_VER)
+#ifndef _WIN_64
+    __asm mov esp,ebx
+#endif
+#elif defined(_WIN_ALL) && defined(__BORLANDC__)
+    _ESP=_EBX;
+#endif
+    if (RetCode==0)
+      DllVolAborted=true;
+    else
+      CharToWide(NextNameA,NextName,NameSize);
+  }
+
+  // We quit only on 'abort' condition, but not on 'name not changed'.
+  // It is legitimate for program to return the same name when waiting
+  // for currently non-existent volume.
+  // Also we quit to prevent an infinite loop if no callback is defined.
+  if (DllVolAborted || Cmd->Callback==NULL && Cmd->ChangeVolProc==NULL)
+  {
+    Cmd->DllError=ERAR_EOPEN;
+    return false;
+  }
+  return true;
+}
+#endif
+
+
+#ifdef RARDLL
+bool DllVolNotify(RAROptions *Cmd,wchar *NextName)
+{
+  char NextNameA[NM];
+  WideToChar(NextName,NextNameA,ASIZE(NextNameA));
+  if (Cmd->Callback!=NULL)
+  {
+    if (Cmd->Callback(UCM_CHANGEVOLUMEW,Cmd->UserData,(LPARAM)NextName,RAR_VOL_NOTIFY)==-1)
+      return false;
+    if (Cmd->Callback(UCM_CHANGEVOLUME,Cmd->UserData,(LPARAM)NextNameA,RAR_VOL_NOTIFY)==-1)
+      return false;
+  }
+  if (Cmd->ChangeVolProc!=NULL)
+  {
+#if defined(_WIN_ALL) && !defined(_MSC_VER) && !defined(__MINGW32__)
+    _EBX=_ESP;
+#endif
+    int RetCode=Cmd->ChangeVolProc(NextNameA,RAR_VOL_NOTIFY);
+#if defined(_WIN_ALL) && !defined(_MSC_VER) && !defined(__MINGW32__)
+    _ESP=_EBX;
+#endif
+    if (RetCode==0)
+      return false;
+  }
+  return true;
+}
+
+#if defined(RARDLL) && defined(_MSC_VER) && !defined(_WIN_64)
+// Restore the run time stack check for unrar.dll.
+#pragma runtime_checks( "s", restore )
+#endif
+#endif
diff --git a/third_party/unrar/src/volume.hpp b/third_party/unrar/src/volume.hpp
new file mode 100644
index 0000000..2d6a6d5
--- /dev/null
+++ b/third_party/unrar/src/volume.hpp
@@ -0,0 +1,10 @@
+#ifndef _RAR_VOLUME_
+#define _RAR_VOLUME_
+
+void SplitArchive(Archive &Arc,FileHeader *fh,int64 *HeaderPos,
+                  ComprDataIO *DataIO);
+bool MergeArchive(Archive &Arc,ComprDataIO *DataIO,bool ShowFileName,
+                  wchar Command);
+void SetVolWrite(Archive &Dest,int64 VolSize);
+
+#endif
diff --git a/third_party/unrar/src/win32acl.cpp b/third_party/unrar/src/win32acl.cpp
new file mode 100644
index 0000000..44d0bfa
--- /dev/null
+++ b/third_party/unrar/src/win32acl.cpp
@@ -0,0 +1,129 @@
+static void SetACLPrivileges();
+
+static bool ReadSacl=false;
+
+
+
+#ifndef SFX_MODULE
+void ExtractACL20(Archive &Arc,const wchar *FileName)
+{
+  SetACLPrivileges();
+
+  if (Arc.BrokenHeader)
+  {
+    uiMsg(UIERROR_ACLBROKEN,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_CRC);
+    return;
+  }
+
+  if (Arc.EAHead.Method<0x31 || Arc.EAHead.Method>0x35 || Arc.EAHead.UnpVer>VER_PACK)
+  {
+    uiMsg(UIERROR_ACLUNKNOWN,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_WARNING);
+    return;
+  }
+
+  ComprDataIO DataIO;
+  Unpack Unpack(&DataIO);
+  Unpack.Init(0x10000,false);
+
+  Array<byte> UnpData(Arc.EAHead.UnpSize);
+  DataIO.SetUnpackToMemory(&UnpData[0],Arc.EAHead.UnpSize);
+  DataIO.SetPackedSizeToRead(Arc.EAHead.DataSize);
+  DataIO.EnableShowProgress(false);
+  DataIO.SetFiles(&Arc,NULL);
+  DataIO.UnpHash.Init(HASH_CRC32,1);
+  Unpack.SetDestSize(Arc.EAHead.UnpSize);
+  Unpack.DoUnpack(Arc.EAHead.UnpVer,false);
+
+  if (Arc.EAHead.EACRC!=DataIO.UnpHash.GetCRC32())
+  {
+    uiMsg(UIERROR_ACLBROKEN,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_CRC);
+    return;
+  }
+
+  SECURITY_INFORMATION  si=OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION|
+                           DACL_SECURITY_INFORMATION;
+  if (ReadSacl)
+    si|=SACL_SECURITY_INFORMATION;
+  SECURITY_DESCRIPTOR *sd=(SECURITY_DESCRIPTOR *)&UnpData[0];
+
+  int SetCode=SetFileSecurity(FileName,si,sd);
+
+  if (!SetCode)
+  {
+    uiMsg(UIERROR_ACLSET,Arc.FileName,FileName);
+    ErrHandler.SysErrMsg();
+    ErrHandler.SetErrorCode(RARX_WARNING);
+  }
+}
+#endif
+
+
+void ExtractACL(Archive &Arc,const wchar *FileName)
+{
+  Array<byte> SubData;
+  if (!Arc.ReadSubData(&SubData,NULL))
+    return;
+
+  SetACLPrivileges();
+
+  SECURITY_INFORMATION si=OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION|
+                          DACL_SECURITY_INFORMATION;
+  if (ReadSacl)
+    si|=SACL_SECURITY_INFORMATION;
+  SECURITY_DESCRIPTOR *sd=(SECURITY_DESCRIPTOR *)&SubData[0];
+
+  int SetCode=SetFileSecurity(FileName,si,sd);
+  if (!SetCode)
+  {
+    wchar LongName[NM];
+    if (GetWinLongPath(FileName,LongName,ASIZE(LongName)))
+      SetCode=SetFileSecurity(LongName,si,sd);
+  }
+
+  if (!SetCode)
+  {
+    uiMsg(UIERROR_ACLSET,Arc.FileName,FileName);
+    ErrHandler.SysErrMsg();
+    ErrHandler.SetErrorCode(RARX_WARNING);
+  }
+}
+
+
+void SetACLPrivileges()
+{
+  static bool InitDone=false;
+  if (InitDone)
+    return;
+
+  if (SetPrivilege(SE_SECURITY_NAME))
+    ReadSacl=true;
+  SetPrivilege(SE_RESTORE_NAME);
+
+  InitDone=true;
+}
+
+
+bool SetPrivilege(LPCTSTR PrivName)
+{
+  bool Success=false;
+
+  HANDLE hToken;
+  if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
+  {
+    TOKEN_PRIVILEGES tp;
+    tp.PrivilegeCount = 1;
+    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+    if (LookupPrivilegeValue(NULL,PrivName,&tp.Privileges[0].Luid) &&
+        AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) &&
+        GetLastError() == ERROR_SUCCESS)
+      Success=true;
+
+    CloseHandle(hToken);
+  }
+
+  return Success;
+}
diff --git a/third_party/unrar/src/win32lnk.cpp b/third_party/unrar/src/win32lnk.cpp
new file mode 100644
index 0000000..a68ed75
--- /dev/null
+++ b/third_party/unrar/src/win32lnk.cpp
@@ -0,0 +1,174 @@
+#define SYMLINK_FLAG_RELATIVE 1
+
+typedef struct _REPARSE_DATA_BUFFER {
+  ULONG  ReparseTag;
+  USHORT ReparseDataLength;
+  USHORT Reserved;
+  union {
+    struct {
+      USHORT SubstituteNameOffset;
+      USHORT SubstituteNameLength;
+      USHORT PrintNameOffset;
+      USHORT PrintNameLength;
+      ULONG  Flags;
+      WCHAR  PathBuffer[1];
+    } SymbolicLinkReparseBuffer;
+    struct {
+      USHORT SubstituteNameOffset;
+      USHORT SubstituteNameLength;
+      USHORT PrintNameOffset;
+      USHORT PrintNameLength;
+      WCHAR  PathBuffer[1];
+    } MountPointReparseBuffer;
+    struct {
+      UCHAR DataBuffer[1];
+    } GenericReparseBuffer;
+  };
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+
+
+
+
+bool CreateReparsePoint(CommandData *Cmd,const wchar *Name,FileHeader *hd)
+{
+  static bool PrivSet=false;
+  if (!PrivSet)
+  {
+    SetPrivilege(SE_RESTORE_NAME);
+    // Not sure if we really need it, but let's request anyway.
+    SetPrivilege(SE_CREATE_SYMBOLIC_LINK_NAME);
+    PrivSet=true;
+  }
+
+  const DWORD BufSize=sizeof(REPARSE_DATA_BUFFER)+2*NM+1024;
+  Array<byte> Buf(BufSize);
+  REPARSE_DATA_BUFFER *rdb=(REPARSE_DATA_BUFFER *)&Buf[0];
+
+  wchar SubstName[NM];
+  wcsncpyz(SubstName,hd->RedirName,ASIZE(SubstName));
+  size_t SubstLength=wcslen(SubstName);
+
+  wchar PrintName[NM],*PrintNameSrc=SubstName,*PrintNameDst=PrintName;
+  bool WinPrefix=wcsncmp(PrintNameSrc,L"\\??\\",4)==0;
+  if (WinPrefix)
+    PrintNameSrc+=4;
+  if (WinPrefix && wcsncmp(PrintNameSrc,L"UNC\\",4)==0)
+  {
+    *(PrintNameDst++)='\\'; // Insert second \ in beginning of share name.
+    PrintNameSrc+=3;
+  }
+  wcscpy(PrintNameDst,PrintNameSrc);
+
+  size_t PrintLength=wcslen(PrintName);
+
+  bool AbsPath=WinPrefix;
+  // IsFullPath is not really needed here, AbsPath check is enough.
+  // We added it just for extra safety, in case some Windows version would
+  // allow to create absolute targets with SYMLINK_FLAG_RELATIVE.
+  // Use hd->FileName instead of Name, since Name can include the destination
+  // path as a prefix, which can confuse IsRelativeSymlinkSafe algorithm.
+  if (!Cmd->AbsoluteLinks && (AbsPath || IsFullPath(hd->RedirName) ||
+      !IsRelativeSymlinkSafe(Cmd,hd->FileName,Name,hd->RedirName)))
+    return false;
+
+  CreatePath(Name,true);
+
+  // 'DirTarget' check is important for Unix symlinks to directories.
+  // Unix symlinks do not have their own 'directory' attribute.
+  if (hd->Dir || hd->DirTarget)
+  {
+    if (!CreateDirectory(Name,NULL))
+      return false;
+  }
+  else
+  {
+    HANDLE hFile=CreateFile(Name,GENERIC_WRITE,0,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
+    if (hFile == INVALID_HANDLE_VALUE)
+      return false;
+    CloseHandle(hFile);
+  }
+
+
+  if (hd->RedirType==FSREDIR_JUNCTION)
+  {
+    rdb->ReparseTag=IO_REPARSE_TAG_MOUNT_POINT;
+    rdb->ReparseDataLength=USHORT(
+      sizeof(rdb->MountPointReparseBuffer.SubstituteNameOffset)+
+      sizeof(rdb->MountPointReparseBuffer.SubstituteNameLength)+
+      sizeof(rdb->MountPointReparseBuffer.PrintNameOffset)+
+      sizeof(rdb->MountPointReparseBuffer.PrintNameLength)+
+      (SubstLength+1)*sizeof(WCHAR)+(PrintLength+1)*sizeof(WCHAR));
+    rdb->Reserved=0;
+
+    rdb->MountPointReparseBuffer.SubstituteNameOffset=0;
+    rdb->MountPointReparseBuffer.SubstituteNameLength=USHORT(SubstLength*sizeof(WCHAR));
+    wcscpy(rdb->MountPointReparseBuffer.PathBuffer,SubstName);
+
+    rdb->MountPointReparseBuffer.PrintNameOffset=USHORT((SubstLength+1)*sizeof(WCHAR));
+    rdb->MountPointReparseBuffer.PrintNameLength=USHORT(PrintLength*sizeof(WCHAR));
+    wcscpy(rdb->MountPointReparseBuffer.PathBuffer+SubstLength+1,PrintName);
+  }
+  else
+    if (hd->RedirType==FSREDIR_WINSYMLINK || hd->RedirType==FSREDIR_UNIXSYMLINK)
+    {
+      rdb->ReparseTag=IO_REPARSE_TAG_SYMLINK;
+      rdb->ReparseDataLength=USHORT(
+        sizeof(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset)+
+        sizeof(rdb->SymbolicLinkReparseBuffer.SubstituteNameLength)+
+        sizeof(rdb->SymbolicLinkReparseBuffer.PrintNameOffset)+
+        sizeof(rdb->SymbolicLinkReparseBuffer.PrintNameLength)+
+        sizeof(rdb->SymbolicLinkReparseBuffer.Flags)+
+        (SubstLength+1)*sizeof(WCHAR)+(PrintLength+1)*sizeof(WCHAR));
+      rdb->Reserved=0;
+
+      rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset=0;
+      rdb->SymbolicLinkReparseBuffer.SubstituteNameLength=USHORT(SubstLength*sizeof(WCHAR));
+      wcscpy(rdb->SymbolicLinkReparseBuffer.PathBuffer,SubstName);
+
+      rdb->SymbolicLinkReparseBuffer.PrintNameOffset=USHORT((SubstLength+1)*sizeof(WCHAR));
+      rdb->SymbolicLinkReparseBuffer.PrintNameLength=USHORT(PrintLength*sizeof(WCHAR));
+      wcscpy(rdb->SymbolicLinkReparseBuffer.PathBuffer+SubstLength+1,PrintName);
+
+      rdb->SymbolicLinkReparseBuffer.Flags=AbsPath ? 0:SYMLINK_FLAG_RELATIVE;
+    }
+    else
+      return false;
+
+  HANDLE hFile=CreateFile(Name,GENERIC_READ|GENERIC_WRITE,0,NULL,
+               OPEN_EXISTING,FILE_FLAG_OPEN_REPARSE_POINT| 
+               FILE_FLAG_BACKUP_SEMANTICS,NULL);
+  if (hFile==INVALID_HANDLE_VALUE)
+    return false;
+
+  DWORD Returned;
+  if (!DeviceIoControl(hFile,FSCTL_SET_REPARSE_POINT,rdb, 
+      FIELD_OFFSET(REPARSE_DATA_BUFFER,GenericReparseBuffer)+
+      rdb->ReparseDataLength,NULL,0,&Returned,NULL))
+  { 
+    CloseHandle(hFile);
+    uiMsg(UIERROR_SLINKCREATE,UINULL,Name);
+
+    DWORD LastError=GetLastError();
+    if ((LastError==ERROR_ACCESS_DENIED || LastError==ERROR_PRIVILEGE_NOT_HELD) &&
+        !IsUserAdmin())
+      uiMsg(UIERROR_NEEDADMIN);
+    ErrHandler.SysErrMsg();
+    ErrHandler.SetErrorCode(RARX_CREATE);
+
+    if (hd->Dir)
+      RemoveDirectory(Name);
+    else
+      DeleteFile(Name);
+    return false;
+  }
+  File LinkFile;
+  LinkFile.SetHandle(hFile);
+  LinkFile.SetOpenFileTime(
+    Cmd->xmtime==EXTTIME_NONE ? NULL:&hd->mtime,
+    Cmd->xctime==EXTTIME_NONE ? NULL:&hd->ctime,
+    Cmd->xatime==EXTTIME_NONE ? NULL:&hd->atime);
+  LinkFile.Close();
+  if (!Cmd->IgnoreGeneralAttr)
+    SetFileAttr(Name,hd->FileAttr);
+  return true;
+}
diff --git a/third_party/unrar/src/win32stm.cpp b/third_party/unrar/src/win32stm.cpp
new file mode 100644
index 0000000..9e24c13
--- /dev/null
+++ b/third_party/unrar/src/win32stm.cpp
@@ -0,0 +1,147 @@
+
+
+#if !defined(SFX_MODULE) && defined(_WIN_ALL)
+void ExtractStreams20(Archive &Arc,const wchar *FileName)
+{
+  if (Arc.BrokenHeader)
+  {
+    uiMsg(UIERROR_STREAMBROKEN,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_CRC);
+    return;
+  }
+
+  if (Arc.StreamHead.Method<0x31 || Arc.StreamHead.Method>0x35 || Arc.StreamHead.UnpVer>VER_PACK)
+  {
+    uiMsg(UIERROR_STREAMUNKNOWN,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_WARNING);
+    return;
+  }
+
+  wchar StreamName[NM+2];
+  if (FileName[0]!=0 && FileName[1]==0)
+  {
+    wcscpy(StreamName,L".\\");
+    wcscpy(StreamName+2,FileName);
+  }
+  else
+    wcscpy(StreamName,FileName);
+  if (wcslen(StreamName)+strlen(Arc.StreamHead.StreamName)>=ASIZE(StreamName) ||
+      Arc.StreamHead.StreamName[0]!=':')
+  {
+    uiMsg(UIERROR_STREAMBROKEN,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_CRC);
+    return;
+  }
+
+  wchar StoredName[NM];
+  CharToWide(Arc.StreamHead.StreamName,StoredName,ASIZE(StoredName));
+  ConvertPath(StoredName+1,StoredName+1);
+
+  wcsncatz(StreamName,StoredName,ASIZE(StreamName));
+
+  FindData fd;
+  bool Found=FindFile::FastFind(FileName,&fd);
+
+  if ((fd.FileAttr & FILE_ATTRIBUTE_READONLY)!=0)
+    SetFileAttr(FileName,fd.FileAttr & ~FILE_ATTRIBUTE_READONLY);
+
+  File CurFile;
+  if (CurFile.WCreate(StreamName))
+  {
+    ComprDataIO DataIO;
+    Unpack Unpack(&DataIO);
+    Unpack.Init(0x10000,false);
+
+    DataIO.SetPackedSizeToRead(Arc.StreamHead.DataSize);
+    DataIO.EnableShowProgress(false);
+    DataIO.SetFiles(&Arc,&CurFile);
+    DataIO.UnpHash.Init(HASH_CRC32,1);
+    Unpack.SetDestSize(Arc.StreamHead.UnpSize);
+    Unpack.DoUnpack(Arc.StreamHead.UnpVer,false);
+
+    if (Arc.StreamHead.StreamCRC!=DataIO.UnpHash.GetCRC32())
+    {
+      uiMsg(UIERROR_STREAMBROKEN,Arc.FileName,StreamName);
+      ErrHandler.SetErrorCode(RARX_CRC);
+    }
+    else
+      CurFile.Close();
+  }
+  File HostFile;
+  if (Found && HostFile.Open(FileName,FMF_OPENSHARED|FMF_UPDATE))
+    SetFileTime(HostFile.GetHandle(),&fd.ftCreationTime,&fd.ftLastAccessTime,
+                &fd.ftLastWriteTime);
+  if ((fd.FileAttr & FILE_ATTRIBUTE_READONLY)!=0)
+    SetFileAttr(FileName,fd.FileAttr);
+}
+#endif
+
+
+#ifdef _WIN_ALL
+void ExtractStreams(Archive &Arc,const wchar *FileName,bool TestMode)
+{
+  wchar FullName[NM+2];
+  if (FileName[0]!=0 && FileName[1]==0)
+  {
+    wcscpy(FullName,L".\\");
+    wcsncpyz(FullName+2,FileName,ASIZE(FullName)-2);
+  }
+  else
+    wcsncpyz(FullName,FileName,ASIZE(FullName));
+
+  wchar StreamName[NM];
+  GetStreamNameNTFS(Arc,StreamName,ASIZE(StreamName));
+  if (*StreamName!=':')
+  {
+    uiMsg(UIERROR_STREAMBROKEN,Arc.FileName,FileName);
+    ErrHandler.SetErrorCode(RARX_CRC);
+    return;
+  }
+
+  if (TestMode)
+  {
+    Arc.ReadSubData(NULL,NULL);
+    return;
+  }
+
+  wcsncatz(FullName,StreamName,ASIZE(FullName));
+
+  FindData fd;
+  bool Found=FindFile::FastFind(FileName,&fd);
+
+  if ((fd.FileAttr & FILE_ATTRIBUTE_READONLY)!=0)
+    SetFileAttr(FileName,fd.FileAttr & ~FILE_ATTRIBUTE_READONLY);
+  File CurFile;
+  if (CurFile.WCreate(FullName) && Arc.ReadSubData(NULL,&CurFile))
+    CurFile.Close();
+  File HostFile;
+  if (Found && HostFile.Open(FileName,FMF_OPENSHARED|FMF_UPDATE))
+    SetFileTime(HostFile.GetHandle(),&fd.ftCreationTime,&fd.ftLastAccessTime,
+                &fd.ftLastWriteTime);
+
+  // Restoring original file attributes. Important if file was read only
+  // or did not have "Archive" attribute
+  SetFileAttr(FileName,fd.FileAttr);
+}
+#endif
+
+
+void GetStreamNameNTFS(Archive &Arc,wchar *StreamName,size_t MaxSize)
+{
+  byte *Data=&Arc.SubHead.SubData[0];
+  size_t DataSize=Arc.SubHead.SubData.Size();
+  if (Arc.Format==RARFMT15)
+  {
+    size_t DestSize=Min(DataSize/2,MaxSize-1);
+    RawToWide(Data,StreamName,DestSize);
+    StreamName[DestSize]=0;
+  }
+  else
+  {
+    char UtfString[NM*4];
+    size_t DestSize=Min(DataSize,ASIZE(UtfString)-1);
+    memcpy(UtfString,Data,DestSize);
+    UtfString[DestSize]=0;
+    UtfToWide(UtfString,StreamName,MaxSize);
+  }
+}