diff --git a/OWML.Common/IModConfig.cs b/OWML.Common/IModConfig.cs index a648ca8c2..9fceb375e 100644 --- a/OWML.Common/IModConfig.cs +++ b/OWML.Common/IModConfig.cs @@ -4,6 +4,7 @@ namespace OWML.Common { public interface IModConfig { + bool RequireVR { get; } Dictionary Settings { get; } T GetSetting(string key); } diff --git a/OWML.Common/IModManifest.cs b/OWML.Common/IModManifest.cs index 5d170ebb8..6e45b5ad0 100644 --- a/OWML.Common/IModManifest.cs +++ b/OWML.Common/IModManifest.cs @@ -9,7 +9,7 @@ public interface IModManifest string OWMLVersion { get; } string AssemblyPath { get; } string UniqueName { get; } - string FolderPath { get; set; } + string ModFolderPath { get; set; } bool Enabled { get; } } } diff --git a/OWML.Common/IOwmlConfig.cs b/OWML.Common/IOwmlConfig.cs index 09cc052b4..b53b3d9a8 100644 --- a/OWML.Common/IOwmlConfig.cs +++ b/OWML.Common/IOwmlConfig.cs @@ -4,6 +4,8 @@ public interface IOwmlConfig { string GamePath { get; set; } string ManagedPath { get; } + string PluginsPath { get; } + string DataPath { get; } string OWMLPath { get; } string ModsPath { get; } string OutputFilePath { get; } diff --git a/OWML.Launcher/App.cs b/OWML.Launcher/App.cs index e16b3b264..d46d531f3 100644 --- a/OWML.Launcher/App.cs +++ b/OWML.Launcher/App.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using Newtonsoft.Json; using OWML.Common; +using OWML.ModHelper; using OWML.Patcher; using OWML.Update; @@ -11,25 +13,27 @@ namespace OWML.Launcher { public class App { - private const string Version = "0.3.20"; + private const string Version = "0.3.21"; private readonly IOwmlConfig _owmlConfig; private readonly IModConsole _writer; private readonly IModFinder _modFinder; private readonly OutputListener _listener; private readonly PathFinder _pathFinder; - private readonly ModPatcher _patcher; + private readonly OWPatcher _owPatcher; + private readonly VRPatcher _vrPatcher; private readonly ModUpdate _update; public App(IOwmlConfig owmlConfig, IModConsole writer, IModFinder modFinder, - OutputListener listener, PathFinder pathFinder, ModPatcher patcher, ModUpdate update) + OutputListener listener, PathFinder pathFinder, OWPatcher owPatcher, VRPatcher vrPatcher, ModUpdate update) { _owmlConfig = owmlConfig; _writer = writer; _modFinder = modFinder; _listener = listener; _pathFinder = pathFinder; - _patcher = patcher; + _owPatcher = owPatcher; + _vrPatcher = vrPatcher; _update = update; } @@ -46,9 +50,11 @@ public void Run() ListenForOutput(); - ShowModList(); + var manifests = _modFinder.GetManifests(); + + ShowModList(manifests); - PatchGame(); + PatchGame(manifests); StartGame(); @@ -98,9 +104,8 @@ private void CopyGameFiles() _writer.WriteLine("Game files copied."); } - private void ShowModList() + private void ShowModList(IList manifests) { - var manifests = _modFinder.GetManifests(); if (!manifests.Any()) { _writer.WriteLine("Warning: found no mods."); @@ -130,17 +135,31 @@ private void OnOutput(string s) } } - private void PatchGame() + private void PatchGame(IList manifests) { - _patcher.PatchGame(); - var filesToCopy = new[] { "OWML.ModLoader.dll", "OWML.Common.dll", "OWML.ModHelper.dll", - "OWML.ModHelper.Events.dll", "OWML.ModHelper.Assets.dll", "OWML.ModHelper.Menus.dll", - "Newtonsoft.Json.dll", "System.Runtime.Serialization.dll", "0Harmony.dll", "NAudio-Unity.dll" }; - foreach (var filename in filesToCopy) + var enableVR = IsVRRequired(manifests); + _owPatcher.PatchGame(); + _vrPatcher.PatchVR(enableVR); + } + + private bool IsVRRequired(IList manifests) + { + foreach (var manifest in manifests) { - File.Copy(filename, $"{_owmlConfig.ManagedPath}/{filename}", true); + var configPath = manifest.ModFolderPath + "config.json"; + if (manifest.Enabled && File.Exists(configPath)) + { + var json = File.ReadAllText(configPath); + var config = JsonConvert.DeserializeObject(json); + if (config.RequireVR) + { + _writer.WriteLine($"{manifest.UniqueName} requires VR."); + return true; + } + } } - File.WriteAllText($"{_owmlConfig.ManagedPath}/OWML.Config.json", JsonConvert.SerializeObject(_owmlConfig)); + _writer.WriteLine("No mod requires VR."); + return false; } private void StartGame() diff --git a/OWML.Launcher/Program.cs b/OWML.Launcher/Program.cs index 57c184308..c5b75fe3b 100644 --- a/OWML.Launcher/Program.cs +++ b/OWML.Launcher/Program.cs @@ -18,9 +18,10 @@ static void Main(string[] args) var modFinder = new ModFinder(owmlConfig, writer); var outputListener = new OutputListener(owmlConfig); var pathFinder = new PathFinder(owmlConfig, writer); - var patcher = new ModPatcher(owmlConfig, writer); + var owPatcher = new OWPatcher(owmlConfig, writer); + var vrPatcher = new VRPatcher(owmlConfig, writer); var update = new ModUpdate(writer); - var app = new App(owmlConfig, writer, modFinder, outputListener, pathFinder, patcher, update); + var app = new App(owmlConfig, writer, modFinder, outputListener, pathFinder, owPatcher, vrPatcher, update); app.Run(); } diff --git a/OWML.ModHelper.Assets/ModAssets.cs b/OWML.ModHelper.Assets/ModAssets.cs index 1d88b3c13..80ca911e2 100644 --- a/OWML.ModHelper.Assets/ModAssets.cs +++ b/OWML.ModHelper.Assets/ModAssets.cs @@ -20,7 +20,7 @@ public ModAssets(IModConsole console, IModManifest manifest) public AssetBundle LoadBundle(string filename) { - var path = _manifest.FolderPath + filename; + var path = _manifest.ModFolderPath + filename; _console.WriteLine("Loading asset bundle from " + path); var bundle = AssetBundle.LoadFromFile(path); if (bundle == null) @@ -32,8 +32,8 @@ public AssetBundle LoadBundle(string filename) public IModAsset Load3DObject(string objectFilename, string imageFilename) { - var objectPath = _manifest.FolderPath + objectFilename; - var imagePath = _manifest.FolderPath + imageFilename; + var objectPath = _manifest.ModFolderPath + objectFilename; + var imagePath = _manifest.ModFolderPath + imageFilename; _console.WriteLine("Loading object from " + objectPath); var go = new GameObject(); @@ -47,7 +47,7 @@ public IModAsset Load3DObject(string objectFilename, string imageFil public IModAsset LoadMesh(string objectFilename) { - var objectPath = _manifest.FolderPath + objectFilename; + var objectPath = _manifest.ModFolderPath + objectFilename; _console.WriteLine("Loading mesh from " + objectPath); var go = new GameObject(); @@ -60,7 +60,7 @@ public IModAsset LoadMesh(string objectFilename) public IModAsset LoadTexture(string imageFilename) { - var imagePath = _manifest.FolderPath + imageFilename; + var imagePath = _manifest.ModFolderPath + imageFilename; _console.WriteLine("Loading texture from " + imagePath); var go = new GameObject(); @@ -73,7 +73,7 @@ public IModAsset LoadTexture(string imageFilename) public IModAsset LoadAudio(string audioFilename) { - var audioPath = _manifest.FolderPath + audioFilename; + var audioPath = _manifest.ModFolderPath + audioFilename; _console.WriteLine("Loading audio from " + audioPath); var go = new GameObject(); diff --git a/OWML.ModHelper/ModConfig.cs b/OWML.ModHelper/ModConfig.cs index 5e8b704d1..522cbf6cb 100644 --- a/OWML.ModHelper/ModConfig.cs +++ b/OWML.ModHelper/ModConfig.cs @@ -7,6 +7,9 @@ namespace OWML.ModHelper { public class ModConfig : IModConfig { + [JsonProperty("requireVR")] + public bool RequireVR { get; set; } + [JsonProperty("settings")] public Dictionary Settings { get; set; } diff --git a/OWML.ModHelper/ModManifest.cs b/OWML.ModHelper/ModManifest.cs index ad49c8aa5..9be5865b7 100644 --- a/OWML.ModHelper/ModManifest.cs +++ b/OWML.ModHelper/ModManifest.cs @@ -27,9 +27,9 @@ public class ModManifest : IModManifest public bool Enabled { get; private set; } [JsonIgnore] - public string FolderPath { get; set; } + public string ModFolderPath { get; set; } [JsonIgnore] - public string AssemblyPath => FolderPath + Filename; + public string AssemblyPath => ModFolderPath + Filename; } } diff --git a/OWML.ModHelper/ModStorage.cs b/OWML.ModHelper/ModStorage.cs index 60fe6bf73..925d6eb47 100644 --- a/OWML.ModHelper/ModStorage.cs +++ b/OWML.ModHelper/ModStorage.cs @@ -20,7 +20,7 @@ public ModStorage(IModLogger logger, IModConsole console, IModManifest manifest) public T Load(string filename) { - var path = _manifest.FolderPath + filename; + var path = _manifest.ModFolderPath + filename; _logger.Log($"Loading {path}..."); if (!File.Exists(path)) { @@ -41,7 +41,7 @@ public T Load(string filename) public void Save(T obj, string filename) { - var path = _manifest.FolderPath + filename; + var path = _manifest.ModFolderPath + filename; _logger.Log($"Saving {path}..."); try { diff --git a/OWML.ModHelper/OwmlConfig.cs b/OWML.ModHelper/OwmlConfig.cs index 17146a786..343556a9d 100644 --- a/OWML.ModHelper/OwmlConfig.cs +++ b/OWML.ModHelper/OwmlConfig.cs @@ -12,8 +12,14 @@ public class OwmlConfig : IOwmlConfig public bool Verbose { get; private set; } [JsonIgnore] - public string ManagedPath => $"{GamePath}/OuterWilds_Data/Managed"; + public string DataPath => $"{GamePath}/OuterWilds_Data"; + [JsonIgnore] + public string ManagedPath => $"{DataPath}/Managed"; + + [JsonIgnore] + public string PluginsPath => $"{DataPath}/Plugins"; + [JsonProperty("owmlPath")] public string OWMLPath { get; set; } diff --git a/OWML.ModLoader/ModFinder.cs b/OWML.ModLoader/ModFinder.cs index 6ca5b8eab..6526c2fa9 100644 --- a/OWML.ModLoader/ModFinder.cs +++ b/OWML.ModLoader/ModFinder.cs @@ -30,7 +30,7 @@ public IList GetManifests() { var json = File.ReadAllText(manifestFilename); var manifest = JsonConvert.DeserializeObject(json); - manifest.FolderPath = manifestFilename.Substring(0, manifestFilename.IndexOf("manifest.json")); + manifest.ModFolderPath = manifestFilename.Substring(0, manifestFilename.IndexOf("manifest.json")); manifests.Add(manifest); } return manifests; diff --git a/OWML.Patcher/OWML.Patcher.csproj b/OWML.Patcher/OWML.Patcher.csproj index 16980ffed..2c356290d 100644 --- a/OWML.Patcher/OWML.Patcher.csproj +++ b/OWML.Patcher/OWML.Patcher.csproj @@ -34,19 +34,32 @@ false - + + False + VR\BsPatch.dll + + False dnpatch\dnlib.dll - + + False dnpatch\dnpatch.dll + + False + VR\ICSharpCode.SharpZipLib.dll + + + ..\packages\Json.Net.Unity3D.9.0.1\lib\net35\Newtonsoft.Json.dll + - + + @@ -58,5 +71,8 @@ OWML.ModLoader + + + \ No newline at end of file diff --git a/OWML.Patcher/ModPatcher.cs b/OWML.Patcher/OWPatcher.cs similarity index 66% rename from OWML.Patcher/ModPatcher.cs rename to OWML.Patcher/OWPatcher.cs index e64b46aed..35b34ad10 100644 --- a/OWML.Patcher/ModPatcher.cs +++ b/OWML.Patcher/OWPatcher.cs @@ -1,13 +1,15 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using dnlib.DotNet.Emit; using dnpatch; +using Newtonsoft.Json; using OWML.Common; namespace OWML.Patcher { - public class ModPatcher + public class OWPatcher { private readonly IOwmlConfig _owmlConfig; private readonly IModConsole _writer; @@ -15,11 +17,7 @@ public class ModPatcher private const string PatchClass = "PermanentManager"; private const string PatchMethod = "Awake"; - private readonly Dictionary _oldPatches = new Dictionary - { - }; - - public ModPatcher(IOwmlConfig owmlConfig, IModConsole writer) + public OWPatcher(IOwmlConfig owmlConfig, IModConsole writer) { _owmlConfig = owmlConfig; _writer = writer; @@ -27,50 +25,26 @@ public ModPatcher(IOwmlConfig owmlConfig, IModConsole writer) public void PatchGame() { - var patcher = new dnpatch.Patcher($"{_owmlConfig.ManagedPath}/Assembly-CSharp.dll"); - - RemoveOldPatches(patcher); - - AddCurrentPatch(patcher); - - Save(patcher); + CopyFiles(); + PatchAssembly(); } - private void RemoveOldPatches(dnpatch.Patcher patcher) + private void CopyFiles() { - foreach (var kvPair in _oldPatches) + var filesToCopy = new[] { "OWML.ModLoader.dll", "OWML.Common.dll", "OWML.ModHelper.dll", + "OWML.ModHelper.Events.dll", "OWML.ModHelper.Assets.dll", "OWML.ModHelper.Menus.dll", + "Newtonsoft.Json.dll", "System.Runtime.Serialization.dll", "0Harmony.dll", "NAudio-Unity.dll" }; + foreach (var filename in filesToCopy) { - RemovePatch(patcher, kvPair.Key, kvPair.Value); + File.Copy(filename, $"{_owmlConfig.ManagedPath}/{filename}", true); } + File.WriteAllText($"{_owmlConfig.ManagedPath}/OWML.Config.json", JsonConvert.SerializeObject(_owmlConfig)); } - private void RemovePatch(dnpatch.Patcher patcher, string className, string methodName) + private void PatchAssembly() { - var target = new Target - { - Class = className, - Method = methodName - }; - var instructions = patcher.GetInstructions(target).ToList(); - - var patchedInstructions = GetPatchedInstructions(instructions); - if (!patchedInstructions.Any()) - { - _writer.WriteLine($"No patch found in {className}.{methodName}."); - return; - } - - _writer.WriteLine($"Removing old patch found in {className}.{methodName}."); - foreach (var patchedInstruction in patchedInstructions) - { - instructions.Remove(patchedInstruction); - } - target.Instructions = instructions.ToArray(); - patcher.Patch(target); - } + var patcher = new dnpatch.Patcher($"{_owmlConfig.ManagedPath}/Assembly-CSharp.dll"); - private void AddCurrentPatch(dnpatch.Patcher patcher) - { var target = new Target { Class = PatchClass, @@ -102,6 +76,8 @@ private void AddCurrentPatch(dnpatch.Patcher patcher) target.Instructions = instructions.ToArray(); Patch(patcher, target); + + Save(patcher); } private List GetPatchedInstructions(List instructions) diff --git a/OWML.Patcher/VR/BsPatch.dll b/OWML.Patcher/VR/BsPatch.dll new file mode 100644 index 000000000..aa1abf60b Binary files /dev/null and b/OWML.Patcher/VR/BsPatch.dll differ diff --git a/OWML.Patcher/VR/ICSharpCode.SharpZipLib.dll b/OWML.Patcher/VR/ICSharpCode.SharpZipLib.dll new file mode 100644 index 000000000..fe643ebc6 Binary files /dev/null and b/OWML.Patcher/VR/ICSharpCode.SharpZipLib.dll differ diff --git a/OWML.Patcher/VR/OVRPlugin.dll b/OWML.Patcher/VR/OVRPlugin.dll new file mode 100644 index 000000000..5379411b6 Binary files /dev/null and b/OWML.Patcher/VR/OVRPlugin.dll differ diff --git a/OWML.Patcher/VR/openvr_api.dll b/OWML.Patcher/VR/openvr_api.dll new file mode 100644 index 000000000..5767d725f Binary files /dev/null and b/OWML.Patcher/VR/openvr_api.dll differ diff --git a/OWML.Patcher/VR/patch b/OWML.Patcher/VR/patch new file mode 100644 index 000000000..007ccdcd7 Binary files /dev/null and b/OWML.Patcher/VR/patch differ diff --git a/OWML.Patcher/VrPatcher.cs b/OWML.Patcher/VrPatcher.cs new file mode 100644 index 000000000..f880c9693 --- /dev/null +++ b/OWML.Patcher/VrPatcher.cs @@ -0,0 +1,113 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using BsDiff; +using OWML.Common; + +namespace OWML.Patcher +{ + public class VRPatcher + { + private const string TargetChecksum = "d3979abb3b0d2468c3e03e2ee862d5297f5885bd9fc8f3b16cb16805e010d097"; + + private readonly IOwmlConfig _owmlConfig; + private readonly IModConsole _writer; + private readonly SHA256 _sha; + + public VRPatcher(IOwmlConfig owmlConfig, IModConsole writer) + { + _owmlConfig = owmlConfig; + _writer = writer; + _sha = SHA256.Create(); + } + + public void PatchVR(bool enableVR) + { + CopyFiles(); + PatchGlobalManager(enableVR); + } + + private void CopyFiles() + { + var filenames = new[] { "openvr_api.dll", "OVRPlugin.dll" }; + foreach (var filename in filenames) + { + var from = $"{_owmlConfig.OWMLPath}VR/{filename}"; + var to = $"{_owmlConfig.PluginsPath}/{filename}"; + if (File.Exists(from)) + { + File.Copy(from, to, true); + } + else + { + _writer.WriteLine("Error: file not found: " + from); + } + } + } + + private void PatchGlobalManager(bool enableVR) + { + var originalPath = _owmlConfig.DataPath + "/globalgamemanagers"; + var backupPath = _owmlConfig.DataPath + "/globalgamemanagers.bak"; + var vrPath = _owmlConfig.DataPath + "/globalgamemanagers.vr"; + var patchPath = _owmlConfig.OWMLPath + "VR/patch"; + + if (!File.Exists(originalPath)) + { + _writer.WriteLine("Error: can't find " + originalPath); + return; + } + + if (!File.Exists(backupPath)) + { + _writer.WriteLine("Taking backup of globalgamemanagers."); + File.Copy(originalPath, backupPath, true); + } + + if (enableVR && !File.Exists(vrPath)) + { + _writer.WriteLine("Patching globalgamemanagers for VR..."); + var checksum = CalculateChecksum(originalPath); + if (checksum == TargetChecksum) + { + ApplyPatch(originalPath, vrPath, patchPath); + } + else + { + _writer.WriteLine($"Error: invalid checksum: {checksum}. Only Outer Wilds v1.0.4 is supported."); + } + } + + var copyFrom = enableVR ? vrPath : backupPath; + File.Copy(copyFrom, originalPath, true); + } + + private string CalculateChecksum(string filePath) + { + var bytes = File.ReadAllBytes(filePath); + var hash = _sha.ComputeHash(bytes); + var sb = new StringBuilder(); + foreach (var b in hash) + sb.Append(b.ToString("x2").ToLower()); + return sb.ToString(); + } + + private void ApplyPatch(string oldFile, string newFile, string patchFile) + { + try + { + using (var input = new FileStream(oldFile, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var output = new FileStream(newFile, FileMode.Create)) + { + BinaryPatchUtility.Apply(input, () => new FileStream(patchFile, FileMode.Open, FileAccess.Read, FileShare.Read), output); + } + } + catch (Exception ex) + { + _writer.WriteLine("Error while patching VR: " + ex); + } + } + + } +} diff --git a/OWML.Patcher/packages.config b/OWML.Patcher/packages.config new file mode 100644 index 000000000..8ab0a407c --- /dev/null +++ b/OWML.Patcher/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OWML.SampleMods/OWML.EnableDebugMode/manifest.json b/OWML.SampleMods/OWML.EnableDebugMode/manifest.json index 262c92e44..1040777e1 100644 --- a/OWML.SampleMods/OWML.EnableDebugMode/manifest.json +++ b/OWML.SampleMods/OWML.EnableDebugMode/manifest.json @@ -4,6 +4,6 @@ "name": "EnableDebugMode", "uniqueName": "Alek.EnableDebugMode", "version": "0.2", - "owmlVersion": "0.3.20", + "owmlVersion": "0.3.21", "enabled": false } \ No newline at end of file diff --git a/OWML.SampleMods/OWML.LoadCustomAssets/manifest.json b/OWML.SampleMods/OWML.LoadCustomAssets/manifest.json index 26c282067..01d2bd0ba 100644 --- a/OWML.SampleMods/OWML.LoadCustomAssets/manifest.json +++ b/OWML.SampleMods/OWML.LoadCustomAssets/manifest.json @@ -4,6 +4,6 @@ "name": "LoadCustomAssets", "uniqueName": "Alek.LoadCustomAssets", "version": "0.4", - "owmlVersion": "0.3.20", + "owmlVersion": "0.3.21", "enabled": false } \ No newline at end of file diff --git a/OWML.sln.DotSettings b/OWML.sln.DotSettings index ff44aa276..d17558b76 100644 --- a/OWML.sln.DotSettings +++ b/OWML.sln.DotSettings @@ -2,6 +2,7 @@ IO OW OWML + VR True True True diff --git a/Readme.md b/Readme.md index e27a1f29d..887fcca7f 100644 --- a/Readme.md +++ b/Readme.md @@ -63,6 +63,23 @@ Each mod is defined in a manifest.json file: |owmlVersion|The version of OWML the mod was built for.| |enabled|Whether or not the mod will be loaded.| +Each mod can have an optional config file: config.json. To avoid overwriting the config when updating mods, config-default.json is intended to be included with mods, and is used to generate config.json if it doesn't exist. A config file has a list of settings in the following format: + +~~~~ +{ + "settings": { + "enableMusic": true, + "foo": "bar", + "lol": 1337 + }, + "requireVR": false +} +~~~~ + +The settings list varies per mod. + +If at least one mod requires VR, OWML will patch the game to be "VR enabled". + ## Compatibility * Tested with Outer Wilds 1.0.0, 1.0.2, 1.0.3 and 1.0.4. @@ -94,3 +111,4 @@ Dependencies: * NAudio-Unity: https://github.com/WulfMarius/NAudio-Unity * HtmlAgilityPack: https://html-agility-pack.net/ * HtmlAgilityPack.CssSelector: https://github.com/hcesar/HtmlAgilityPack.CssSelector +* BsDiff: https://github.com/LogosBible/bsdiff.net diff --git a/createrelease.bat b/createrelease.bat index 53fc9c33b..828ee1444 100644 --- a/createrelease.bat +++ b/createrelease.bat @@ -1,12 +1,20 @@ rmdir /Q /S "Release" mkdir "Release" +mkdir "Release\Mods" +mkdir "Release\Logs" +mkdir "Release\VR" copy "OWML.Patcher\bin\Debug\OWML.Patcher.dll" "Release\OWML.Patcher.dll" copy "OWML.ModLoader\bin\Debug\OWML.ModLoader.dll" "Release\OWML.ModLoader.dll" copy "OWML.Launcher\bin\Debug\OWML.Launcher.exe" "Release\OWML.Launcher.exe" copy "OWML.Launcher\bin\Debug\OWML.Config.json" "Release\OWML.Config.json" -copy "OWML.Patcher\bin\Debug\dnpatch.dll" "Release\dnpatch.dll" -copy "OWML.Patcher\bin\Debug\dnlib.dll" "Release\dnlib.dll" +copy "OWML.Patcher\dnpatch\dnpatch.dll" "Release\dnpatch.dll" +copy "OWML.Patcher\dnpatch\dnlib.dll" "Release\dnlib.dll" +copy "OWML.Patcher\VR\openvr_api.dll" "Release\VR\openvr_api.dll" +copy "OWML.Patcher\VR\OVRPlugin.dll" "Release\VR\OVRPlugin.dll" +copy "OWML.Patcher\VR\patch" "Release\VR\patch" +copy "OWML.Patcher\VR\BsPatch.dll" "Release\BsPatch.dll" +copy "OWML.Patcher\VR\ICSharpCode.SharpZipLib.dll" "Release\ICSharpCode.SharpZipLib.dll" copy "OWML.Launcher\bin\Debug\System.Runtime.Serialization.dll" "Release\System.Runtime.Serialization.dll" copy "OWML.Update\bin\Debug\OWML.Update.dll" "Release\OWML.Update.dll" copy "OWML.Update\bin\Debug\HtmlAgilityPack.dll" "Release\HtmlAgilityPack.dll" @@ -21,8 +29,6 @@ copy "OWML.Nuget\bin\Debug\OWML.ModHelper.Menus.dll" "Release\OWML.ModHelper.Men copy "OWML.Nuget\bin\Debug\0Harmony.dll" "Release\0Harmony.dll" copy "OWML.Nuget\bin\Debug\Newtonsoft.Json.dll" "Release\Newtonsoft.Json.dll" -mkdir "Release\Mods" - mkdir "Release\Mods\OWML.EnableDebugMode" copy "OWML.SampleMods\OWML.EnableDebugMode\bin\Debug\OWML.EnableDebugMode.dll" "Release\Mods\OWML.EnableDebugMode\OWML.EnableDebugMode.dll" copy "OWML.SampleMods\OWML.EnableDebugMode\bin\Debug\manifest.json" "Release\Mods\OWML.EnableDebugMode\manifest.json"