private static string GetPathForFork(ExecuteDeviceCommandAsyncTaskData data, Fork fork, FileSystem deviceFileSystem, IEnumerable <ProgramDescription> roms, RomListConfiguration romsConfiguration, ref string destinationDir, out bool retrievalNecessary)
        {
            retrievalNecessary = false;
            string forkPath     = null;
            var    forkFileKind = ProgramFileKind.None;
            var    crc          = 0u;
            var    cfgCrc       = 0u;
            var    errors       = data.Result as FileSystemSyncErrors;

            // Determine what kind of fork this is.
            ILfsFileInfo fileContainingFork = null;
            var          forkKind           = deviceFileSystem.GetForkKind(fork, out fileContainingFork);

            switch (forkKind)
            {
            case ForkKind.Program:
                // Try to fetch LUIGI header from the fork.
                forkFileKind = ProgramFileKind.LuigiFile;
                forkPath     = GetRomPathForForkFromRomList(data, fork, roms, romsConfiguration.RomsDirectory, out crc, out cfgCrc);
                if (forkPath == null)
                {
                    forkPath = GetRomPathForForkFromCache(fork, romsConfiguration.RomsDirectory);
                }
                if ((forkPath == null) && string.IsNullOrEmpty(destinationDir))
                {
                    destinationDir = romsConfiguration.RomsDirectory;
                }
                break;

            case ForkKind.JlpFlash:
                if (string.IsNullOrEmpty(destinationDir))
                {
                    destinationDir = romsConfiguration.RomsDirectory;     // seems sensible to keep save data file(s) next to the ROM
                }
                forkFileKind = ProgramFileKind.SaveData;
                break;

            case ForkKind.Manual:
                if (string.IsNullOrEmpty(destinationDir))
                {
                    destinationDir = romsConfiguration.ManualsDirectory;
                }
                forkFileKind = ProgramFileKind.ManualText;
                break;

            case ForkKind.Vignette:
                if (string.IsNullOrEmpty(destinationDir))
                {
                    destinationDir = Configuration.Instance.VignetteDataAreaPath;     // keep next to ROM?
                }
                forkFileKind = ProgramFileKind.Vignette;
                break;

            case ForkKind.Reserved4:
            case ForkKind.Reserved5:
            case ForkKind.Reserved6:
                if (string.IsNullOrEmpty(destinationDir))
                {
                    destinationDir = Configuration.Instance.ReservedDataAreaPath;     // keep next to ROM?
                }
                forkFileKind = ProgramFileKind.GenericSupportFile;
                errors.UnsupportedForks.Add(new Tuple <ILfsFileInfo, Fork>(fileContainingFork, fork));
                ////throw new UnsupportedForkKindException(forkKind);
                break;

            case ForkKind.None:
                // An orphaned fork. Retrieve it, but we can't really do much with it.
                if (string.IsNullOrEmpty(destinationDir))
                {
                    destinationDir = Configuration.Instance.RecoveredDataAreaPath;     // orphaned fork
                }
                forkFileKind = ProgramFileKind.None;
                errors.OrphanedForks.Add(fork);
                break;

            default:
                throw new UnsupportedForkKindException(forkKind);
            }

            if ((destinationDir != null) && (forkPath == null))
            {
                retrievalNecessary = true;
                var forkFileBaseName = (fileContainingFork == null) ? Configuration.Instance.GetForkDataFileName(fork.GlobalForkNumber) : fileContainingFork.LongName.EnsureValidFileName();
                var extension        = forkFileKind.FileExtension();

                // For the menu position fork, use the default extension; since it's in the manual fork slot, we want
                // to override the .txt extension.
                if (string.IsNullOrEmpty(extension) || (fork.Uid == Fork.MenuPositionForkUid))
                {
                    extension = Configuration.ForkExtension;
                }
                var forkFileName = System.IO.Path.ChangeExtension(forkFileBaseName, extension);
                var destFile     = System.IO.Path.Combine(destinationDir, forkFileName);
                if (System.IO.File.Exists(destFile))
                {
                    var existingCrc    = 0u;
                    var existingCfgCrc = 0u;
                    var luigiHeader    = LuigiFileHeader.GetHeader(destFile);
                    if ((luigiHeader != null) && (luigiHeader.Version > 0))
                    {
                        existingCrc    = luigiHeader.OriginalRomCrc32;
                        existingCfgCrc = luigiHeader.OriginalCfgCrc32;
                    }
                    if (existingCrc == 0)
                    {
                        existingCrc = Crc32.OfFile(destFile);
                    }
                    if (existingCfgCrc == 0)
                    {
                        var destCfgFile = System.IO.Path.ChangeExtension(destFile, ProgramFileKind.CfgFile.FileExtension());
                        if (System.IO.File.Exists(destCfgFile))
                        {
                            existingCfgCrc = Crc32.OfFile(destCfgFile);
                        }
                    }

                    // This is the equivalent of RomComparerStrict: We skip retrieval only if both the ROM CRCs match and, if available, the .cfg CRCs match.
                    if ((crc != 0) && (existingCrc == crc) && ((cfgCrc == 0) || (existingCfgCrc == cfgCrc)))
                    {
                        retrievalNecessary = false;
                        forkPath           = destFile;
                    }
                    else
                    {
                        forkPath = destFile.EnsureUniqueFileName();
                    }
                }
                else
                {
                    forkPath = destFile;
                }
            }
            if (!string.IsNullOrEmpty(forkPath) && !System.IO.File.Exists(forkPath))
            {
                retrievalNecessary = true;
                destinationDir     = System.IO.Path.GetDirectoryName(forkPath);
            }

            return(forkPath);
        }
        private static string GetSourcePathForFork(ExecuteDeviceCommandAsyncTaskData data, Fork fork, FileSystem deviceFileSystem, IEnumerable <ProgramDescription> roms, RomListConfiguration romsConfiguration)
        {
            bool   retrievalNecessary;
            string destinationDir = null;
            var    forkSourcePath = GetPathForFork(data, fork, deviceFileSystem, roms, romsConfiguration, ref destinationDir, out retrievalNecessary);

            if (retrievalNecessary)
            {
                if (!data.Device.RetrieveForkData(data, new[] { fork }, destinationDir, new[] { System.IO.Path.GetFileName(forkSourcePath) }))
                {
                    var syncErrors = data.Result as FileSystemSyncErrors;
                    syncErrors.UnableToRetrieveForks.Add(fork);
                    forkSourcePath = null;
                }
            }
            return(forkSourcePath);
        }