diff --git a/About/Manifest.xml b/About/Manifest.xml
index fb77359..765ec06 100644
--- a/About/Manifest.xml
+++ b/About/Manifest.xml
@@ -1,19 +1,19 @@
-
+
- net.pardeike.rimworld.mod.achtung
- 2.2.8.0
-
- 0.19.0
- 1.0.0
-
-
-
-
-
-
-
-
-
- https://raw.githubusercontent.com/pardeike/Achtung2/master/About/Manifest.xml
- https://github.com/pardeike/Achtung2/releases/latest
+ net.pardeike.rimworld.mod.achtung
+ 2.2.9.0
+
+ 0.19.0
+ 1.0.0
+
+
+
+
+
+
+
+
+
+ https://raw.githubusercontent.com/pardeike/Achtung2/master/About/Manifest.xml
+ https://github.com/pardeike/Achtung2/releases/latest
\ No newline at end of file
diff --git a/About/ModSync.xml b/About/ModSync.xml
index aef4ff5..ef32e42 100644
--- a/About/ModSync.xml
+++ b/About/ModSync.xml
@@ -2,7 +2,7 @@
be673269-db56-463b-8c36-e074881e0d77
Achtung!
- 2.2.8.0
+ 2.2.9.0
False
pardeike
@@ -11,4 +11,4 @@
ModSyncMainPage
master
-
+
\ No newline at end of file
diff --git a/Assemblies/AchtungMod.dll b/Assemblies/AchtungMod.dll
index b5acdde..6154bd4 100644
Binary files a/Assemblies/AchtungMod.dll and b/Assemblies/AchtungMod.dll differ
diff --git a/Source/Achtung.csproj b/Source/Achtung.csproj
index b7ff688..4ca2520 100644
--- a/Source/Achtung.csproj
+++ b/Source/Achtung.csproj
@@ -59,6 +59,7 @@
+
@@ -83,7 +84,10 @@
- "$(ProjectDir)Install.bat" $(ConfigurationName) "$(ProjectDir)" "$(ProjectName)" "About Assemblies Languages Textures"
+ ModBuilder -v AssemblyVersion -file "$(TargetPath)" -save "$(ProjectName)-version"
+ModBuilder -v XMLPut -file "$(SolutionDir)About\Manifest.xml" -xpath /Manifest/version -value "{{$(ProjectName)-version}}"
+ModBuilder -v XMLPut -file "$(SolutionDir)About\ModSync.xml" -xpath /ModSyncNinjaData/Version -value "{{$(ProjectName)-version}}"
+"$(ProjectDir)Install.bat" $(ConfigurationName) "$(ProjectDir)" "$(ProjectName)" "About Assemblies Languages Textures"
diff --git a/Source/CrossPromotion.cs b/Source/CrossPromotion.cs
new file mode 100644
index 0000000..ff6af9e
--- /dev/null
+++ b/Source/CrossPromotion.cs
@@ -0,0 +1,486 @@
+using Harmony;
+using RimWorld;
+using Steamworks;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Threading;
+using UnityEngine;
+using Verse;
+using Verse.Steam;
+
+namespace CameraPlus
+{
+ [StaticConstructorOnStartup]
+ static class CrossPromotion
+ {
+ const string _crosspromotion = "brrainz-crosspromotion";
+ internal const ulong userID = 76561197973010050;
+
+ internal static List promotionMods = new List();
+ internal static Dictionary allVoteStati = new Dictionary();
+ internal static Dictionary previewTextures = new Dictionary();
+ internal static List subscribingMods = new List();
+ internal static ulong? lastPresentedMod = null;
+
+ static CrossPromotion()
+ {
+ var instance = HarmonyInstance.Create(_crosspromotion);
+ if (instance.HasAnyPatches(_crosspromotion))
+ return;
+
+ instance.Patch(
+ SymbolExtensions.GetMethodInfo(() => MainMenuDrawer.Init()),
+ postfix: new HarmonyMethod(SymbolExtensions.GetMethodInfo(() => MainMenuDrawer_Init_Postfix()))
+ );
+
+ instance.Patch(
+ AccessTools.DeclaredMethod(typeof(Page_ModsConfig), nameof(Page_ModsConfig.PostClose)),
+ postfix: new HarmonyMethod(SymbolExtensions.GetMethodInfo(() => Page_ModsConfig_PostClose_Postfix()))
+ );
+
+ instance.Patch(
+ AccessTools.DeclaredMethod(typeof(WorkshopItems), "Notify_Subscribed"),
+ postfix: new HarmonyMethod(SymbolExtensions.GetMethodInfo(() => WorkshopItems_Notify_Subscribed_Postfix(new PublishedFileId_t(0))))
+ );
+
+ instance.Patch(
+ AccessTools.DeclaredMethod(typeof(Page_ModsConfig), nameof(Page_ModsConfig.DoWindowContents)),
+ transpiler: new HarmonyMethod(SymbolExtensions.GetMethodInfo(() => Page_ModsConfig_DoWindowContents_Transpiler(null, null)))
+ );
+ }
+
+ static void MainMenuDrawer_Init_Postfix()
+ {
+ ModPreviewPath(0);
+ new Thread(() => { FetchPromotionMods(); }).Start();
+ }
+
+ static void Page_ModsConfig_PostClose_Postfix()
+ {
+ subscribingMods.Clear();
+ }
+
+ static void WorkshopItems_Notify_Subscribed_Postfix(PublishedFileId_t pfid)
+ {
+ var longID = pfid.m_PublishedFileId;
+ if (subscribingMods.Contains(longID) == false)
+ return;
+ subscribingMods.Remove(longID);
+
+ LongEventHandler.ExecuteWhenFinished(() =>
+ {
+ var mod = ModLister.AllInstalledMods.FirstOrDefault(meta => meta.GetPublishedFileId().m_PublishedFileId == longID);
+ if (mod == null)
+ return;
+
+ ModsConfig.SetActive(mod, true);
+ ModsConfig.Save();
+ Find.WindowStack.Add(new MiniDialog(mod.Name + " added"));
+ });
+ }
+
+ static readonly MethodInfo m_BeginGroup = SymbolExtensions.GetMethodInfo(() => GUI.BeginGroup(new Rect()));
+ static readonly MethodInfo m_EndGroup = SymbolExtensions.GetMethodInfo(() => GUI.EndGroup());
+ static readonly MethodInfo m_Promotion = SymbolExtensions.GetMethodInfo(() => PromotionLayout.Promotion(new Rect(), null));
+
+ static IEnumerable Page_ModsConfig_DoWindowContents_Transpiler(IEnumerable instructions, ILGenerator generator)
+ {
+ var list = instructions.ToList();
+ var beginGroupIndicies = list
+ .Select((instr, idx) => new Pair(idx, instr))
+ .Where(pair => pair.Second.operand == m_BeginGroup)
+ .Select(pair => pair.First).ToArray();
+ var endGroupIndicies = list
+ .Select((instr, idx) => new Pair(idx, instr))
+ .Where(pair => pair.Second.operand == m_EndGroup)
+ .Select(pair => pair.First).ToArray();
+ if (beginGroupIndicies.Length != 2 || endGroupIndicies.Length != 2)
+ return instructions;
+ var iBegin = beginGroupIndicies[1] - 1;
+ var iEnd = endGroupIndicies[0];
+
+ var jump = generator.DefineLabel();
+ list[iEnd + 1].labels.Add(jump);
+ var localPositionVar = list[iBegin];
+ list.InsertRange(iBegin, new[]
+ {
+ localPositionVar.Clone(),
+ new CodeInstruction(OpCodes.Ldarg_0),
+ new CodeInstruction(OpCodes.Call, m_Promotion),
+ new CodeInstruction(OpCodes.Brtrue, jump)
+ });
+
+ return list.AsEnumerable();
+ }
+
+ internal static string ModPreviewPath(ulong modID)
+ {
+ var dir = Path.GetTempPath() + "BrrainzMods" + Path.DirectorySeparatorChar;
+ if (Directory.Exists(dir) == false)
+ Directory.CreateDirectory(dir);
+ return dir + modID + "-preview.jpg";
+ }
+
+ internal static Texture2D PreviewForMod(ulong modID)
+ {
+ if (previewTextures.TryGetValue(modID, out var texture))
+ return texture;
+ var path = ModPreviewPath(modID);
+ if (File.Exists(path) == false)
+ return null;
+ texture = new Texture2D(1, 1, TextureFormat.ARGB32, false);
+ if (texture.LoadImage(File.ReadAllBytes(path)))
+ previewTextures[modID] = texture;
+ return texture;
+ }
+
+ internal static void UpdateVotingStatus(ulong modID, Action callback)
+ {
+ var callDelegate = new CallResult.APIDispatchDelegate(callback);
+ var call = SteamUGC.GetUserItemVote(new PublishedFileId_t(modID));
+ var resultHandle = CallResult.Create(callDelegate);
+ resultHandle.Set(call, null);
+ }
+
+ static void AsyncUserModsQuery(UGCQueryHandle_t query, Action callback)
+ {
+ var callDelegate = new CallResult.APIDispatchDelegate((result, failure) =>
+ {
+ callback(result, failure);
+ SteamUGC.ReleaseQueryUGCRequest(query);
+ });
+ var call = SteamUGC.SendQueryUGCRequest(query);
+ var resultHandle = CallResult.Create(callDelegate);
+ resultHandle.Set(call, null);
+ }
+
+ static void AsyncDownloadQuery(UGCHandle_t content, string path, Action callback)
+ {
+ var callDelegate = new CallResult.APIDispatchDelegate(callback);
+ var call = SteamRemoteStorage.UGCDownloadToLocation(content, path, 0);
+ var resultHandle = CallResult.Create(callDelegate);
+ resultHandle.Set(call, null);
+ }
+
+ internal static void FetchPromotionMods()
+ {
+ if (SteamManager.Initialized == false)
+ return;
+
+ var rimworldID = SteamUtils.GetAppID();
+ unchecked
+ {
+ var aID = new AccountID_t((uint)userID);
+ var itemQuery = SteamUGC.CreateQueryUserUGCRequest(aID,
+ EUserUGCList.k_EUserUGCList_Published, EUGCMatchingUGCType.k_EUGCMatchingUGCType_UsableInGame,
+ EUserUGCListSortOrder.k_EUserUGCListSortOrder_VoteScoreDesc, rimworldID, rimworldID,
+ 1);
+ SteamUGC.SetReturnLongDescription(itemQuery, true);
+ SteamUGC.SetRankedByTrendDays(itemQuery, 7);
+ AsyncUserModsQuery(itemQuery, (result, failure) =>
+ {
+ for (uint i = 0; i < result.m_unNumResultsReturned; i++)
+ if (SteamUGC.GetQueryUGCResult(result.m_handle, i, out var mod))
+ if (promotionMods.Any(m => m.m_nPublishedFileId.m_PublishedFileId == mod.m_nPublishedFileId.m_PublishedFileId) == false)
+ {
+ promotionMods.Add(mod);
+ var modID = mod.m_nPublishedFileId.m_PublishedFileId;
+
+ var path = ModPreviewPath(modID);
+ if (File.Exists(path) == false || new FileInfo(path).Length != mod.m_nPreviewFileSize)
+ {
+ AsyncDownloadQuery(mod.m_hPreviewFile, path, (result2, failure2) =>
+ {
+ if (File.Exists(path))
+ {
+ if (previewTextures.ContainsKey(modID))
+ previewTextures.Remove(modID);
+ }
+ });
+ }
+
+ UpdateVotingStatus(modID, (result2, failure2) =>
+ {
+ allVoteStati[modID] = (result2.m_eResult == EResult.k_EResultOK) ? result2.m_bVotedUp : (bool?)null;
+ });
+ }
+ });
+ }
+ }
+ }
+
+ [StaticConstructorOnStartup]
+ internal class PromotionLayout
+ {
+ static readonly AccessTools.FieldRef ref_steamAuthor = AccessTools.FieldRefAccess("steamAuthor");
+ internal static bool Promotion(Rect mainRect, Page_ModsConfig page)
+ {
+ if (SteamManager.Initialized == false)
+ return false;
+
+ var mod = page.selectedMod;
+ if (mod == null
+ || ref_steamAuthor(mod.GetWorkshopItemHook()).m_SteamID != CrossPromotion.userID
+ || CrossPromotion.promotionMods.Count == 0)
+ return false;
+
+ var leftColumn = mainRect.width * 2 / 3;
+ var rightColumn = mainRect.width - leftColumn - 10f;
+
+ GUI.BeginGroup(mainRect);
+ ContentPart(mainRect, leftColumn, mod, page);
+ PromotionPart(mainRect, leftColumn, rightColumn, mod, page);
+ GUI.EndGroup();
+
+ return true;
+ }
+
+ static Vector2 leftScroll = Vector2.zero;
+ static Vector2 rightScroll = Vector2.zero;
+
+ static void ContentPart(Rect mainRect, float leftColumn, ModMetaData mod, Page_ModsConfig page)
+ {
+ var workshopMods = WorkshopItems.AllSubscribedItems.Select(wi => wi.PublishedFileId.m_PublishedFileId).ToList();
+
+ var mainModID = mod.GetPublishedFileId().m_PublishedFileId;
+ var promoMods = CrossPromotion.promotionMods.ToArray();
+ var thisMod = promoMods.FirstOrDefault(m => m.m_nPublishedFileId.m_PublishedFileId == mainModID);
+ var isLocalFile = ModLister.AllInstalledMods.Any(meta => meta.GetPublishedFileId().m_PublishedFileId == mainModID && meta.Source == ContentSource.LocalFolder);
+ var isSubbed = workshopMods.Contains(mainModID);
+
+ if (CrossPromotion.lastPresentedMod != mainModID)
+ {
+ leftScroll = Vector2.zero;
+ rightScroll = Vector2.zero;
+ CrossPromotion.lastPresentedMod = mainModID;
+
+ new Thread(() =>
+ {
+ foreach (var promoMod in promoMods)
+ CrossPromotion.UpdateVotingStatus(promoMod.m_nPublishedFileId.m_PublishedFileId, (result2, failure2) =>
+ {
+ CrossPromotion.allVoteStati[promoMod.m_nPublishedFileId.m_PublishedFileId] = (result2.m_eResult == EResult.k_EResultOK) ? result2.m_bVotedUp : (bool?)null;
+ });
+ }).Start();
+ }
+
+ var description = thisMod.m_rgchDescription;
+ if (description == null || description.Length == 0)
+ description = mod.Description;
+
+ var outRect = new Rect(0f, 0f, leftColumn, mainRect.height);
+ var width = outRect.width - 20f;
+ var imageRect = new Rect(0f, 0f, width, width * mod.previewImage.height / mod.previewImage.width);
+ var textRect = new Rect(0f, 24f + 10f + imageRect.height, width, Text.CalcHeight(description, width));
+ var innerRect = new Rect(0f, 0f, width, imageRect.height + 20f + 8f + 10f + textRect.height);
+ Widgets.BeginScrollView(outRect, ref leftScroll, innerRect, true);
+ GUI.DrawTexture(imageRect, mod.previewImage, ScaleMode.ScaleToFit);
+ var widgetRow = new WidgetRow(imageRect.xMax, imageRect.yMax + 8f, UIDirection.LeftThenDown, width, 8f);
+ if (isLocalFile == false)
+ {
+ if (widgetRow.ButtonText("Unsubscribe".Translate(), null, true, true))
+ {
+ Find.WindowStack.Add(Dialog_MessageBox.CreateConfirmation("ConfirmUnsubscribe".Translate(mod.Name), delegate
+ {
+ mod.enabled = false;
+ new Thread(() =>
+ {
+ AccessTools.Method(typeof(Workshop), "Unsubscribe").Invoke(null, new object[] { mod });
+ AccessTools.Method(typeof(Page_ModsConfig), "Notify_SteamItemUnsubscribed").Invoke(page, new object[] { mainModID });
+ }).Start();
+ }, true, null));
+ }
+ }
+ if (isSubbed)
+ {
+ if (widgetRow.ButtonText("WorkshopPage".Translate(), null, true, true))
+ SteamUtility.OpenWorkshopPage(new PublishedFileId_t(mainModID));
+ }
+ if (Prefs.DevMode && mod.CanToUploadToWorkshop())
+ {
+ widgetRow = new WidgetRow(imageRect.xMin, imageRect.yMax + 8f, UIDirection.RightThenDown, width, 8f);
+ if (widgetRow.ButtonText("Upload", null, true, true))
+ Find.WindowStack.Add(Dialog_MessageBox.CreateConfirmation("ConfirmSteamWorkshopUpload".Translate(), delegate
+ {
+ AccessTools.Method(typeof(Workshop), "Upload").Invoke(null, new object[] { mod });
+ }, true, null));
+ }
+
+ Widgets.Label(textRect, description);
+ Widgets.EndScrollView();
+ }
+
+ static void PromotionPart(Rect mainRect, float leftColumn, float rightColumn, ModMetaData mod, Page_ModsConfig page)
+ {
+ var mainModID = mod.GetPublishedFileId();
+
+ Text.Font = GameFont.Tiny;
+ var headerHeight = 30f;
+ var headerRect = new Rect(leftColumn + 10f, -4f, rightColumn - 20f, headerHeight);
+ Text.Anchor = TextAnchor.UpperCenter;
+ Widgets.Label(headerRect, "Mods of " + mod.Author.Replace("Andreas Pardeike", "Brrainz") + ":".Truncate(headerRect.width, null));
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ var outRect = new Rect(leftColumn + 10f, headerHeight - 4f, rightColumn, mainRect.height - (headerHeight - 4f));
+ var width = outRect.width - 20f;
+ var previewHeight = width * 319f / 588f;
+ var promoMods = CrossPromotion.promotionMods.ToArray().Where(m => m.m_nPublishedFileId != mainModID);
+ var workshopMods = WorkshopItems.AllSubscribedItems.Select(wi => wi.PublishedFileId.m_PublishedFileId).ToList();
+ var activeMods = ModLister.AllInstalledMods.Where(meta => meta.Active).Select(meta => meta.GetPublishedFileId().m_PublishedFileId).ToList();
+
+ var height = 0f;
+ foreach (var promoMod in promoMods)
+ {
+ var myModID = promoMod.m_nPublishedFileId.m_PublishedFileId;
+ var isLocalFile = ModLister.AllInstalledMods.Any(meta => meta.GetPublishedFileId().m_PublishedFileId == myModID && meta.Source == ContentSource.LocalFolder);
+ var isSubbed = workshopMods.Contains(myModID);
+ CrossPromotion.allVoteStati.TryGetValue(myModID, out var voteStatus);
+
+ if (height > 0)
+ height += 10f;
+ var preview = CrossPromotion.PreviewForMod(promoMod.m_nPublishedFileId.m_PublishedFileId);
+ if (preview != null)
+ {
+ height += width * preview.height / preview.width + 2f;
+ if (isLocalFile == false && (isSubbed == false || (voteStatus == false)))
+ height += 16f;
+ }
+ }
+
+ Widgets.BeginScrollView(outRect, ref rightScroll, new Rect(0f, 0f, width, height), true);
+ var firstTime = true;
+ var modRect = new Rect(0f, 0f, width, 0f);
+ foreach (var promoMod in promoMods)
+ {
+ var myModID = promoMod.m_nPublishedFileId.m_PublishedFileId;
+ var isLocalFile = ModLister.AllInstalledMods.Any(meta => meta.GetPublishedFileId().m_PublishedFileId == myModID && meta.Source == ContentSource.LocalFolder);
+ var isSubbed = workshopMods.Contains(myModID);
+ var isActive = activeMods.Contains(myModID);
+ CrossPromotion.allVoteStati.TryGetValue(myModID, out var voteStatus);
+
+ if (firstTime == false)
+ modRect.y += 10f;
+
+ var preview = CrossPromotion.PreviewForMod(promoMod.m_nPublishedFileId.m_PublishedFileId);
+ if (preview != null)
+ {
+ modRect.height = width * preview.height / preview.width;
+ GUI.DrawTexture(modRect, preview, ScaleMode.ScaleToFit);
+
+ var checkRect = modRect;
+ checkRect.xMax -= 4f;
+ checkRect.yMax -= 4f;
+ checkRect.xMin = checkRect.xMax - 18f;
+ checkRect.yMin = checkRect.yMax - 18f;
+ var active = isActive;
+ GUI.DrawTexture(checkRect.ContractedBy(-2f), CheckboxBackground);
+ Widgets.Checkbox(checkRect.xMin, checkRect.yMin, ref active, checkRect.width);
+ if (active != isActive)
+ {
+ var clickedMod = ModLister.AllInstalledMods.FirstOrDefault(meta => meta.GetPublishedFileId().m_PublishedFileId == myModID);
+ if (clickedMod != null)
+ {
+ ModsConfig.SetActive(clickedMod, active);
+ ModsConfig.Save();
+ }
+ }
+
+ if (Mouse.IsOver(checkRect) == false)
+ {
+ Widgets.DrawHighlightIfMouseover(modRect);
+ if (Widgets.ButtonInvisible(modRect, true))
+ {
+ var description = promoMod.m_rgchTitle + "\n\n" + promoMod.m_rgchDescription;
+ var actionButton = isSubbed || isLocalFile ? "Select" : "Subscribe";
+ void actionButtonAction()
+ {
+ if (isSubbed || isLocalFile)
+ {
+ var orderedMods = (IEnumerable)AccessTools.Method(typeof(Page_ModsConfig), "ModsInListOrder").Invoke(page, new object[0]);
+ page.selectedMod = orderedMods.FirstOrDefault(meta => meta.GetPublishedFileId().m_PublishedFileId == myModID);
+ var modsBefore = orderedMods.FirstIndexOf(m => m == page.selectedMod);
+ if (modsBefore >= 0)
+ Traverse.Create(page).Field("modListScrollPosition").SetValue(new Vector2(0f, modsBefore * 26f + 4f));
+ }
+ else
+ new Thread(() =>
+ {
+ CrossPromotion.subscribingMods.Add(myModID);
+ SteamUGC.SubscribeItem(new PublishedFileId_t(myModID));
+ }).Start();
+ }
+ var infoWindow = new Dialog_MessageBox(description, "Close".Translate(), null, actionButton, actionButtonAction, null, false, null, null);
+ Find.WindowStack.Add(infoWindow);
+ }
+ }
+ modRect.y += modRect.height + 2f;
+
+ modRect.height = 0f;
+ if (isLocalFile == false)
+ {
+ if (isSubbed == false)
+ {
+ modRect.height = 16f;
+ if (CrossPromotion.subscribingMods.Contains(myModID))
+ Widgets.Label(modRect, WaitingString);
+ else if (Widgets.ButtonText(modRect, "Subscribe", false, true, true))
+ new Thread(() =>
+ {
+ CrossPromotion.subscribingMods.Add(myModID);
+ SteamUGC.SubscribeItem(new PublishedFileId_t(myModID));
+ }).Start();
+ }
+ else if (voteStatus != null && voteStatus == false)
+ {
+ modRect.height = 16f;
+ if (Widgets.ButtonText(modRect, "Like", false, true, true))
+ {
+ new Thread(() =>
+ {
+ CrossPromotion.allVoteStati[myModID] = true;
+ SteamUGC.SetUserItemVote(new PublishedFileId_t(myModID), true);
+ }).Start();
+ }
+ }
+ }
+ modRect.y += modRect.height;
+ }
+
+ firstTime = false;
+ }
+ Widgets.EndScrollView();
+ }
+
+ static Texture2D _checkboxBackground;
+ static Texture2D CheckboxBackground
+ {
+ get
+ {
+ if (_checkboxBackground == null)
+ _checkboxBackground = SolidColorMaterials.NewSolidColorTexture(new Color(0f, 0f, 0f, 0.5f));
+ return _checkboxBackground;
+ }
+ }
+
+ static string WaitingString
+ {
+ get
+ {
+ var i = (DateTime.Now.Ticks / 20) % 4;
+ return new string[] { "....", "... .", ".. ..", ". ..." }[i];
+ }
+ }
+ }
+
+ internal class MiniDialog : Dialog_MessageBox
+ {
+ internal MiniDialog(string text, string buttonAText = null, Action buttonAAction = null, string buttonBText = null, Action buttonBAction = null, string title = null, bool buttonADestructive = false, Action acceptAction = null, Action cancelAction = null)
+ : base(text, buttonAText, buttonAAction, buttonBText, buttonBAction, title, buttonADestructive, acceptAction, cancelAction) { }
+
+ public override Vector2 InitialSize => new Vector2(320, 240);
+ }
+}
\ No newline at end of file
diff --git a/Source/Properties/AssemblyInfo.cs b/Source/Properties/AssemblyInfo.cs
index f626a02..d1ac4c8 100644
--- a/Source/Properties/AssemblyInfo.cs
+++ b/Source/Properties/AssemblyInfo.cs
@@ -31,5 +31,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.2.8.0")]
-[assembly: AssemblyFileVersion("2.2.8.0")]
+[assembly: AssemblyVersion("2.2.9.0")]
+[assembly: AssemblyFileVersion("2.2.9.0")]