/// <summary>Get metadata about a mod.</summary> /// <param name="id">The CurseForge mod ID.</param> /// <returns>Returns the mod info if found, else <c>null</c>.</returns> public async Task <CurseForgeMod> GetModAsync(long id) { // get raw data ModModel mod = await this.Client .GetAsync($"addon/{id}") .As <ModModel>(); if (mod == null) { return(null); } // get latest versions string invalidVersion = null; ISemanticVersion latest = null; foreach (ModFileModel file in mod.LatestFiles) { // extract version ISemanticVersion version; { string raw = this.GetRawVersion(file); if (raw == null) { continue; } if (!SemanticVersion.TryParse(raw, out version)) { invalidVersion ??= raw; continue; } } // track latest version if (latest == null || version.IsNewerThan(latest)) { latest = version; } } // get error string error = null; if (latest == null && invalidVersion == null) { error = mod.LatestFiles.Any() ? $"CurseForge mod {id} has no downloads which specify the version in a recognised format." : $"CurseForge mod {id} has no downloads."; } // generate result return(new CurseForgeMod { Name = mod.Name, LatestVersion = latest?.ToString() ?? invalidVersion, Url = mod.WebsiteUrl, Error = error }); }
/// <summary> /// Build save data for the current game state. /// </summary> private SaveData GetSerializableData() { return(new SaveData { Version = Version.ToString(), ChestEntries = BuildChestEntries() }); }
/********* ** Internal methods *********/ /// <summary>Get the SMAPI version to recommend for an older game version, if any.</summary> /// <param name="version">The game version to search.</param> /// <returns>Returns the compatible SMAPI version, or <c>null</c> if none was found.</returns> internal static ISemanticVersion GetCompatibleApiVersion(ISemanticVersion version) { // This covers all officially supported public game updates. It might seem like version // ranges would be better, but the given SMAPI versions may not be compatible with // intermediate unlisted versions (e.g. private beta updates). // // Nonstandard versions are normalized by GameVersion (e.g. 1.07 => 1.0.7). switch (version.ToString()) { case "1.4.1": case "1.4.0": return(new SemanticVersion("3.0.1")); case "1.3.36": return(new SemanticVersion("2.11.2")); case "1.3.33": case "1.3.32": return(new SemanticVersion("2.10.2")); case "1.3.28": return(new SemanticVersion("2.7.0")); case "1.2.33": case "1.2.32": case "1.2.31": case "1.2.30": return(new SemanticVersion("2.5.5")); case "1.2.29": case "1.2.28": case "1.2.27": case "1.2.26": return(new SemanticVersion("1.13.1")); case "1.1.1": case "1.1.0": return(new SemanticVersion("1.9.0")); case "1.0.7.1": case "1.0.7": case "1.0.6": case "1.0.5.2": case "1.0.5.1": case "1.0.5": case "1.0.4": case "1.0.3": case "1.0.2": case "1.0.1": case "1.0.0": return(new SemanticVersion("0.40.0")); default: return(null); } }
/// <summary>Get a semantic remote version for update checks.</summary> /// <param name="version">The remote version to normalise.</param> public ISemanticVersion GetRemoteVersionForUpdateChecks(ISemanticVersion version) { if (version == null) { return(null); } string rawVersion = this.DataRecord.GetRemoteVersionForUpdateChecks(version.ToString()); return(rawVersion != null ? new SemanticVersion(rawVersion) : version); }
/********* ** Internal methods *********/ /// <summary>Get the SMAPI version to recommend for an older game version, if any.</summary> /// <param name="version">The game version to search.</param> /// <returns>Returns the compatible SMAPI version, or <c>null</c> if none was found.</returns> internal static ISemanticVersion GetCompatibleApiVersion(ISemanticVersion version) { switch (version.ToString()) { case "1.3.28": return new SemanticVersion(2, 7, 0); case "1.2.30": case "1.2.31": case "1.2.32": case "1.2.33": return new SemanticVersion(2, 5, 5); } return null; }
public bool GetUpdateStatuses(out IList <ModStatus> statuses) { statuses = new List <ModStatus>(); object registry = this.helper.ModRegistry.GetType() .GetField("Registry", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this.helper.ModRegistry); bool addedNonSkippedStatus = false; foreach (object modMetaData in (IEnumerable <object>)registry.GetType().GetField("Mods", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(registry)) { ModEntryModel result = GetInstanceProperty <ModEntryModel>(modMetaData, "UpdateCheckData"); IManifest manifest = GetInstanceProperty <IManifest>(modMetaData, "Manifest"); if (result == null) { statuses.Add(new ModStatus(UpdateStatus.Skipped, manifest, "", null, "SMAPI didn't check for an update")); continue; } if (!(bool)modMetaData.GetType().GetMethod("HasValidUpdateKeys").Invoke(modMetaData, null)) { statuses.Add(new ModStatus(UpdateStatus.Skipped, manifest, "", null, "Mod has no update keys")); continue; } ModDataRecordVersionedFields dataRecord = GetInstanceProperty <ModDataRecordVersionedFields>(modMetaData, "DataRecord"); //This section largely taken from https://github.com/Pathoschild/SMAPI/blob/924c3a5d3fe6bfad483834112883156bdf202b57/src/SMAPI/Framework/SCore.cs#L618-L630 bool useBetaInfo = result.HasBetaInfo && Constants.ApiVersion.IsPrerelease(); ISemanticVersion localVersion = dataRecord?.GetLocalVersionForUpdateChecks(manifest.Version) ?? manifest.Version; ISemanticVersion latestVersion = dataRecord?.GetRemoteVersionForUpdateChecks(result.Main?.Version) ?? result.Main?.Version; ISemanticVersion optionalVersion = dataRecord?.GetRemoteVersionForUpdateChecks(result.Optional?.Version) ?? result.Optional?.Version; ISemanticVersion unofficialVersion = useBetaInfo ? result.UnofficialForBeta?.Version : result.Unofficial?.Version; if (this.IsValidUpdate(localVersion, latestVersion, useBetaChannel: true)) { statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, result.Main?.Url, latestVersion.ToString())); } else if (this.IsValidUpdate(localVersion, optionalVersion, useBetaChannel: localVersion.IsPrerelease())) { statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, result.Optional?.Url, optionalVersion.ToString())); } else if (this.IsValidUpdate(localVersion, unofficialVersion, useBetaChannel: GetEnumName(modMetaData, "Status") == "Failed")) { statuses.Add(new ModStatus(UpdateStatus.OutOfDate, manifest, useBetaInfo ? result.UnofficialForBeta?.Url : result.Unofficial?.Url, unofficialVersion.ToString())); } else { string updateURL = null; UpdateStatus updateStatus = UpdateStatus.UpToDate; if (localVersion.Equals(latestVersion)) { updateURL = result.Main?.Url; } else if (localVersion.Equals(optionalVersion)) { updateURL = result.Optional?.Url; } else if (localVersion.Equals(unofficialVersion)) { updateURL = useBetaInfo ? result.UnofficialForBeta?.Url : result.Unofficial?.Url; } else if (latestVersion != null && this.IsValidUpdate(latestVersion, localVersion, useBetaChannel: true)) { updateURL = result.Main?.Url; updateStatus = UpdateStatus.VeryNew; } else if (optionalVersion != null && this.IsValidUpdate(optionalVersion, localVersion, useBetaChannel: localVersion.IsPrerelease())) { updateURL = result.Optional?.Url; updateStatus = UpdateStatus.VeryNew; } else if (unofficialVersion != null && this.IsValidUpdate(unofficialVersion, localVersion, useBetaChannel: GetEnumName(modMetaData, "Status") == "Failed")) { updateURL = useBetaInfo ? result.UnofficialForBeta?.Url : result.Unofficial?.Url; updateStatus = UpdateStatus.VeryNew; } if (updateURL != null) { statuses.Add(new ModStatus(updateStatus, manifest, updateURL)); } else if (result.Errors != null && result.Errors.Any()) { statuses.Add(new ModStatus(UpdateStatus.Error, manifest, "", "", result.Errors[0])); } else { statuses.Add(new ModStatus(UpdateStatus.Error, manifest, "", "", "Unknown Error")); } } addedNonSkippedStatus = true; } return(addedNonSkippedStatus); }
private void Multiplayer_ModMessageReceived(object sender, StardewModdingAPI.Events.ModMessageReceivedEventArgs e) { if (!string.Equals(e.FromModID, CurrentModId, StringComparison.OrdinalIgnoreCase)) { return; } ISemanticVersion ver = Helper.Multiplayer.GetConnectedPlayer(e.FromPlayerID)?.GetMod(CurrentModId)?.Version ?? null; bool compatible = ver?.IsNewerThan(CurrentVersion) ?? false; if (!compatible) { Monitor.Log(string.Format("Cannot use this mod during this online session because the host is currently using a more recent version of the mod. Update this mod to enable it on this server. RemoteVer: {0}. LocalVer: {1}", ver?.ToString() ?? "Null", CurrentVersion)); Settings.Remote = Settings.DefaultDisabled; return; } foreach (Farmer farmer in Game1.getAllFarmers()) { if (farmer.UniqueMultiplayerID == e.FromPlayerID) { if (farmer.IsMainPlayer) { // Message received from server if (string.Equals(e.Type, MultiplayerSettingsMessageType, StringComparison.OrdinalIgnoreCase)) { try { Settings data = e.ReadAs <Settings>(); if (data != null) { Settings.Remote = data; Monitor.Log(string.Format("Remote settings updated from server. FarmerId: {0}.", e.FromPlayerID)); } } catch (Exception ex) { Monitor.Log(string.Format("Remote settings cannot be updated from server. FarmerId: {0}. Ex: {1}. Msg: {2}. InnerEx: {3}", e.FromPlayerID, ex.GetType().Name, ex.Message, ex.InnerException?.GetType().Name ?? "None")); } } else { Monitor.Log(string.Format("Unknown mod message received from server. FarmerId: {0}. Type: {1}.", e.FromPlayerID, e.Type)); } } else { Monitor.Log(string.Format("Remote mod message received from a non-host farmer. FarmerId: {0}. Type: {1}.", e.FromPlayerID, e.Type)); } return; } } Monitor.Log(string.Format("Remote mod message received from an unknown farmer. PlayerId: {0}. Type: {1}", e.FromPlayerID, e.Type)); }
/********* ** Public methods *********/ /// <summary>Get whether the specified version is compatible according to this metadata.</summary> /// <param name="version">The current version of the matching mod.</param> public bool IsCompatible(ISemanticVersion version) { ISemanticVersion lowerVersion = this.LowerVersion != null ? new SemanticVersion(this.LowerVersion) : null; ISemanticVersion upperVersion = new SemanticVersion(this.UpperVersion); // ignore versions not in range if (lowerVersion != null && version.IsOlderThan(lowerVersion)) { return(true); } if (version.IsNewerThan(upperVersion)) { return(true); } // allow versions matching override return(!string.IsNullOrWhiteSpace(this.ForceCompatibleVersion) && Regex.IsMatch(version.ToString(), this.ForceCompatibleVersion, RegexOptions.IgnoreCase)); }
/********* ** Public methods *********/ /// <summary>Get whether the specified version is compatible according to this metadata.</summary> /// <param name="version">The current version of the matching mod.</param> public bool IsCompatible(ISemanticVersion version) { ISemanticVersion incompatibleVersion = new SemanticVersion(this.Version); // allow newer versions if (version.IsNewerThan(incompatibleVersion)) { return(true); } // allow versions matching override return(!string.IsNullOrWhiteSpace(this.ForceCompatibleVersion) && Regex.IsMatch(version.ToString(), this.ForceCompatibleVersion, RegexOptions.IgnoreCase)); }
/// <summary>Get a semantic local version for update checks.</summary> /// <param name="version">The remote version to normalize.</param> public ISemanticVersion GetLocalVersionForUpdateChecks(ISemanticVersion version) { return(this.MapLocalVersions != null && this.MapLocalVersions.TryGetValue(version.ToString(), out string newVersion) ? new SemanticVersion(newVersion) : version); }
/// <summary>Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version.</summary> /// <param name="other">The version to compare with this instance.</param> /// <exception cref="ArgumentNullException">The <paramref name="other"/> value is null.</exception> /// <remarks>The implementation is defined by Semantic Version 2.0 (http://semver.org/).</remarks> public int CompareTo(ISemanticVersion other) { if (other == null) { throw new ArgumentNullException(nameof(other)); } const int same = 0; const int curNewer = 1; const int curOlder = -1; // compare stable versions if (this.MajorVersion != other.MajorVersion) { return(this.MajorVersion.CompareTo(other.MajorVersion)); } if (this.MinorVersion != other.MinorVersion) { return(this.MinorVersion.CompareTo(other.MinorVersion)); } if (this.PatchVersion != other.PatchVersion) { return(this.PatchVersion.CompareTo(other.PatchVersion)); } if (this.Build == other.Build) { return(same); } // stable supercedes pre-release bool curIsStable = string.IsNullOrWhiteSpace(this.Build); bool otherIsStable = string.IsNullOrWhiteSpace(other.Build); if (curIsStable) { return(curNewer); } if (otherIsStable) { return(curOlder); } // compare two pre-release tag values string[] curParts = this.Build.Split('.'); string[] otherParts = other.Build.Split('.'); for (int i = 0; i < curParts.Length; i++) { // longer prerelease tag supercedes if otherwise equal if (otherParts.Length <= i) { return(curNewer); } // compare if different if (curParts[i] != otherParts[i]) { // compare numerically if possible { int curNum, otherNum; if (int.TryParse(curParts[i], out curNum) && int.TryParse(otherParts[i], out otherNum)) { return(curNum.CompareTo(otherNum)); } } // else compare lexically return(string.Compare(curParts[i], otherParts[i], StringComparison.OrdinalIgnoreCase)); } } // fallback (this should never happen) return(string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase)); }
/// <summary>Get whether the cache entry is up-to-date for the given assembly hash.</summary> /// <param name="paths">The paths for the cached assembly.</param> /// <param name="hash">The MD5 hash of the original assembly.</param> /// <param name="currentVersion">The current SMAPI version.</param> public bool IsUpToDate(CachePaths paths, string hash, ISemanticVersion currentVersion) { return(hash == this.Hash && this.ApiVersion == currentVersion.ToString() && (!this.UseCachedAssembly || File.Exists(paths.Assembly))); }