private async Task AutoSaveReplays()
        {
            const string ourPrefix = "自动保存";

            // filename and last write time
            Dictionary<string, DateTime> previousFiles = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);
            // filename and file size
            Dictionary<string, long> lastReplays = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase);

            while(true)
            {
                try
                {
                    var changed = from fileName in Directory.GetFiles(_properties.ReplayFolderPath, "*.RA3Replay")
                                  let info = new FileInfo(fileName)
                                  where !info.Name.StartsWith(ourPrefix)
                                  where !previousFiles.ContainsKey(info.FullName) || previousFiles[info.FullName] != info.LastWriteTimeUtc
                                  select info;

                    foreach (var info in changed)
                    {
                        previousFiles[info.FullName] = info.LastWriteTimeUtc;
                    }

                    var replays = changed.Select(info =>
                    {
                        try
                        {
                            return new Replay(info.FullName);
                        }
                        catch (Exception)
                        {
                            return null;
                        }
                    }).Where(replay => replay != null);

                    var newLastReplays = from replay in replays
                                         let threshold = Math.Abs((DateTime.UtcNow - replay.Date).TotalSeconds)
                                         where threshold < 20
                                         select replay;

                    var toBeChecked = newLastReplays.ToDictionary(replay => replay.FileName, StringComparer.OrdinalIgnoreCase);
                    foreach (var savedLastReplay in lastReplays)
                    {
                        if (!toBeChecked.ContainsKey(savedLastReplay.Key))
                        {
                            try
                            {
                                toBeChecked.Add(savedLastReplay.Key, new Replay(savedLastReplay.Key));
                            }
                            catch(Exception)
                            {

                            }
                        }
                    }

                    foreach (var kv in toBeChecked)
                    {
                        var replay = kv.Value;
                        if (lastReplays.TryGetValue(kv.Key, out var fileSize))
                        {
                            if (fileSize == replay.Size)
                            {
                                // skip if size is not changed
                                continue;
                            }
                        }
                        lastReplays[kv.Key] = replay.Size;

                        var date = replay.Date;
                        var numberOfPlayers = replay.NumberOfPlayingPlayers;
                        var playerString = $"{numberOfPlayers}名玩家";
                        if (replay.NumberOfPlayingPlayers <= 2)
                        {
                            var playingPlayers = from player in replay.Players
                                                 let faction = ModData.GetFaction(replay.Mod, player.FactionID)
                                                 where faction.Kind != FactionKind.Observer
                                                 select $"{player}({faction.Name})";
                            playerString = playingPlayers.Aggregate((x, y) => x + y);
                        }

                        var dateString = $"{date.Year}-{date.Month}-{date.Day}_{date.Hour}:{date.Minute}";

                        File.Copy(replay.FileName, $"{_properties.ReplayFolderPath}/{ourPrefix}-{playerString}{dateString}.RA3Replay");
                    }
                }
                catch(Exception e)
                {
                    _ = Dispatcher.InvokeAsync(() => MessageBox.Show($"自动保存录像时出现错误:\r\n{e}"));
                }
                
                await Task.Delay(10 * 1000);
            }
        }