diff --git a/src/BizHawk.Client.Common/movie/HeaderKeys.cs b/src/BizHawk.Client.Common/movie/HeaderKeys.cs index 1f6c6905df..2ec87ba6cb 100644 --- a/src/BizHawk.Client.Common/movie/HeaderKeys.cs +++ b/src/BizHawk.Client.Common/movie/HeaderKeys.cs @@ -15,6 +15,9 @@ namespace BizHawk.Client.Common public const string StartsFromSaveram = "StartsFromSaveRam"; public const string SavestateBinaryBase64Blob = "SavestateBinaryBase64Blob"; // this string will not contain base64: ; it's implicit (this is to avoid another big string op to dice off the base64: substring) public const string Sha1 = "SHA1"; // misleading name; either CRC32, MD5, or SHA1, hex-encoded, unprefixed + public const string Sha256 = "SHA256"; + public const string Md5 = "MD5"; + public const string Crc32 = "CRC32"; public const string FirmwareSha1 = "FirmwareSHA1"; public const string Pal = "PAL"; public const string BoardName = "BoardName"; diff --git a/src/BizHawk.Client.Common/movie/import/FcmImport.cs b/src/BizHawk.Client.Common/movie/import/FcmImport.cs index 8e1d7a44cd..ef0d919639 100644 --- a/src/BizHawk.Client.Common/movie/import/FcmImport.cs +++ b/src/BizHawk.Client.Common/movie/import/FcmImport.cs @@ -107,7 +107,7 @@ namespace BizHawk.Client.Common.movie.import // 020 16-byte md5sum of the ROM used byte[] md5 = r.ReadBytes(16); - Result.Movie.HeaderEntries[Md5] = md5.BytesToHexString().ToLower(); + Result.Movie.HeaderEntries[HeaderKeys.Md5] = md5.BytesToHexString().ToLower(); // 030 4-byte little-endian unsigned int: version of the emulator used uint emuVersion = r.ReadUInt32(); diff --git a/src/BizHawk.Client.Common/movie/import/Fm2Import.cs b/src/BizHawk.Client.Common/movie/import/Fm2Import.cs index b7562df451..df81b59dcf 100644 --- a/src/BizHawk.Client.Common/movie/import/Fm2Import.cs +++ b/src/BizHawk.Client.Common/movie/import/Fm2Import.cs @@ -84,7 +84,7 @@ namespace BizHawk.Client.Common byte[] md5 = DecodeBlob(blob); if (md5 != null && md5.Length == 16) { - Result.Movie.HeaderEntries[Md5] = md5.BytesToHexString().ToLower(); + Result.Movie.HeaderEntries[HeaderKeys.Md5] = md5.BytesToHexString().ToLower(); } else { diff --git a/src/BizHawk.Client.Common/movie/import/IMovieImport.cs b/src/BizHawk.Client.Common/movie/import/IMovieImport.cs index a8247df728..dcfaa7e286 100644 --- a/src/BizHawk.Client.Common/movie/import/IMovieImport.cs +++ b/src/BizHawk.Client.Common/movie/import/IMovieImport.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using BizHawk.Emulation.Common; +using BizHawk.Common; namespace BizHawk.Client.Common { @@ -12,7 +12,6 @@ namespace BizHawk.Client.Common ImportResult Import( IDialogParent dialogParent, IMovieSession session, - IEmulator emulator, string path, Config config); } @@ -20,26 +19,14 @@ namespace BizHawk.Client.Common internal abstract class MovieImporter : IMovieImport { protected const string EmulationOrigin = "emuOrigin"; - protected const string Md5 = "MD5"; protected const string MovieOrigin = "MovieOrigin"; protected IDialogParent _dialogParent; - - protected void MaybeSetCorePreference(string sysID, string coreName, string fileExt) - { - if (Config.PreferredCores[sysID] != coreName - && _dialogParent.ModalMessageBox2( - $"{fileExt} movies will have a better chance of syncing using the {coreName} core. Change your core preference for {sysID} roms to {coreName} now?", - icon: EMsgBoxIcon.Question)) - { - Config.PreferredCores[sysID] = coreName; - } - } + private delegate bool MatchesMovieHash(ReadOnlySpan romData); public ImportResult Import( IDialogParent dialogParent, IMovieSession session, - IEmulator emulator, string path, Config config) { @@ -55,17 +42,69 @@ namespace BizHawk.Client.Common var newFileName = $"{SourceFile.FullName}.{Bk2Movie.Extension}"; Result.Movie = session.Get(newFileName); - Result.Movie.Attach(emulator); RunImport(); if (!Result.Errors.Any()) { + if (string.IsNullOrEmpty(Result.Movie.Hash)) + { + string hash = null; + // try to generate a matching hash from the original ROM + if (Result.Movie.HeaderEntries.TryGetValue(HeaderKeys.Crc32, out string crcHash)) + { + hash = PromptForRom(data => string.Equals(CRC32Checksum.ComputeDigestHex(data), crcHash, StringComparison.OrdinalIgnoreCase)); + } + else if (Result.Movie.HeaderEntries.TryGetValue(HeaderKeys.Md5, out string md5Hash)) + { + hash = PromptForRom(data => string.Equals(MD5Checksum.ComputeDigestHex(data), md5Hash, StringComparison.OrdinalIgnoreCase)); + } + else if (Result.Movie.HeaderEntries.TryGetValue(HeaderKeys.Sha256, out string sha256Hash)) + { + hash = PromptForRom(data => string.Equals(SHA256Checksum.ComputeDigestHex(data), sha256Hash, StringComparison.OrdinalIgnoreCase)); + } + + if (hash is not null) + Result.Movie.Hash = hash; + } + Result.Movie.Save(); } return Result; } + /// + /// Prompts the user for a ROM file that matches the original movie file's hash + /// and returns a SHA1 hash of that ROM file. + /// + /// Function that checks whether the ROM data matches the original hash + /// SHA1 hash of the selected ROM file + private string PromptForRom(MatchesMovieHash matchesMovieHash) + { + string messageBoxText = "Please select the original ROM to finalize the import process."; + while (true) + { + if (!_dialogParent.ModalMessageBox2(messageBoxText, "ROM required to populate hash", useOKCancel: true)) + return null; + + var result = _dialogParent.ShowFileOpenDialog( + filter: RomLoader.RomFilter, + initDir: Config.PathEntries.RomAbsolutePath(Result.Movie.SystemID)); + if (result is null) + return null; // skip hash migration when the dialog was canceled + + using var rom = new HawkFile(result); + if (rom.IsArchive) rom.BindFirst(); + var romData = (ReadOnlySpan) rom.ReadAllBytes(); + if (romData.Length % 1024 == 512) + romData = romData.Slice(512, romData.Length - 512); + if (matchesMovieHash(romData)) + return SHA1Checksum.ComputeDigestHex(romData); + + messageBoxText = "The selected ROM does not match the movie's hash. Please try again."; + } + } + protected Config Config { get; private set; } protected ImportResult Result { get; } = new ImportResult(); diff --git a/src/BizHawk.Client.Common/movie/import/LsmvImport.cs b/src/BizHawk.Client.Common/movie/import/LsmvImport.cs index d11ff42bcd..564e9f6192 100644 --- a/src/BizHawk.Client.Common/movie/import/LsmvImport.cs +++ b/src/BizHawk.Client.Common/movie/import/LsmvImport.cs @@ -4,6 +4,7 @@ using System.IO.Compression; using System.Linq; using System.Text; using BizHawk.Common.IOExtensions; +using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores; using BizHawk.Emulation.Cores.Nintendo.BSNES; @@ -214,10 +215,9 @@ namespace BizHawk.Client.Common.movie.import else if (item.FullName.EndsWith(".sha256")) { using var stream = item.Open(); - string rom = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); - int pos = item.FullName.LastIndexOf(".sha256"); - string name = item.FullName.Substring(0, pos); - Result.Movie.HeaderEntries[$"SHA256_{name}"] = rom; + string sha256Hash = Encoding.UTF8.GetString(stream.ReadAllBytes()).Trim(); + string name = item.FullName.RemoveSuffix(".sha256"); + Result.Movie.HeaderEntries[name is "rom" ? HeaderKeys.Sha256 : $"SHA256_{name}"] = sha256Hash; } else if (item.FullName == "savestate") { diff --git a/src/BizHawk.Client.Common/movie/import/MmvImport.cs b/src/BizHawk.Client.Common/movie/import/MmvImport.cs index 9e97157026..22ec738295 100644 --- a/src/BizHawk.Client.Common/movie/import/MmvImport.cs +++ b/src/BizHawk.Client.Common/movie/import/MmvImport.cs @@ -92,7 +92,7 @@ namespace BizHawk.Client.Common.movie.import // 00e4-00f3: binary: rom MD5 digest byte[] md5 = r.ReadBytes(16); - Result.Movie.HeaderEntries[Md5] = md5.BytesToHexString().ToLower(); + Result.Movie.HeaderEntries[HeaderKeys.Md5] = md5.BytesToHexString().ToLower(); var ss = new SMS.SmsSyncSettings(); var cd = new SMSControllerDeck(ss.Port1, ss.Port2, isGameGear, ss.UseKeyboard); diff --git a/src/BizHawk.Client.Common/movie/import/MovieImport.cs b/src/BizHawk.Client.Common/movie/import/MovieImport.cs index 38e09b98d4..72310e478e 100644 --- a/src/BizHawk.Client.Common/movie/import/MovieImport.cs +++ b/src/BizHawk.Client.Common/movie/import/MovieImport.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.IO; using System.Reflection; -using BizHawk.Emulation.Common; namespace BizHawk.Client.Common { @@ -36,7 +35,6 @@ namespace BizHawk.Client.Common public static ImportResult ImportFile( IDialogParent dialogParent, IMovieSession session, - IEmulator emulator, string path, Config config) { @@ -51,7 +49,7 @@ namespace BizHawk.Client.Common // Create a new instance of the importer class using the no-argument constructor return importerType.GetConstructor(Array.Empty())?.Invoke(Array.Empty()) is IMovieImport importer - ? importer.Import(dialogParent, session, emulator, path, config) + ? importer.Import(dialogParent, session, path, config) : ImportResult.Error($"No importer found for file type {ext}"); } diff --git a/src/BizHawk.Client.Common/movie/import/SmvImport.cs b/src/BizHawk.Client.Common/movie/import/SmvImport.cs index ef7d3cf94e..b6889eb760 100644 --- a/src/BizHawk.Client.Common/movie/import/SmvImport.cs +++ b/src/BizHawk.Client.Common/movie/import/SmvImport.cs @@ -183,7 +183,7 @@ namespace BizHawk.Client.Common.movie.import // 000 3 bytes of zero padding: 00 00 00 003 4-byte integer: CRC32 of the ROM 007 23-byte ascii string r.ReadBytes(3); int crc32 = r.ReadInt32(); - Result.Movie.HeaderEntries["CRC32"] = crc32.ToString("X08"); + Result.Movie.HeaderEntries[HeaderKeys.Crc32] = crc32.ToString("X08"); // the game name copied from the ROM, truncated to 23 bytes (the game name in the ROM is 21 bytes) string gameName = NullTerminated(Encoding.UTF8.GetString(r.ReadBytes(23))); diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Movie.cs b/src/BizHawk.Client.EmuHawk/MainForm.Movie.cs index fa08f5bbe9..6412e72012 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Movie.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Movie.cs @@ -54,7 +54,11 @@ namespace BizHawk.Client.EmuHawk SetMainformMovieInfo(); - if (MovieSession.Movie.Hash != Game.Hash) + if (string.IsNullOrEmpty(MovieSession.Movie.Hash)) + { + AddOnScreenMessage("Movie is missing hash, skipping hash check"); + } + else if (MovieSession.Movie.Hash != Game.Hash) { AddOnScreenMessage("Warning: Movie hash does not match the ROM"); } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 2ca287af51..98da8daac5 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -4150,7 +4150,7 @@ namespace BizHawk.Client.EmuHawk private void ProcessMovieImport(string fn, bool start) { - var result = MovieImport.ImportFile(this, MovieSession, Emulator, fn, Config); + var result = MovieImport.ImportFile(this, MovieSession, fn, Config); if (result.Errors.Any()) {