Pantheon Patch unpacker (Pack1 Compression)

Extraction and unpacking of game archives and compression, encryption, obfuscation, decoding of unknown files
Ehnoah
Posts: 51
Joined: Tue Jan 26, 2016 12:45 pm

Pantheon Patch unpacker (Pack1 Compression)

Post by Ehnoah »

hey,

so I currently messing arround with Pantheon a bit and I wounder, if you can help me get a Script to unpack the Patch Files.
I am not sure if it actually just Patch files or if it simply replace them.

The Compression of the Archives is Pack1. The Patcher that is used is:

https://github.com/patchkit-net/patchkit-patcher-unity

Code: Select all

   if (compressionMethod == "pack1")
            {
               usedSuffix = "_";
               return new Pack1Unarchiver(this._packagePath, this._pack1Meta, destinationDir, this._packagePassword, "_");
            }
         }
         throw new UnknownPackageCompressionModeException(string.Format("Unknown compression method: {0}", this._diffSummary.CompressionMethod));
      }



Code: Select all

{
   // Token: 0x02000018 RID: 24
   public class Pack1Unarchiver : IUnarchiver
   {
      // Token: 0x0600007A RID: 122 RVA: 0x00002538 File Offset: 0x00000738
      public Pack1Unarchiver(string packagePath, Pack1Meta metaData, string destinationDirPath, string key, string suffix = "") : this(packagePath, metaData, destinationDirPath, Encoding.ASCII.GetBytes(key), suffix, new BytesRange(0L, -1L))
      {
      }

      // Token: 0x0600007B RID: 123 RVA: 0x0000255A File Offset: 0x0000075A
      public Pack1Unarchiver(string packagePath, Pack1Meta metaData, string destinationDirPath, string key, string suffix, BytesRange range) : this(packagePath, metaData, destinationDirPath, Encoding.ASCII.GetBytes(key), suffix, range)
      {
      }

      // Token: 0x0600007C RID: 124 RVA: 0x00006874 File Offset: 0x00004A74
      public Pack1Unarchiver(string packagePath, Pack1Meta metaData, string destinationDirPath, byte[] key, string suffix, BytesRange range)
      {
         Checks.ArgumentFileExists(packagePath, "packagePath");
         Checks.ArgumentDirectoryExists(destinationDirPath, "destinationDirPath");
         Checks.ArgumentNotNull(suffix, "suffix");
         if (range.Start == 0L)
         {
            Assert.AreEqual<MagicBytes.FileType>(MagicBytes.Pack1, MagicBytes.ReadFileType(packagePath), "Is not Pack1 format");
         }
         Pack1Unarchiver.DebugLogger.LogConstructor();
         Pack1Unarchiver.DebugLogger.LogVariable(packagePath, "packagePath");
         Pack1Unarchiver.DebugLogger.LogVariable(destinationDirPath, "destinationDirPath");
         Pack1Unarchiver.DebugLogger.LogVariable(suffix, "suffix");
         this._packagePath = packagePath;
         this._metaData = metaData;
         this._destinationDirPath = destinationDirPath;
         this._suffix = suffix;
         this._range = range;
         using (SHA256 sha = SHA256.Create())
         {
            this._key = sha.ComputeHash(key);
         }
         this._iv = Convert.FromBase64String(this._metaData.Iv);
      }

      // Token: 0x14000002 RID: 2
      // (add) Token: 0x0600007D RID: 125 RVA: 0x00006978 File Offset: 0x00004B78
      // (remove) Token: 0x0600007E RID: 126 RVA: 0x000069B0 File Offset: 0x00004BB0
      [DebuggerBrowsable(DebuggerBrowsableState.Never)]
      public event UnarchiveProgressChangedHandler UnarchiveProgressChanged;

      // Token: 0x0600007F RID: 127 RVA: 0x000069E8 File Offset: 0x00004BE8
      public void Unarchive(CancellationToken cancellationToken)
      {
         int num = 1;
         Pack1Unarchiver.DebugLogger.Log("Unpacking " + this._metaData.Files.Length + " files...");
         Pack1Meta.FileEntry[] files = this._metaData.Files;
         for (int i = 0; i < files.Length; i++)
         {
            Pack1Meta.FileEntry fileEntry = files[i];
            this.OnUnarchiveProgressChanged(fileEntry.Name, fileEntry.Type == "regular", num, this._metaData.Files.Length, 0.0);
            Pack1Meta.FileEntry currentFile = fileEntry;
            int currentEntry = num;
            if (this.CanUnpack(fileEntry))
            {
               this.Unpack(fileEntry, delegate(double progress)
               {
                  this.OnUnarchiveProgressChanged(currentFile.Name, currentFile.Type == "regular", currentEntry, this._metaData.Files.Length, progress);
               }, cancellationToken, null);
            }
            else
            {
               Pack1Unarchiver.DebugLogger.LogWarning(string.Format("The file {0} couldn't be unpacked.", fileEntry.Name));
            }
            this.OnUnarchiveProgressChanged(fileEntry.Name, fileEntry.Type == "regular", num, this._metaData.Files.Length, 1.0);
            num++;
         }
         Pack1Unarchiver.DebugLogger.Log("Unpacking finished succesfully!");
      }

      // Token: 0x06000080 RID: 128 RVA: 0x00006B20 File Offset: 0x00004D20
      public void UnarchiveSingleFile(Pack1Meta.FileEntry file, CancellationToken cancellationToken, string destinationDirPath = null)
      {
         this.OnUnarchiveProgressChanged(file.Name, file.Type == "regular", 0, 1, 0.0);
         if (!this.CanUnpack(file))
         {
            throw new ArgumentOutOfRangeException("file", file, null);
         }
         this.Unpack(file, delegate(double progress)
         {
            this.OnUnarchiveProgressChanged(file.Name, file.Type == "regular", 1, 1, progress);
         }, cancellationToken, destinationDirPath);
         this.OnUnarchiveProgressChanged(file.Name, file.Type == "regular", 0, 1, 1.0);
      }

      // Token: 0x06000081 RID: 129 RVA: 0x00006BE0 File Offset: 0x00004DE0
      private bool CanUnpack(Pack1Meta.FileEntry file)
      {
         if (file.Type != "regular")
         {
            return true;
         }
         if (this._range.Start == 0L && this._range.End == -1L)
         {
            return true;
         }
         bool result;
         if (file.Offset >= this._range.Start)
         {
            long? offset = file.Offset;
            bool flag = offset != null;
            long? size = file.Size;
            result = (((!(flag & size != null)) ? null : new long?(offset.GetValueOrDefault() + size.GetValueOrDefault())) <= this._range.End);
         }
         else
         {
            result = false;
         }
         return result;
      }

      // Token: 0x06000082 RID: 130 RVA: 0x00006CD8 File Offset: 0x00004ED8
      private void Unpack(Pack1Meta.FileEntry file, Action<double> progress, CancellationToken cancellationToken, string destinationDirPath = null)
      {
         string type = file.Type;
         if (type != null)
         {
            if (type == "regular")
            {
               this.UnpackRegularFile(file, progress, cancellationToken, destinationDirPath);
               return;
            }
            if (type == "directory")
            {
               progress(0.0);
               this.UnpackDirectory(file);
               progress(1.0);
               return;
            }
            if (type == "symlink")
            {
               progress(0.0);
               this.UnpackSymlink(file);
               progress(1.0);
               return;
            }
         }
         Pack1Unarchiver.DebugLogger.LogWarning("Unknown file type: " + file.Type);
      }

      // Token: 0x06000083 RID: 131 RVA: 0x00006DAC File Offset: 0x00004FAC
      private void UnpackDirectory(Pack1Meta.FileEntry file)
      {
         string text = Path.Combine(this._destinationDirPath, file.Name);
         Pack1Unarchiver.DebugLogger.Log("Creating directory " + text);
         Directory.CreateDirectory(text);
         Pack1Unarchiver.DebugLogger.Log("Directory " + text + " created successfully!");
      }

      // Token: 0x06000084 RID: 132 RVA: 0x00006E04 File Offset: 0x00005004
      private void UnpackSymlink(Pack1Meta.FileEntry file)
      {
         string str = Path.Combine(this._destinationDirPath, file.Name);
         Pack1Unarchiver.DebugLogger.Log("Creating symlink: " + str);
      }

      // Token: 0x06000085 RID: 133 RVA: 0x00006E38 File Offset: 0x00005038
      private void UnpackRegularFile(Pack1Meta.FileEntry file, Action<double> onProgress, CancellationToken cancellationToken, string destinationDirPath = null)
      {
         string text = Path.Combine((destinationDirPath != null) ? destinationDirPath : this._destinationDirPath, file.Name + this._suffix);
         Pack1Unarchiver.DebugLogger.LogFormat("Unpacking regular file {0} to {1}", new object[]
         {
            file,
            text
         });
         Files.CreateParents(text);
         RijndaelManaged rijndaelManaged = new RijndaelManaged
         {
            Mode = CipherMode.CBC,
            Padding = PaddingMode.None,
            KeySize = 256
         };
         ICryptoTransform decryptor = rijndaelManaged.CreateDecryptor(this._key, this._iv);
         using (FileStream fileStream = new FileStream(this._packagePath, FileMode.Open))
         {
            fileStream.Seek(file.Offset.Value - this._range.Start, SeekOrigin.Begin);
            using (LimitedStream limitedStream = new LimitedStream(fileStream, file.Size.Value))
            {
               using (FileStream fileStream2 = new FileStream(text, FileMode.Create))
               {
                  this.ExtractFileFromStream(limitedStream, fileStream2, file, decryptor, onProgress, cancellationToken);
               }
               if (Platform.IsPosix())
               {
                  Chmod.SetMode(file.Mode.Substring(3), text);
               }
            }
         }
         Pack1Unarchiver.DebugLogger.Log("File " + file.Name + " unpacked successfully!");
      }

      // Token: 0x06000086 RID: 134 RVA: 0x00006FCC File Offset: 0x000051CC
      private void ExtractFileFromStream(Stream sourceStream, Stream targetStream, Pack1Meta.FileEntry file, ICryptoTransform decryptor, Action<double> onProgress, CancellationToken cancellationToken)
      {
         using (CryptoStream cryptoStream = new CryptoStream(sourceStream, decryptor, CryptoStreamMode.Read))
         {
            using (GZipStream gzipStream = new GZipStream(cryptoStream, CompressionMode.Decompress))
            {
               long num = 0L;
               byte[] buffer = new byte[131072];
               int num2;
               while ((num2 = gzipStream.Read(buffer, 0, 131072)) != 0)
               {
                  cancellationToken.ThrowIfCancellationRequested();
                  targetStream.Write(buffer, 0, num2);
                  num += (long)num2;
                  onProgress((double)gzipStream.Position / (double)file.Size.Value);
               }
            }
         }
      }

      // Token: 0x06000087 RID: 135 RVA: 0x0000708C File Offset: 0x0000528C
      protected virtual void OnUnarchiveProgressChanged(string name, bool isFile, int entry, int amount, double entryProgress)
      {
         UnarchiveProgressChangedHandler unarchiveProgressChanged = this.UnarchiveProgressChanged;
         if (unarchiveProgressChanged != null)
         {
            unarchiveProgressChanged(name, isFile, entry, amount, entryProgress);
         }
      }

      // Token: 0x04000035 RID: 53
      private static readonly DebugLogger DebugLogger = new DebugLogger(typeof(Pack1Unarchiver));

      // Token: 0x04000036 RID: 54
      private readonly string _packagePath;

      // Token: 0x04000037 RID: 55
      private readonly Pack1Meta _metaData;

      // Token: 0x04000038 RID: 56
      private readonly string _destinationDirPath;

      // Token: 0x04000039 RID: 57
      private readonly string _suffix;

      // Token: 0x0400003A RID: 58
      private readonly byte[] _key;

      // Token: 0x0400003B RID: 59
      private readonly byte[] _iv;

      // Token: 0x0400003C RID: 60
      private readonly BytesRange _range;
   }
}
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by aluigi »

Try to use the reimport feature of quickbms (not reimport2):
http://aluigi.org/bms/patchkit_pack1.bms
Ehnoah
Posts: 51
Joined: Tue Jan 26, 2016 12:45 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by Ehnoah »

aluigi wrote:Try to use the reimport feature of quickbms (not reimport2):
http://aluigi.org/bms/patchkit_pack1.bms



Reimport to Patch Files? Or to extract?

The Files are normaly installed like : Game_Data/Mono/Managed/RANDOM.dll stuff

I uploaded an example Patch File.

https://www17.zippyshare.com/v/rw9fALed/file.html
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by aluigi »

I have updated the script in the meantime.

First you must set the key in the variable at the beginning of the script (currently it's "test123")
Then you can use the script to extract the files and later to reimport those you edited.
Ehnoah
Posts: 51
Joined: Tue Jan 26, 2016 12:45 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by Ehnoah »

Is the Key : "iv":"jkfcbf6u2HP39cSH96dWTQ==" ? (Meta Files always provide this and it is different for every file)

Or is it store in the Patcher somewhere? I through they dont use any Key or such. Prob. my Key is wrong.

Code: Select all

KEY  jkfcbf6u2HP39cSH96dWTQ==
KEY  4ea5c38ee2535cef83bd4d8c53bc45300de6d8d5760a0bae42965e78b0c01ff1
IVEC d7a6a2bbab215f40c3a4036e437a3338
  00000009 112        Pantheon.exe
Info:  algorithm   14
       offset      00000009
       input size  0x00000070 112
       output size 0x00000070 112
       result      0xffffffff -1

Error: the uncompressed data (-1) is bigger than the allocated buffer (112)

Last script line before the error or that produced the error:
  61  clog FNAME FOFFSET FSIZE FSIZE 1
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by aluigi »

KEY is mandatory and not stored in the files.
No key, no files.
(just in case I already tried with KEY "")
Ehnoah
Posts: 51
Joined: Tue Jan 26, 2016 12:45 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by Ehnoah »

aluigi wrote:KEY is mandatory and not stored in the files.
No key, no files.
(just in case I already tried with KEY "")



How long is the Key? The Launcher passing a Patcher-Key:

--secret nf+d/zv/Of89/5n/Nf+V/zP/l/+P/5f/lf+V/5n/O/+T/zf/N/+T/4//j/+N/5n/mf+d/53/j/+P/5X/k/+T/w==

If I print the Password Variable (on Unpacking): it shows me different Passwords:

Current Version: $MTFiY2EzZTVmNDg0NTUzYjZkZDY4ODkzMzExODg1NjYxNDk=
PAtch 61/62: $MTFiY2EzZTVmNDg0NTUzYjZkZDY4ODkzMzExODg1NjY2MQ==
$MTFiY2EzZTVmNDg0NTUzYjZkZDY4ODkzMzExODg1NjY2Mg==
Patch 101: $MTFiY2EzZTVmNDg0NTUzYjZkZDY4ODkzMzExODg1NjYxMDE=
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by aluigi »

Very close but not right yet.
The only remaining field is VersionId, it's not 1 or 0 apparently in that sample.
Ehnoah
Posts: 51
Joined: Tue Jan 26, 2016 12:45 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by Ehnoah »

aluigi wrote:Very close but not right yet.
The only remaining field is VersionId, it's not 1 or 0 apparently in that sample.



Jump $MTFiY2EzZTVmNDg0NTUzYjZkZDY4ODkzMzExODg1NjY2MQ== 61

VersionID is 61 as the Package File :) if this was the Question?

Sorry, just very tired, hope I dont tell any shit. Where is the Key suposed to be stored?
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by aluigi »

Yeah 61 is correct :)
Script 0.1.2, remember to set appSecret and VersionId and now you can extract and reimport the files of that sample
Ehnoah
Posts: 51
Joined: Tue Jan 26, 2016 12:45 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by Ehnoah »

Working perfectly. The ZIP Format (they used it in the first few Clients) is just a normal ZIP right?

Sadly they are all diff files or so, since I cant open the Files with NetDecompiler.

Do the Meta File contain Information how the files are patched? Or do the Patcher simply merge them?

(maybe you can take a loot at both files, I already corrected the File-Header)

the Csharp3 = Original and the 4 MB File is the one from the Patcher (actually from the very first Zip Patcher)

If you check both files in Hex Editor, they look exactly same so :X

https://www95.zippyshare.com/v/veJbJjEd/file.html
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by aluigi »

The zip files are normal password encrypted zip archives indeed, at least as stated in the source code.

Regarding csharp, no thanks I have limited time and no desire, sorry.
Ehnoah
Posts: 51
Joined: Tue Jan 26, 2016 12:45 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by Ehnoah »

on unpacking the Full Content File 7 GB. I used 4 GB Version :

Code: Select all

 000000008964e409 975989392  Pantheon_Data/sharedassets3.assets.resS

- error in src\extra\xalloc.c line 618: xdbg_malloc()

Error: memory allocation problem
       Fr diesen Befehl ist nicht gengend Speicher verfgbar.


press ENTER to quit
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by aluigi »

Is it related to the content6.meta you uploaded?
It doesn't seem like that because offset and size don't match.
Ehnoah
Posts: 51
Joined: Tue Jan 26, 2016 12:45 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by Ehnoah »

aluigi wrote:Is it related to the content6.meta you uploaded?
It doesn't seem like that because offset and size don't match.


No actually not. It is the "Big" File. The one I uploaded is a Patch File, the one I tried now was the "Full Content" Package, it has the Same Format (Pack1 and the Meta File)

It also worked to the half way, but stopped on like 50% or so with the error above.
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by aluigi »

Upload the meta file.
aluigi
Site Admin
Posts: 12984
Joined: Wed Jul 30, 2014 9:32 pm

Re: Pantheon Patch unpacker (Pack1 Compression)

Post by aluigi »

Ok everything is correct.
It's just a problem of memory as reported by quickbms (often that message is caused by bad scripts/files/formats).
Basically you have a file with a size of almost 1Gb compressed with gzip, so it's necessary to create another buffer in memory for the decompressed size which result in too much memory to allocate.
Doesn't exist a fix for these issues.