/// <summary> /// Update the feature flags. /// </summary> /// <param name="features">The new feature flags.</param> /// <param name="forceFeatureUpdate">If <c>true</c>, apply ROM features even if already set via config file.</param> /// <remarks>This also updates the CRC of the header.</remarks> public void UpdateFeatures(LuigiFeatureFlags features, bool forceFeatureUpdate) { if (WouldModifyFeatures(features, forceFeatureUpdate)) { Features = features; var flatData = SerializeToBuffer(); Crc = Crc8.OfBlock(flatData); } }
/// <summary> /// Determines whether the given features would cause a change to this header's feature bits. /// </summary> /// <param name="features">The proposed new features to apply to the header.</param> /// <param name="forceFeatureUpdate">If set to <c>true</c> force feature update, even if the features were set explicitly by a .cfg file.</param> /// <returns><c>true</c>, if the new features differ from existing ones, and would cause an update, <c>false</c> otherwise.</returns> public bool WouldModifyFeatures(LuigiFeatureFlags features, bool forceFeatureUpdate) { var willModifyFeatures = false; var hasFlagsFromCfgFile = Features.HasFlag(LuigiFeatureFlags.FeatureFlagsExplicitlySet); if (forceFeatureUpdate || !hasFlagsFromCfgFile) { var currentFlags = Features & ~(LuigiFeatureFlags.UnusedMask | LuigiFeatureFlags.FeatureFlagsExplicitlySet); var newFlags = features & ~(LuigiFeatureFlags.UnusedMask | LuigiFeatureFlags.FeatureFlagsExplicitlySet); willModifyFeatures = currentFlags != newFlags; } return(willModifyFeatures); }
private static ProgramFeatures CreateProgramFeaturesWithoutCoreCompatibility(LuigiFeatureFlags featuresUnderTest) { var programFeaturesHavingNoLuigiFeatureFlags = new ProgramFeatures() { Ecs = EcsFeatures.Incompatible, IntellivisionII = FeatureCompatibility.Incompatible, Intellivoice = FeatureCompatibility.Incompatible, KeyboardComponent = KeyboardComponentFeatures.Incompatible, }; if (featuresUnderTest.ExtendedPeripheralCompatabilityBitsVersion() > 0) { programFeaturesHavingNoLuigiFeatureFlags.Tutorvision = FeatureCompatibility.Incompatible; } programFeaturesHavingNoLuigiFeatureFlags.SetUnrecongizedRomFeatures(); return(programFeaturesHavingNoLuigiFeatureFlags); }
public void FeatureCompatibility_TutorvisionFeaturesCompatibilityToLuigiFeatureFlags_ProducesCorrectLuigiFeatureFlags(FeatureCompatibility compatibility, LuigiFeatureFlags expectedFeatureFlags) { Assert.Equal(expectedFeatureFlags, compatibility.ToLuigiFeatureFlags(FeatureCategory.Tutorvision)); }
public void FeatureCompatibility_KeyboardComponentFeaturesCompatibilityToLuigiFeatureFlags_ProducesCorrectLuigiFeatureFlags(FeatureCompatibility compatibility, LuigiFeatureFlags expectedFeatureFlags) { Assert.Equal(expectedFeatureFlags, compatibility.ToLuigiFeatureFlags(FeatureCategory.KeyboardComponent)); }
public void LuigiFeatureFlags_JlpFlashSectorsToFlags_ProducesCorrectFlags(ushort sectorCount, LuigiFeatureFlags expectedFeatureFlags) { var featureFlags = sectorCount.MinimumFlashSaveDataSectorsCountToLuigiFeatureFlags(); Assert.Equal(expectedFeatureFlags, featureFlags); }
public void LuigiFeatureFlags_ToProgramFeatures_ProducesExpectedProgramFeatures(LuigiFeatureFlags featureFlags, ProgramFeatures expectedProgramFeatures) { Assert.Equal(expectedProgramFeatures, featureFlags.ToProgramFeatures()); }
public void LuigiFeatureFlags_LuigiFeatureFlagsForJlpToProgramFeatures_ProduceCorrectlyFormedProgramFeaturesJlpFields(LuigiFeatureFlags flagsToTest, JlpHardwareVersion expectedJlpHardwareVersion, JlpFeatures expectedJlpFeatures) { var expectedProgramFeatures = CreateProgramFeaturesWithoutCoreCompatibility(flagsToTest); expectedProgramFeatures.JlpHardwareVersion = expectedJlpHardwareVersion; expectedProgramFeatures.Jlp = expectedJlpFeatures; var programFeatures = flagsToTest.ToProgramFeatures(); Assert.Equal(expectedProgramFeatures, programFeatures); }
public void LuigiFileHeader_UpdateFeaturesOnLuigiWithMetadata_UpdatesFeatures(LuigiFeatureFlags newFeatures, bool forceFeatureUpdate) { var romPath = LuigiFileHeaderTestStorageAccess.Initialize(TestRomResources.TestLuigiScrambledForDevice1Path).First(); var header = LuigiFileHeader.GetHeader(romPath); var originalCrc = header.Crc; var expectedFeatures = header.WouldModifyFeatures(newFeatures, forceFeatureUpdate) ? newFeatures : header.Features; Assert.Equal(0x8B, originalCrc); var crcShouldChange = header.Features != expectedFeatures; header.UpdateFeatures(newFeatures, forceFeatureUpdate); Assert.Equal(expectedFeatures, header.Features); Assert.Equal(crcShouldChange, header.Crc != originalCrc); }
public void LuigiFileHeader_UpdateFeaturesOnScrambledLuigiRom_UpdatesFeatures(LuigiFeatureFlags newFeatures, bool forceFeatureUpdate) { var romPath = LuigiFileHeaderTestStorageAccess.Initialize(TestRomResources.TestLuigiScrambledForDevice0Path).First(); var header = LuigiFileHeader.GetHeader(romPath); var originalCrc = header.Crc; Assert.Equal(0xEC, header.Crc); var crcShouldChange = header.Features != newFeatures; header.UpdateFeatures(newFeatures, forceFeatureUpdate); Assert.Equal(newFeatures, header.Features); Assert.Equal(crcShouldChange, header.Crc != originalCrc); }
public void LuigiFileHeader_WouldModifyFeatures_ProducesCorrectResult(LuigiFeatureFlags newFeatures, bool forceFeatureUpdate, bool expectedWouldModify) { var header = new LuigiFileHeader(); Assert.Equal(expectedWouldModify, header.WouldModifyFeatures(newFeatures, forceFeatureUpdate)); }
/// <summary> /// Extracts the version number of extended peripheral compatibility flags data from the feature flags. /// </summary> /// <param name="featureFlags">The flags whose extended peripheral compatibility flags are needed.</param> /// <returns>The extended peripheral flags version number.</returns> public static byte ExtendedPeripheralCompatabilityBitsVersion(this LuigiFeatureFlags featureFlags) { byte compatibilitySubVersion = (byte)((ulong)(featureFlags & LuigiFeatureFlags.ExtendedPeripheralCompatibilityVersionMask) >> ExtendedPeripheralCompatibiltyVersionOffset); return(compatibilitySubVersion); }
/// <summary> /// Extracts the minimum number of JLP Flash save data sectors from the feature flags. /// </summary> /// <param name="featureFlags">The flags containing the encoded sector count.</param> /// <returns>The minimum number of required save data sectors.</returns> public static ushort MinimumFlashSaveDataSectors(this LuigiFeatureFlags featureFlags) { var minimumJlpFlashSaveSectors = (ulong)(featureFlags & LuigiFeatureFlags.JlpFlashMinimumSaveDataSectorCountMask) >> JlpFlashMinimumSaveDataSectorsCountOffset; return((ushort)minimumJlpFlashSaveSectors); }
/// <summary> /// Convert LuigiFeatureFlags into a ProgramFeatures object. /// </summary> /// <param name="featureFlags">The flags to convert,</param> /// <returns>ProgramFeatures representing the compatibility modes described by the feature flags.</returns> public static ProgramFeatures ToProgramFeatures(this LuigiFeatureFlags featureFlags) { var programFeatures = ProgramFeatures.GetUnrecognizedRomFeatures(); var intellivoiceCompatibililty = (uint)(featureFlags & LuigiFeatureFlags.IntellivoiceMask); programFeatures.Intellivoice = (FeatureCompatibility)(intellivoiceCompatibililty >> IntellivoiceOffset); var ecsCompatibility = (uint)(featureFlags & LuigiFeatureFlags.EcsMask); programFeatures.Ecs = (EcsFeatures)(ecsCompatibility >> EcsOffset); var intellivisionIICompatibility = (uint)(featureFlags & LuigiFeatureFlags.IntellivisionIIMask); programFeatures.IntellivisionII = (FeatureCompatibility)(intellivisionIICompatibility >> IntellivisionIIOffset); var keyboardComponentCompatibility = (uint)(featureFlags & LuigiFeatureFlags.KeyboardComponentMask); programFeatures.KeyboardComponent = (KeyboardComponentFeatures)(keyboardComponentCompatibility >> KeyboardComponentOffset); var extendedPeripheralCompatibilityVersion = featureFlags.ExtendedPeripheralCompatabilityBitsVersion(); if (extendedPeripheralCompatibilityVersion > 0) { var tutorvisionCompatibility = (uint)(featureFlags & LuigiFeatureFlags.TutorVisionMask); programFeatures.Tutorvision = (FeatureCompatibility)(tutorvisionCompatibility >> TutorVisionOffset); } if (extendedPeripheralCompatibilityVersion > 1) { // TBD } if (extendedPeripheralCompatibilityVersion > 2) { // TBD } var jlpAccelerationCompatibility = (uint)(featureFlags & LuigiFeatureFlags.JlpAccelerationMask); programFeatures.Jlp = (JlpFeatures)(jlpAccelerationCompatibility >> JlpAccelerationOffset); programFeatures.JlpFlashMinimumSaveSectors = featureFlags.MinimumFlashSaveDataSectors(); if (programFeatures.JlpFlashMinimumSaveSectors > 0) { programFeatures.Jlp |= JlpFeatures.SaveDataRequired; } if ((jlpAccelerationCompatibility != 0) || (programFeatures.JlpFlashMinimumSaveSectors > 0)) { if (programFeatures.JlpHardwareVersion == JlpHardwareVersion.None) { programFeatures.JlpHardwareVersion = JlpHardwareVersion.Jlp03; } } var ltoFlashMemoryMapper = (ulong)(featureFlags & LuigiFeatureFlags.LtoFlashMemoryMapperEnabled); if (ltoFlashMemoryMapper != 0) { programFeatures.LtoFlash |= LtoFlashFeatures.LtoFlashMemoryMapped; } return(programFeatures); }
/// <summary> /// Prepares a ROM for deployment to a Locutus device. /// </summary> /// <param name="rom">The ROM being prepared.</param> /// <param name="updateMode">Specifies the behavior of the LUIGI file generation.</param> /// <returns>The fully qualified path of the prepared output data file to deploy to Locutus.</returns> public static string PrepareForDeployment(this IRom rom, LuigiGenerationMode updateMode) { #if REPORT_PERFORMANCE #if RECORD_PREPARE_FOR_DEPLOYMENT_VISITS int visits; if (PrepareForDeploymentVisits.TryGetValue(rom.RomPath, out visits)) { PrepareForDeploymentVisits[rom.RomPath] = ++visits; } else { PrepareForDeploymentVisits[rom.RomPath] = 1; } #endif // RECORD_PREPARE_FOR_DEPLOYMENT_VISITS var stopwatch = System.Diagnostics.Stopwatch.StartNew(); try { var stopwatch2 = System.Diagnostics.Stopwatch.StartNew(); #endif // REPORT_PERFORMANCE rom.Validate(); #if REPORT_PERFORMANCE stopwatch2.Stop(); _accumulatedPrepareValidateTime += stopwatch2.Elapsed; #endif // REPORT_PERFORMANCE if ((updateMode == LuigiGenerationMode.Passthrough) && (rom.Format == RomFormat.Luigi)) { return(rom.RomPath); } #if REPORT_PERFORMANCE stopwatch2.Restart(); var stopwatch3 = System.Diagnostics.Stopwatch.StartNew(); #endif // REPORT_PERFORMANCE var jzIntvConfiguration = SingleInstanceApplication.Instance.GetConfiguration <INTV.JzIntv.Model.Configuration>(); var converterApps = jzIntvConfiguration.GetConverterApps(rom, RomFormat.Luigi); if (!converterApps.Any()) { converterApps = new[] { new Tuple <string, RomFormat>(JustCopy, RomFormat.Luigi) }; } var converterApp = converterApps.First(); // rom.GetConverterApp(jzIntvConfiguration); if ((converterApp.Item1 != JustCopy) && (string.IsNullOrEmpty(converterApp.Item1) || !System.IO.File.Exists(converterApp.Item1)) && (rom.Format != RomFormat.Luigi)) { var message = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.RomToLuigiFailed_ConversionToolNotFound_Error_Format, converterApp); throw new LuigiFileGenerationException(message, Resources.Strings.RomToLuigiFailed_ConversionToolNotFound_Error_Description); } #if REPORT_PERFORMANCE stopwatch3.Stop(); _accumulatedPrepareConverterAppsTime += stopwatch3.Elapsed; stopwatch3.Restart(); #endif // REPORT_PERFORMANCE var romStagingArea = SingleInstanceApplication.Instance.GetConfiguration <Configuration>().RomsStagingAreaPath; var stagingAreaPath = rom.GetStagingAreaPath(romStagingArea); var cachedRomPath = rom.GetCachedRomFilePath(stagingAreaPath); var cachedConfigPath = rom.GetCachedConfigFilePath(stagingAreaPath); var luigiFile = rom.GetOutputFilePath(stagingAreaPath, ProgramFileKind.LuigiFile); #if REPORT_PERFORMANCE stopwatch3.Stop(); _accumulatedPrepareStagingTime += stopwatch3.Elapsed; stopwatch3.Restart(); #endif // REPORT_PERFORMANCE bool createLuigiFile = true; bool changed; bool isSourceFileInCache = rom.IsInCache(stagingAreaPath, out changed); #if REPORT_PERFORMANCE stopwatch3.Stop(); _accumulatedPrepareCacheLookupTime += stopwatch3.Elapsed; #endif // REPORT_PERFORMANCE #if REPORT_PERFORMANCE stopwatch2.Stop(); _accumulatedPrepareSetupTime += stopwatch2.Elapsed; stopwatch2.Restart(); #endif // REPORT_PERFORMANCE var luigiHeader = rom.GetLuigiHeader(); if (luigiHeader != null) { // If the given ROM is already a LUIGI file, see if we can determine whether it's already in our cache. #if REPORT_OLD_LUIGI_FILES System.Diagnostics.Debug.Assert(luigiHeader.Version > 0, "Really, you've got some OLD LUIGI files. Delete them."); #endif // REPORT_OLD_LUIGI_FILES var crc24 = INTV.Core.Utility.Crc24.OfFile(rom.RomPath); var size = new System.IO.FileInfo(rom.RomPath).Length; var entry = CacheIndex.Find(crc24, (uint)size); isSourceFileInCache = entry != null; if (isSourceFileInCache) { // Cases have been found in which, by moving files around on disk, the staging area path can change. // The result of this, though, is that the *new* path in the cache is different than the extant one // found in the cache. In this case, if the entry's location is different than the newly computed // one, ignore the cache entry and make a new one by acting as if the file is not in the cache. // FIXME This is a lazy fix. A better fix would be to remove the cached files and existing cache // entry and re-add this new one. Or patch up the existing entry. Hell, maybe scrap the entire cache // altogether as it's a bit of a bug farm and creating LUIGI files isn't all *that* expensive. var stagingDirectory = System.IO.Path.GetFileName(stagingAreaPath); var stagingPathChanged = !entry.LuigiPath.StartsWith(stagingDirectory); if (stagingPathChanged) { isSourceFileInCache = false; } else { luigiFile = System.IO.Path.Combine(romStagingArea, entry.LuigiPath); cachedRomPath = System.IO.Path.Combine(romStagingArea, entry.RomPath); if (!string.IsNullOrEmpty(entry.CfgPath)) { cachedConfigPath = System.IO.Path.Combine(romStagingArea, entry.CfgPath); } } } } #if REPORT_PERFORMANCE stopwatch2.Stop(); _accumulatedPrepareLuigiHeaderTime += stopwatch2.Elapsed; stopwatch2.Restart(); #endif // REPORT_PERFORMANCE if (isSourceFileInCache) { createLuigiFile = changed || !System.IO.File.Exists(luigiFile); } if (!isSourceFileInCache || changed) { cachedRomPath.ClearReadOnlyAttribute(); cachedConfigPath.ClearReadOnlyAttribute(); System.IO.File.Copy(rom.RomPath, cachedRomPath, true); if (!string.IsNullOrWhiteSpace(cachedConfigPath) && !string.IsNullOrEmpty(rom.ConfigPath) && System.IO.File.Exists(rom.ConfigPath) && (rom.ConfigPath != rom.RomPath)) { System.IO.File.Copy(rom.ConfigPath, cachedConfigPath, true); } else if ((string.IsNullOrEmpty(rom.ConfigPath) || !System.IO.File.Exists(rom.ConfigPath)) && System.IO.File.Exists(cachedConfigPath)) { // The ROM's configuration file path doesn't exist, but there's one in the cache. Remove it. FileUtilities.DeleteFile(cachedConfigPath, false, 2); cachedConfigPath = null; // this is OK, because the ClearReadOnlyAttribute() extension method is null-safe } cachedRomPath.ClearReadOnlyAttribute(); cachedConfigPath.ClearReadOnlyAttribute(); } #if REPORT_PERFORMANCE stopwatch2.Stop(); _accumulatedPrepareCachedChangedTime += stopwatch2.Elapsed; stopwatch2.Restart(); #endif // REPORT_PERFORMANCE if (createLuigiFile || ((updateMode == LuigiGenerationMode.FeatureUpdate) || (updateMode == LuigiGenerationMode.Reset))) { var argument = "\"" + cachedRomPath + "\"" + " \"" + luigiFile + "\""; var result = -1; if (JustCopy == converterApp.Item1) { System.IO.File.Copy(rom.RomPath, luigiFile, true); result = 0; } else { result = INTV.Shared.Utility.RunExternalProgram.Call(converterApp.Item1, argument, stagingAreaPath); } if (result == 0) { if (!System.IO.File.Exists(luigiFile)) { var message = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.RomToLuigiFailed_OutputFileNotFound_Error_Format, rom.RomPath, System.IO.Path.GetFileNameWithoutExtension(luigiFile)); throw new LuigiFileGenerationException(message, Resources.Strings.RomToLuigiFailed_OutputFileNotFound_Error_Description_Format); } else if ((new System.IO.FileInfo(luigiFile)).Length > Device.TotalRAMSize) { var message = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.RomToLuigiFailed_TooLarge_Error_Message_Format, rom.RomPath, luigiFile); throw new LuigiFileGenerationException(message, Resources.Strings.RomToLuigiFailed_TooLarge_Description); } var description = INTV.Shared.Model.Program.ProgramCollection.Roms.FirstOrDefault(d => rom.IsEquivalentTo(d.Rom, RomComparerStrict.Default)); if (description != null) { LuigiFeatureFlags features = LuigiFeatureFlags.None; #if DEBUG var complainAboutOldLuigiFile = false; #endif // DEBUG luigiHeader = LuigiFileHeader.GetHeader(luigiFile); features = description.Features.LuigiFeaturesLo; #if DEBUG var isRecognizedRom = !description.Features.GeneralFeatures.HasFlag(GeneralFeatures.UnrecognizedRom); var hasFlagsFromCfgFile = luigiHeader.Features.HasFlag(LuigiFeatureFlags.FeatureFlagsExplicitlySet); complainAboutOldLuigiFile = isRecognizedRom && hasFlagsFromCfgFile && ((luigiHeader.Features & ~LuigiFeatureFlags.FeatureFlagsExplicitlySet) != features); #endif // DEBUG #if DEBUG if (complainAboutOldLuigiFile) { var message = "Known ROM has explicit flags from utility that are different than those set by LUI:\n\n"; message += " LUI: " + features + "\n"; message += " Utility: " + (luigiHeader.Features & ~LuigiFeatureFlags.FeatureFlagsExplicitlySet); INTV.Shared.View.OSMessageBox.Show(message, "Feature Flags Inconsistency"); } #endif // DEBUG if (luigiHeader.WouldModifyFeatures(features, updateMode == LuigiGenerationMode.FeatureUpdate)) { using (var file = System.IO.File.Open(luigiFile, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite)) { luigiHeader.UpdateFeatures(features, updateMode == LuigiGenerationMode.FeatureUpdate); file.Seek(0, System.IO.SeekOrigin.Begin); luigiHeader.Serialize(new Core.Utility.BinaryWriter(file)); } } } try { var cacheIndexEntry = new CacheIndexEntry(rom, cachedRomPath); CacheIndex.Instance.AddEntry(cacheIndexEntry); } catch (Exception e) { var message = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.PrepareForDeployment_ErrorCreatingCacheEntryFormat, rom.RomPath); throw new LuigiFileGenerationException(message, e.Message, e); } } else { var message = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.RomToLuigiFailed_InvalidOperation_Error_Message_Format, result); var description = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.RomToLuigiFailed_Error_Description_Format, converterApp); throw new LuigiFileGenerationException(message, description); } } else { // If this is a different ROM that produces the same LUIGI, add an entry. var crc24 = INTV.Core.Utility.Crc24.OfFile(luigiFile); var size = (uint)(new System.IO.FileInfo(luigiFile)).Length; if (CacheIndex.Find(crc24, size) == null) { try { var cacheIndexEntry = new CacheIndexEntry(rom, cachedRomPath); CacheIndex.Instance.AddEntry(cacheIndexEntry); } catch (Exception e) { var message = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.PrepareForDeployment_ErrorCreatingCacheEntryFormat, rom.RomPath); throw new LuigiFileGenerationException(message, e.Message, e); } } } ////catch (System.IO.PathTooLongException e) ////catch (System.IO.IOException e) ////catch (UnauthorizedAccessException e) ////catch (InvalidOperationException e) ////catch (LuigiFileGenerationException e) #if REPORT_PERFORMANCE stopwatch2.Stop(); _accumulatedPrepareLuigiUpdateTime += stopwatch2.Elapsed; #endif // REPORT_PERFORMANCE if (string.IsNullOrEmpty(luigiFile) || !System.IO.File.Exists(luigiFile)) { var message = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.RomToLuigiFailed_Error_Description_Format, rom); var description = string.Format(System.Globalization.CultureInfo.CurrentCulture, Resources.Strings.RomToLuigiFailed_InvalidOutputFileFormat, luigiFile); throw new LuigiFileGenerationException(message, description); } #if REPORT_PERFORMANCE stopwatch.Stop(); #endif // REPORT_PERFORMANCE return(luigiFile); #if REPORT_PERFORMANCE } finally { stopwatch.Stop(); _accumulatedPrepareTime += stopwatch.Elapsed; } #endif // REPORT_PERFORMANCE }