public void PutFile(CacheIndex index, int fileId, CacheFile file)
        {
            // Throw an exception if the output directory is not yet set or does not exist.
            if (string.IsNullOrWhiteSpace(this.BaseDirectory))
            {
                throw new InvalidOperationException("Base directory must be set before writing files.");
            }

            if (file.Data.Length == 0)
            {
                throw new ArgumentException("You cannot put an empty file into a FlatFileCache.");
            }

            var indexDirectory = this.GetIndexDirectory(index);

            // Create index directory when it does not exist yet.
            Directory.CreateDirectory(indexDirectory);

            // Clean existing files (to make sure no variants with different extensions exist).
            foreach (var existingFilePath in this.GetExistingFilePaths(index, fileId))
            {
                if (!this.OverwriteFiles)
                {
                    Log.Debug($"{(int)index}/{fileId} already exists.");
                    return;
                }

                Log.Debug($"Deleting existing {existingFilePath}.");
                System.IO.File.Delete(existingFilePath);
            }

            string?extension;

            if (file.Info.HasEntries)
            {
                // TODO: Add an option to extract entries individually (which generates tons of files but is more useful).
                extension = "entries";
            }
            else
            {
                extension = ExtensionGuesser.GuessExtension(file.Data);
            }
            extension = extension != null ? $".{extension}" : "";

            var filePath = $"{indexDirectory}{fileId}{extension}";

            System.IO.File.WriteAllBytes(filePath, file.Data);
        }
        public static string GuessExtension(byte[] fileData)
        {
            // ogg (OggS)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x4f, 0x67, 0x67, 0x53 }))
            {
                return("ogg");
            }

            // jaga (JAGA)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x4a, 0x41, 0x47, 0x41 }))
            {
                return("jaga");
            }

            // png (0x89504e470d0a1a0a)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }))
            {
                return("png");
            }

            // gif (GIF87a and GIF89a)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }) || ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }))
            {
                return("gif");
            }

            // bmp (BM)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x42, 0x4d }))
            {
                return("bmp");
            }

            // midi (MThd)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x4d, 0x54, 0x68, 0x64 }))
            {
                return("mid");
            }

            // gzip (0x1f8b)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x1f, 0x8b }))
            {
                return("gz");
            }

            // bzip2 (BZh)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x42, 0x5a, 0x68 }))
            {
                return("bz2");
            }

            // tiff (0x49492a00 and 0x4d4d002a)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x49, 0x49, 0x2a, 0x00 }) || ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x4d, 0x4d, 0x00, 0x2a }))
            {
                return("tiff");
            }

            // mp3 (0xfffb and ID3).
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0xff, 0xfb }) || ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x49, 0x44, 0x33 }))
            {
                return("mp3");
            }

            // jpeg (0xffd8ff). Actually multiple numbers, but they all start with the same bytes anyway.
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0xff, 0xd8, 0xff }))
            {
                return("jpg");
            }

            // zip (0x504b). Same thing here.
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x50, 0x4b }))
            {
                return("zip");
            }

            // wav (RIFF). Same thing here.
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x52, 0x49, 0x46, 0x46 }))
            {
                return("wav");
            }

            // tar (ustar). Same thing here.
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72 }))
            {
                return("tar");
            }

            // 7zip (0x377abcaf271c)
            if (ExtensionGuesser.DataHasMagicNumber(fileData, new byte[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c }))
            {
                return("7z");
            }

            return(null);
        }