Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/developer setup tool #722

Draft
wants to merge 9 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions SetupDevEnvironment/IO/AssemblyPublicizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//elliotttate/Bepinex-Tools

using dnlib.DotNet;
using System.ComponentModel;
using System.Reflection;
using FieldAttributes = dnlib.DotNet.FieldAttributes;
using MethodAttributes = dnlib.DotNet.MethodAttributes;
using TypeAttributes = dnlib.DotNet.TypeAttributes;

namespace SetupDevEnvironment.IO;

internal class AssemblyPublicizer
{
public const string
MODNAME = "Bepinex_Publicizer",
AUTHOR = "MrPurple6411",
GUID = AUTHOR + "_" + MODNAME,
VERSION = "1.0.0.0";

public event ProgressChangedEventHandler? OnLogMessage;

private void Log(string msg)
{
if (OnLogMessage != null)
{
OnLogMessage(this, new ProgressChangedEventArgs(0, msg));
}
}

public void ProcessAssemblies(string valheimPlusInstallDir)
{
var managedFolder = Path.Combine(valheimPlusInstallDir, "valheim_Data\\Managed\\");
var files = Directory.GetFiles(
managedFolder, "assembly*.dll", SearchOption.TopDirectoryOnly);

foreach (var file in files)
{
try
{
var assembly = Assembly.LoadFile(file);
ProcessAssembly(assembly);
} catch(Exception ex)
{
Log(ex.Message);
}
}
}

void ProcessAssembly(Assembly assembly)
{
string assemblyPath = assembly.Location;
string filename = assembly.GetName().Name;
string outputPath = Path.Combine(Path.GetDirectoryName(assemblyPath), "publicized_assemblies");
string outputSuffix = "_publicized";

Directory.CreateDirectory(outputPath);

string curHash = ComputeHash(assembly);

string hashPath = Path.Combine(outputPath, $"{filename}{outputSuffix}.hash");
string lastHash = null;

if (File.Exists(hashPath))
lastHash = File.ReadAllText(hashPath);

if (curHash == lastHash)
return;

Log($"Making a public assembly from {filename}");
RewriteAssembly(assemblyPath).Write($"{Path.Combine(outputPath, filename)}{outputSuffix}.dll");
File.WriteAllText(hashPath, curHash);
}

static string ComputeHash(Assembly assembly)
{
return assembly.ManifestModule.ModuleVersionId.ToString();
}

static ModuleDef RewriteAssembly(string assemblyPath)
{
ModuleDef assembly = ModuleDefMD.Load(assemblyPath);

foreach (var type in assembly.GetTypes())
{
type.Attributes &= ~TypeAttributes.VisibilityMask;

if (type.IsNested)
type.Attributes |= TypeAttributes.NestedPublic;
else
type.Attributes |= TypeAttributes.Public;

foreach (MethodDef method in type.Methods)
{
method.Attributes &= ~MethodAttributes.MemberAccessMask;
method.Attributes |= MethodAttributes.Public;
}

List<string> eventNames = new List<string>();
foreach (EventDef ev in type.Events)
eventNames.Add(ev.Name);

foreach (FieldDef field in type.Fields)
{
if (!eventNames.Contains(field.Name))
{
field.Attributes &= ~FieldAttributes.FieldAccessMask;
field.Attributes |= FieldAttributes.Public;
}
}
}
return assembly;
}
}
17 changes: 17 additions & 0 deletions SetupDevEnvironment/IO/Downloader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace SetupDevEnvironment.IO;

internal partial class Downloader
{

public static async Task<string> Download(string url, string? fileName = null)
{
var tempFile = fileName ?? Path.GetTempFileName();

using (var client = new HttpClient())
{
var data = await client.GetByteArrayAsync(url);
await File.WriteAllBytesAsync(tempFile, data);
return tempFile;
}
}
}
13 changes: 13 additions & 0 deletions SetupDevEnvironment/IO/ExtensionsToDirectionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace SetupDevEnvironment.IO
{
public static class DirectoryHelper
{
public static string CreateTempFolder()
{
var tempFolder = Path.GetTempPath();
var folder = Path.Combine(tempFolder, Path.GetRandomFileName());
Directory.CreateDirectory(folder);
return folder;
}
}
}
25 changes: 25 additions & 0 deletions SetupDevEnvironment/IO/FileMover.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace SetupDevEnvironment.IO
{
internal class FileMover
{
public static void CopyFiles(string sourceFolder, string destinationFolder)
{
Directory.CreateDirectory(destinationFolder);

var sourceFiles =
Directory.GetFiles(sourceFolder, "*.*", SearchOption.AllDirectories);

foreach (string file in sourceFiles)
{
var relativeFile = file.Replace(sourceFolder, "").Substring(1);
var destFile = Path.Combine(destinationFolder, relativeFile);

// we may need new subfolders
var folder = Path.GetDirectoryName(destFile);
Directory.CreateDirectory(folder);

File.Copy(file, destFile, true);
}
}
}
}
16 changes: 16 additions & 0 deletions SetupDevEnvironment/IO/Links.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace SetupDevEnvironment.IO;

internal static class Links
{
public static string DefaultValheimInstallFolder =
@"C:\Program Files (x86)\Steam\steamapps\common\Valheim";

public static string DefaultValheimPlusDevInstallFolder =
@"C:\Program Files (x86)\Steam\steamapps\common\Valheim Plus Development";

public static readonly string BepInEx =
@"https://valheim.thunderstore.io/package/download/denikson/BepInExPack_Valheim/5.4.1900/";

public static readonly string AssemblyPublicizer =
@"https://github.com/elliotttate/Bepinex-Tools/releases/download/1.0.0-Publicizer/Bepinex-Publicizer.zip";
}
11 changes: 11 additions & 0 deletions SetupDevEnvironment/IO/LogEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace SetupDevEnvironment.IO;

internal class LogEvent : EventArgs
{
public string Message { get; private set; }

public LogEvent(string message)
{
Message = message;
}
}
29 changes: 29 additions & 0 deletions SetupDevEnvironment/IO/Unzipper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.IO.Compression;

namespace SetupDevEnvironment.IO
{
internal static class Unzipper
{
/// <summary>
/// returns all entries relative to the target folder
/// </summary>
/// <param name="filename"></param>
/// <param name="targetFolder"></param>
/// <returns></returns>
public static string[] Unzip(string filename, string? targetFolder = null)
{
targetFolder = targetFolder ?? DirectoryHelper.CreateTempFolder();
Directory.CreateDirectory(targetFolder);

using (var zipFileStream = new FileStream(filename, FileMode.Open))
using (var zipfile = new ZipArchive(zipFileStream))
{
zipfile.ExtractToDirectory(targetFolder, true);

return zipfile.Entries
.Select(entry => Path.Combine(targetFolder, entry.FullName))
.ToArray();
}
}
}
}
108 changes: 108 additions & 0 deletions SetupDevEnvironment/InstallScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using SetupDevEnvironment.IO;
using System.ComponentModel;
using System.Diagnostics;

namespace SetupDevEnvironment
{
internal class InstallScript
{
private readonly string _valheimInstallFolder;
private readonly string _devInstallFolder;

public event ProgressChangedEventHandler? OnLogMessage;

private void Log(string msg)
{
if (OnLogMessage != null)
{
OnLogMessage(this, new ProgressChangedEventArgs(0, msg ));
}
}

public InstallScript(string valheimFolder, string valheimPlusFolder)
{
_valheimInstallFolder = valheimFolder;
_devInstallFolder = valheimPlusFolder;
}

public async Task Install()
{
CopyValheimFiles();
await InstallBepInEx();
PublicizeAssembliesDirectly();
await ConfigureEnvironment();
}

private void CopyValheimFiles()
{
Log("Copying existing Valheim files to Dev Environment...");
FileMover.CopyFiles(_valheimInstallFolder, _devInstallFolder);
Log("Done!");
}

private async Task ConfigureEnvironment()
{
Log("Configuring environment...");
Environment.SetEnvironmentVariable("VALHEIM_INSTALL", _devInstallFolder, EnvironmentVariableTarget.Machine);
await Task.CompletedTask;
Log("Done!");
}

public async Task InstallBepInEx()
{
Log("Installing BepInEx...");
var tempFolder = DirectoryHelper.CreateTempFolder();
var bepInExZip = await Downloader.Download(Links.BepInEx, Path.Combine(tempFolder, "bepInExPack.zip"));
var bepInExFiles = Unzipper.Unzip(bepInExZip);
File.Delete(bepInExZip);

var sourceFolder = bepInExFiles.Single(file => file.EndsWith("BepInExPack_Valheim/"));
FileMover.CopyFiles(sourceFolder, _devInstallFolder);

var unstripped_corlibFiles = bepInExFiles
.Where(path => path.Contains("unstripped_corlib"));

var managedFolder = Path.Combine(_devInstallFolder, $"valheim_Data\\Managed\\");
Directory.CreateDirectory(managedFolder);
foreach (var source in unstripped_corlibFiles)
{
var file = Path.GetFileName(source);
if (string.IsNullOrEmpty(file))
{
continue;
}

var destination = Path.Combine($"{managedFolder}{file}");
File.Copy(source, destination, true);
}
Log("Done!");
}

/// <summary>
/// Install a plugin that automatically reads any file that matches "assembly_*.dll" and
/// creates a new dll where all properties, classes and fields are made public.
/// This allows devs to include these publicized dlls and use the correct consts, methods, etc.
/// </summary>
/// <returns></returns>
public async Task PublicizeAssemblies()
{
var publicizerZip = await Downloader.Download(Links.AssemblyPublicizer);
var publicizerFiles = Unzipper.Unzip(publicizerZip);

var file = "BepInEx-Publicizer.dll";
var pluginDll = publicizerFiles.Single(file => file.EndsWith(
@"plugins/Bepinex-Publicizer/Bepinex-Publicizer.dll", StringComparison.InvariantCultureIgnoreCase));

File.Copy(pluginDll, Path.Combine(_devInstallFolder, $"BepInEx\\plugins\\{file}"), true);
}

public void PublicizeAssembliesDirectly()
{
Log("Publicizing assemblies...");
var pubber = new AssemblyPublicizer();
pubber.OnLogMessage += this.OnLogMessage;
pubber.ProcessAssemblies(_devInstallFolder);
Log("Done!");
}
}
}
17 changes: 17 additions & 0 deletions SetupDevEnvironment/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace SetupDevEnvironment
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new SetupForm());
}
}
}
Loading