/// <summary>
        ///  Gets the KIFINT lookup with the specified known type in the collection.
        /// </summary>
        /// <param name="type">The type of the KIFINT lookup to get.</param>
        /// <returns>The KIFINT lookup with the specified type in the collection.</returns>
        ///
        /// <exception cref="ArgumentException">
        ///  <paramref name="type"/> is not defined or is <see cref="KifintType.Unknown"/>.
        /// </exception>
        /// <exception cref="KeyNotFoundException">
        ///  A known KIFINT lookup of <paramref name="type"/> is not present in the collection.
        /// </exception>
        public KifintLookup this[KifintType type] {
            get {
                ThrowIfUndefinedOrUnknown(type);
                return(knownLookups[type]);
            }
            set {
                ThrowIfUndefinedOrUnknown(type);

                if (knownLookups.TryGetValue(type, out KifintLookup lookup))
                {
                    lookups.Remove(lookup);
                }
                if (value == null)
                {
                    knownLookups.Remove(type);
                }
                else
                {
                    value.Update = Update;
                    lookups.Add(value);
                    knownLookups[type] = value;
                }
                if (type == KifintType.Update)
                {
                    AssignUpdateLookup(value);
                }
                else if (type == KifintType.Image)
                {
                    Image = value;
                }
            }
        }
        /// <summary>
        ///  Decrypts the KIFINT archives using the known archive type, install directory, and name of executable with
        ///  the V_CODE2 used to decrypt.
        /// </summary>
        /// <param name="type">The type of archive to look for and decrypt.</param>
        /// <param name="installDir">The installation directory for both the archives and executable.</param>
        /// <param name="vcode2">The V_CODE2 key obtained from the exe, used to decrypt the file names.</param>
        /// <param name="callback">The optional callback for progress made during decryption.</param>
        /// <returns>The <see cref="KifintLookup"/> merged with all loaded archives.</returns>
        ///
        /// <exception cref="ArgumentNullException">
        ///  <paramref name="installDir"/> or <paramref name="vcode2"/> is null.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///  <paramref name="type"/> is <see cref="KifintType.Unknown"/>.
        /// </exception>
        public static KifintLookup Decrypt(KifintType type, string installDir, string vcode2,
                                           KifintProgressCallback callback = null)
        {
            if (vcode2 == null)
            {
                throw new ArgumentNullException(nameof(vcode2));
            }
            if (type == KifintType.Unknown)
            {
                throw new ArgumentException($"{nameof(type)} cannot be {nameof(KifintType.Unknown)}!", nameof(type));
            }
            KifintLookup lookup   = new KifintLookup(type);
            string       wildcard = EnumInfo <KifintType> .GetAttribute <KifintWildcardAttribute>(type).Wildcard;

            string[]           files    = Directory.GetFiles(installDir, wildcard);
            KifintProgressArgs progress = new KifintProgressArgs {
                ArchiveType  = type,
                ArchiveIndex = 0,
                ArchiveCount = files.Length,
            };

            foreach (string kifintPath in files)
            {
                progress.ArchiveName = Path.GetFileName(kifintPath);
                using (Stream stream = File.OpenRead(kifintPath))
                    lookup.Merge(Decrypt(type, stream, kifintPath, vcode2, progress, callback));
                progress.ArchiveIndex++;
            }
            progress.EntryIndex = 0;
            progress.EntryCount = 0;
            callback?.Invoke(progress);
            return(lookup);
        }
        public static void RestoreEncryptedArchives(KifintType type, string installDir)
        {
            if (type == KifintType.Unknown)
            {
                throw new ArgumentException($"{nameof(type)} cannot be {nameof(KifintType.Unknown)}!", nameof(type));
            }
            string backupDir = Path.Combine(installDir, "intbackup");

            if (!Directory.Exists(backupDir))
            {
                return;
            }
            var files        = Enumerable.Empty <string>();
            var wildcardAttr = EnumInfo.GetAttribute <KifintType, KifintWildcardAttribute>(type);

            foreach (string wildcard in wildcardAttr.Wildcards)
            {
                files = files.Concat(Directory.GetFiles(installDir, wildcard));
            }
            //string[] files = Directory.GetFiles(backupDir, wildcard);
            foreach (string kifintBackup in files)
            {
                string kifintPath = Path.Combine(installDir, Path.GetFileName(kifintBackup));
                if (File.Exists(kifintBackup) && File.Exists(kifintPath))
                {
                    File.Delete(kifintPath);
                    File.Move(kifintBackup, kifintPath);
                }
            }
        }
        /// <summary>
        ///  Loads and decrypts the KIFINT archive entries using the V_CODE2.<para/>
        ///  Using this will initialize with the specified KIFINT archive type.
        /// </summary>
        /// <param name="kifintPath">The path to the KIFINT archive to decrypt.</param>
        /// <param name="type">The type of archive to create.</param>
        /// <param name="vcode2">The V_CODE2 key obtained from the exe, used to decrypt the file names.</param>
        /// <param name="callback">The optional callback for progress made during loading.</param>
        /// <returns>The <see cref="KifintArchive"/> with all of the loaded entries.</returns>
        ///
        /// <exception cref="ArgumentNullException">
        ///  <paramref name="kifintPath"/> or <paramref name="vcode2"/> is null.
        /// </exception>
        public static KifintArchive LoadArchive(string kifintPath, KifintType type, string vcode2,
                                                KifintProgressCallback callback = null)
        {
            if (kifintPath == null)
            {
                throw new ArgumentNullException(nameof(kifintPath));
            }
            if (vcode2 == null)
            {
                throw new ArgumentNullException(nameof(vcode2));
            }
            KifintArchive      archive;
            KifintProgressArgs progress = new KifintProgressArgs {
                ArchiveType  = type,
                ArchiveIndex = 0,
                ArchiveCount = 1,
            };

            progress.ArchiveName = Path.GetFileName(kifintPath);
            using (Stream stream = File.OpenRead(kifintPath))
                archive = LoadLookup(type, stream, kifintPath, vcode2, progress, callback);
            progress.ArchiveIndex++;

            progress.EntryName  = null;
            progress.EntryIndex = 0;
            progress.EntryCount = 0;
            callback?.Invoke(progress);
            return(archive);
        }
        /*public static KifintLookupCollection DecryptLookups(KifintType type, string installDir, string vcode2,
         *      KifintProgressCallback callback = null)
         * {
         *
         * }*/

        #endregion

        #region DecryptArchive

        public static void DecryptArchives(string wildcard, string installDir, string vcode2,
                                           KifintProgressCallback callback = null)
        {
            if (vcode2 == null)
            {
                throw new ArgumentNullException(nameof(vcode2));
            }
            KifintType type = KifintType.Unknown;

            string[]           files    = Directory.GetFiles(installDir, wildcard);
            KifintProgressArgs progress = new KifintProgressArgs {
                ArchiveType  = type,
                ArchiveIndex = 0,
                ArchiveCount = files.Length,
            };

            foreach (string kifintPath in files)
            {
                progress.ArchiveName = Path.GetFileName(kifintPath);
                string kifintBackup = Path.Combine(Path.GetDirectoryName(installDir) + "intbackups");
                using (Stream stream = File.OpenRead(kifintPath))
                    DecryptArchive(stream, kifintPath, kifintBackup, vcode2, false, progress, callback);
                progress.ArchiveIndex++;
            }
            progress.EntryName  = null;
            progress.EntryIndex = 0;
            progress.EntryCount = 0;
            callback?.Invoke(progress);
        }
        /// <summary>
        ///  Decrypts the KIFINT archives using the wildcard search, install directory, and name of executable with the
        ///  V_CODE2 used to decrypt.<para/>
        ///  Using this will initialize with <see cref="KifintType.Unknown"/>.
        /// </summary>
        /// <param name="wildcard">The wildcard name of the files to look for and merge.</param>
        /// <param name="installDir">The installation directory for both the archives and executable.</param>
        /// <param name="vcode2">The V_CODE2 key obtained from the exe, used to decrypt the file names.</param>
        /// <param name="callback">The optional callback for progress made during decryption.</param>
        /// <returns>The <see cref="KifintLookup"/> merged with all loaded archives.</returns>
        ///
        /// <exception cref="ArgumentNullException">
        ///  <paramref name="wildcard"/>, <paramref name="installDir"/>, or <paramref name="vcode2"/> is null.
        /// </exception>
        public static KifintLookup LoadLookup(string wildcard, string installDir, string vcode2,
                                              KifintProgressCallback callback = null)
        {
            if (vcode2 == null)
            {
                throw new ArgumentNullException(nameof(vcode2));
            }
            KifintType   type   = KifintType.Unknown;
            KifintLookup lookup = new KifintLookup(type);

            string[]           files    = Directory.GetFiles(installDir, wildcard);
            KifintProgressArgs progress = new KifintProgressArgs {
                ArchiveType  = type,
                ArchiveIndex = 0,
                ArchiveCount = files.Length,
            };

            foreach (string kifintPath in files)
            {
                progress.ArchiveName = Path.GetFileName(kifintPath);
                using (Stream stream = File.OpenRead(kifintPath))
                    lookup.Merge(LoadLookup(type, stream, kifintPath, vcode2, progress, callback));
                progress.ArchiveIndex++;
            }
            progress.EntryName  = null;
            progress.EntryIndex = 0;
            progress.EntryCount = 0;
            callback?.Invoke(progress);
            return(lookup);
        }
        /// <summary>
        ///  Constructs a cached KIFINT archive with the specified path, entries, and file key.
        /// </summary>
        /// <param name="kifintPath">The absolute path to the KIFINT archive.</param>
        /// <param name="kifEntries">The array of unobfuscated KIFENTRIES inside the KIFINT.</param>
        /// <param name="decrypt">True if the file key is required.</param>
        /// <param name="fileKey">The file key when <paramref name="decrypt"/> is true.</param>
        private Kifint(string kifintPath, Kifint.KIFENTRY[] kifEntries, bool decrypt, uint fileKey, KifintType type)
        {
            FilePath    = kifintPath;
            IsEncrypted = decrypt;
            if (IsEncrypted)
            {
                FileKey = fileKey;
            }
            ArchiveType = type;
            Dictionary <string, KifintEntry> entries = new Dictionary <string, KifintEntry>(kifEntries.Length);

            foreach (var kifEntry in kifEntries)
            {
                string fileName = kifEntry.FileName;
                if (fileName != "__key__.dat")
                {
                    entries.Add(fileName, new KifintEntry(fileName, kifEntry, this));
                }
            }
            Entries = new ReadOnlyDictionary <string, KifintEntry>(entries);
        }
        public static void DecryptArchives(KifintType type, string installDir, string vcode2,
                                           KifintProgressCallback callback = null)
        {
            if (vcode2 == null)
            {
                throw new ArgumentNullException(nameof(vcode2));
            }
            if (type == KifintType.Unknown)
            {
                throw new ArgumentException($"{nameof(type)} cannot be {nameof(KifintType.Unknown)}!", nameof(type));
            }
            var files        = Enumerable.Empty <string>();
            var wildcardAttr = EnumInfo.GetAttribute <KifintType, KifintWildcardAttribute>(type);

            foreach (string wildcard in wildcardAttr.Wildcards)
            {
                files = files.Concat(Directory.GetFiles(installDir, wildcard));
            }
            //string wildcard = EnumInfo.GetAttribute<KifintType, KifintWildcardAttribute>(type).Wildcard;
            //string[] files = Directory.GetFiles(installDir, wildcard);
            KifintProgressArgs progress = new KifintProgressArgs {
                ArchiveType  = type,
                ArchiveIndex = 0,
                ArchiveCount = files.Count(),
            };

            foreach (string kifintPath in files)
            {
                progress.ArchiveName = Path.GetFileName(kifintPath);
                string kifintBackup = Path.Combine(Path.GetDirectoryName(installDir) + "intbackups");
                using (Stream stream = File.OpenRead(kifintPath))
                    DecryptArchive(stream, kifintPath, kifintBackup, vcode2, false, progress, callback);
                progress.ArchiveIndex++;
            }
            progress.EntryName  = null;
            progress.EntryIndex = 0;
            progress.EntryCount = 0;
            callback?.Invoke(progress);
        }
        /// <summary>
        ///  Decrypts the KIFINT archives using the known archive type, install directory, and name of executable with
        ///  the V_CODE2 used to decrypt.
        /// </summary>
        /// <param name="type">The type of archive to look for and decrypt.</param>
        /// <param name="stream">The stream to the open KIFINT archive.</param>
        /// <param name="installDir">The installation directory for both the archives and executable.</param>
        /// <param name="exePath">The path to the executable to extract the V_CODE2 key from.</param>
        /// <returns>The <see cref="KifintLookup"/> merged with all loaded archives.</returns>
        ///
        /// <exception cref="ArgumentNullException">
        ///  <paramref name="stream"/>, <paramref name="kifintPath"/>, or <paramref name="exePath"/> is null.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///  The <paramref name="stream"/> is closed.
        /// </exception>
        private static Kifint Decrypt(KifintType type, Stream stream, string kifintPath, string vcode2,
                                      KifintProgressArgs progress, KifintProgressCallback callback)
        {
            if (kifintPath == null)
            {
                throw new ArgumentNullException(nameof(kifintPath));
            }
            if (vcode2 == null)
            {
                throw new ArgumentNullException(nameof(vcode2));
            }

            BinaryReader reader = new BinaryReader(stream);
            KIFHDR       hdr    = reader.ReadUnmanaged <KIFHDR>();

            if (hdr.Signature != "KIF")             // It's really a KIF INT file
            {
                throw new UnexpectedFileTypeException(kifintPath, "KIF");
            }

            KIFENTRY[] entries = reader.ReadUnmanagedArray <KIFENTRY>(hdr.EntryCount);

            progress.EntryIndex = 0;
            progress.EntryCount = entries.Length;

            uint tocSeed = GenTocSeed(vcode2);
            uint fileKey = 0;
            bool decrypt = false;

            // Obtain the decryption file key if one exists
            for (int i = 0; i < hdr.EntryCount; i++)
            {
                if (entries[i].FileName == "__key__.dat")
                {
                    fileKey = MersenneTwister.GenRand(entries[i].Length);
                    decrypt = true;
                    break;
                }
            }

            const int ProgressThreshold = 500;

            // Decrypt the KIFINT entries using the file key
            if (decrypt)
            {
                for (uint i = 0; i < hdr.EntryCount; i++)
                {
                    if (entries[i].FileName == "__key__.dat")
                    {
                        continue;
                    }

                    progress.EntryIndex++;
                    if (i % ProgressThreshold == 0)
                    {
                        callback?.Invoke(progress);
                    }

                    // Give the entry the correct name
                    UnobfuscateFileName(entries[i].FileNameRaw, tocSeed + i);
                    // Give apply the extra offset to be decrypted
                    entries[i].Offset += i;
                    // Decrypt the entry's length and offset
                    DecryptEntry(ref entries[i].Info, fileKey);
                }
            }

            return(new Kifint(kifintPath, entries, decrypt, fileKey, type));
        }
        /// <summary>
        ///  Decrypts the KIFINT archives using the known archive type, install directory, and name of executable with
        ///  the V_CODE2 used to decrypt.
        /// </summary>
        /// <param name="type">The type of archive to look for and decrypt.</param>
        /// <param name="stream">The stream to the open KIFINT archive.</param>
        /// <param name="installDir">The installation directory for both the archives and executable.</param>
        /// <param name="exePath">The path to the executable to extract the V_CODE2 key from.</param>
        /// <returns>The <see cref="KifintLookup"/> merged with all loaded archives.</returns>
        ///
        /// <exception cref="ArgumentNullException">
        ///  <paramref name="stream"/>, <paramref name="kifintPath"/>, or <paramref name="exePath"/> is null.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///  The <paramref name="stream"/> is closed.
        /// </exception>
        private static KifintArchive LoadLookup(KifintType type, Stream stream, string kifintPath, string vcode2,
                                                KifintProgressArgs progress, KifintProgressCallback callback)
        {
            if (kifintPath == null)
            {
                throw new ArgumentNullException(nameof(kifintPath));
            }
            if (vcode2 == null)
            {
                throw new ArgumentNullException(nameof(vcode2));
            }

            BinaryReader reader = new BinaryReader(stream);
            KIFHDR       hdr    = reader.ReadUnmanaged <KIFHDR>();

            UnexpectedFileTypeException.ThrowIfInvalid(hdr.Signature, KIFHDR.ExpectedSignature);

            KIFENTRY[] entries = reader.ReadUnmanagedArray <KIFENTRY>(hdr.EntryCount);

            progress.EntryName  = null;
            progress.EntryIndex = 0;
            progress.EntryCount = entries.Length;

            // Table of contents seed
            uint     tocSeed      = GenerateTocSeed(vcode2);
            uint     fileKey      = 0;
            int      fileKeyIndex = -1;
            Blowfish blowfish     = null;

            // Obtain the decryption file key if one exists
            for (int i = 0; i < hdr.EntryCount; i++)
            {
                if (entries[i].FileName == KeyFileName)
                {
                    fileKey      = MersenneTwister.GenRand(entries[i].Length);
                    blowfish     = CatDebug.NewBlowfish(fileKey);
                    fileKeyIndex = i;
                    break;
                }
            }

            const int ProgressThreshold = 500;

            // Decrypt the KIFINT entries using the file key
            if (fileKeyIndex != -1)
            {
                for (uint i = 0; i < hdr.EntryCount; i++, progress.EntryIndex++)
                {
                    if (unchecked ((int)i) == fileKeyIndex)
                    {
                        continue;
                    }

                    // Give the entry the correct name
                    UnobfuscateFileName(entries[i].FileNameRaw, unchecked (tocSeed + i));
                    // Apply the extra offset before decryption
                    entries[i].Offset += i;
                    // Decrypt the entry's offset and length
                    blowfish.Decrypt(ref entries[i].Info);

                    progress.EntryName = entries[i].FileName;
                    if (i % ProgressThreshold == 0 || i + 1 == hdr.EntryCount)
                    {
                        callback?.Invoke(progress);
                    }
                }
            }

            return(new KifintArchive(kifintPath, entries, fileKeyIndex != -1, fileKey, type, blowfish));
        }
예제 #11
0
        /// <summary>
        ///  Reads the KIFINT archive from the stream. For use with <see cref="KifintLookup"/>.
        /// </summary>
        /// <param name="reader">The reader for the current stream.</param>
        /// <param name="version">The current version being read.</param>
        /// <param name="installDir">The installation directory where the archive is located.</param>
        /// <returns>The loaded cached KIFINT archive.</returns>
        internal static Kifint Read(BinaryReader reader, int version, string installDir, KifintType type)
        {
            Kifint kifint = new Kifint {
                FilePath    = Path.Combine(installDir, reader.ReadString()),
                ArchiveType = type,
            };

            kifint.IsEncrypted = reader.ReadBoolean();
            kifint.FileKey     = reader.ReadUInt32();
            if (!kifint.IsEncrypted)
            {
                kifint.FileKey = 0;
            }

            int count = reader.ReadInt32();
            Dictionary <string, KifintEntry> entries = new Dictionary <string, KifintEntry>(count);

            for (int i = 0; i < count; i++)
            {
                KifintEntry entry = KifintEntry.Read(reader, version, kifint);
                entries.Add(entry.FileName, entry);
            }
            kifint.Entries = new ReadOnlyDictionary <string, KifintEntry>(entries);
            return(kifint);
        }