public CardReplacer(Th095Converter parent, bool hideUntriedCards)
            {
                this.evaluator = new MatchEvaluator(match =>
                {
                    var level = LevelParser.Parse(match.Groups[1].Value);
                    var scene = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
                    var type  = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);

                    var key = new LevelScenePair(level, scene);
                    if (!SpellCards.ContainsKey(key))
                    {
                        return(match.ToString());
                    }

                    if (hideUntriedCards)
                    {
                        var score = parent.allScoreData.Scores.Find(
                            elem => (elem != null) && elem.LevelScene.Equals(key));
                        if (score == null)
                        {
                            return("??????????");
                        }
                    }

                    return((type == 1) ? SpellCards[key].Enemy.ToLongName() : SpellCards[key].Card);
                });
            }
        protected override void ConvertBestShot(Stream input, Stream output)
        {
            using (var decoded = new MemoryStream())
            {
                var outputFile = output as FileStream;

                var reader = new BinaryReader(input);
                var header = new BestShotHeader();
                header.ReadFrom(reader);

                var key = new LevelScenePair(header.Level, header.Scene);
                if (this.bestshots == null)
                {
                    this.bestshots = new Dictionary <LevelScenePair, BestShotPair>(SpellCards.Count);
                }
                if (!this.bestshots.ContainsKey(key))
                {
                    this.bestshots.Add(key, new BestShotPair(outputFile.Name, header));
                }

                Lzss.Extract(input, decoded);

                decoded.Seek(0, SeekOrigin.Begin);
                using (var bitmap = new Bitmap(header.Width, header.Height, PixelFormat.Format24bppRgb))
                {
                    try
                    {
                        var permission = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);
                        permission.Demand();

                        var bitmapData = bitmap.LockBits(
                            new Rectangle(0, 0, header.Width, header.Height),
                            ImageLockMode.WriteOnly,
                            bitmap.PixelFormat);

                        var source       = decoded.ToArray();
                        var sourceStride = 3 * header.Width;    // "3" means 24bpp.
                        var destination  = bitmapData.Scan0;
                        for (var sourceIndex = 0; sourceIndex < source.Length; sourceIndex += sourceStride)
                        {
                            Marshal.Copy(source, sourceIndex, destination, sourceStride);
                            destination = destination + bitmapData.Stride;
                        }

                        bitmap.UnlockBits(bitmapData);
                    }
                    catch (SecurityException e)
                    {
                        Console.WriteLine(e.ToString());
                    }

                    bitmap.Save(output, ImageFormat.Png);
                    output.Flush();
                    output.SetLength(output.Position);
                }
            }
        }
            public ShotReplacer(Th095Converter parent, string outputFilePath)
            {
                this.evaluator = new MatchEvaluator(match =>
                {
                    var level = LevelParser.Parse(match.Groups[1].Value);
                    var scene = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);

                    var key = new LevelScenePair(level, scene);
                    if (!SpellCards.ContainsKey(key))
                    {
                        return(match.ToString());
                    }

                    BestShotPair bestshot;
                    if (!string.IsNullOrEmpty(outputFilePath) &&
                        parent.bestshots.TryGetValue(key, out bestshot))
                    {
                        var relativePath = new Uri(outputFilePath)
                                           .MakeRelativeUri(new Uri(bestshot.Path)).OriginalString;
                        var alternativeString = Utils.Format(
                            "ClearData: {0}{3}Slow: {1:F6}%{3}SpellName: {2}",
                            Utils.ToNumberString(bestshot.Header.Score),
                            bestshot.Header.SlowRate,
                            Encoding.Default.GetString(bestshot.Header.CardName).TrimEnd('\0'),
                            Environment.NewLine);
                        return(Utils.Format(
                                   "<img src=\"{0}\" alt=\"{1}\" title=\"{1}\" border=0>",
                                   relativePath,
                                   alternativeString));
                    }
                    else
                    {
                        return(string.Empty);
                    }
                });
            }
            public ScoreReplacer(Th095Converter parent)
            {
                this.evaluator = new MatchEvaluator(match =>
                {
                    var level = LevelParser.Parse(match.Groups[1].Value);
                    var scene = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
                    var type  = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);

                    var key = new LevelScenePair(level, scene);
                    if (!SpellCards.ContainsKey(key))
                    {
                        return(match.ToString());
                    }

                    var score = parent.allScoreData.Scores.Find(
                        elem => (elem != null) && elem.LevelScene.Equals(key));

                    switch (type)
                    {
                    case 1:         // high score
                        return((score != null) ? Utils.ToNumberString(score.HighScore) : "0");

                    case 2:         // bestshot score
                        return((score != null) ? Utils.ToNumberString(score.BestshotScore) : "0");

                    case 3:         // num of shots
                        return((score != null) ? Utils.ToNumberString(score.TrialCount) : "0");

                    case 4:         // slow rate
                        return((score != null) ? Utils.Format("{0:F3}%", score.SlowRate2) : "-----%");

                    default:        // unreachable
                        return(match.ToString());
                    }
                });
            }
            public ShotExReplacer(Th095Converter parent, string outputFilePath)
            {
                this.evaluator = new MatchEvaluator(match =>
                {
                    var level = LevelParser.Parse(match.Groups[1].Value);
                    var scene = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
                    var type  = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);

                    var key = new LevelScenePair(level, scene);
                    if (!SpellCards.ContainsKey(key))
                    {
                        return(match.ToString());
                    }

                    BestShotPair bestshot;
                    if (!string.IsNullOrEmpty(outputFilePath) &&
                        parent.bestshots.TryGetValue(key, out bestshot))
                    {
                        switch (type)
                        {
                        case 1:         // relative path to the bestshot file
                            return(new Uri(outputFilePath)
                                   .MakeRelativeUri(new Uri(bestshot.Path)).OriginalString);

                        case 2:         // width
                            return(bestshot.Header.Width.ToString(CultureInfo.InvariantCulture));

                        case 3:         // height
                            return(bestshot.Header.Height.ToString(CultureInfo.InvariantCulture));

                        case 4:         // score
                            return(Utils.ToNumberString(bestshot.Header.Score));

                        case 5:         // slow rate
                            return(Utils.Format("{0:F6}%", bestshot.Header.SlowRate));

                        case 6:         // date & time
                            {
                                var score = parent.allScoreData.Scores.Find(
                                    elem => (elem != null) && elem.LevelScene.Equals(key));
                                if (score != null)
                                {
                                    return(new DateTime(1970, 1, 1)
                                           .AddSeconds(score.DateTime).ToLocalTime()
                                           .ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.CurrentCulture));
                                }
                                else
                                {
                                    return("----/--/-- --:--:--");
                                }
                            }

                        default:        // unreachable
                            return(match.ToString());
                        }
                    }
                    else
                    {
                        switch (type)
                        {
                        case 1: return(string.Empty);

                        case 2: return("0");

                        case 3: return("0");

                        case 4: return("--------");

                        case 5: return("-----%");

                        case 6: return("----/--/-- --:--:--");

                        default: return(match.ToString());
                        }
                    }
                });
            }