Compare commits

..

74 Commits

Author SHA1 Message Date
ed694ae95b Move SetupBasicGameInfo to initializer so it can be called before ui thread init 2023-06-27 15:51:12 -04:00
e9a9e180a8 Move Init back to where it was to prevent UI init issues 2023-06-27 15:02:13 -04:00
a8dfaf6239 Fix MyEntities init 2023-06-27 13:04:52 -04:00
bbdd1c7e01 Update package dependency version 2023-06-21 22:41:31 -04:00
e70e1ca4e6 Fix NRE in MyGameService static constructor 2023-06-18 01:06:22 -04:00
zznty
d65c20a05d update se to 1.202.120 2023-06-13 22:42:35 +07:00
f21976cf2d Depot tool is still broken 2023-06-12 22:50:44 -04:00
0c918106bc Update package dependency 2023-05-29 12:46:40 -04:00
6c9ec57d87 Update for latest SE version 2023-05-29 12:32:49 -04:00
zznty
b0f491ac88 do not exit without autostart 2023-04-25 16:32:52 +07:00
zznty
a426ad9e02 fix steam redist missing on automatic download 2023-04-22 21:22:46 +07:00
zznty
ba75b1583a do not copy dlls in service mode as it can mess up with runtimes 2023-04-22 18:30:02 +07:00
zznty
45068ea932 remove requirement o sta thread in no gui scenarios 2023-04-22 02:51:29 +07:00
zznty
181e9297a1 fix packaging 2023-04-14 11:36:04 +07:00
zznty
e0417d3235 breaking: bump to v2 lmao 2023-04-14 11:15:57 +07:00
zznty
17a244a536 update nuget package and use autoversioning 2023-04-14 11:13:34 +07:00
zznty
bd27360655 update to new se 2023-04-14 11:05:36 +07:00
zznty
b24eee3ecf remove beta from depot tool install 2023-03-27 16:03:15 +07:00
zznty
9068558a53 fix patcher compat with __local thing 2023-03-24 14:49:42 +07:00
zznty
9c22948ce9 fix keen compiler assemblies fuckery 2023-02-21 22:43:30 +07:00
zznty
2b1a5d4c6e fix script compiler compat with event block mods 2023-02-19 19:44:00 +07:00
zznty
2860dda41b windows moment 2023-02-17 16:32:48 +07:00
zznty
5483728a4e use depot downloader instead of steamcmd because its having some troubles with branching 2023-02-17 16:11:35 +07:00
zznty
32d318be5e fix auto-updates and crash handler restart 2023-02-17 15:47:44 +07:00
zznty
f349366b58 add a bit more configuration for steam cmd updates 2023-02-17 12:47:39 +07:00
zznty
73ce979b54 update packages to new se version
keen has thrown a bunch of new static ctors so factory patch cannot be revied :(
2023-02-17 12:43:45 +07:00
zznty
73b95472bc remove harmony compat warning 2023-02-17 12:26:16 +07:00
zznty
9b832a998d use beta for steamcmd 2023-02-17 12:25:05 +07:00
zznty
b1087822c9 gitignore moment 2023-02-08 21:50:23 +07:00
zznty
ef2d35879c bump harmony version 2023-02-08 21:10:17 +07:00
zznty
83d8eea9ef use harmony instead of torch patcher 2023-02-08 21:00:21 +07:00
zznty
8cdd992350 build torch reference assemblies package so it will reference both torch.server and se assemblies packages 2023-02-08 16:10:07 +07:00
zznty
b9cb71e11f introduction of nuget packages as plugins support 2023-02-08 15:56:50 +07:00
zznty
1a1a7e779a versions for all packages 2023-01-30 18:00:38 +07:00
zznty
6bcd2ea58e windows moment 2023-01-24 16:27:31 +07:00
zznty
b8a06f7bd7 publish nuget package on release 2023-01-24 16:25:10 +07:00
zznty
d44b1a592c rewrite torch mod because casitard broke original 2023-01-24 16:09:19 +07:00
zznty
d7d556d2f2 fix for restart on crash too 2023-01-12 23:37:43 +07:00
zznty
850b313269 fix updates again 2023-01-12 23:29:37 +07:00
zznty
fe985e1a9c fix restart again 2023-01-12 23:20:44 +07:00
zznty
aeea192860 exit on restart 2023-01-09 22:02:54 +07:00
zznty
b8be5b2dce fix auto-updates 2023-01-05 00:48:11 +07:00
zznty
ea08d60d58 update protobuf 2023-01-05 00:41:49 +07:00
zznty
6493a305f7 remove retarded assembly version checks 2023-01-04 23:59:33 +07:00
zznty
bc0a2b89b8 fix entity updates and steam errors on game exit 2022-12-14 19:57:14 +07:00
zznty
846c2aa42e remove cache from ci 2022-12-12 13:29:40 +03:00
zznty
ac1ec431fd use lock files 2022-12-12 17:27:21 +07:00
zznty
3acaf25376 fix plugins ui crashes 2022-12-12 17:25:03 +07:00
zznty
e8928b6b3b fix PropertyGrid readonly is not copyable (#516)
(cherry picked from commit 09ddad495988beed896077f879998bf62cd0c8a8)
2022-12-02 15:45:59 +07:00
zznty
8478ee3752 cleanup for webapi things 2022-12-02 15:44:38 +07:00
zznty
ead8e3a4fc update 2022-12-02 15:23:11 +07:00
zznty
f73b9df924 net7 things 2022-11-20 23:13:16 +07:00
zznty
d524651da9 im a true retard 2022-11-06 21:07:18 +06:00
zznty
2743e5b42d okay 2022-11-06 21:02:41 +06:00
zznty
02bd9df059 fix because retarded exmaples 2022-11-06 21:00:24 +06:00
zznty
a5cc132151 add version string normalization 2022-11-06 20:57:56 +06:00
zznty
92dea1986c linux ci v5 2022-11-06 20:49:18 +06:00
zznty
c03eb79f81 linux ci v4 2022-11-06 20:46:54 +06:00
zznty
0ee9c5f97c test linux ci v3 2022-11-06 20:44:17 +06:00
zznty
c283059106 test linux ci v2 2022-11-06 20:41:26 +06:00
zznty
422963517f test building linux 2022-11-06 17:36:29 +03:00
zznty
d2ac0e44be correct whitelist for compiler 2022-10-16 02:09:44 +07:00
zznty
c5acf61f7c add auto-updates from github 2022-10-15 15:33:57 +07:00
zznty
197d04a661 fix steamcmd default directory 2022-10-15 15:33:09 +07:00
zznty
99ab7d0eea Merge remote-tracking branch 'pve/master' 2022-10-15 02:19:05 +07:00
zznty
17f97af52f add gslt login option (#515)
(cherry picked from commit c81f139fe6b5de0c9f7a005dc2cbe576f4ca8f67)
2022-10-15 02:09:09 +07:00
zznty
ed7c897bd2 Update README.md 2022-10-14 21:59:18 +03:00
zznty
e4d3c3987f fix ci changelog building 2022-10-14 21:48:43 +03:00
zznty
067d8802b6 change instance path resolving back to bin folder
i dont think we need to place our instance at game dir by default
2022-10-15 00:55:44 +07:00
zznty
8f32f64ede change script compilation manager lifetime
seems game loads faster than torch internals
2022-10-15 00:50:10 +07:00
zznty
90ff3f93f0 disable gc patch because conflicting with the same from plugin 2022-10-15 00:44:49 +07:00
zznty
ad28b302f9 add harmony logging 2022-10-15 00:42:23 +07:00
zznty
a0d0976a6a c# 10 and assembly unloading for in-game scripts 2022-10-15 00:16:00 +07:00
zznty
a9c9a0de68 refactor restart and config handling 2022-10-12 16:36:25 +07:00
76 changed files with 3119 additions and 1542 deletions

View File

@@ -2,60 +2,60 @@ name: Release
on: on:
push: push:
tags: branches: [master]
- '*'
jobs: jobs:
build: build:
name: Build name: Build
runs-on: windows-latest runs-on: ubuntu-latest
env: env:
VERSION: ${{ github.ref_name }}
BUILD_CONFIGURATION: Release BUILD_CONFIGURATION: Release
DOTNET_NOLOGO: true DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true DOTNET_CLI_TELEMETRY_OPTOUT: true
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@master
name: Checkout name: Checkout
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- uses: actions/setup-dotnet@v3 - uses: actions/setup-dotnet@v3
name: Setup dotnet name: Setup dotnet
with: with:
dotnet-version: '6.0.x' dotnet-version: '7.0.x'
- uses: actions/cache@v1
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Add Gh Packages Nuget Source - name: Add Gh Packages Nuget Source
run: dotnet nuget add source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github run: dotnet nuget add source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github
- name: Restore dependencies - name: Restore dependencies
run: dotnet restore Torch.Server/Torch.Server.csproj --use-lock-file run: dotnet restore Torch.Server/Torch.Server.csproj --locked-mode
- name: Git Version
id: version
uses: codacy/git-version@2.7.1
- name: Build - name: Build
run: dotnet build Torch.Server/Torch.Server.csproj --no-restore -c $env:BUILD_CONFIGURATION /p:AssemblyVersion=$env:VERSION /p:Version=$env:VERSION run: dotnet build Torch.Server/Torch.Server.csproj --no-restore -c ${{ env.BUILD_CONFIGURATION }} -p:Version="${{ steps.version.outputs.version }}" -p:AssemblyVersion="${{ steps.version.outputs.version }}"
- name: Publish - name: Publish
run: dotnet publish Torch.Server/Torch.Server.csproj --no-build -r win-x64 --sc -c $env:BUILD_CONFIGURATION -o ./publish run: dotnet publish Torch.Server/Torch.Server.csproj --no-build -r win-x64 --sc -c ${{ env.BUILD_CONFIGURATION }} -o ./publish
- uses: vimtor/action-zip@v1 - uses: vimtor/action-zip@v1
name: Zip Release name: Zip Release
with: with:
files: publish/ files: publish/
dest: release.zip dest: release.zip
- name: Build Changelog
id: build_changelog
uses: mikepenz/release-changelog-builder-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create release - name: Create release
id: create_release id: create_release
uses: actions/create-release@v1 uses: actions/create-release@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tag_name: ${{ env.VERSION }} tag_name: ${{ steps.version.outputs.version }}
release_name: Release v${{ env.VERSION }} release_name: Release v${{ steps.version.outputs.version }}
body: ${{ steps.github_release.outputs.changelog }} body: ${{ steps.github_release.outputs.changelog }}
draft: true draft: true
prerelease: false prerelease: false
- name: Upload release asset - name: Upload release asset
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
env: env:
@@ -65,9 +65,16 @@ jobs:
asset_path: release.zip asset_path: release.zip
asset_name: torch-server.zip asset_name: torch-server.zip
asset_content_type: application/zip asset_content_type: application/zip
- name: Publish release
uses: StuYarrow/publish-release@v1 - uses: eregon/publish-release@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
id: ${{ steps.create_release.outputs.id }} release_id: ${{ steps.create_release.outputs.id }}
- run: dotnet pack -c Release ./Torch.API/Torch.API.csproj -o pack --include-symbols -p:SymbolPackageFormat=snupkg -p:Version="${{ steps.version.outputs.version }}" -p:AssemblyVersion="${{ steps.version.outputs.version }}" --no-build
- run: dotnet pack -c Release ./Torch/Torch.csproj -o pack --include-symbols -p:SymbolPackageFormat=snupkg -p:Version="${{ steps.version.outputs.version }}" -p:AssemblyVersion="${{ steps.version.outputs.version }}" --no-build
- run: dotnet pack -c Release ./Torch.Server/Torch.Server.csproj -o pack --include-symbols -p:SymbolPackageFormat=snupkg -p:Version="${{ steps.version.outputs.version }}" -p:AssemblyVersion="${{ steps.version.outputs.version }}" --no-build
- run: mkdir blank && sed -i 's/torchVersion/${{ steps.version.outputs.version }}/g' Torch.Server.ReferenceAssemblies.net7.nuspec && nuget pack Torch.Server.ReferenceAssemblies.net7.nuspec -BasePath ./blank -OutputDirectory pack -NonInteractive -NoPackageAnalysis
- run: dotnet nuget push ./pack/*.nupkg -s github

4
.gitignore vendored
View File

@@ -159,10 +159,6 @@ publish/
# NuGet Packages # NuGet Packages
*.nupkg *.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed # Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config #!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files # NuGet v3's project.json files produces more ignoreable files

View File

@@ -12,7 +12,10 @@ Torch is the successor to SE Server Extender and gives server admins the tools t
### Fork Difference ### Fork Difference
* .NET 6.0 runtime * .NET 6.0 runtime
* Additional options & features * Optimized in-game scripts (also newer compiler & language versions)
* Better configuration via cli arguments, environment variables or xml config
* Designed to run multiple instance from same install directory without having you to waste disk space
* Mostly compatible with original torch's plugins
### Discord ### Discord
@@ -21,10 +24,13 @@ If you have any questions or issues please join our [discord](https://discord.gg
### Installation ### Installation
* Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files. * Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files.
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder. - If you already have a DS installed you can:
* Unzip the Torch files into the folder that contains the DedicatedServer64 folder.
* Pass path to game files using config parameter `gamePath`
# Building # Building
To build Torch you must first have a complete SE Dedicated installation somewhere. Before you open the solution, run the Setup batch file and enter the path of that installation's DedicatedServer64 folder. The script will make a symlink to that folder so the Torch solution can find the DLL references it needs.
As a regular dotnet project with cli by running `dotnet build Torch.Server/Torch.Server.csproj`, with VS 2022 or higher, with JB Rider or Fleet.
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon. (https://www.patreon.com/TorchSE) If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon. (https://www.patreon.com/TorchSE)

View File

@@ -31,6 +31,24 @@ namespace Torch
int FontSize { get; set; } int FontSize { get; set; }
UGCServiceType UgcServiceType { get; set; } UGCServiceType UgcServiceType { get; set; }
bool EntityManagerEnabled { get; set; } bool EntityManagerEnabled { get; set; }
string LoginToken { get; set; }
UpdateSource UpdateSource { get; set; }
List<string> Packages { get; set; }
void Save(string path = null); void Save(string path = null);
} }
public class UpdateSource
{
public UpdateSourceType SourceType { get; set; }
public string Url { get; set; }
public string Repository { get; set; }
public string Branch { get; set; }
}
public enum UpdateSourceType
{
Github,
Jenkins
}
} }

View File

@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Loader;
using Torch.API.WebAPI.Plugins;
namespace Torch.API.Managers;
public interface IPackageManager : IManager
{
IReadOnlySet<Package> Packages { get; }
bool TryGetPackageAssemblies(Package package, [MaybeNullWhen(false)] out Assembly[] assemblies);
}

View File

@@ -1,16 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net7-windows</TargetFramework>
<LangVersion>10</LangVersion>
<AssemblyTitle>Torch API</AssemblyTitle> <AssemblyTitle>Torch API</AssemblyTitle>
<Product>Torch</Product> <Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright> <Copyright>Copyright © Torch API 2017</Copyright>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath> <OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath>
<UseWpf>True</UseWpf> <UseWpf>True</UseWpf>
<PlatformTarget>x64</PlatformTarget> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<Configurations>Debug;Release</Configurations> <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<Platforms>AnyCPU</Platforms>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="$(Configuration) == 'Release'"> <PropertyGroup Condition="$(Configuration) == 'Release'">
@@ -18,11 +16,15 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NLog" Version="5.0.4" /> <PackageReference Include="JorgeSerrano.Json.JsonSnakeCaseNamingPolicy" Version="0.9.0" />
<PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="NuGet.Commands" Version="6.4.0" />
<PackageReference Include="NuGet.DependencyResolver.Core" Version="6.4.0" />
<PackageReference Include="SemanticVersioning" Version="2.0.2" /> <PackageReference Include="SemanticVersioning" Version="2.0.2" />
<PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.201.13"> <PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.202.120">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile</IncludeAssets> <IncludeAssets>compile</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using NLog;
using Torch.API.Utils;
using Version = SemanticVersioning.Version;
namespace Torch.API.WebAPI
{
public class JenkinsQuery
{
private const string BRANCH_QUERY = "http://136.243.80.164:2690/job/Torch/job/{0}/" + API_PATH;
private const string ARTIFACT_PATH = "artifact/bin/torch-server.zip";
private const string API_PATH = "api/json";
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static JenkinsQuery _instance;
public static JenkinsQuery Instance => _instance ??= new JenkinsQuery();
private HttpClient _client;
private JenkinsQuery()
{
_client = new HttpClient();
}
public async Task<Job> GetLatestVersion(string branch)
{
var h = await _client.GetAsync(string.Format(BRANCH_QUERY, branch));
if (!h.IsSuccessStatusCode)
{
Log.Error($"'{branch}' Branch query failed with code {h.StatusCode}");
if(h.StatusCode == HttpStatusCode.NotFound)
Log.Error("This likely means you're trying to update a branch that is not public on Jenkins. Sorry :(");
return null;
}
var branchResponse = await h.Content.ReadFromJsonAsync<BranchResponse>();
if (branchResponse is null)
{
Log.Error("Error reading branch response");
return null;
}
h = await _client.GetAsync($"{branchResponse.LastStableBuild.Url}{API_PATH}");
if (h.IsSuccessStatusCode)
return await h.Content.ReadFromJsonAsync<Job>();
Log.Error($"Job query failed with code {h.StatusCode}");
return null;
}
public async Task<bool> DownloadRelease(Job job, string path)
{
var h = await _client.GetAsync(job.Url + ARTIFACT_PATH);
if (!h.IsSuccessStatusCode)
{
Log.Error($"Job download failed with code {h.StatusCode}");
return false;
}
var s = await h.Content.ReadAsStreamAsync();
#if !NETFRAMEWORK
await using var fs = new FileStream(path, FileMode.Create);
#else
using var fs = new FileStream(path, FileMode.Create);
#endif
await s.CopyToAsync(fs);
return true;
}
}
public record BranchResponse(string Name, string Url, Build LastBuild, Build LastStableBuild);
public record Build(int Number, string Url);
public record Job(int Number, bool Building, string Description, string Result, string Url,
[property: JsonConverter(typeof(SemanticVersionConverter))] Version Version);
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;
namespace Torch.API.WebAPI.Plugin;
public interface IPluginQuery
{
Task<PluginsResponse> QueryAll();
Task<PluginItem> QueryOne(Guid guid);
Task<bool> DownloadPlugin(Guid guid, string path = null);
Task<bool> DownloadPlugin(PluginItem item, string path = null);
}

View File

@@ -0,0 +1,81 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
using NLog;
namespace Torch.API.WebAPI.Plugin;
public class LegacyPluginQuery : IPluginQuery
{
private const string BASE_URL = "https://torchapi.com/";
private readonly HttpClient _client;
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private LegacyPluginQuery()
{
_client = new()
{
BaseAddress = new(BASE_URL)
};
}
public static LegacyPluginQuery Instance { get; } = new();
public async Task<PluginsResponse> QueryAll()
{
return await _client.GetFromJsonAsync<PluginsResponse>("/api/plugins/", CancellationToken.None);
}
public async Task<PluginItem> QueryOne(Guid guid)
{
using var res = await _client.GetAsync($"/api/plugins/search/{guid}");
if (!res.IsSuccessStatusCode)
return null;
return await res.Content.ReadFromJsonAsync<PluginItem>();
}
public async Task<bool> DownloadPlugin(Guid guid, string path = null)
{
var item = await QueryOne(guid);
if (item is null) return false;
return await DownloadPlugin(item, path);
}
public async Task<bool> DownloadPlugin(PluginItem item, string path = null)
{
try
{
path ??= Path.Combine(AppContext.BaseDirectory, "Plugins", $"{item.Name}.zip");
if (item.Versions.Length == 0)
{
Log.Error($"Selected plugin {item.Name} does not have any versions to download!");
return false;
}
var version = item.Versions.FirstOrDefault(v => v.Version == item.LatestVersion);
if (version is null)
{
Log.Error($"Could not find latest version for selected plugin {item.Name}");
return false;
}
var s = await _client.GetStreamAsync(version.Url);
if(File.Exists(path))
File.Delete(path);
await using var f = File.Create(path);
await s.CopyToAsync(f);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to download plugin!");
return false;
}
return true;
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Text.Json.Serialization;
namespace Torch.API.WebAPI.Plugin;
public record PluginItem(Guid Id, string Name, string Author, string Description, string LatestVersion,
VersionItem[] Versions)
{
[JsonIgnore]
public bool Installed { get; set; }
}

View File

@@ -0,0 +1,3 @@
namespace Torch.API.WebAPI.Plugin;
public record PluginsResponse(PluginItem[] Plugins);

View File

@@ -0,0 +1,6 @@
using System.Text.Json.Serialization;
namespace Torch.API.WebAPI.Plugin;
public record VersionItem(string Version, string Note, [property: JsonPropertyName("is_beta")] bool IsBeta,
string Url);

View File

@@ -1,104 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using NLog;
namespace Torch.API.WebAPI
{
public class PluginQuery
{
private const string ALL_QUERY = "https://torchapi.com/api/plugins/";
private const string PLUGIN_QUERY = "https://torchapi.com/api/plugins/search/{0}";
private readonly HttpClient _client;
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static PluginQuery _instance;
public static PluginQuery Instance => _instance ??= new();
private PluginQuery()
{
_client = new();
}
public async Task<PluginsResponse> QueryAll()
{
return (PluginsResponse) await _client.GetFromJsonAsync(ALL_QUERY, typeof(PluginsResponse), CancellationToken.None);
}
public Task<PluginItem> QueryOne(Guid guid)
{
return QueryOne(guid.ToString());
}
public async Task<PluginItem> QueryOne(string guid)
{
using var res = await _client.GetAsync(string.Format(PLUGIN_QUERY, guid));
if (!res.IsSuccessStatusCode)
return null;
return await res.Content.ReadFromJsonAsync<PluginItem>();
}
public Task<bool> DownloadPlugin(Guid guid, string path = null)
{
return DownloadPlugin(guid.ToString(), path);
}
public async Task<bool> DownloadPlugin(string guid, string path = null)
{
var item = await QueryOne(guid);
if (item is null) return false;
return await DownloadPlugin(item, path);
}
public async Task<bool> DownloadPlugin(PluginItem item, string path = null)
{
try
{
path ??= Path.Combine(AppContext.BaseDirectory, "Plugins", $"{item.Name}.zip");
if (item.Versions.Length == 0)
{
Log.Error($"Selected plugin {item.Name} does not have any versions to download!");
return false;
}
var version = item.Versions.FirstOrDefault(v => v.Version == item.LatestVersion);
if (version is null)
{
Log.Error($"Could not find latest version for selected plugin {item.Name}");
return false;
}
var s = await _client.GetStreamAsync(version.Url);
if(File.Exists(path))
File.Delete(path);
await using var f = File.Create(path);
await s.CopyToAsync(f);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to download plugin!");
}
return true;
}
}
public record PluginsResponse(PluginItem[] Plugins);
public record PluginItem(Guid Id, string Name, string Author, string Description, string LatestVersion,
VersionItem[] Versions)
{
[JsonIgnore]
public bool Installed { get; set; }
}
public record VersionItem(string Version, string Note, [property: JsonPropertyName("is_beta")] bool IsBeta,
string Url);
}

View File

@@ -0,0 +1,11 @@
#nullable enable
using System.IO;
using System.Threading.Tasks;
namespace Torch.API.WebAPI.Plugins;
public interface IPackageItem
{
Task<Stream> OpenFileAsync();
public string FileName { get; }
}

View File

@@ -0,0 +1,12 @@
#nullable enable
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Torch.API.WebAPI.Plugins;
public interface IPackageReader
{
Task<(IEnumerable<IPackageItem> Root, IReadOnlyDictionary<PackageDependency, IEnumerable<IPackageItem>> Dependencies)> GetItemsAsync();
}

View File

@@ -0,0 +1,11 @@
#nullable enable
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Torch.API.WebAPI.Plugins;
public interface IPackageResolver
{
Task<IEnumerable<Package>> ResolvePackagesAsync(IReadOnlyDictionary<string, string> packages);
Task<IPackageReader> GetPackageAsync(Package package);
}

View File

@@ -0,0 +1,87 @@
#nullable enable
using System;
using System.Threading.Tasks;
using NuGet.Common;
namespace Torch.API.WebAPI.Plugins;
internal class NLogLogger : ILogger
{
private readonly NLog.ILogger _logger;
public NLogLogger(NLog.ILogger logger)
{
_logger = logger;
}
public void LogDebug(string data)
{
_logger.Debug(data);
}
public void LogVerbose(string data)
{
_logger.Trace(data);
}
public void LogInformation(string data)
{
_logger.Info(data);
}
public void LogMinimal(string data)
{
_logger.Debug(data);
}
public void LogWarning(string data)
{
_logger.Warn(data);
}
public void LogError(string data)
{
_logger.Error(data);
}
public void LogInformationSummary(string data)
{
_logger.Info(data);
}
public void Log(LogLevel level, string data)
{
_logger.Log(ToNLogLevel(level), data);
}
private static NLog.LogLevel ToNLogLevel(LogLevel level)
{
return level switch
{
LogLevel.Debug => NLog.LogLevel.Debug,
LogLevel.Verbose => NLog.LogLevel.Trace,
LogLevel.Information => NLog.LogLevel.Info,
LogLevel.Minimal => NLog.LogLevel.Debug,
LogLevel.Warning => NLog.LogLevel.Warn,
LogLevel.Error => NLog.LogLevel.Error,
_ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
};
}
public Task LogAsync(LogLevel level, string data)
{
Log(level, data);
return Task.CompletedTask;
}
public void Log(ILogMessage message)
{
_logger.Log(ToNLogLevel(message.Level), message.FormatWithCode);
}
public Task LogAsync(ILogMessage message)
{
Log(message);
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using NuGet.DependencyResolver;
using SemanticVersioning;
namespace Torch.API.WebAPI.Plugins;
public record Package(string Name, Version Version, IReadOnlySet<PackageDependency> Dependencies)
{
internal GraphItem<RemoteResolveResult> Graph { get; init; }
}

View File

@@ -0,0 +1,9 @@
using NuGet.DependencyResolver;
using SemanticVersioning;
namespace Torch.API.WebAPI.Plugins;
public record PackageDependency(string Name, Version Version, PackageDependencyKind Kind)
{
internal RemoteMatch Match { get; init; }
}

View File

@@ -0,0 +1,8 @@
namespace Torch.API.WebAPI.Plugins;
public enum PackageDependencyKind
{
None,
Transitive,
Direct
}

View File

@@ -0,0 +1,91 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.DependencyResolver;
using NuGet.Frameworks;
using NuGet.Packaging;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;
namespace Torch.API.WebAPI.Plugins;
public class PackageReader : IPackageReader
{
private readonly Package _package;
private readonly SourceCacheContext _cacheContext;
private readonly ILogger _logger;
private readonly NuGetFramework _framework;
private readonly IFrameworkCompatibilityProvider _compatibilityProvider;
private readonly DirectoryInfo _packagesDirectory;
public PackageReader(Package package, SourceCacheContext cacheContext, ILogger logger, NuGetFramework framework,
IFrameworkCompatibilityProvider compatibilityProvider, DirectoryInfo packagesDirectory)
{
_package = package;
_cacheContext = cacheContext;
_logger = logger;
_framework = framework;
_compatibilityProvider = compatibilityProvider;
_packagesDirectory = packagesDirectory;
}
public async Task<(IEnumerable<IPackageItem> Root, IReadOnlyDictionary<PackageDependency, IEnumerable<IPackageItem>>
Dependencies)>
GetItemsAsync()
{
async Task<IEnumerable<IPackageItem>> GetPackageItemsAsync(string id, NuGetVersion version,
IRemoteDependencyProvider provider)
{
var downloader =
await provider.GetPackageDownloaderAsync(new(id, version), _cacheContext, _logger,
CancellationToken.None);
await downloader.CopyNupkgFileToAsync(Path.Combine(_packagesDirectory.FullName, $"{id}.{version}.nupkg"),
CancellationToken.None);
var frameworks = await downloader.ContentReader.GetReferenceItemsAsync(CancellationToken.None);
var items = frameworks.Where(b => _compatibilityProvider.IsCompatible(_framework, b.TargetFramework))
.MaxBy(b => b.TargetFramework.Version)?.Items;
return items?.Select(b => new PackageItem(b, downloader)) ?? ImmutableArray<PackageItem>.Empty;
}
var rootIdentity = _package.Graph.Key;
return (await GetPackageItemsAsync(rootIdentity.Name, rootIdentity.Version, _package.Graph.Data.Match.Provider),
await _package.Dependencies.ToAsyncEnumerable().SelectManyAwait(async b =>
(await GetPackageItemsAsync(
b.Match.Library.Name,
b.Match.Library.Version,
b.Match.Provider))
.ToAsyncEnumerable()
.Select(c => (b, c)))
.GroupBy(b => b.b, b => b.c)
.ToDictionaryAwaitAsync<IAsyncGrouping<PackageDependency, IPackageItem>, PackageDependency,
IEnumerable<IPackageItem>>(b => ValueTask.FromResult(b.Key),
async b => await b.ToArrayAsync()));
}
}
file class PackageItem : IPackageItem
{
private readonly string _path;
private readonly IPackageDownloader _downloader;
public string FileName => Path.GetFileName(_path);
public PackageItem(string path, IPackageDownloader downloader)
{
_path = path;
_downloader = downloader;
}
public Task<Stream> OpenFileAsync()
{
return _downloader.CoreReader.GetStreamAsync(_path, CancellationToken.None);
}
}

View File

@@ -0,0 +1,104 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using NuGet.Commands;
using NuGet.Configuration;
using NuGet.DependencyResolver;
using NuGet.Frameworks;
using NuGet.LibraryModel;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;
using Version = SemanticVersioning.Version;
namespace Torch.API.WebAPI.Plugins;
public class PackageResolver : IPackageResolver
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
private readonly NuGetFramework _framework = NuGetFramework.Parse("net7.0-windows7.0");
private readonly NLogLogger _logger = new(Log);
private readonly SourceCacheContext _sourceCacheContext = new();
private readonly RemoteWalkContext _remoteWalkContext;
private readonly DirectoryInfo _packagesDirectory;
private readonly IFrameworkCompatibilityProvider _compatibilityProvider = DefaultCompatibilityProvider.Instance;
public PackageResolver(IEnumerable<PackageSource> sources, DirectoryInfo packagesDirectory)
{
_packagesDirectory = packagesDirectory;
IReadOnlySet<PackageSource> packageSources = sources.Where(b => b.Type is PackageSourceType.NuGet).ToImmutableHashSet();
var mapping = new PackageSourceMapping(packageSources.ToDictionary(b => b.Name, b => b.Patterns));
_remoteWalkContext = new RemoteWalkContext(_sourceCacheContext, mapping, _logger);
foreach (var (name, url, _, _) in packageSources)
{
var packageSource = new NuGet.Configuration.PackageSource(url, name);
var sourceRepository = new SourceRepository(packageSource, new INuGetResourceProvider[]
{
new DownloadResourceV3Provider(),
new DependencyInfoResourceV3Provider(),
new ServiceIndexResourceV3Provider(),
new RemoteV3FindPackageByIdResourceProvider(),
new V3FeedListResourceProvider(),
new HttpSourceResourceProvider(),
new RegistrationResourceV3Provider(),
new HttpHandlerResourceV3Provider()
}.Select(b => new Lazy<INuGetResourceProvider>(b)), FeedType.HttpV3);
_remoteWalkContext.RemoteLibraryProviders.Add(
new SourceRepositoryDependencyProvider(sourceRepository, _logger, _sourceCacheContext, true, false));
}
}
public async Task<IEnumerable<Package>> ResolvePackagesAsync(
IReadOnlyDictionary<string, string> packages)
{
Log.Info("Restoring {0} packages", packages.Count);
var graphs = await Task.WhenAll(packages.Select(b =>
{
var (key, versionRange) = b;
var libraryRange = new LibraryRange(key, VersionRange.Parse(versionRange), LibraryDependencyTarget.All);
return ResolverUtility.FindLibraryEntryAsync(libraryRange, _framework, "win-x64",
_remoteWalkContext, CancellationToken.None);
}));
return await graphs.ToAsyncEnumerable().SelectAwait(async graph =>
{
return new Package(graph.Key.Name, Version.Parse(graph.Key.Version.ToFullString()),
await graph.Data.Dependencies
.ToAsyncEnumerable()
.SelectAwait(async b =>
{
var match = await ResolverUtility.FindLibraryByVersionAsync(
b.LibraryRange, _framework, _remoteWalkContext.RemoteLibraryProviders,
_sourceCacheContext, _logger, CancellationToken.None);
return new PackageDependency(
b.Name, Version.Parse(match.Library.Version.ToFullString()),
(PackageDependencyKind)b.ReferenceType)
{
Match = match
};
})
.ToHashSetAsync())
{
Graph = graph
};
}).ToArrayAsync();
}
public Task<IPackageReader> GetPackageAsync(Package package)
{
var reader = new PackageReader(package, _sourceCacheContext, _logger, _framework, _compatibilityProvider, _packagesDirectory);
return Task.FromResult<IPackageReader>(reader);
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace Torch.API.WebAPI.Plugins;
#nullable enable
public record PackageSource
#nullable restore
(string Name, string Url, IReadOnlyList<string> Patterns, PackageSourceType Type);

View File

@@ -0,0 +1,8 @@
#nullable enable
namespace Torch.API.WebAPI.Plugins;
public enum PackageSourceType
{
NuGet,
LegacyTorch
}

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using JorgeSerrano.Json;
using Version = SemanticVersioning.Version;
namespace Torch.API.WebAPI.Update;
public class GithubQuery : IUpdateQuery
{
private readonly HttpClient _client;
public GithubQuery(string url)
{
if (url == null) throw new ArgumentNullException(nameof(url));
_client = new()
{
BaseAddress = new(url),
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher,
DefaultRequestHeaders =
{
{"User-Agent", "TorchAPI"}
}
};
}
public void Dispose()
{
_client?.Dispose();
}
public async Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null)
{
var response = await _client.GetFromJsonAsync<Release>($"/repos/{repository}/releases/latest", new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy()
});
if (response is null)
throw new($"Unable to get latest release for {repository}");
return new(Version.Parse(response.TagName), response.Assets.First(b => b.Name == "torch-server.zip").BrowserDownloadUrl);
}
private record Asset(
string Url,
int Id,
string NodeId,
string Name,
string Label,
Uploader Uploader,
string ContentType,
string State,
int Size,
int DownloadCount,
DateTime CreatedAt,
DateTime UpdatedAt,
string BrowserDownloadUrl
);
private record Author(
string Login,
int Id,
string NodeId,
string AvatarUrl,
string GravatarId,
string Url,
string HtmlUrl,
string FollowersUrl,
string FollowingUrl,
string GistsUrl,
string StarredUrl,
string SubscriptionsUrl,
string OrganizationsUrl,
string ReposUrl,
string EventsUrl,
string ReceivedEventsUrl,
string Type,
bool SiteAdmin
);
private record Release(
string Url,
string AssetsUrl,
string UploadUrl,
string HtmlUrl,
int Id,
Author Author,
string NodeId,
string TagName,
string TargetCommitish,
string Name,
bool Draft,
bool Prerelease,
DateTime CreatedAt,
DateTime PublishedAt,
IReadOnlyList<Asset> Assets,
string TarballUrl,
string ZipballUrl,
string Body
);
private record Uploader(
string Login,
int Id,
string NodeId,
string AvatarUrl,
string GravatarId,
string Url,
string HtmlUrl,
string FollowersUrl,
string FollowingUrl,
string GistsUrl,
string StarredUrl,
string SubscriptionsUrl,
string OrganizationsUrl,
string ReposUrl,
string EventsUrl,
string ReceivedEventsUrl,
string Type,
bool SiteAdmin
);
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Threading.Tasks;
namespace Torch.API.WebAPI.Update;
public interface IUpdateQuery : IDisposable
{
Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null);
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Torch.API.Utils;
using Version = SemanticVersioning.Version;
namespace Torch.API.WebAPI.Update
{
public class JenkinsQuery : IUpdateQuery
{
private const string ApiPath = "api/json";
private readonly HttpClient _client;
public JenkinsQuery(string url)
{
if (url == null) throw new ArgumentNullException(nameof(url));
_client = new()
{
BaseAddress = new(url)
};
}
public async Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null)
{
branch ??= "master";
var response = await _client.GetFromJsonAsync<BranchResponse>($"/job/{repository}/job/{branch}/{ApiPath}");
if (response is null)
throw new($"Unable to get latest release for {repository}");
var job = await _client.GetFromJsonAsync<Job>(
$"/job/{repository}/job/{branch}/{response.LastBuild.Number}/{ApiPath}");
if (job is null)
throw new($"Unable to get latest release for job {repository}/{response.LastBuild.Number}");
return new(job.Version, job.Url + job.Artifacts.First(b => b.FileName == "torch-server.zip").RelativePath);
}
public void Dispose()
{
_client?.Dispose();
}
}
public record BranchResponse(string Name, string Url, Build LastBuild, Build LastStableBuild);
public record Build(int Number, string Url);
public record Job(int Number, bool Building, string Description, string Result, string Url,
[property: JsonConverter(typeof(SemanticVersionConverter))] Version Version,
IReadOnlyList<Artifact> Artifacts);
public record Artifact(
string DisplayPath,
string FileName,
string RelativePath
);
}

View File

@@ -0,0 +1,5 @@
using SemanticVersioning;
namespace Torch.API.WebAPI.Update;
public record UpdateRelease(Version Version, string ArtifactUrl);

View File

@@ -0,0 +1,178 @@
{
"version": 1,
"dependencies": {
"net7.0-windows7.0": {
"JorgeSerrano.Json.JsonSnakeCaseNamingPolicy": {
"type": "Direct",
"requested": "[0.9.0, )",
"resolved": "0.9.0",
"contentHash": "xCqODS+wzpUXNtg4bMMvXG5PLbP0iTwRzRn2R+zWHKm83E6tbV2bCagawXp1EnZeNpd5OXpMxehulZWns8efzQ=="
},
"NLog": {
"type": "Direct",
"requested": "[5.1.0, )",
"resolved": "5.1.0",
"contentHash": "oW7ekrkRG9okpDMUcEglunWj8Qf2RY8qkgl+/chJoavzg3dbT13y32t19R54FKkmq80fKzw4ZekZkCrRGanKgQ=="
},
"NuGet.Commands": {
"type": "Direct",
"requested": "[6.4.0, )",
"resolved": "6.4.0",
"contentHash": "j3ma45boGZADsHpJcu3Y2yq+n2luicC6ezd61TXTTzbOzA452oAPaSsFGUB1stIsuP/DVoqkTzjHXjaCHuJKPQ==",
"dependencies": {
"NuGet.Credentials": "6.4.0",
"NuGet.ProjectModel": "6.4.0"
}
},
"NuGet.DependencyResolver.Core": {
"type": "Direct",
"requested": "[6.4.0, )",
"resolved": "6.4.0",
"contentHash": "AKomZEKuhQlshujuiHbKvwl2cZNGq3SHsXFbpjCfCjMFMLwwA8saJGQQZp1lzsqQWcQWa6hLOcPtm7T3rd0SVg==",
"dependencies": {
"NuGet.Configuration": "6.4.0",
"NuGet.LibraryModel": "6.4.0",
"NuGet.Protocol": "6.4.0"
}
},
"SemanticVersioning": {
"type": "Direct",
"requested": "[2.0.2, )",
"resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA=="
},
"SpaceEngineersDedicated.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.202.120, )",
"resolved": "1.202.120",
"contentHash": "HTP48NOSZY3eQPX3GJN+gjxdW83SM7DX6NcKCFqNIIvjz/J+l8uimHAQp9dnLySb0wb1K1Z1xBPIXyxMQRREIw==",
"dependencies": {
"protobuf-net": "1.0.0"
}
},
"System.Linq.Async": {
"type": "Direct",
"requested": "[6.0.1, )",
"resolved": "6.0.1",
"contentHash": "0YhHcaroWpQ9UCot3Pizah7ryAzQhNvobLMSxeDIGmnXfkQn8u5owvpOH0K6EVB+z9L7u6Cc4W17Br/+jyttEQ==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "6.0.0"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.1",
"contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
},
"NuGet.Common": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "srECugLk+LB1bXelDCDhHoi6do/EYTXzuntKhjHraS4roVB3NfWohEdCSiAPdpSV9M40Q6jo6MV2Srml9e+jHQ==",
"dependencies": {
"NuGet.Frameworks": "6.4.0"
}
},
"NuGet.Configuration": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "vPjauG9AoacEjiZWGIs+d11FCRVmseqAw78FIApfLvZrYMEbbwc9vc0LdC3PpoW5FxYkktyZSiiXVKXGLu+gXw==",
"dependencies": {
"NuGet.Common": "6.4.0",
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
},
"NuGet.Credentials": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "tebsxclknVz3D4FrvE2MzVcsOyf6PffjGNQ77X9Yvbj9x5YpVWfumVPetqETcdsNEgiN0bBzfMre33lhrY7Itw==",
"dependencies": {
"NuGet.Protocol": "6.4.0"
}
},
"NuGet.Frameworks": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "qcufbjJIDtyY/Hah7JJfcRVpRYM3scgPqYBnukjO9kfADCFGr2azvVBozuwzljA6w/cR3w8bXLq6vW5xGrsmHw=="
},
"NuGet.LibraryModel": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "K6ROQpWr34Aje81G0HfipiznLTB8vD4BO8sF6FEwx1KjJVdFkSmGZPmAhc6L1vZPs8TKY5BqoH72zG13zVzW2w==",
"dependencies": {
"NuGet.Common": "6.4.0",
"NuGet.Versioning": "6.4.0"
}
},
"NuGet.Packaging": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "aR10aYqcUMGC2mwMGH5rls/MGaz3EVH8DKTTHQ/EC91hXNtrCTTAQonaRR+v1EItcoxtQeZ/WQOorv4z270Tgg==",
"dependencies": {
"Newtonsoft.Json": "13.0.1",
"NuGet.Configuration": "6.4.0",
"NuGet.Versioning": "6.4.0",
"System.Security.Cryptography.Cng": "5.0.0",
"System.Security.Cryptography.Pkcs": "5.0.0"
}
},
"NuGet.ProjectModel": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "eW9Q7vPk8cpXDd5b+vtIPkl8dSDCPkPJPrjXPTfZGxhstldnhJrj1XPaonsDZLQ24YY7LrYCzC0BiHh3iO5zUA==",
"dependencies": {
"NuGet.DependencyResolver.Core": "6.4.0"
}
},
"NuGet.Protocol": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "KIPjsWP0P3EMsDsXaa6YBCTvYKur/zI0luS1kO5G37ci8mHs2kJFsuG5qaMhGvgyHASu54sxlic1n1oza2Pcbw==",
"dependencies": {
"NuGet.Packaging": "6.4.0"
}
},
"NuGet.Versioning": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "YE8p3TpX4jIw+Gb24maE8YRDoqWA4imLmCbdOj5IvslLrZJXQ8akeFOGOplxICNVevON1g1SFYT2+cq4yy0nQQ=="
},
"protobuf-net": {
"type": "Transitive",
"resolved": "1.0.0",
"contentHash": "kTGOK0E87473sOImOjgZOnz3kTC2aMLffoRWQLYNuBLJnwNNmjanF9IkevZ9Q7yYLeABQfcF3BpeepuMntMVNw=="
},
"System.Formats.Asn1": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "MTvUIktmemNB+El0Fgw9egyqT9AYSIk6DTJeoDSpc3GIHxHCMo8COqkWT1mptX5tZ1SlQ6HJZ0OsSvMth1c12w=="
},
"System.Security.Cryptography.Cng": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "jIMXsKn94T9JY7PvPq/tMfqa6GAaHpElRDpmG+SuL+D3+sTw2M8VhnibKnN8Tq+4JqbPJ/f+BwtLeDMEnzAvRg==",
"dependencies": {
"System.Formats.Asn1": "5.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "9TPLGjBCGKmNvG8pjwPeuYy0SMVmGZRwlTZvyPHDbYv/DRkoeumJdfumaaDNQzVGMEmbWtg07zUpSW9q70IlDQ==",
"dependencies": {
"System.Formats.Asn1": "5.0.0",
"System.Security.Cryptography.Cng": "5.0.0"
}
},
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog=="
}
}
}
}

View File

@@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Torch.Mod.Messages
{
/// <summary>
/// shim to store incoming message data
/// </summary>
internal class IncomingMessage : MessageBase
{
public IncomingMessage()
{
}
public override void ProcessClient()
{
throw new Exception();
}
public override void ProcessServer()
{
throw new Exception();
}
}
}

View File

@@ -32,11 +32,6 @@ namespace Torch.Mod.Messages
public override void ProcessClient() public override void ProcessClient()
{ {
if (TorchModCore.Debug)
{
MyAPIGateway.Utilities.ShowMessage("Torch", $"Joining server {Address} with delay {Delay}");
}
if (Delay <= 0) if (Delay <= 0)
{ {
MyAPIGateway.Multiplayer.JoinServer(Address); MyAPIGateway.Multiplayer.JoinServer(Address);

View File

@@ -1,9 +1,4 @@
using System; using ProtoBuf;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ProtoBuf;
namespace Torch.Mod.Messages namespace Torch.Mod.Messages
{ {
@@ -17,36 +12,9 @@ namespace Torch.Mod.Messages
[ProtoContract] [ProtoContract]
public abstract class MessageBase public abstract class MessageBase
{ {
[ProtoMember(101)]
public ulong SenderId; public ulong SenderId;
public abstract void ProcessClient(); public abstract void ProcessClient();
public abstract void ProcessServer(); public abstract void ProcessServer();
//members below not serialized, they're just metadata about the intended target(s) of this message
internal MessageTarget TargetType;
internal ulong Target;
internal ulong[] Ignore;
internal byte[] CompressedData;
}
public enum MessageTarget
{
/// <summary>
/// Send to Target
/// </summary>
Single,
/// <summary>
/// Send to Server
/// </summary>
Server,
/// <summary>
/// Send to all Clients (only valid from server)
/// </summary>
AllClients,
/// <summary>
/// Send to all except those steam ID listed in Ignore
/// </summary>
AllExcept,
} }
} }

View File

@@ -1,161 +1,50 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using Torch.Mod.Messages; using Torch.Mod.Messages;
using VRage; using VRage.Game.Components;
using VRage.Collections;
using VRage.Game.ModAPI; using VRage.Game.ModAPI;
using VRage.Network; #if TORCH
using VRage.Utils; using Torch.Utils;
using Task = ParallelTasks.Task; using VRage.Library.Collections;
using System.Reflection;
#endif
namespace Torch.Mod namespace Torch.Mod
{ {
public static class ModCommunication [MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)]
public class ModCommunication : MySessionComponentBase
{ {
public const ushort NET_ID = 4352; public const ulong MOD_ID = 2915950488;
private static bool _closing = false; private const ushort CHANNEL = 7654;
private static BlockingCollection<MessageBase> _processing;
private static MyConcurrentPool<IncomingMessage> _messagePool;
private static List<IMyPlayer> _playerCache;
public static void Register() public override void BeforeStart()
{ {
MyLog.Default.WriteLineAndConsole("TORCH MOD: Registering mod communication."); base.BeforeStart();
_processing = new BlockingCollection<MessageBase>(new ConcurrentQueue<MessageBase>()); MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(CHANNEL, MessageHandler);
_playerCache = new List<IMyPlayer>();
_messagePool = new MyConcurrentPool<IncomingMessage>(8);
MyAPIGateway.Multiplayer.RegisterMessageHandler(NET_ID, MessageHandler);
//background thread to handle de/compression and processing
_closing = false;
MyAPIGateway.Parallel.StartBackground(DoProcessing);
MyLog.Default.WriteLineAndConsole("TORCH MOD: Mod communication registered successfully.");
} }
public static void Unregister() private void MessageHandler(ushort channel, byte[] data, ulong sender, bool fromServer)
{ {
MyLog.Default.WriteLineAndConsole("TORCH MOD: Unregistering mod communication."); if (!fromServer)
MyAPIGateway.Multiplayer?.UnregisterMessageHandler(NET_ID, MessageHandler); return;
_processing?.CompleteAdding();
_closing = true; var message = MyAPIGateway.Utilities.SerializeFromBinary<MessageBase>(data);
//_task.Wait(); message.SenderId = sender;
if (MyAPIGateway.Multiplayer.IsServer) message.ProcessServer();
else message.ProcessClient();
} }
private static void MessageHandler(byte[] bytes)
{
var m = _messagePool.Get();
m.CompressedData = bytes;
#if TORCH #if TORCH
m.SenderId = MyEventContext.Current.Sender.Value; [ReflectedMethodInfo(typeof(MyAPIUtilities), "VRage.Game.ModAPI.IMyUtilities.SerializeToBinary")]
#endif private static MethodInfo _serializeMethod = null!;
_processing.Add(m); private static readonly CacheList<IMyPlayer> Players = new();
}
public static void DoProcessing() private static byte[] Serialize(MessageBase message)
{ {
while (!_closing) return (byte[])_serializeMethod.MakeGenericMethod(message.GetType())
{ .Invoke(MyAPIGateway.Utilities, new object[] { message });
try
{
MessageBase m;
try
{
m = _processing.Take();
}
catch
{
continue;
}
MyLog.Default.WriteLineAndConsole($"Processing message: {m.GetType().Name}");
if (m is IncomingMessage) //process incoming messages
{
MessageBase i;
try
{
var o = MyCompression.Decompress(m.CompressedData);
m.CompressedData = null;
_messagePool.Return((IncomingMessage)m);
i = MyAPIGateway.Utilities.SerializeFromBinary<MessageBase>(o);
}
catch (Exception ex)
{
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Failed to deserialize message! {ex}");
continue;
}
if (TorchModCore.Debug)
MyAPIGateway.Utilities.ShowMessage("Torch", $"Received message of type {i.GetType().Name}");
if (MyAPIGateway.Multiplayer.IsServer)
i.ProcessServer();
else
i.ProcessClient();
}
else //process outgoing messages
{
if (TorchModCore.Debug)
MyAPIGateway.Utilities.ShowMessage("Torch", $"Sending message of type {m.GetType().Name}");
var b = MyAPIGateway.Utilities.SerializeToBinary(m);
m.CompressedData = MyCompression.Compress(b);
switch (m.TargetType)
{
case MessageTarget.Single:
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, m.Target);
break;
case MessageTarget.Server:
MyAPIGateway.Multiplayer.SendMessageToServer(NET_ID, m.CompressedData);
break;
case MessageTarget.AllClients:
MyAPIGateway.Players.GetPlayers(_playerCache);
foreach (var p in _playerCache)
{
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId)
continue;
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
}
break;
case MessageTarget.AllExcept:
MyAPIGateway.Players.GetPlayers(_playerCache);
foreach (var p in _playerCache)
{
if (p.SteamUserId == MyAPIGateway.Multiplayer.MyId || m.Ignore.Contains(p.SteamUserId))
continue;
MyAPIGateway.Multiplayer.SendMessageTo(NET_ID, m.CompressedData, p.SteamUserId);
}
break;
default:
throw new Exception();
}
_playerCache.Clear();
}
}
catch (Exception ex)
{
MyLog.Default.WriteLineAndConsole($"TORCH MOD: Exception occurred in communication thread! {ex}");
}
}
MyLog.Default.WriteLineAndConsole("TORCH MOD: INFO: Communication thread shut down successfully! THIS IS NOT AN ERROR");
//exit signal received. Clean everything and GTFO
_processing?.Dispose();
_processing = null;
_messagePool?.Clean();
_messagePool = null;
_playerCache = null;
} }
public static void SendMessageTo(MessageBase message, ulong target) public static void SendMessageTo(MessageBase message, ulong target)
@@ -163,12 +52,7 @@ namespace Torch.Mod
if (!MyAPIGateway.Multiplayer.IsServer) if (!MyAPIGateway.Multiplayer.IsServer)
throw new Exception("Only server can send targeted messages"); throw new Exception("Only server can send targeted messages");
if (_closing) MyAPIGateway.Multiplayer.SendMessageTo(CHANNEL, Serialize(message), target);
return;
message.Target = target;
message.TargetType = MessageTarget.Single;
_processing.Add(message);
} }
public static void SendMessageToClients(MessageBase message) public static void SendMessageToClients(MessageBase message)
@@ -176,11 +60,7 @@ namespace Torch.Mod
if (!MyAPIGateway.Multiplayer.IsServer) if (!MyAPIGateway.Multiplayer.IsServer)
throw new Exception("Only server can send targeted messages"); throw new Exception("Only server can send targeted messages");
if (_closing) MyAPIGateway.Multiplayer.SendMessageToOthers(CHANNEL, Serialize(message));
return;
message.TargetType = MessageTarget.AllClients;
_processing.Add(message);
} }
public static void SendMessageExcept(MessageBase message, params ulong[] ignoredUsers) public static void SendMessageExcept(MessageBase message, params ulong[] ignoredUsers)
@@ -188,21 +68,20 @@ namespace Torch.Mod
if (!MyAPIGateway.Multiplayer.IsServer) if (!MyAPIGateway.Multiplayer.IsServer)
throw new Exception("Only server can send targeted messages"); throw new Exception("Only server can send targeted messages");
if (_closing) using var players = Players;
return; MyAPIGateway.Multiplayer.Players.GetPlayers(players, player => !ignoredUsers.Contains(player.SteamUserId));
message.TargetType = MessageTarget.AllExcept; var data = Serialize(message);
message.Ignore = ignoredUsers; foreach (var player in players)
_processing.Add(message); {
MyAPIGateway.Multiplayer.SendMessageTo(CHANNEL, data, player.SteamUserId);
}
} }
public static void SendMessageToServer(MessageBase message) public static void SendMessageToServer(MessageBase message)
{ {
if (_closing) throw new NotSupportedException();
return;
message.TargetType = MessageTarget.Server;
_processing.Add(message);
} }
#endif
} }
} }

View File

@@ -9,13 +9,11 @@
<Import_RootNamespace>Torch.Mod</Import_RootNamespace> <Import_RootNamespace>Torch.Mod</Import_RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Messages\IncomingMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\JoinServerMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\JoinServerMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\NotificationMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\NotificationMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\DialogMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\DialogMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\MessageBase.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\MessageBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\VoxelResetMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\VoxelResetMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ModCommunication.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ModCommunication.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TorchModCore.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.ModAPI;
using VRage.Game.Components;
namespace Torch.Mod
{
[MySessionComponentDescriptor(MyUpdateOrder.AfterSimulation)]
public class TorchModCore : MySessionComponentBase
{
public const ulong MOD_ID = 2722000298;
private static bool _init;
public static bool Debug;
public override void UpdateAfterSimulation()
{
if (_init)
return;
_init = true;
ModCommunication.Register();
MyAPIGateway.Utilities.MessageEntered += Utilities_MessageEntered;
}
private void Utilities_MessageEntered(string messageText, ref bool sendToOthers)
{
if (messageText == "@!debug")
{
Debug = !Debug;
MyAPIGateway.Utilities.ShowMessage("Torch", $"Debug: {Debug}");
sendToOthers = false;
}
}
protected override void UnloadData()
{
try
{
MyAPIGateway.Utilities.MessageEntered -= Utilities_MessageEntered;
ModCommunication.Unregister();
}
catch
{
//session unloading, don't care
}
}
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>Torch.Server.ReferenceAssemblies.net7</id>
<version>torchVersion</version>
<title>Torch Server Reference Assemblies</title>
<authors>zznty</authors>
<owners>zznty</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Torch Server Reference Assemblies (.NET 7 edition)</description>
<repository type="git" url="https://github.com/PveTeam/Torch" />
<dependencies>
<group targetFramework="net7.0-windows7.0">
<dependency id="SpaceEngineersDedicated.ReferenceAssemblies" version="1.202.120" />
<dependency id="Torch.Server" version="torchVersion" />
</group>
</dependencies>
<frameworkReferences>
<group targetFramework="net7.0-windows7.0">
<frameworkReference name="Microsoft.WindowsDesktop.App.WPF" />
</group>
</frameworkReferences>
</metadata>
</package>

View File

@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net7-windows</TargetFramework>
<LangVersion>10</LangVersion>
<NoWarn>1591,0649</NoWarn> <NoWarn>1591,0649</NoWarn>
<AssemblyTitle>Torch Server Tests</AssemblyTitle> <AssemblyTitle>Torch Server Tests</AssemblyTitle>
<Product>Torch</Product> <Product>Torch</Product>
@@ -18,8 +17,8 @@
</PropertyGroup> </PropertyGroup>
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> --> <!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="NLog" Version="5.0.4" /> <PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -16,6 +16,7 @@ using Microsoft.Extensions.Configuration;
using NLog; using NLog;
using NLog.Targets; using NLog.Targets;
using Sandbox.Engine.Utils; using Sandbox.Engine.Utils;
using SpaceEngineers.Game;
using Torch.Utils; using Torch.Utils;
using VRage.FileSystem; using VRage.FileSystem;
@@ -27,10 +28,11 @@ namespace Torch.Server
private static readonly Logger Log = LogManager.GetLogger(nameof(Initializer)); private static readonly Logger Log = LogManager.GetLogger(nameof(Initializer));
private bool _init; private bool _init;
private const string STEAMCMD_DIR = "steamcmd"; private const string TOOL_DIR = "tool";
private const string STEAMCMD_ZIP = "temp.zip"; private const string TOOL_ZIP = "temp.zip";
private static readonly string STEAMCMD_EXE = "steamcmd.exe"; private static readonly string TOOL_EXE = "DepotDownloader.exe";
private const string STEAMCMD_ARGS = "+force_install_dir \"{0}\" +login anonymous +app_update 298740 +quit"; private const string TOOL_ARGS = "-app 298740 -depot {1} -dir \"{0}\" -manifest {2}";
private static readonly int[] Depots = { 298741, 1004 };
private TorchServer _server; private TorchServer _server;
internal Persistent<TorchConfig> ConfigPersistent { get; } internal Persistent<TorchConfig> ConfigPersistent { get; }
@@ -56,8 +58,8 @@ namespace Torch.Server
Log.Debug("Debug logging enabled."); Log.Debug("Debug logging enabled.");
#endif #endif
if (!configuration.GetValue("noupdate", false)) if (configuration.GetValue("getGameUpdates", true) && !configuration.GetValue("noupdate", false))
RunSteamCmd(); RunSteamCmdAsync(configuration).Wait();
var processPid = configuration.GetValue<int>("waitForPid"); var processPid = configuration.GetValue<int>("waitForPid");
if (processPid != 0) if (processPid != 0)
@@ -102,76 +104,88 @@ namespace Torch.Server
} }
#endif #endif
var gameThread = new Thread(() => SpaceEngineersGame.SetupBasicGameInfo();
SpaceEngineersGame.SetupPerGameSettings();
var uiThread = new Thread(() =>
{ {
_server.Init(); var ui = new TorchUI(_server);
if (Config.Autostart || Config.TempAutostart) SynchronizationContext.SetSynchronizationContext(
{ new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
Config.TempAutostart = false;
_server.Start(); ui.ShowDialog();
}
}); });
gameThread.Start(); uiThread.SetApartmentState(ApartmentState.STA);
uiThread.Start();
_server.Init();
var ui = new TorchUI(_server);
if (Config.Autostart || Config.TempAutostart)
SynchronizationContext.SetSynchronizationContext( {
new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher)); Config.TempAutostart = false;
_server.Start();
ui.ShowDialog(); }
uiThread.Join();
} }
} }
public static void RunSteamCmd() public static async Task RunSteamCmdAsync(IConfiguration configuration)
{ {
var log = LogManager.GetLogger("SteamCMD"); var log = LogManager.GetLogger("SteamTool");
var path = Environment.GetEnvironmentVariable("TORCH_STEAMCMD") ?? Path.GetFullPath(STEAMCMD_DIR); var path = configuration.GetValue<string>("steamToolPath") ?? ApplicationContext.Current.TorchDirectory
.CreateSubdirectory(TOOL_DIR).FullName;
if (!Directory.Exists(path)) if (!Directory.Exists(path))
{ {
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
} }
var steamCmdExePath = Path.Combine(path, STEAMCMD_EXE); var steamCmdExePath = Path.Combine(path, TOOL_EXE);
if (!File.Exists(steamCmdExePath)) if (!File.Exists(steamCmdExePath))
{ {
try try
{ {
log.Info("Downloading SteamCMD."); log.Info("Downloading Steam Tool.");
using (var client = new HttpClient()) using (var client = new HttpClient())
using (var file = File.Create(STEAMCMD_ZIP)) await using (var file = File.Create(TOOL_ZIP))
client.GetStreamAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip").Result.CopyTo(file); await using (var stream = await client.GetStreamAsync("https://github.com/SteamRE/DepotDownloader/releases/download/DepotDownloader_2.4.7/depotdownloader-2.4.7.zip"))
await stream.CopyToAsync(file);
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, path); ZipFile.ExtractToDirectory(TOOL_ZIP, path);
File.Delete(STEAMCMD_ZIP); File.Delete(TOOL_ZIP);
log.Info("SteamCMD downloaded successfully!"); log.Info("Steam Tool downloaded successfully!");
} }
catch (Exception e) catch (Exception e)
{ {
log.Error(e, "Failed to download SteamCMD, unable to update the DS."); log.Error(e, "Failed to download Steam Tool, unable to update the DS.");
return; return;
} }
} }
log.Info("Checking for DS updates."); log.Info("Checking for DS updates.");
var steamCmdProc = new ProcessStartInfo(steamCmdExePath) foreach (var depot in Depots)
{ {
Arguments = string.Format(STEAMCMD_ARGS, Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? "../"), await DownloadDepot(depot);
WorkingDirectory = path, }
UseShellExecute = false,
RedirectStandardOutput = true, async Task DownloadDepot(int depotId)
StandardOutputEncoding = Encoding.ASCII {
}; var steamCmdProc = new ProcessStartInfo(steamCmdExePath)
var cmd = Process.Start(steamCmdProc); {
Arguments = string.Format(TOOL_ARGS, configuration.GetValue("gamePath", "../"), depotId, "3111493184861426002"),
WorkingDirectory = path,
RedirectStandardOutput = true
};
var cmd = Process.Start(steamCmdProc)!;
// ReSharper disable once PossibleNullReferenceException while (!cmd.HasExited)
while (!cmd.HasExited) {
{ if (await cmd.StandardOutput.ReadLineAsync() is { } line)
log.Info(cmd.StandardOutput.ReadLine()); log.Info(line);
Thread.Sleep(100); }
} }
} }
} }

View File

@@ -90,8 +90,7 @@ namespace Torch.Server.Managers
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error("Failed to load world at path: " + f); Log.Error(ex, "Failed to load world at path: " + f);
continue;
} }
} }
@@ -137,7 +136,7 @@ namespace Torch.Server.Managers
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error("Failed to load world at path: " + worldPath); Log.Error(ex, "Failed to load world at path: " + worldPath);
DedicatedConfig.LoadWorld = null; DedicatedConfig.LoadWorld = null;
return; return;
} }
@@ -147,7 +146,7 @@ namespace Torch.Server.Managers
{ {
DedicatedConfig.Mods.Clear(); DedicatedConfig.Mods.Clear();
//remove the Torch mod to avoid running multiple copies of it //remove the Torch mod to avoid running multiple copies of it
DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID); DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == ModCommunication.MOD_ID);
foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods) foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods)
DedicatedConfig.Mods.Add(new ModItemInfo(m)); DedicatedConfig.Mods.Add(new ModItemInfo(m));
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync()); Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());
@@ -162,7 +161,7 @@ namespace Torch.Server.Managers
{ {
DedicatedConfig.Mods.Clear(); DedicatedConfig.Mods.Clear();
//remove the Torch mod to avoid running multiple copies of it //remove the Torch mod to avoid running multiple copies of it
DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == TorchModCore.MOD_ID); DedicatedConfig.SelectedWorld.WorldConfiguration.Mods.RemoveAll(m => m.PublishedFileId == ModCommunication.MOD_ID);
foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods) foreach (var m in DedicatedConfig.SelectedWorld.WorldConfiguration.Mods)
DedicatedConfig.Mods.Add(new ModItemInfo(m)); DedicatedConfig.Mods.Add(new ModItemInfo(m));
Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync()); Task.Run(() => DedicatedConfig.UpdateAllModInfosAsync());

View File

@@ -32,7 +32,7 @@ namespace Torch.Server.Managers
{ {
if (newstate == TorchGameState.Loading && MySandboxGame.ConfigDedicated.RemoteApiEnabled && !string.IsNullOrEmpty(MySandboxGame.ConfigDedicated.RemoteSecurityKey)) if (newstate == TorchGameState.Loading && MySandboxGame.ConfigDedicated.RemoteApiEnabled && !string.IsNullOrEmpty(MySandboxGame.ConfigDedicated.RemoteSecurityKey))
{ {
var myRemoteServer = new MyRemoteServer(MySandboxGame.ConfigDedicated.RemoteApiPort, MySandboxGame.ConfigDedicated.RemoteSecurityKey); var myRemoteServer = new MyRemoteServer(MySandboxGame.ConfigDedicated.RemoteApiIP, MySandboxGame.ConfigDedicated.RemoteApiPort, MySandboxGame.ConfigDedicated.RemoteSecurityKey);
LogManager.GetCurrentClassLogger().Info($"Remote API started on port {myRemoteServer.Port}"); LogManager.GetCurrentClassLogger().Info($"Remote API started on port {myRemoteServer.Port}");
} }
} }

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using NLog;
using Steamworks;
using Torch.Managers.PatchManager;
using Torch.Utils;
namespace Torch.Patches;
[PatchShim]
public static class SteamLoginPatch
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
[ReflectedMethodInfo(null, "LogOnAnonymous", TypeName = "VRage.Steam.MySteamGameServer, VRage.Steam")]
private static MethodInfo LoginMethod = null!;
public static void Patch(PatchContext context)
{
context.GetPattern(LoginMethod).AddPrefix();
}
private static bool Prefix()
{
#pragma warning disable CS0618
var token = TorchBase.Instance.Config.LoginToken;
#pragma warning restore CS0618
if (string.IsNullOrEmpty(token))
return true;
Log.Info("Logging in to Steam with GSLT");
SteamGameServer.LogOn(token);
return false;
}
}

View File

@@ -12,7 +12,7 @@ namespace Torch.Server
{ {
internal static class Program internal static class Program
{ {
[STAThread] [MTAThread]
public static void Main(string[] args) public static void Main(string[] args)
{ {
var configurationBuilder = new ConfigurationBuilder() var configurationBuilder = new ConfigurationBuilder()
@@ -89,8 +89,6 @@ namespace Torch.Server
var workingDir = AppContext.BaseDirectory; var workingDir = AppContext.BaseDirectory;
var gamePath = configuration.GetValue("gamePath", workingDir); var gamePath = configuration.GetValue("gamePath", workingDir);
var binDir = Path.Combine(gamePath, "DedicatedServer64"); var binDir = Path.Combine(gamePath, "DedicatedServer64");
Directory.SetCurrentDirectory(gamePath);
var instanceName = configuration.GetValue("instanceName", "Instance"); var instanceName = configuration.GetValue("instanceName", "Instance");
string instancePath; string instancePath;
@@ -105,6 +103,8 @@ namespace Torch.Server
instancePath = Directory.CreateDirectory(instanceName!).FullName; instancePath = Directory.CreateDirectory(instanceName!).FullName;
} }
Directory.SetCurrentDirectory(gamePath);
return new ApplicationContext(new(workingDir), new(gamePath), new(binDir), return new ApplicationContext(new(workingDir), new(gamePath), new(binDir),
new(instancePath), instanceName, isService); new(instancePath), instanceName, isService);
} }

View File

@@ -1,21 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net7-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<LangVersion>10</LangVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<PublishUrl>publish\</PublishUrl> <PublishUrl>publish\</PublishUrl>
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<AssemblyTitle>Torch Server</AssemblyTitle> <AssemblyTitle>Torch Server</AssemblyTitle>
<Product>Torch</Product> <Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright> <Copyright>Copyright © Torch API 2017</Copyright>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath> <OutputPath>..\bin\$(Platform)\$(Configuration)\</OutputPath>
<UseWPF>true</UseWPF> <UseWPF>true</UseWPF>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<PlatformTarget>x64</PlatformTarget> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<TieredPGO>true</TieredPGO>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
@@ -35,20 +34,24 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoCompleteTextBox" Version="1.6.0" /> <PackageReference Include="AutoCompleteTextBox" Version="1.6.0" />
<PackageReference Include="Ben.Demystifier" Version="0.4.1" /> <PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="ControlzEx" Version="5.0.1" /> <PackageReference Include="ControlzEx" Version="5.0.2" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" /> <PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="MdXaml" Version="1.16.0" /> <PackageReference Include="MdXaml" Version="1.16.2" />
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.2.332302" /> <PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.2.343001" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Xml" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Xml" Version="7.0.0" />
<PackageReference Include="NLog" Version="5.0.4" /> <PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="PropertyChanged.Fody" Version="4.0.3" PrivateAssets="all" /> <PackageReference Include="PropertyChanged.Fody" Version="4.0.3" PrivateAssets="all" />
<PackageReference Include="Steamworks.NET" Version="20.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Management" Version="6.0.0" /> <PackageReference Include="System.Management" Version="7.0.0" />
<PackageReference Include="nulastudio.NetCoreBeauty" Version="1.2.9.3" /> <PackageReference Include="nulastudio.NetCoreBeauty" Version="1.2.9.3" />
<PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.201.13"> <PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.202.120">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile</IncludeAssets> <IncludeAssets>compile</IncludeAssets>
</PackageReference> </PackageReference>

View File

@@ -1,167 +1,127 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Xml.Serialization; using System.Xml.Serialization;
using NLog;
using Torch.API; using Torch.API;
using Torch.Views; using Torch.Views;
namespace Torch.Server namespace Torch.Server;
public class TorchConfig : ViewModel, ITorchConfig
{ {
// TODO: redesign this gerbage public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate;
public class TorchConfig : CommandLine, ITorchConfig, INotifyPropertyChanged public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate;
/// <inheritdoc />
[XmlIgnore]
public bool NoUpdate { get; set; }
/// <inheritdoc />
[XmlIgnore]
public bool ForceUpdate { get; set; }
/// <summary>
/// Permanent flag to ALWAYS automatically start the server
/// </summary>
[Display(Name = "Auto Start", Description = "Permanent flag to ALWAYS automatically start the server.", GroupName = "Server")]
public bool Autostart { get; set; }
/// <summary>
/// Temporary flag to automatically start the server only on the next run
/// </summary>
[XmlIgnore]
public bool TempAutostart { get; set; }
/// <inheritdoc />
[Display(Name = "Restart On Crash", Description = "Automatically restart the server if it crashes.", GroupName = "Server")]
public bool RestartOnCrash { get; set; }
public string InstancePath { get; set; }
/// <inheritdoc />
[Display(Name = "No GUI", Description = "Do not show the Torch UI.", GroupName = "Window")]
public bool NoGui { get; set; }
/// <inheritdoc />
[Display(Name = "Update Torch", Description = "Check every start for new versions of torch.",
GroupName = "Server")]
public bool GetTorchUpdates { get; set; } = true;
public string InstanceName { get; set; }
/// <inheritdoc />
[Display(Name = "Update Plugins", Description = "Check every start for new versions of plugins.",
GroupName = "Server")]
public bool GetPluginUpdates { get; set; } = true;
/// <inheritdoc />
[Display(Name = "Watchdog Timeout", Description = "Watchdog timeout (in seconds).", GroupName = "Server")]
public int TickTimeout { get; set; } = 60;
/// <inheritdoc />
public List<Guid> Plugins { get; set; } = new();
[Display(Name = "Local Plugins", Description = "Loads all pluhins from disk, ignores the plugins defined in config.", GroupName = "In-Game")]
public bool LocalPlugins { get; set; }
[Display(Name = "Auto Disconnect", Description = "When server restarts, all clients are rejected to main menu to prevent auto rejoin.", GroupName = "In-Game")]
public bool DisconnectOnRestart { get; set; }
[Display(Name = "Chat Name", Description = "Default name for chat from gui, broadcasts etc..",
GroupName = "In-Game")]
public string ChatName { get; set; } = "Server";
[Display(Name = "Chat Color",
Description = "Default color for chat from gui, broadcasts etc.. (Red, Blue, White, Green)",
GroupName = "In-Game")]
public string ChatColor { get; set; } = "Red";
[Display(Name = "Enable Whitelist", Description = "Enable Whitelist to prevent random players join while maintance, tests or other.", GroupName = "In-Game")]
public bool EnableWhitelist { get; set; }
[Display(Name = "Whitelist", Description = "Collection of whitelisted steam ids.", GroupName = "In-Game")]
public List<ulong> Whitelist { get; set; } = new();
[Display(Name = "Width", Description = "Default window width.", GroupName = "Window")]
public int WindowWidth { get; set; } = 980;
[Display(Name = "Height", Description = "Default window height", GroupName = "Window")]
public int WindowHeight { get; set; } = 588;
[Display(Name = "Font Size", Description = "Font size for logging text box. (default is 16)",
GroupName = "Window")]
public int FontSize { get; set; } = 16;
[Display(Name = "UGC Service Type", Description = "Service for downloading mods", GroupName = "Server")]
public UGCServiceType UgcServiceType { get; set; } = UGCServiceType.Steam;
public string LastUsedTheme { get; set; } = "Torch Theme";
[Display(Name = "Independent Console", Description = "Keeps a separate console window open after the main UI loads.", GroupName = "Window")]
public bool IndependentConsole { get; set; }
[XmlIgnore]
public string TestPlugin { get; set; }
[Display(Name = "Enable Asserts", Description = "Enable Keen's assert logging.", GroupName = "Server")]
public bool EnableAsserts { get; set; }
[Display(Name = "Enable Entity Manager", Description = "Enable Entity Manager tab. (can affect performance)",
GroupName = "Server")]
public bool EntityManagerEnabled { get; set; } = true;
[Display(Name = "Login Token", Description = "Steam GSLT (can be used if you have dynamic ip)", GroupName = "Server")]
public string LoginToken { get; set; }
public UpdateSource UpdateSource { get; set; } = new()
{ {
private static Logger _log = LogManager.GetLogger("Config"); Repository = "PveTeam/Torch",
Url = "https://api.github.com",
SourceType = UpdateSourceType.Github
};
public bool ShouldUpdatePlugins => (GetPluginUpdates && !NoUpdate) || ForceUpdate; [Display(Name = "Packages", Description = "Packages to install and use.", GroupName = "Server")]
public bool ShouldUpdateTorch => (GetTorchUpdates && !NoUpdate) || ForceUpdate; public List<string> Packages { get; set; } = new();
private bool _autostart;
private bool _restartOnCrash;
private bool _noGui;
private bool _getPluginUpdates = true;
private bool _getTorchUpdates = true;
private int _tickTimeout = 60;
private bool _localPlugins;
private bool _disconnectOnRestart;
private string _chatName = "Server";
private string _chatColor = "Red";
private bool _enableWhitelist = false;
private List<ulong> _whitelist = new List<ulong>();
private int _windowWidth = 980;
private int _windowHeight = 588;
private bool _independentConsole = false;
private bool _enableAsserts = false;
private int _fontSize = 16;
private UGCServiceType _ugcServiceType = UGCServiceType.Steam;
private bool _entityManagerEnabled = true;
/// <inheritdoc /> // for backward compatibility
[XmlIgnore, Arg("noupdate", "Disable automatically downloading game and plugin updates.")] public void Save(string path = null) => Initializer.Instance?.ConfigPersistent?.Save(path);
public bool NoUpdate { get; set; } }
/// <inheritdoc />
[XmlIgnore, Arg("forceupdate", "Manually check for and install updates.")]
public bool ForceUpdate { get; set; }
/// <summary>
/// Permanent flag to ALWAYS automatically start the server
/// </summary>
[Display(Name = "Auto Start", Description = "Permanent flag to ALWAYS automatically start the server.", GroupName = "Server")]
public bool Autostart { get => _autostart; set => Set(value, ref _autostart); }
/// <summary>
/// Temporary flag to automatically start the server only on the next run
/// </summary>
[Arg("autostart", "Start the server immediately.")]
[XmlIgnore]
public bool TempAutostart { get; set; }
/// <inheritdoc />
[Arg("restartoncrash", "Automatically restart the server if it crashes.")]
[Display(Name = "Restart On Crash", Description = "Automatically restart the server if it crashes.", GroupName = "Server")]
public bool RestartOnCrash { get => _restartOnCrash; set => Set(value, ref _restartOnCrash); }
public string InstancePath { get; set; }
/// <inheritdoc />
[Arg("nogui", "Do not show the Torch UI.")]
[Display(Name = "No GUI", Description = "Do not show the Torch UI.", GroupName = "Window")]
public bool NoGui { get => _noGui; set => Set(value, ref _noGui); }
/// <inheritdoc />
[Display(Name = "Update Torch", Description = "Check every start for new versions of torch.", GroupName = "Server")]
public bool GetTorchUpdates { get => _getTorchUpdates; set => Set(value, ref _getTorchUpdates); }
public string InstanceName { get; set; }
/// <inheritdoc />
[Display(Name = "Update Plugins", Description = "Check every start for new versions of plugins.", GroupName = "Server")]
public bool GetPluginUpdates { get => _getPluginUpdates; set => Set(value, ref _getPluginUpdates); }
/// <inheritdoc />
[Display(Name = "Watchdog Timeout", Description = "Watchdog timeout (in seconds).", GroupName = "Server")]
public int TickTimeout { get => _tickTimeout; set => Set(value, ref _tickTimeout); }
/// <inheritdoc />
[Arg("plugins", "Starts Torch with the given plugin GUIDs (space delimited).")]
public List<Guid> Plugins { get; set; } = new List<Guid>();
[Arg("localplugins", "Loads all pluhins from disk, ignores the plugins defined in config.")]
[Display(Name = "Local Plugins", Description = "Loads all pluhins from disk, ignores the plugins defined in config.", GroupName = "In-Game")]
public bool LocalPlugins { get => _localPlugins; set => Set(value, ref _localPlugins); }
[Arg("disconnect", "When server restarts, all clients are rejected to main menu to prevent auto rejoin.")]
[Display(Name = "Auto Disconnect", Description = "When server restarts, all clients are rejected to main menu to prevent auto rejoin.", GroupName = "In-Game")]
public bool DisconnectOnRestart { get => _disconnectOnRestart; set => Set(value, ref _disconnectOnRestart); }
[Display(Name = "Chat Name", Description = "Default name for chat from gui, broadcasts etc..", GroupName = "In-Game")]
public string ChatName { get => _chatName; set => Set(value, ref _chatName); }
[Display(Name = "Chat Color", Description = "Default color for chat from gui, broadcasts etc.. (Red, Blue, White, Green)", GroupName = "In-Game")]
public string ChatColor { get => _chatColor; set => Set(value, ref _chatColor); }
[Display(Name = "Enable Whitelist", Description = "Enable Whitelist to prevent random players join while maintance, tests or other.", GroupName = "In-Game")]
public bool EnableWhitelist { get => _enableWhitelist; set => Set(value, ref _enableWhitelist); }
[Display(Name = "Whitelist", Description = "Collection of whitelisted steam ids.", GroupName = "In-Game")]
public List<ulong> Whitelist { get => _whitelist; set => Set(value, ref _whitelist); }
[Display(Name = "Width", Description = "Default window width.", GroupName = "Window")]
public int WindowWidth { get => _windowWidth; set => Set(value, ref _windowWidth); }
[Display(Name = "Height", Description = "Default window height", GroupName = "Window")]
public int WindowHeight { get => _windowHeight; set => Set(value, ref _windowHeight); }
[Display(Name = "Font Size", Description = "Font size for logging text box. (default is 16)", GroupName = "Window")]
public int FontSize { get => _fontSize; set => Set(value, ref _fontSize); }
[Display(Name = "UGC Service Type", Description = "Service for downloading mods", GroupName = "Server")]
public UGCServiceType UgcServiceType
{
get => _ugcServiceType;
set => Set(value, ref _ugcServiceType);
}
public string LastUsedTheme { get; set; } = "Torch Theme";
//Prevent reserved players being written to disk, but allow it to be read
//remove this when ReservedPlayers is removed
private bool ShouldSerializeReservedPlayers() => false;
[Arg("console", "Keeps a separate console window open after the main UI loads.")]
[Display(Name = "Independent Console", Description = "Keeps a separate console window open after the main UI loads.", GroupName = "Window")]
public bool IndependentConsole { get => _independentConsole; set => Set(value, ref _independentConsole); }
[XmlIgnore]
[Arg("testplugin", "Path to a plugin to debug. For development use only.")]
public string TestPlugin { get; set; }
[Arg("asserts", "Enable Keen's assert logging.")]
[Display(Name = "Enable Asserts", Description = "Enable Keen's assert logging.", GroupName = "Server")]
public bool EnableAsserts { get => _enableAsserts; set => Set(value, ref _enableAsserts); }
[Display(Name = "Enable Entity Manager", Description = "Enable Entity Manager tab. (can affect performance)",
GroupName = "Server")]
public bool EntityManagerEnabled
{
get => _entityManagerEnabled;
set => Set(value, ref _entityManagerEnabled);
}
public event PropertyChangedEventHandler PropertyChanged;
public TorchConfig() { }
protected void Set<T>(T value, ref T field, [CallerMemberName] string callerName = default)
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(callerName));
}
// for backward compatibility
public void Save(string path = null) => Initializer.Instance?.ConfigPersistent?.Save(path);
}
}

View File

@@ -3,11 +3,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Microsoft.Diagnostics.Runtime; using Microsoft.Diagnostics.Runtime;
using NLog; using NLog;
using PropertyChanged; using PropertyChanged;
@@ -21,8 +20,6 @@ using Torch.API.Session;
using Torch.Commands; using Torch.Commands;
using Torch.Managers.PatchManager; using Torch.Managers.PatchManager;
using Torch.Mod; using Torch.Mod;
using Torch.Mod.Messages;
using Torch.Patches;
using Torch.Server.Commands; using Torch.Server.Commands;
using Torch.Server.Managers; using Torch.Server.Managers;
using Torch.Utils; using Torch.Utils;
@@ -59,9 +56,7 @@ namespace Torch.Server
AddManager(new EntityControlManager(this)); AddManager(new EntityControlManager(this));
AddManager(new RemoteAPIManager(this)); AddManager(new RemoteAPIManager(this));
var sessionManager = Managers.GetManager<ITorchSessionManager>();
sessionManager.AddFactory(x => new MultiplayerManagerDedicated(this));
sessionManager.SessionStateChanged += OnSessionStateChanged;
// Needs to be done at some point after MyVRageWindows.Init // Needs to be done at some point after MyVRageWindows.Init
// where the debug listeners are registered // where the debug listeners are registered
@@ -131,6 +126,10 @@ namespace Torch.Server
{ {
Log.Info("Initializing server"); Log.Info("Initializing server");
base.Init(); base.Init();
var sessionManager = Managers.GetManager<ITorchSessionManager>();
sessionManager.AddFactory(x => new MultiplayerManagerDedicated(this));
sessionManager.SessionStateChanged += OnSessionStateChanged;
GetManager<InstanceManager>().LoadInstance(InstancePath); GetManager<InstanceManager>().LoadInstance(InstancePath);
CanRun = true; CanRun = true;
Initialized?.Invoke(this); Initialized?.Invoke(this);
@@ -205,15 +204,31 @@ namespace Torch.Server
new Thread(() => new Thread(() =>
{ {
StopInternal(); StopInternal();
var config = (TorchConfig)Config;
LogManager.Flush(); LogManager.Flush();
string exe = Assembly.GetExecutingAssembly().Location.Replace("dll", "exe");
config.TempAutostart = true; #if DEBUG
Environment.Exit(0);
Process.Start(exe, $"-waitForPid {Environment.ProcessId} {config}"); #endif
var exe = Path.Combine(AppContext.BaseDirectory, "Torch.Server.exe");
var args = Environment.GetCommandLineArgs();
for (var i = 0; i < args.Length; i++)
{
if (args[i].Contains(' '))
args[i] = $"\"{args[i]}\"";
if (!args[i].Contains("--tempAutostart", StringComparison.InvariantCultureIgnoreCase) &&
!args[i].Contains("--waitForPid", StringComparison.InvariantCultureIgnoreCase))
continue;
args[i] = string.Empty;
args[++i] = string.Empty;
}
Process.Start(exe, $"--waitForPid {Environment.ProcessId} --tempAutostart true {string.Join(" ", args)}");
Environment.Exit(0); Environment.Exit(0);
}) })
{ {
@@ -229,14 +244,12 @@ namespace Torch.Server
case TorchSessionState.Unloading: case TorchSessionState.Unloading:
_watchdog?.Dispose(); _watchdog?.Dispose();
_watchdog = null; _watchdog = null;
ModCommunication.Unregister();
break; break;
case TorchSessionState.Loaded: case TorchSessionState.Loaded:
_multiplayerManagerDedicated = CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>(); _multiplayerManagerDedicated = CurrentSession.Managers.GetManager<MultiplayerManagerDedicated>();
_multiplayerManagerDedicated.PlayerJoined += MultiplayerManagerDedicatedOnPlayerJoined; _multiplayerManagerDedicated.PlayerJoined += MultiplayerManagerDedicatedOnPlayerJoined;
_multiplayerManagerDedicated.PlayerLeft += MultiplayerManagerDedicatedOnPlayerLeft; _multiplayerManagerDedicated.PlayerLeft += MultiplayerManagerDedicatedOnPlayerLeft;
CurrentSession.Managers.GetManager<CommandManager>().RegisterCommandModule(typeof(WhitelistCommands)); CurrentSession.Managers.GetManager<CommandManager>().RegisterCommandModule(typeof(WhitelistCommands));
ModCommunication.Register();
break; break;
case TorchSessionState.Loading: case TorchSessionState.Loading:
case TorchSessionState.Unloaded: case TorchSessionState.Unloaded:

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Threading; using System.Threading;
using NLog; using NLog;
using VRage; using VRage;
@@ -31,9 +32,25 @@ internal class UnhandledExceptionHandler
{ {
Console.WriteLine("Restarting in 5 seconds."); Console.WriteLine("Restarting in 5 seconds.");
Thread.Sleep(5000); Thread.Sleep(5000);
var exe = typeof(Program).Assembly.Location;
Process.Start(exe, $"-waitForPid {Environment.ProcessId} {_config}"); var exe = Path.Combine(AppContext.BaseDirectory, "Torch.Server.exe");
var args = Environment.GetCommandLineArgs();
for (var i = 0; i < args.Length; i++)
{
if (args[i].Contains(' '))
args[i] = $"\"{args[i]}\"";
if (!args[i].Contains("--tempAutostart", StringComparison.InvariantCultureIgnoreCase) &&
!args[i].Contains("--waitForPid", StringComparison.InvariantCultureIgnoreCase))
continue;
args[i] = string.Empty;
args[++i] = string.Empty;
}
Process.Start(exe, $"--waitForPid {Environment.ProcessId} --tempAutostart true {string.Join(" ", args)}");
} }
else else
{ {

View File

@@ -22,6 +22,8 @@ using Torch.Collections;
using Torch.Server.Annotations; using Torch.Server.Annotations;
using Torch.Managers; using Torch.Managers;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.API.Plugins;
using Torch.API.WebAPI.Plugin;
namespace Torch.Server.Views namespace Torch.Server.Views
{ {
@@ -55,39 +57,38 @@ namespace Torch.Server.Views
InitializeComponent(); InitializeComponent();
var installedPlugins = pluginManager.Plugins; var installedPlugins = pluginManager.Plugins;
BindingOperations.EnableCollectionSynchronization(Plugins, _syncLock); LoadAsync(installedPlugins);
Task.Run(async () =>
{
try
{
var res = await PluginQuery.Instance.QueryAll();
foreach (var item in res.Plugins.OrderBy(i => i.Name)) {
lock (_syncLock)
{
var pluginItem = item with
{
Description = item.Description.Replace("&lt;", "<").Replace("&gt;", ">"),
Installed = installedPlugins.Keys.Contains(item.Id)
};
Plugins.Add(pluginItem);
PluginsSource.Add(pluginItem);
}
}
Dispatcher.Invoke(() => PluginsList.SelectedIndex = 0);
CurrentDescription = "Please select a plugin...";
}
catch (Exception e)
{
MessageBox.Show(e.ToString(), "An Error Occurred", MessageBoxButton.OK, MessageBoxImage.Error);
Close();
throw;
}
});
MarkdownFlow.CommandBindings.Add(new CommandBinding(NavigationCommands.GoToPage, (sender, e) => OpenUri((string)e.Parameter))); MarkdownFlow.CommandBindings.Add(new CommandBinding(NavigationCommands.GoToPage, (sender, e) => OpenUri((string)e.Parameter)));
} }
private async void LoadAsync(IReadOnlyDictionary<Guid, ITorchPlugin> installedPlugins)
{
try
{
var res = await LegacyPluginQuery.Instance.QueryAll();
foreach (var item in res.Plugins.OrderBy(i => i.Name))
{
var pluginItem = item with
{
Description = item.Description?.Replace("&lt;", "<").Replace("&gt;", ">") ?? string.Empty,
Installed = installedPlugins.Keys.Contains(item.Id)
};
Plugins.Add(pluginItem);
PluginsSource.Add(pluginItem);
}
PluginsList.SelectedIndex = 0;
CurrentDescription = "Please select a plugin...";
}
catch (Exception e)
{
MessageBox.Show(e.ToString(), "An Error Occurred", MessageBoxButton.OK, MessageBoxImage.Error);
Close();
throw;
}
}
public static bool IsValidUri(string uri) public static bool IsValidUri(string uri)
{ {
if (!Uri.IsWellFormedUriString(uri, UriKind.Absolute)) if (!Uri.IsWellFormedUriString(uri, UriKind.Absolute))

View File

@@ -14,6 +14,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using System.ComponentModel; using System.ComponentModel;
using Torch.API.WebAPI.Plugin;
namespace Torch.Server.Views namespace Torch.Server.Views
{ {
@@ -50,7 +51,7 @@ namespace Torch.Server.Views
var PercentChangeOnDownload = 100 / PluginsToDownload.Count; var PercentChangeOnDownload = 100 / PluginsToDownload.Count;
foreach (PluginItem PluginItem in PluginsToDownload) { foreach (PluginItem PluginItem in PluginsToDownload) {
if (!PluginQuery.Instance.DownloadPlugin(PluginItem.Id).Result) { if (!LegacyPluginQuery.Instance.DownloadPlugin(PluginItem.Id).Result) {
failedDownloads++; failedDownloads++;
DownloadProgress += PercentChangeOnDownload; DownloadProgress += PercentChangeOnDownload;
(sender as BackgroundWorker).ReportProgress(DownloadProgress); (sender as BackgroundWorker).ReportProgress(DownloadProgress);

View File

@@ -71,7 +71,7 @@ namespace Torch.Server.Views
private void OpenFolder_OnClick(object sender, RoutedEventArgs e) private void OpenFolder_OnClick(object sender, RoutedEventArgs e)
{ {
if (_plugins?.PluginDir != null) if (_plugins?.PluginDir != null)
Process.Start(_plugins.PluginDir); Process.Start("explorer", _plugins.PluginDir);
} }
private void BrowsPlugins_OnClick(object sender, RoutedEventArgs e) private void BrowsPlugins_OnClick(object sender, RoutedEventArgs e)

View File

@@ -0,0 +1,659 @@
{
"version": 1,
"dependencies": {
"net7.0-windows7.0": {
"AutoCompleteTextBox": {
"type": "Direct",
"requested": "[1.6.0, )",
"resolved": "1.6.0",
"contentHash": "xTQB0o3yZzBqKqWTZYlu22wTpIeXAsBU3+wswQ44D6rTMMTBXqr3AjxqrvjFcnoZXtLZgjPNRIUwnfsFSsH46g=="
},
"Ben.Demystifier": {
"type": "Direct",
"requested": "[0.4.1, )",
"resolved": "0.4.1",
"contentHash": "axFeEMfmEORy3ipAzOXG/lE+KcNptRbei3F0C4kQCdeiQtW+qJW90K5iIovITGrdLt8AjhNCwk5qLSX9/rFpoA==",
"dependencies": {
"System.Reflection.Metadata": "5.0.0"
}
},
"ControlzEx": {
"type": "Direct",
"requested": "[5.0.2, )",
"resolved": "5.0.2",
"contentHash": "f724LoDJ36LxaLR62G4ek9ZAJI8BiiYRJJ04furC/qjXSeIwU0qmHFIe19xB1/FwxyZjevdFguEr9ZUjf3dZgw==",
"dependencies": {
"Microsoft.Xaml.Behaviors.Wpf": "1.1.31",
"System.Text.Json": "5.0.1"
}
},
"MahApps.Metro": {
"type": "Direct",
"requested": "[2.4.9, )",
"resolved": "2.4.9",
"contentHash": "eMTkg6TBnCwHzszw7CP+pxsBeB4ZMsJFiTJJoifUVBysRyEenzzR+TKQJuMvvKK6KzvLxwHmJsFhi9o5p4vxhQ==",
"dependencies": {
"ControlzEx": "[4.4.0, 6.0.0)"
}
},
"MdXaml": {
"type": "Direct",
"requested": "[1.16.2, )",
"resolved": "1.16.2",
"contentHash": "UxYWJaXugQ+v4txS7K8sw26bxS6xvjSW+f79U/oMBtm+cVvZSmBGRZkB77rzaoXAyrbixX0Vk0U+1f22TT2uWg==",
"dependencies": {
"AvalonEdit": "6.0.0",
"MdXaml.Plugins": "1.16.2"
}
},
"Microsoft.Diagnostics.Runtime": {
"type": "Direct",
"requested": "[2.2.343001, )",
"resolved": "2.2.343001",
"contentHash": "nVwXLEJiXbnnNkQPcWy64DMWNBvMg9XKWpfBBNMVs1gbEJ9HUVTcAA862mOjgyKryAtvGVKkeQ/9cW6pK8YAiA==",
"dependencies": {
"Microsoft.Diagnostics.NETCore.Client": "0.2.251802",
"System.Collections.Immutable": "5.0.0",
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Direct",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "tgU4u7bZsoS9MKVRiotVMAwHtbREHr5/5zSEV+JPhg46+ox47Au84E3D2IacAaB0bk5ePNaNieTlPrfjbbRJkg==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0"
}
},
"Microsoft.Extensions.Configuration.CommandLine": {
"type": "Direct",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "a8Iq8SCw5m8W5pZJcPCgBpBO4E89+NaObPng+ApIhrGSv9X4JPrcFAaGM4sDgR0X83uhLgsNJq8VnGP/wqhr8A==",
"dependencies": {
"Microsoft.Extensions.Configuration": "7.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0"
}
},
"Microsoft.Extensions.Configuration.EnvironmentVariables": {
"type": "Direct",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "RIkfqCkvrAogirjsqSrG1E1FxgrLsOZU2nhRbl07lrajnxzSU2isj2lwQah0CtCbLWo/pOIukQzM1GfneBUnxA==",
"dependencies": {
"Microsoft.Extensions.Configuration": "7.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0"
}
},
"Microsoft.Extensions.Configuration.Xml": {
"type": "Direct",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "KXmN/llitPZydHbW3cac7MQHGPUXPi4783tYxsBqSAFJgcaYTdLmsboFL7Ss4TsnelF0+v4ZMpEkKTtCtpysiA==",
"dependencies": {
"Microsoft.Extensions.Configuration": "7.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
"Microsoft.Extensions.Configuration.FileExtensions": "7.0.0",
"Microsoft.Extensions.FileProviders.Abstractions": "7.0.0",
"System.Security.Cryptography.Xml": "7.0.0"
}
},
"NLog": {
"type": "Direct",
"requested": "[5.1.0, )",
"resolved": "5.1.0",
"contentHash": "oW7ekrkRG9okpDMUcEglunWj8Qf2RY8qkgl+/chJoavzg3dbT13y32t19R54FKkmq80fKzw4ZekZkCrRGanKgQ=="
},
"nulastudio.NetCoreBeauty": {
"type": "Direct",
"requested": "[1.2.9.3, )",
"resolved": "1.2.9.3",
"contentHash": "ToUOoX/sNCg6oeMcSrHYBBRPjKp1gDS7I5WNnY+59t7iNFPcAojpc2lYmgCm3hxwXW/xCxyQIV4k5agegqc3fg=="
},
"PropertyChanged.Fody": {
"type": "Direct",
"requested": "[4.0.3, )",
"resolved": "4.0.3",
"contentHash": "vRxEDydahhcetswKaoUug7mQywgpZpHoeHZWa7Opb+H+eWPUQo+f+v37Jy+rrG1RVOCkyziTgCkNetXzNlUXJw==",
"dependencies": {
"Fody": "6.6.3"
}
},
"SpaceEngineersDedicated.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.202.120, )",
"resolved": "1.202.120",
"contentHash": "HTP48NOSZY3eQPX3GJN+gjxdW83SM7DX6NcKCFqNIIvjz/J+l8uimHAQp9dnLySb0wb1K1Z1xBPIXyxMQRREIw==",
"dependencies": {
"protobuf-net": "1.0.0"
}
},
"Steamworks.NET": {
"type": "Direct",
"requested": "[20.1.0, )",
"resolved": "20.1.0",
"contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ=="
},
"System.ComponentModel.Annotations": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
},
"System.Management": {
"type": "Direct",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "A4jed4QUviDOm7fJNKAJObEAEkEUXmkGL/w0iyCYTzrl1rezTj8LGFHfsVst4Vb9JwFcTpboiDrvdST48avBpw==",
"dependencies": {
"System.CodeDom": "7.0.0"
}
},
"AvalonEdit": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "QMbyJrlhOuWzLRPqvW724ly9XbSEkp8Xg2mQY7tvsh1se1pDEJnmDjS6c6OuqDe2Q37uCnXwKdV8tJUx2iLUnw=="
},
"Fody": {
"type": "Transitive",
"resolved": "6.6.3",
"contentHash": "X1WKKgSNYVtLzdO8dK3YWmfmljA4L0soePcWNouq9X6WbVF4bmivuJdlhTZx/2L3ml6tMvhFe8skIbVQ24PAYA=="
},
"HarmonyX": {
"type": "Transitive",
"resolved": "2.10.2-prerelease.4",
"contentHash": "M/44/DhSu1WR8gL62aLPGKqAJ0rrgkw38nT3DgEvLAvyIoQLJmL2Jsn4/d4CC0eDT1Vv82cB9Mxgg0sFD3/qrQ==",
"dependencies": {
"MonoModReorg.RuntimeDetour": "23.3.22.1"
}
},
"JorgeSerrano.Json.JsonSnakeCaseNamingPolicy": {
"type": "Transitive",
"resolved": "0.9.0",
"contentHash": "xCqODS+wzpUXNtg4bMMvXG5PLbP0iTwRzRn2R+zWHKm83E6tbV2bCagawXp1EnZeNpd5OXpMxehulZWns8efzQ=="
},
"MdXaml.Plugins": {
"type": "Transitive",
"resolved": "1.16.2",
"contentHash": "EOMENHbLkpRk7+UItuutDuETiJpSCZgBATBRk7WSuxO16NUfzrS39ja4RhIQ0Ax8u0RH1wCAS0VINYPZqxfJeg=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
},
"Microsoft.CodeAnalysis.Analyzers": {
"type": "Transitive",
"resolved": "3.3.3",
"contentHash": "j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ=="
},
"Microsoft.CodeAnalysis.Common": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "JfHupS/B7Jb5MZoYkFFABn3mux0wQgxi2D8F/rJYZeRBK2ZOyk7TjQ2Kq9rh6W/DCh0KNbbSbn5qoFar+ueHqw==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "3.3.3",
"System.Collections.Immutable": "6.0.0",
"System.Memory": "4.5.5",
"System.Reflection.Metadata": "5.0.0",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encoding.CodePages": "6.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.CodeAnalysis.CSharp": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "eD2w0xHRoaqK07hjlOKGR9eLNy3nimiGNeCClNax1NDgS/+DBtBqCjXelOa+TNy99kIB3nHhUqDmr46nDXy/RQ==",
"dependencies": {
"Microsoft.CodeAnalysis.Common": "[4.4.0]"
}
},
"Microsoft.Diagnostics.NETCore.Client": {
"type": "Transitive",
"resolved": "0.2.251802",
"contentHash": "bqnYl6AdSeboeN4v25hSukK6Odm6/54E3Y2B8rBvgqvAW0mF8fo7XNRVE2DMOG7Rk0fiuA079QIH28+V+W1Zdg==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "1.1.0",
"Microsoft.Extensions.Logging": "2.1.1"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "tldQUBWt/xeH2K7/hMPPo5g8zuLc3Ro9I5d4o/XrxvxOCA2EZBtW7bCHHTc49fcBtvB8tLAb/Qsmfrq+2SJ4vA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
"Microsoft.Extensions.Primitives": "7.0.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==",
"dependencies": {
"Microsoft.Extensions.Primitives": "7.0.0"
}
},
"Microsoft.Extensions.Configuration.FileExtensions": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "xk2lRJ1RDuqe57BmgvRPyCt6zyePKUmvT6iuXqiHR+/OIIgWVR8Ff5k2p6DwmqY8a17hx/OnrekEhziEIeQP6Q==",
"dependencies": {
"Microsoft.Extensions.Configuration": "7.0.0",
"Microsoft.Extensions.Configuration.Abstractions": "7.0.0",
"Microsoft.Extensions.FileProviders.Abstractions": "7.0.0",
"Microsoft.Extensions.FileProviders.Physical": "7.0.0",
"Microsoft.Extensions.Primitives": "7.0.0"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "2.1.1",
"contentHash": "MgYpU5cwZohUMKKg3sbPhvGG+eAZ/59E9UwPwlrUkyXU+PGzqwZg9yyQNjhxuAWmoNoFReoemeCku50prYSGzA=="
},
"Microsoft.Extensions.FileProviders.Abstractions": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "NyawiW9ZT/liQb34k9YqBSNPLuuPkrjMgQZ24Y/xXX1RoiBkLUdPMaQTmxhZ5TYu8ZKZ9qayzil75JX95vGQUg==",
"dependencies": {
"Microsoft.Extensions.Primitives": "7.0.0"
}
},
"Microsoft.Extensions.FileProviders.Physical": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "K8D2MTR+EtzkbZ8z80LrG7Ur64R7ZZdRLt1J5cgpc/pUWl0C6IkAUapPuK28oionHueCPELUqq0oYEvZfalNdg==",
"dependencies": {
"Microsoft.Extensions.FileProviders.Abstractions": "7.0.0",
"Microsoft.Extensions.FileSystemGlobbing": "7.0.0",
"Microsoft.Extensions.Primitives": "7.0.0"
}
},
"Microsoft.Extensions.FileSystemGlobbing": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "2jONjKHiF+E92ynz2ZFcr9OvxIw+rTGMPEH+UZGeHTEComVav93jQUWGkso8yWwVBcEJGcNcZAaqY01FFJcj7w=="
},
"Microsoft.Extensions.Logging": {
"type": "Transitive",
"resolved": "2.1.1",
"contentHash": "hh+mkOAQDTp6XH80xJt3+wwYVzkbwYQl9XZRCz4Um0JjP/o7N9vHM3rZ6wwwtr+BBe/L6iBO2sz0px6OWBzqZQ==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.1.1",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.1.1",
"Microsoft.Extensions.Logging.Abstractions": "2.1.1",
"Microsoft.Extensions.Options": "2.1.1"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
"resolved": "2.1.1",
"contentHash": "XRzK7ZF+O6FzdfWrlFTi1Rgj2080ZDsd46vzOjadHUB0Cz5kOvDG8vI7caa5YFrsHQpcfn0DxtjS4E46N4FZsA=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.1.1",
"contentHash": "V7lXCU78lAbzaulCGFKojcCyG8RTJicEbiBkPJjFqiqXwndEBBIehdXRMWEVU3UtzQ1yDvphiWUL9th6/4gJ7w==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.1.1",
"Microsoft.Extensions.Primitives": "2.1.1"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q=="
},
"Microsoft.Xaml.Behaviors.Wpf": {
"type": "Transitive",
"resolved": "1.1.31",
"contentHash": "LZpuf82ACZWldmfMuv3CTUMDh3o0xo0uHUaybR5HgqVLDBJJ9RZLykplQ/bTJd0/VDt3EhD4iDgUgbdIUAM+Kg=="
},
"Mono.Cecil": {
"type": "Transitive",
"resolved": "0.11.4",
"contentHash": "IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ=="
},
"MonoModReorg.Backports": {
"type": "Transitive",
"resolved": "23.3.22.1",
"contentHash": "nLx18prtZbS0nVO8YHElP+TUj1kkDswRgQpa9agt3tyNWke9Kqk5lVkGu87Y3cCMhG+m5Ai0ZDH0+V5B6bXF+Q==",
"dependencies": {
"MonoModReorg.ILHelpers": "23.3.22.1"
}
},
"MonoModReorg.Core": {
"type": "Transitive",
"resolved": "23.3.22.1",
"contentHash": "Q3ZOgm6AOTL0icgcHsT/6mV5kYt6meqX3DEN1p/et5PxJsXUhVkiZfAptp8y/i+cO6HNgkcESpXEQ4jUWVwsDA==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "23.3.22.1",
"MonoModReorg.ILHelpers": "23.3.22.1",
"MonoModReorg.Utils": "23.3.22.1"
}
},
"MonoModReorg.ILHelpers": {
"type": "Transitive",
"resolved": "23.3.22.1",
"contentHash": "dHE/3VZtnTUTkQ34j8Gu6CxKes1QDL8tBuF3QElxHHpPl/QabGCK938k47WWRcRBMoP7IAthkn+Bob4H97rBgw=="
},
"MonoModReorg.RuntimeDetour": {
"type": "Transitive",
"resolved": "23.3.22.1",
"contentHash": "cUES3Hi7QJtBNe0qwVdjZOCDkZDaCpdqS6Au0RaCyQ1PDIR4av13E9qBNAEYew46sgPe0fOrU334md6DLDGHlQ==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "23.3.22.1",
"MonoModReorg.Core": "23.3.22.1",
"MonoModReorg.ILHelpers": "23.3.22.1",
"MonoModReorg.Utils": "23.3.22.1"
}
},
"MonoModReorg.Utils": {
"type": "Transitive",
"resolved": "23.3.22.1",
"contentHash": "q1cX0mbYxdw2WcE/qGBB84lVY/ffobEr46VQFuwGcWhtw6f1Ql1k4TZfs8gebpesSxHnca13grLeVHrUSpy1+g==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "23.3.22.1",
"MonoModReorg.ILHelpers": "23.3.22.1"
}
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.1",
"contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
},
"NuGet.Commands": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "j3ma45boGZADsHpJcu3Y2yq+n2luicC6ezd61TXTTzbOzA452oAPaSsFGUB1stIsuP/DVoqkTzjHXjaCHuJKPQ==",
"dependencies": {
"NuGet.Credentials": "6.4.0",
"NuGet.ProjectModel": "6.4.0"
}
},
"NuGet.Common": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "srECugLk+LB1bXelDCDhHoi6do/EYTXzuntKhjHraS4roVB3NfWohEdCSiAPdpSV9M40Q6jo6MV2Srml9e+jHQ==",
"dependencies": {
"NuGet.Frameworks": "6.4.0"
}
},
"NuGet.Configuration": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "vPjauG9AoacEjiZWGIs+d11FCRVmseqAw78FIApfLvZrYMEbbwc9vc0LdC3PpoW5FxYkktyZSiiXVKXGLu+gXw==",
"dependencies": {
"NuGet.Common": "6.4.0",
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
},
"NuGet.Credentials": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "tebsxclknVz3D4FrvE2MzVcsOyf6PffjGNQ77X9Yvbj9x5YpVWfumVPetqETcdsNEgiN0bBzfMre33lhrY7Itw==",
"dependencies": {
"NuGet.Protocol": "6.4.0"
}
},
"NuGet.DependencyResolver.Core": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "AKomZEKuhQlshujuiHbKvwl2cZNGq3SHsXFbpjCfCjMFMLwwA8saJGQQZp1lzsqQWcQWa6hLOcPtm7T3rd0SVg==",
"dependencies": {
"NuGet.Configuration": "6.4.0",
"NuGet.LibraryModel": "6.4.0",
"NuGet.Protocol": "6.4.0"
}
},
"NuGet.Frameworks": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "qcufbjJIDtyY/Hah7JJfcRVpRYM3scgPqYBnukjO9kfADCFGr2azvVBozuwzljA6w/cR3w8bXLq6vW5xGrsmHw=="
},
"NuGet.LibraryModel": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "K6ROQpWr34Aje81G0HfipiznLTB8vD4BO8sF6FEwx1KjJVdFkSmGZPmAhc6L1vZPs8TKY5BqoH72zG13zVzW2w==",
"dependencies": {
"NuGet.Common": "6.4.0",
"NuGet.Versioning": "6.4.0"
}
},
"NuGet.Packaging": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "aR10aYqcUMGC2mwMGH5rls/MGaz3EVH8DKTTHQ/EC91hXNtrCTTAQonaRR+v1EItcoxtQeZ/WQOorv4z270Tgg==",
"dependencies": {
"Newtonsoft.Json": "13.0.1",
"NuGet.Configuration": "6.4.0",
"NuGet.Versioning": "6.4.0",
"System.Security.Cryptography.Cng": "5.0.0",
"System.Security.Cryptography.Pkcs": "5.0.0"
}
},
"NuGet.ProjectModel": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "eW9Q7vPk8cpXDd5b+vtIPkl8dSDCPkPJPrjXPTfZGxhstldnhJrj1XPaonsDZLQ24YY7LrYCzC0BiHh3iO5zUA==",
"dependencies": {
"NuGet.DependencyResolver.Core": "6.4.0"
}
},
"NuGet.Protocol": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "KIPjsWP0P3EMsDsXaa6YBCTvYKur/zI0luS1kO5G37ci8mHs2kJFsuG5qaMhGvgyHASu54sxlic1n1oza2Pcbw==",
"dependencies": {
"NuGet.Packaging": "6.4.0"
}
},
"NuGet.Versioning": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "YE8p3TpX4jIw+Gb24maE8YRDoqWA4imLmCbdOj5IvslLrZJXQ8akeFOGOplxICNVevON1g1SFYT2+cq4yy0nQQ=="
},
"protobuf-net": {
"type": "Transitive",
"resolved": "3.1.26",
"contentHash": "iy+VROYtWZEqDvkUjS6FfzKZNpWRoVGc5h0cupGQT/37Ox0LxPblRKLnw6V0RT1MPclLo8nZp7E2M0f7PNn/Tw==",
"dependencies": {
"protobuf-net.Core": "3.1.26"
}
},
"protobuf-net.Core": {
"type": "Transitive",
"resolved": "3.1.26",
"contentHash": "fuKoDWgAf5ju+ixbJoasVFhnUaT0oWUgrWh1AZ5D0Y5keKHpA93vQB5g6aQNtB4zgeQ4hGEeh0MY9bIbAUPgEw=="
},
"SemanticVersioning": {
"type": "Transitive",
"resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA=="
},
"SixLabors.Core": {
"type": "Transitive",
"resolved": "1.0.0-beta0007",
"contentHash": "s9aPl6yxwcvoKRD0u0zjkCISZCCifbUi9/XVFjdvlx5Pt7vRYmGV0anq1EEftUjIEHbEu5aNBipbUSBIV2CE7w==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"System.CodeDom": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "GLltyqEsE5/3IE+zYRP5sNa1l44qKl9v+bfdMcwg+M9qnQf47wK3H0SUR/T+3N4JEQXF3vV4CSuuo0rsg+nq2A=="
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Formats.Asn1": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "+nfpV0afLmvJW8+pLlHxRjz3oZJw4fkyU9MMEaMhCsHi/SN9bGF9q79ROubDiwTiCHezmK0uCWkPP7tGFP/4yg=="
},
"System.Linq.Async": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "0YhHcaroWpQ9UCot3Pizah7ryAzQhNvobLMSxeDIGmnXfkQn8u5owvpOH0K6EVB+z9L7u6Cc4W17Br/+jyttEQ==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "6.0.0"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Security.Cryptography.Cng": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "jIMXsKn94T9JY7PvPq/tMfqa6GAaHpElRDpmG+SuL+D3+sTw2M8VhnibKnN8Tq+4JqbPJ/f+BwtLeDMEnzAvRg==",
"dependencies": {
"System.Formats.Asn1": "5.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "mjUbEXkR6DYRef6dnEYKdfec9otcAkibExL+1f9hmbGlWIUyaCnS3Y3oGZEet38waXmuY1ORE8vgv4sgD5nMYg==",
"dependencies": {
"System.Formats.Asn1": "7.0.0"
}
},
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog=="
},
"System.Security.Cryptography.Xml": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "LHc5PUypYGAMNf/2DzCxOXREKamwCtdUtxb/WpCnHngORVYZbUdSMnm1PcKvEvzKUTKSC0CL6aVAywzbEEeNQg==",
"dependencies": {
"System.Security.Cryptography.Pkcs": "7.0.0"
}
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Text.Json": {
"type": "Transitive",
"resolved": "5.0.1",
"contentHash": "/UM3UK1dXKl8Ybysg/21gM4S8DJgkR+yLU8JwqCVbuNqQNImelntgYFAN5QxR8sJJ1kMx//hOUdf0lltosi8cQ=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg=="
},
"Torch.SixLabors.ImageSharp": {
"type": "Transitive",
"resolved": "1.0.0-beta6",
"contentHash": "WJ7ocT79HgmuKi0+ltpvXTiMI80UcI3DeS8XSfYwJtTB1tcQws6zLPGuUwra6qe6qRrFfpABeDP3xvHV1rJgfg==",
"dependencies": {
"SixLabors.Core": "1.0.0-beta0007",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"torch": {
"type": "Project",
"dependencies": {
"ControlzEx": "[5.0.2, )",
"HarmonyX": "[2.10.2-prerelease.4, )",
"MahApps.Metro": "[2.4.9, )",
"Microsoft.CodeAnalysis.CSharp": "[4.4.0, )",
"Microsoft.CodeAnalysis.Common": "[4.4.0, )",
"MonoModReorg.RuntimeDetour": "[23.3.22.1, )",
"NLog": "[5.1.0, )",
"System.ComponentModel.Annotations": "[5.0.0, )",
"Torch.API": "[1.0.0, )",
"Torch.SixLabors.ImageSharp": "[1.0.0-beta6, )",
"protobuf-net": "[3.1.26, )"
}
},
"torch.api": {
"type": "Project",
"dependencies": {
"JorgeSerrano.Json.JsonSnakeCaseNamingPolicy": "[0.9.0, )",
"NLog": "[5.1.0, )",
"NuGet.Commands": "[6.4.0, )",
"NuGet.DependencyResolver.Core": "[6.4.0, )",
"SemanticVersioning": "[2.0.2, )",
"System.Linq.Async": "[6.0.1, )"
}
}
},
"net7.0-windows7.0/win-x64": {
"Steamworks.NET": {
"type": "Direct",
"requested": "[20.1.0, )",
"resolved": "20.1.0",
"contentHash": "+GntwnyJ5tCNvUIaQxv2+ehDvZJzGUqlSB5xRBk1hTj1qqBJ6s4vK/OfGD/jae7aTmXiGSm8wpJORosNtQevJQ=="
},
"System.Management": {
"type": "Direct",
"requested": "[7.0.0, )",
"resolved": "7.0.0",
"contentHash": "A4jed4QUviDOm7fJNKAJObEAEkEUXmkGL/w0iyCYTzrl1rezTj8LGFHfsVst4Vb9JwFcTpboiDrvdST48avBpw==",
"dependencies": {
"System.CodeDom": "7.0.0"
}
},
"System.Security.Cryptography.Cng": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "jIMXsKn94T9JY7PvPq/tMfqa6GAaHpElRDpmG+SuL+D3+sTw2M8VhnibKnN8Tq+4JqbPJ/f+BwtLeDMEnzAvRg==",
"dependencies": {
"System.Formats.Asn1": "5.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
"type": "Transitive",
"resolved": "7.0.0",
"contentHash": "mjUbEXkR6DYRef6dnEYKdfec9otcAkibExL+1f9hmbGlWIUyaCnS3Y3oGZEet38waXmuY1ORE8vgv4sgD5nMYg==",
"dependencies": {
"System.Formats.Asn1": "7.0.0"
}
},
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog=="
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
}
}
}
}

View File

@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net7-windows</TargetFramework>
<LangVersion>10</LangVersion>
<NoWarn>1591,0649</NoWarn> <NoWarn>1591,0649</NoWarn>
<AssemblyTitle>Torch Tests</AssemblyTitle> <AssemblyTitle>Torch Tests</AssemblyTitle>
<Product>Torch</Product> <Product>Torch</Product>
@@ -18,8 +17,8 @@
</PropertyGroup> </PropertyGroup>
<!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> --> <!-- <Import Project="$(SolutionDir)\TransformOnBuild.targets" /> -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="NLog" Version="5.0.4" /> <PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,138 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using NLog;
namespace Torch
{
/// <summary>
/// Base class that adds tools for setting type properties through the command line.
/// </summary>
public abstract class CommandLine
{
private readonly string _argPrefix;
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
private readonly Logger _log = LogManager.GetCurrentClassLogger();
protected CommandLine(string argPrefix = "-")
{
_argPrefix = argPrefix;
foreach (var prop in GetType().GetProperties())
{
var attr = prop.GetCustomAttribute<ArgAttribute>();
if (attr == null)
continue;
_args.Add(attr, prop);
}
}
public string GetHelp()
{
var sb = new StringBuilder();
foreach (var property in _args)
{
var attr = property.Key;
sb.AppendLine($"{_argPrefix}{attr.Name.PadRight(24)}{attr.Description}");
}
return sb.ToString();
}
public override string ToString()
{
var args = new List<string>();
foreach (var prop in _args)
{
var attr = prop.Key;
if (prop.Value.PropertyType == typeof(bool) && (bool)prop.Value.GetValue(this))
{
args.Add($"{_argPrefix}{attr.Name}");
}
else if (prop.Value.PropertyType == typeof(string))
{
var str = (string)prop.Value.GetValue(this);
if (string.IsNullOrEmpty(str))
continue;
args.Add($"{_argPrefix}{attr.Name} \"{str}\"");
}
}
return string.Join(" ", args);
}
public bool Parse(string[] args)
{
if (args.Length == 0)
return true;
if (args[0] == $"{_argPrefix}help")
{
Console.WriteLine(GetHelp());
return false;
}
for (var i = 0; i < args.Length; i++)
{
if (!args[i].StartsWith(_argPrefix))
continue;
foreach (var property in _args)
{
var argName = property.Key.Name;
if (argName == null)
continue;
try
{
if (string.Compare(argName, 0, args[i], 1, argName.Length, StringComparison.InvariantCultureIgnoreCase) == 0)
{
if (property.Value.PropertyType == typeof(bool))
property.Value.SetValue(this, true);
if (property.Value.PropertyType == typeof(string))
property.Value.SetValue(this, args[++i]);
if (property.Value.PropertyType == typeof(List<Guid>))
{
i++;
var l = new List<Guid>(16);
while (i < args.Length && !args[i].StartsWith(_argPrefix))
{
if (Guid.TryParse(args[i], out Guid g))
{
l.Add(g);
_log.Info($"added plugin {g}");
}
else
_log.Warn($"Failed to parse GUID {args[i]}");
i++;
}
property.Value.SetValue(this, l);
}
}
}
catch
{
Console.WriteLine($"Error parsing arg {argName}");
}
}
}
return true;
}
public class ArgAttribute : Attribute
{
public string Name { get; }
public string Description { get; }
public ArgAttribute(string name, string description)
{
Name = name;
Description = description;
}
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
@@ -9,7 +10,7 @@ using Torch.API;
namespace Torch.Managers namespace Torch.Managers
{ {
public class FilesystemManager : Manager public partial class FilesystemManager : Manager
{ {
private static readonly Logger _log = LogManager.GetCurrentClassLogger(); private static readonly Logger _log = LogManager.GetCurrentClassLogger();
/// <summary> /// <summary>
@@ -51,19 +52,31 @@ namespace Torch.Managers
} }
} }
/// <summary>
/// Move the given file (if it exists) to a temporary directory that will be cleared the next time the application starts.
/// </summary>
public void SoftDelete(string path, string file) public void SoftDelete(string path, string file)
{ {
string source = Path.Combine(path, file); var source = Path.Combine(path, file);
if (!File.Exists(source)) if (!File.Exists(source))
return; return;
var rand = Path.GetRandomFileName();
var dest = Path.Combine(TempDirectory, rand); try
File.Move(source, rand); {
string rsource = Path.Combine(path, rand); File.Delete(source);
File.Move(rsource, dest); }
catch (Exception e) when (e is IOException or UnauthorizedAccessException)
{
var tempFilePath = Path.Combine(path, file + ".old");
if (File.Exists(tempFilePath))
File.Delete(tempFilePath);
try
{
File.Move(source, tempFilePath);
}
catch (UnauthorizedAccessException)
{
// ignore
}
}
} }
} }
} }

View File

@@ -1,46 +1,34 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
using System.Runtime.CompilerServices; using HarmonyLib;
using System.Runtime.InteropServices;
using MonoMod.Cil;
using MonoMod.RuntimeDetour;
using MonoMod.Utils; using MonoMod.Utils;
using MonoMod.Utils.Cil;
using NLog; using NLog;
using Torch.Managers.PatchManager.MSIL; using Torch.Managers.PatchManager.MSIL;
using Torch.Managers.PatchManager.Transpile; using Torch.Managers.PatchManager.Transpile;
using Torch.Utils;
namespace Torch.Managers.PatchManager namespace Torch.Managers.PatchManager
{ {
internal class DecoratedMethod : MethodRewritePattern internal class DecoratedMethod : MethodRewritePattern
{ {
private static Action<ILHook, bool> IsAppliedSetter; private static readonly ConcurrentDictionary<MethodBase, DecoratedMethod> Methods = new();
[ReflectedMethodInfo(typeof(MethodBase), nameof(MethodBase.GetMethodFromHandle), Parameters = new[] {typeof(RuntimeMethodHandle)})]
private static MethodInfo _getMethodFromHandle = null!;
[ReflectedMethodInfo(typeof(MethodBase), nameof(MethodBase.GetMethodFromHandle), Parameters = new[] {typeof(RuntimeMethodHandle), typeof(RuntimeTypeHandle)})]
private static MethodInfo _getMethodFromHandleGeneric = null!;
private static readonly Logger _log = LogManager.GetCurrentClassLogger(); private static readonly Logger _log = LogManager.GetCurrentClassLogger();
private readonly MethodBase _method; private readonly MethodBase _method;
private readonly Harmony _harmony;
private ILHook _hook; private readonly PatchProcessor _processor;
private bool _hasRan;
internal DecoratedMethod(MethodBase method) : base(null) internal DecoratedMethod(MethodBase method, Harmony harmony) : base(null)
{ {
_method = method; _method = method;
if (IsAppliedSetter == null) _harmony = harmony;
{ _processor = harmony.CreateProcessor(method);
IsAppliedSetter = typeof(ILHook).GetProperty(nameof(ILHook.IsApplied)).CreateSetter<ILHook, bool>(); Methods[method] = this;
}
} }
internal bool HasChanged() internal bool HasChanged()
@@ -62,31 +50,25 @@ namespace Torch.Managers.PatchManager
_log.Log(PrintMode != 0 ? LogLevel.Info : LogLevel.Debug, _log.Log(PrintMode != 0 ? LogLevel.Info : LogLevel.Debug,
$"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})"); $"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})");
if (_hook == null) foreach (var prefix in Prefixes)
_hook = new ILHook(_method, Manipulator, new ILHookConfig {ManualApply = true});
IsAppliedSetter(_hook, false);
try
{ {
_hook.Apply(); _processor.AddPrefix(prefix);
} }
catch (InvalidProgramException e)
foreach (var suffix in Suffixes)
{ {
IsAppliedSetter(_hook, false); _processor.AddPostfix(suffix);
PrintMode = PrintModeEnum.Emitted | PrintModeEnum.Original; }
try
{
_hook.Apply();
}
catch
{
// Ignore, we are already know there is an error in IL
}
throw; if (Transpilers.Any() || PostTranspilers.Any())
} _processor.AddTranspiler(SymbolExtensions.GetMethodInfo(() => TranspilerProxy(null, null, null)));
_processor.Patch();
_log.Log(PrintMode != 0 ? LogLevel.Info : LogLevel.Debug, _log.Log(PrintMode != 0 ? LogLevel.Info : LogLevel.Debug,
$"Done patching {_method.GetID()})"); $"Done patching {_method.GetID()})");
_hasRan = true;
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -95,332 +77,40 @@ namespace Torch.Managers.PatchManager
} }
} }
private static IEnumerable<CodeInstruction> TranspilerProxy(IEnumerable<CodeInstruction> instructions,
MethodBase __originalMethod,
ILGenerator generator)
{
if (!Methods.TryGetValue(__originalMethod, out var decoratedMethod))
throw new Exception($"Unknown method {__originalMethod.GetID()}");
var loggingGenerator = new LoggingIlGenerator(generator, decoratedMethod.PrintMode != 0 ? LogLevel.Info : LogLevel.Debug);
MsilLocal LocalFactory(Type type) => new(loggingGenerator.DeclareLocal(type));
foreach (var transpiler in decoratedMethod.Transpilers.Concat(decoratedMethod.PostTranspilers))
{
var ins = (IEnumerable<MsilInstruction>) transpiler.Invoke(null, transpiler.GetParameters().Select<ParameterInfo, object>(b => b switch
{
_ when b.ParameterType.IsAssignableTo(typeof(MethodBase)) => __originalMethod,
_ when b.ParameterType.IsAssignableTo(typeof(IEnumerable<MsilInstruction>)) => instructions.Select(c => new MsilInstruction(c)),
_ when b.ParameterType.IsAssignableTo(typeof(Func<Type, MsilLocal>)) => new Func<Type, MsilLocal>(LocalFactory),
_ => null
}).ToArray());
instructions = ins!.Select(b => b.ToCodeIns(loggingGenerator)).ToList();
}
return instructions;
}
internal void Revert() internal void Revert()
{ {
if (_hook == null) if (!_hasRan)
return; return;
_log.Debug($"Revert {_method.GetID()}"); _log.Debug($"Revert {_method.GetID()}");
_hook.Dispose(); _processor.Unpatch(HarmonyPatchType.All, _harmony.Id);
_hook = null;
} }
#region Create
public const string INSTANCE_PARAMETER = "__instance";
public const string RESULT_PARAMETER = "__result";
public const string PREFIX_SKIPPED_PARAMETER = "__prefixSkipped";
public const string ORIGINAL_PARAMETER = "__original";
public const string LOCAL_PARAMETER = "__local";
private void SavePatchedMethod(string target)
{
throw new NotSupportedException();
// var asmBuilder =
// AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("SomeName"), AssemblyBuilderAccess.RunAndSave, Path.GetDirectoryName(target));
// var moduleBuilder = asmBuilder.DefineDynamicModule(Path.GetFileNameWithoutExtension(target), Path.GetFileName(target));
// var typeBuilder = moduleBuilder.DefineType("Test", TypeAttributes.Public);
//
//
// var methodName = _method.Name + $"_{_patchSalt}";
// var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
// var parameters = _method.GetParameters();
// var parameterTypes = (_method.IsStatic ? Enumerable.Empty<Type>() : new[] {_method.DeclaringType})
// .Concat(parameters.Select(x => x.ParameterType)).ToArray();
//
// var patchMethod = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard,
// returnType, parameterTypes);
// if (!_method.IsStatic)
// patchMethod.DefineParameter(0, ParameterAttributes.None, INSTANCE_PARAMETER);
// for (var i = 0; i < parameters.Length; i++)
// patchMethod.DefineParameter((patchMethod.IsStatic ? 0 : 1) + i, parameters[i].Attributes, parameters[i].Name);
//
// var generator = new LoggingIlGenerator(patchMethod.GetILGenerator(), LogLevel.Trace);
// List<MsilInstruction> il = EmitPatched((type, pinned) => new MsilLocal(generator.DeclareLocal(type, pinned))).ToList();
//
// MethodTranspiler.EmitMethod(il, generator);
//
// Type res = typeBuilder.CreateType();
// asmBuilder.Save(Path.GetFileName(target));
// foreach (var method in res.GetMethods(BindingFlags.Public | BindingFlags.Static))
// _log.Info($"Information " + method);
}
public void Manipulator(ILContext context)
{
context.IL.Clear();
var generator = new LoggingIlGenerator(new CecilILGenerator(context.IL),
PrintMode.HasFlag(PrintModeEnum.EmittedReflection) ? LogLevel.Info : LogLevel.Trace);
List<MsilInstruction> il = EmitPatched((type, pinned) => new MsilLocal(generator.DeclareLocal(type, pinned))).ToList();
var dumpTarget = DumpTarget != null ? File.CreateText(DumpTarget) : null;
try
{
const string gap = "\n\n\n\n\n";
void LogTarget(PrintModeEnum mode, bool err, string msg)
{
if (DumpMode.HasFlag(mode))
dumpTarget?.WriteLine((err ? "ERROR " : "") + msg);
if (!PrintMode.HasFlag(mode)) return;
if (err)
_log.Error(msg);
else
_log.Info(msg);
}
#pragma warning disable CS0612
if (PrintMsil || DumpTarget != null)
#pragma warning restore CS0612
{
lock (_log)
{
var ctx = new MethodContext(_method);
ctx.Read();
LogTarget(PrintModeEnum.Original, false, "========== Original method ==========");
MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Original, a, b), ctx.Instructions, true);
LogTarget(PrintModeEnum.Original, false, gap);
LogTarget(PrintModeEnum.Emitted, false, "========== Desired method ==========");
MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Emitted, a, b), il);
LogTarget(PrintModeEnum.Emitted, false, gap);
// If the method is invalid the program is likely to hard crash in EmitMethod or Compile, so flush the log
LogManager.Flush();
}
}
MethodTranspiler.EmitMethod(il, generator);
#pragma warning disable CS0612
if (PrintMsil || DumpTarget != null)
#pragma warning restore CS0612
{
lock (_log)
{
var instructions = context.Body.Instructions
.Select(b => b.ToMsilInstruction()).ToList();
LogTarget(PrintModeEnum.Patched, false, "========== Patched method ==========");
MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Patched, a, b), instructions, true);
LogTarget(PrintModeEnum.Patched, false, gap);
}
}
}
finally
{
dumpTarget?.Close();
}
}
#endregion
#region Emit
private IEnumerable<MsilInstruction> EmitPatched(Func<Type, bool, MsilLocal> declareLocal)
{
var methodBody = _method.GetMethodBody();
Debug.Assert(methodBody != null, "Method body is null");
foreach (var localVar in methodBody.LocalVariables)
{
Debug.Assert(localVar.LocalType != null);
declareLocal(localVar.LocalType, localVar.IsPinned);
}
var instructions = new List<MsilInstruction>();
var specialVariables = new Dictionary<string, MsilLocal>();
var labelAfterOriginalContent = new MsilLabel();
var labelSkipMethodContent = new MsilLabel();
Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
MsilLocal resultVariable = null;
if (returnType != typeof(void))
{
if (Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER)
|| Prefixes.Any(x => x.ReturnType == typeof(bool)))
resultVariable = declareLocal(returnType, false);
}
if (resultVariable != null)
instructions.AddRange(resultVariable.SetToDefault());
MsilLocal prefixSkippedVariable = null;
if (Prefixes.Count > 0 && Suffixes.Any(x => x.GetParameters()
.Any(y => y.Name.Equals(PREFIX_SKIPPED_PARAMETER))))
{
prefixSkippedVariable = declareLocal(typeof(bool), false);
specialVariables.Add(PREFIX_SKIPPED_PARAMETER, prefixSkippedVariable);
}
if (resultVariable != null)
specialVariables.Add(RESULT_PARAMETER, resultVariable);
// Create special variables
foreach (var m in Prefixes.Concat(Suffixes))
foreach (var param in m.GetParameters())
if (param.Name.StartsWith(LOCAL_PARAMETER))
{
var requiredType = param.ParameterType.IsByRef ? param.ParameterType.GetElementType() : param.ParameterType;
if (specialVariables.TryGetValue(param.Name, out var existingParam))
{
if (existingParam.Type != requiredType)
throw new ArgumentException(
$"Trying to use injected local {param.Name} for {m.DeclaringType?.FullName}#{m.ToString()} with type {requiredType} but a local with the same name already exists with type {existingParam.Type}",
param.Name);
}
else
specialVariables.Add(param.Name, declareLocal(requiredType, false));
}
foreach (MethodInfo prefix in Prefixes)
{
instructions.AddRange(EmitMonkeyCall(prefix, specialVariables));
if (prefix.ReturnType == typeof(bool))
instructions.Add(new MsilInstruction(OpCodes.Brfalse).InlineTarget(labelSkipMethodContent));
else if (prefix.ReturnType != typeof(void))
throw new PatchException(
$"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}", prefix);
}
instructions.AddRange(MethodTranspiler.Transpile(_method, (x) => declareLocal(x, false), Transpilers, labelAfterOriginalContent));
instructions.Add(new MsilInstruction(OpCodes.Nop).LabelWith(labelAfterOriginalContent));
if (resultVariable != null)
instructions.Add(new MsilInstruction(OpCodes.Stloc).InlineValue(resultVariable));
var notSkip = new MsilLabel();
instructions.Add(new MsilInstruction(OpCodes.Br).InlineTarget(notSkip));
instructions.Add(new MsilInstruction(OpCodes.Nop).LabelWith(labelSkipMethodContent));
if (prefixSkippedVariable != null)
{
instructions.Add(new MsilInstruction(OpCodes.Ldc_I4_1));
instructions.Add(new MsilInstruction(OpCodes.Stloc).InlineValue(prefixSkippedVariable));
}
instructions.Add(new MsilInstruction(OpCodes.Nop).LabelWith(notSkip));
foreach (MethodInfo suffix in Suffixes)
{
instructions.AddRange(EmitMonkeyCall(suffix, specialVariables));
if (suffix.ReturnType != typeof(void))
throw new PatchException($"Suffixes must return void. {suffix.DeclaringType?.FullName}.{suffix.Name} returns {suffix.ReturnType}", suffix);
}
if (resultVariable != null)
instructions.Add(new MsilInstruction(OpCodes.Ldloc).InlineValue(resultVariable));
instructions.Add(new MsilInstruction(OpCodes.Ret));
var result = MethodTranspiler.Transpile(_method, instructions, (x) => declareLocal(x, false), PostTranspilers, null).ToList();
if (result.Last().OpCode != OpCodes.Ret)
result.Add(new MsilInstruction(OpCodes.Ret));
return result;
}
private IEnumerable<MsilInstruction> EmitMonkeyCall(MethodInfo patch,
IReadOnlyDictionary<string, MsilLocal> specialVariables)
{
foreach (var param in patch.GetParameters())
{
switch (param.Name)
{
case INSTANCE_PARAMETER:
{
if (_method.IsStatic)
throw new PatchException("Can't use an instance parameter for a static method", _method);
yield return new MsilInstruction(OpCodes.Ldarg_0);
break;
}
case ORIGINAL_PARAMETER:
{
if (!typeof(MethodBase).IsAssignableFrom(param.ParameterType))
throw new PatchException($"Original parameter should be assignable to {nameof(MethodBase)}",
_method);
yield return new MsilInstruction(OpCodes.Ldtoken).InlineValue(_method);
if (_method.DeclaringType!.ContainsGenericParameters)
{
yield return new MsilInstruction(OpCodes.Ldtoken).InlineValue(_method.DeclaringType);
yield return new MsilInstruction(OpCodes.Call).InlineValue(_getMethodFromHandleGeneric);
}
else
yield return new MsilInstruction(OpCodes.Call).InlineValue(_getMethodFromHandle);
if (param.ParameterType != typeof(MethodBase))
yield return new MsilInstruction(OpCodes.Castclass).InlineValue(param.ParameterType);
break;
}
case PREFIX_SKIPPED_PARAMETER:
{
if (param.ParameterType != typeof(bool))
throw new PatchException($"Prefix skipped parameter {param.ParameterType} must be of type bool", _method);
if (param.ParameterType.IsByRef || param.IsOut)
throw new PatchException($"Prefix skipped parameter {param.ParameterType} can't be a reference type", _method);
if (specialVariables.TryGetValue(PREFIX_SKIPPED_PARAMETER, out MsilLocal prefixSkip))
yield return new MsilInstruction(OpCodes.Ldloc).InlineValue(prefixSkip);
else
yield return new MsilInstruction(OpCodes.Ldc_I4_0);
break;
}
case RESULT_PARAMETER:
{
var retType = param.ParameterType.IsByRef
? param.ParameterType.GetElementType()
: param.ParameterType;
if (retType == null || !retType.IsAssignableFrom(specialVariables[RESULT_PARAMETER].Type))
throw new PatchException(
$"Return type {specialVariables[RESULT_PARAMETER].Type} can't be assigned to result parameter type {retType}", _method);
yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc)
.InlineValue(specialVariables[RESULT_PARAMETER]);
break;
}
default:
{
if (specialVariables.TryGetValue(param.Name, out var specialVar))
{
yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc)
.InlineValue(specialVar);
break;
}
if (param.Name.StartsWith("__field_"))
{
var fieldName = param.Name.Substring(8);
var fieldDef = _method.DeclaringType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).FirstOrDefault(x => x.Name == fieldName);
if (fieldDef == null) throw new PatchException($"Could not find field {fieldName}", _method);
if (fieldDef.IsStatic)
yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldsflda : OpCodes.Ldsfld)
.InlineValue(fieldDef);
else
{
yield return new MsilInstruction(OpCodes.Ldarg_0);
yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldflda : OpCodes.Ldfld)
.InlineValue(fieldDef);
}
break;
}
ParameterInfo declParam = _method.GetParameters().FirstOrDefault(x => x.Name == param.Name);
if (declParam == null)
throw new PatchException($"Parameter name {param.Name} not found", _method);
int paramIdx = (_method.IsStatic ? 0 : 1) + declParam.Position;
bool patchByRef = param.IsOut || param.ParameterType.IsByRef;
bool declByRef = declParam.IsOut || declParam.ParameterType.IsByRef;
if (patchByRef == declByRef)
yield return new MsilInstruction(OpCodes.Ldarg).InlineValue(new MsilArgument(paramIdx));
else if (patchByRef)
yield return new MsilInstruction(OpCodes.Ldarga).InlineValue(new MsilArgument(paramIdx));
else
{
yield return new MsilInstruction(OpCodes.Ldarg).InlineValue(new MsilArgument(paramIdx));
yield return EmitExtensions.EmitDereference(declParam.ParameterType);
}
break;
}
}
}
yield return new MsilInstruction(OpCodes.Call).InlineValue(patch);
}
#endregion
} }
} }

View File

@@ -7,6 +7,7 @@ using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
using System.Text; using System.Text;
using System.Windows.Documents; using System.Windows.Documents;
using HarmonyLib;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using MonoMod.Utils; using MonoMod.Utils;
@@ -88,6 +89,94 @@ namespace Torch.Managers.PatchManager.MSIL
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
} }
public MsilInstruction(CodeInstruction instruction) : this(instruction.opcode)
{
switch (instruction.operand)
{
case LocalBuilder builder when Operand is MsilOperandInline.MsilOperandLocal operandLocal:
operandLocal.Value = new(builder);
break;
case MethodBase methodBase when Operand is MsilOperandInline.MsilOperandReflected<MethodBase> operandMethod:
operandMethod.Value = methodBase;
break;
case Type type when Operand is MsilOperandInline.MsilOperandReflected<Type> operandType:
operandType.Value = type;
break;
case MemberInfo info when Operand is MsilOperandInline.MsilOperandReflected<MemberInfo> operandMember:
operandMember.Value = info;
break;
case IEnumerable<Label> labels when Operand is MsilOperandSwitch operandSwitch:
operandSwitch.Labels = labels.Select(b => new MsilLabel(b)).ToArray();
break;
case float single when Operand is MsilOperandInline.MsilOperandSingle operandSingle:
operandSingle.Value = single;
break;
case FieldInfo fieldInfo when Operand is MsilOperandInline.MsilOperandReflected<FieldInfo> operandField:
operandField.Value = fieldInfo;
break;
case string str when Operand is MsilOperandInline.MsilOperandString operandString:
operandString.Value = str;
break;
case SignatureHelper signatureHelper when Operand is MsilOperandInline.MsilOperandSignature operandSignature:
operandSignature.Value = signatureHelper;
break;
case int int32 when Operand is MsilOperandInline.MsilOperandInt32 operandInt32:
operandInt32.Value = int32;
break;
case long int64 when Operand is MsilOperandInline.MsilOperandInt64 operandInt64:
operandInt64.Value = int64;
break;
case Label label when Operand is MsilOperandBrTarget operandBrTarget:
operandBrTarget.Target = new(label);
break;
case double @double when Operand is MsilOperandInline.MsilOperandDouble operandDouble:
operandDouble.Value = @double;
break;
case byte @byte when Operand is MsilOperandInline.MsilOperandArgument operandArgument:
operandArgument.Value = new(@byte);
break;
case byte @byte when Operand is MsilOperandInline.MsilOperandLocal operandArgument:
operandArgument.Value = new(@byte);
break;
}
Labels = instruction.labels.Select(b => new MsilLabel(b)).ToHashSet();
TryCatchOperations =
instruction.blocks.Select(
b => new MsilTryCatchOperation((MsilTryCatchOperationType)b.blockType, b.catchType == typeof(object) ? null : b.catchType)).ToList();
}
internal CodeInstruction ToCodeIns(LoggingIlGenerator generator)
{
var ins = new CodeInstruction(OpCode, Operand switch
{
MsilOperandBrTarget msilOperandBrTarget => msilOperandBrTarget.Target.LabelFor(generator),
MsilOperandInline.MsilOperandReflected<MethodBase> msilOperandReflected => msilOperandReflected.Value,
MsilOperandInline.MsilOperandReflected<MemberInfo> msilOperandReflected => msilOperandReflected.Value,
MsilOperandInline.MsilOperandReflected<FieldInfo> msilOperandReflected => msilOperandReflected.Value,
MsilOperandInline.MsilOperandReflected<Type> msilOperandReflected => msilOperandReflected.Value,
MsilOperandInline.MsilOperandDouble msilOperandDouble => msilOperandDouble.Value,
MsilOperandInline.MsilOperandInt32 msilOperandInt32 => msilOperandInt32.Value,
MsilOperandInline.MsilOperandInt64 msilOperandInt64 => msilOperandInt64.Value,
MsilOperandInline.MsilOperandLocal msilOperandLocal when OpCode.OperandType == OperandType.InlineVar => msilOperandLocal.Value.Local,
MsilOperandInline.MsilOperandLocal msilOperandLocal when OpCode.OperandType == OperandType.ShortInlineVar => (byte)msilOperandLocal.Value.Index,
MsilOperandInline.MsilOperandArgument msilOperandArgument when OpCode.OperandType == OperandType.InlineVar => msilOperandArgument.Value,
MsilOperandInline.MsilOperandArgument msilOperandArgument when OpCode.OperandType == OperandType.ShortInlineVar => (byte)msilOperandArgument.Value.Position,
MsilOperandInline.MsilOperandSignature msilOperandSignature => msilOperandSignature.Value,
MsilOperandInline.MsilOperandSingle msilOperandSingle => msilOperandSingle.Value,
MsilOperandInline.MsilOperandString msilOperandString => msilOperandString.Value,
MsilOperandSwitch msilOperandSwitch => msilOperandSwitch.Labels.Select(b => b.LabelFor(generator)).ToArray(),
_ => null
})
{
labels = Labels.Select(b => b.LabelFor(generator)).ToList(),
blocks = TryCatchOperations.Select(b => new ExceptionBlock((ExceptionBlockType)b.Type, b.CatchType))
.ToList()
};
return ins;
}
/// <summary> /// <summary>
/// Opcode of this instruction /// Opcode of this instruction

View File

@@ -13,14 +13,14 @@ namespace Torch.Managers.PatchManager.MSIL
private readonly List<KeyValuePair<WeakReference<LoggingIlGenerator>, Label>> _labelInstances = private readonly List<KeyValuePair<WeakReference<LoggingIlGenerator>, Label>> _labelInstances =
new List<KeyValuePair<WeakReference<LoggingIlGenerator>, Label>>(); new List<KeyValuePair<WeakReference<LoggingIlGenerator>, Label>>();
private readonly Label? _overrideLabel; internal readonly Label? OverrideLabel;
/// <summary> /// <summary>
/// Creates an empty label the allocates a new <see cref="Label" /> when requested. /// Creates an empty label the allocates a new <see cref="Label" /> when requested.
/// </summary> /// </summary>
public MsilLabel() public MsilLabel()
{ {
_overrideLabel = null; OverrideLabel = null;
} }
/// <summary> /// <summary>
@@ -28,7 +28,7 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary> /// </summary>
public MsilLabel(Label overrideLabel) public MsilLabel(Label overrideLabel)
{ {
_overrideLabel = overrideLabel; OverrideLabel = overrideLabel;
} }
/// <summary> /// <summary>
@@ -46,8 +46,8 @@ namespace Torch.Managers.PatchManager.MSIL
internal Label LabelFor(LoggingIlGenerator gen) internal Label LabelFor(LoggingIlGenerator gen)
{ {
if (_overrideLabel.HasValue) if (OverrideLabel.HasValue)
return _overrideLabel.Value; return OverrideLabel.Value;
foreach (KeyValuePair<WeakReference<LoggingIlGenerator>, Label> kv in _labelInstances) foreach (KeyValuePair<WeakReference<LoggingIlGenerator>, Label> kv in _labelInstances)
if (kv.Key.TryGetTarget(out LoggingIlGenerator gen2) && gen2 == gen) if (kv.Key.TryGetTarget(out LoggingIlGenerator gen2) && gen2 == gen)
return kv.Value; return kv.Value;

View File

@@ -14,6 +14,8 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary> /// </summary>
public class MsilLocal public class MsilLocal
{ {
internal LocalBuilder Local { get; }
/// <summary> /// <summary>
/// The index of this local. /// The index of this local.
/// </summary> /// </summary>
@@ -31,6 +33,7 @@ namespace Torch.Managers.PatchManager.MSIL
internal MsilLocal(LocalBuilder local) internal MsilLocal(LocalBuilder local)
{ {
Local = local;
Index = local.LocalIndex; Index = local.LocalIndex;
Type = local.LocalType; Type = local.LocalType;
Name = null; Name = null;

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using HarmonyLib;
using NLog; using NLog;
using Torch.API; using Torch.API;
using Torch.Managers.PatchManager.Transpile; using Torch.Managers.PatchManager.Transpile;
@@ -65,6 +66,7 @@ namespace Torch.Managers.PatchManager
private static readonly Dictionary<Assembly, List<PatchContext>> _contexts = new Dictionary<Assembly, List<PatchContext>>(); private static readonly Dictionary<Assembly, List<PatchContext>> _contexts = new Dictionary<Assembly, List<PatchContext>>();
// ReSharper disable once CollectionNeverQueried.Local because we may want this in the future. // ReSharper disable once CollectionNeverQueried.Local because we may want this in the future.
private static readonly List<PatchContext> _coreContexts = new List<PatchContext>(); private static readonly List<PatchContext> _coreContexts = new List<PatchContext>();
private static readonly Harmony HarmonyInstance = new("PatchManager");
/// <inheritdoc cref="GetPattern"/> /// <inheritdoc cref="GetPattern"/>
internal static MethodRewritePattern GetPatternInternal(MethodBase method) internal static MethodRewritePattern GetPatternInternal(MethodBase method)
@@ -73,7 +75,7 @@ namespace Torch.Managers.PatchManager
{ {
if (_rewritePatterns.TryGetValue(method, out DecoratedMethod pattern)) if (_rewritePatterns.TryGetValue(method, out DecoratedMethod pattern))
return pattern; return pattern;
var res = new DecoratedMethod(method); var res = new DecoratedMethod(method, HarmonyInstance);
_rewritePatterns.Add(method, res); _rewritePatterns.Add(method, res);
return res; return res;
} }

View File

@@ -25,7 +25,7 @@ namespace Torch.Managers.PatchManager.Transpile
/// <summary> /// <summary>
/// Backing generator /// Backing generator
/// </summary> /// </summary>
public ILGeneratorShim Backing { get; } public ILGenerator Backing { get; }
private readonly LogLevel _level; private readonly LogLevel _level;
@@ -33,7 +33,7 @@ namespace Torch.Managers.PatchManager.Transpile
/// Creates a new logging IL generator backed by the given generator. /// Creates a new logging IL generator backed by the given generator.
/// </summary> /// </summary>
/// <param name="backing">Backing generator</param> /// <param name="backing">Backing generator</param>
public LoggingIlGenerator(ILGeneratorShim backing, LogLevel level) public LoggingIlGenerator(ILGenerator backing, LogLevel level)
{ {
Backing = backing; Backing = backing;
_level = level; _level = level;

View File

@@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Emit;
using Sandbox.Game.Entities.Blocks;
using Sandbox.Game.EntityComponents;
using Sandbox.ModAPI;
using Sandbox.ModAPI.Ingame;
using Torch.API;
using Torch.Utils;
using VRage;
using VRage.ModAPI;
using VRage.Scripting;
namespace Torch.Managers;
public class ScriptCompilationManager : Manager
{
[ReflectedSetter(Name = "m_terminationReason")]
private static Action<MyProgrammableBlock, MyProgrammableBlock.ScriptTerminationReason> TerminationReasonSetter = null!;
[ReflectedGetter(Name = "ScriptComponent")]
private static Func<MyProgrammableBlock, MyIngameScriptComponent> ScriptComponentGetter = null!;
[ReflectedMethod]
private static Action<MyProgrammableBlock, string> SetDetailedInfo = null!;
[ReflectedSetter(Name = "m_instance")]
private static Action<MyProgrammableBlock, IMyGridProgram> InstanceSetter = null!;
[ReflectedSetter(Name = "m_assembly")]
private static Action<MyProgrammableBlock, Assembly> AssemblySetter = null!;
[ReflectedMethod]
private static Func<MyProgrammableBlock, Assembly, IEnumerable<string>, string, bool> CreateInstance = null!;
[ReflectedGetter(Name = "m_compilerErrors")]
private static Func<MyProgrammableBlock, List<string>> CompilerErrorsGetter = null!;
[ReflectedGetter(Name = "m_modApiWhitelistDiagnosticAnalyzer")]
private static Func<MyScriptCompiler, DiagnosticAnalyzer> ModWhitelistAnalyzer = null!;
[ReflectedGetter(Name = "m_ingameWhitelistDiagnosticAnalyzer")]
private static Func<MyScriptCompiler, DiagnosticAnalyzer> ScriptWhitelistAnalyzer = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, CSharpCompilation, SyntaxTree, int, SyntaxTree> InjectMod = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, CSharpCompilation, SyntaxTree, SyntaxTree> InjectInstructionCounter = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, CompilationWithAnalyzers, EmitResult, List<Message>, bool, Task<bool>> EmitDiagnostics = null!;
[ReflectedMethod]
private static Func<MyScriptCompiler, MyApiTarget, string, IList<SyntaxTree>, string, Task> WriteDiagnostics = null!;
[ReflectedMethod(Name = "WriteDiagnostics")]
private static Func<MyScriptCompiler, MyApiTarget, string, IEnumerable<Message>, bool, Task> WriteDiagnostics2 = null!;
[ReflectedGetter(Name = "m_metadataReferences")]
private static Func<MyScriptCompiler, List<MetadataReference>> MetadataReferencesGetter = null!;
private readonly ConditionalWeakTable<MyProgrammableBlock, AssemblyLoadContext> _contexts = new();
public ScriptCompilationManager(ITorchBase torchInstance) : base(torchInstance)
{
}
public async void CompileAsync(MyProgrammableBlock block, string program, string storage, bool instantiate)
{
TerminationReasonSetter(block, MyProgrammableBlock.ScriptTerminationReason.None);
var component = ScriptComponentGetter(block);
component.NextUpdate = UpdateType.None;
component.NeedsUpdate = MyEntityUpdateEnum.NONE;
try
{
if (_contexts.TryGetValue(block, out var context))
{
InstanceSetter(block, null);
AssemblySetter(block, null);
context!.Unload();
}
_contexts.AddOrUpdate(block, context = new AssemblyLoadContext(null, true));
var messages = new List<Message>();
var assembly = await CompileAsync(context, MyApiTarget.Ingame,
$"pb_{block.EntityId}_{Random.Shared.NextInt64()}",
new[]
{
MyVRage.Platform.Scripting.GetIngameScript(
program, "Program", nameof(MyGridProgram))
}, messages, $"PB: {block.DisplayName} ({block.EntityId})");
AssemblySetter(block, assembly);
var errors = CompilerErrorsGetter(block);
errors.Clear();
errors.AddRange(messages.Select(b => b.Text));
if (instantiate)
await Torch.InvokeAsync(() => CreateInstance(block, assembly, errors, storage));
}
catch (Exception e)
{
await Torch.InvokeAsync(() => SetDetailedInfo(block, e.ToString()));
throw;
}
}
public async Task<Assembly> CompileAsync(AssemblyLoadContext context, MyApiTarget target, string assemblyName, IEnumerable<Script> scripts, List<Message> messages, string friendlyName, bool enableDebugInformation = false)
{
friendlyName ??= "<No Name>";
Func<CSharpCompilation, SyntaxTree, SyntaxTree> syntaxTreeInjector;
DiagnosticAnalyzer whitelistAnalyzer;
switch (target)
{
case MyApiTarget.None:
whitelistAnalyzer = null;
syntaxTreeInjector = null;
break;
case MyApiTarget.Mod:
{
var modId = MyModWatchdog.AllocateModId(friendlyName);
whitelistAnalyzer = ModWhitelistAnalyzer(MyScriptCompiler.Static);
syntaxTreeInjector = (c, st) => InjectMod(MyScriptCompiler.Static, c, st, modId);
break;
}
case MyApiTarget.Ingame:
syntaxTreeInjector = (c, t) => InjectInstructionCounter(MyScriptCompiler.Static, c, t);
whitelistAnalyzer = ScriptWhitelistAnalyzer(MyScriptCompiler.Static);
break;
default:
throw new ArgumentOutOfRangeException(nameof(target), target, "Invalid compilation target");
}
var compilation = CreateCompilation(assemblyName, scripts);
await WriteDiagnostics(MyScriptCompiler.Static, target, assemblyName, compilation.SyntaxTrees, null).ConfigureAwait(false);
var injectionFailed = false;
var compilationWithoutInjection = compilation;
if (syntaxTreeInjector != null)
{
SyntaxTree[] newSyntaxTrees = null;
try
{
var syntaxTrees = compilation.SyntaxTrees;
if (syntaxTrees.Length == 1)
{
newSyntaxTrees = new[] { syntaxTreeInjector(compilation, syntaxTrees[0]) };
}
else
{
newSyntaxTrees = await Task
.WhenAll(syntaxTrees.Select(
x => Task.Run(() => syntaxTreeInjector(compilation, x))))
.ConfigureAwait(false);
}
}
catch
{
injectionFailed = true;
}
if (!injectionFailed)
{
await WriteDiagnostics(MyScriptCompiler.Static, target, assemblyName, newSyntaxTrees, ".injected").ConfigureAwait(false);
compilation = compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newSyntaxTrees);
}
}
CompilationWithAnalyzers analyticCompilation = null;
if (whitelistAnalyzer != null)
{
analyticCompilation = compilation.WithAnalyzers(ImmutableArray.Create(whitelistAnalyzer));
compilation = (CSharpCompilation)analyticCompilation.Compilation;
}
using var assemblyStream = new MemoryStream();
var emitResult = compilation.Emit(assemblyStream);
var success = emitResult.Success;
var myBlacklistSyntaxVisitor = new MyBlacklistSyntaxVisitor();
foreach (var syntaxTree in compilation.SyntaxTrees)
{
myBlacklistSyntaxVisitor.SetSemanticModel(compilation.GetSemanticModel(syntaxTree, false));
myBlacklistSyntaxVisitor.Visit(await syntaxTree.GetRootAsync());
}
if (myBlacklistSyntaxVisitor.HasAnyResult())
{
myBlacklistSyntaxVisitor.GetResultMessages(messages);
}
else
{
success = await EmitDiagnostics(MyScriptCompiler.Static, analyticCompilation, emitResult, messages, success).ConfigureAwait(false);
await WriteDiagnostics2(MyScriptCompiler.Static, target, assemblyName, messages, success).ConfigureAwait(false);
assemblyStream.Seek(0, SeekOrigin.Begin);
if (injectionFailed) return null;
if (success)
return context.LoadFromStream(assemblyStream);
await EmitDiagnostics(MyScriptCompiler.Static, analyticCompilation, compilationWithoutInjection.Emit(assemblyStream), messages, false).ConfigureAwait(false);
}
return null;
}
private readonly CSharpCompilationOptions _compilationOptions = new(OutputKind.DynamicallyLinkedLibrary);
private readonly CSharpParseOptions _parseOptions = new(LanguageVersion.CSharp11, DocumentationMode.None);
private CSharpCompilation CreateCompilation(string assemblyFileName, IEnumerable<Script> scripts)
{
if (scripts == null)
return CSharpCompilation.Create(assemblyFileName, null, MetadataReferencesGetter(MyScriptCompiler.Static),
_compilationOptions);
var parseOptions = _parseOptions.WithPreprocessorSymbols(MyScriptCompiler.Static.ConditionalCompilationSymbols);
var enumerable = scripts.Select(s => CSharpSyntaxTree.ParseText(s.Code, parseOptions, s.Name, Encoding.UTF8));
return CSharpCompilation.Create(assemblyFileName, enumerable, MetadataReferencesGetter(MyScriptCompiler.Static), _compilationOptions);
}
}

View File

@@ -5,6 +5,7 @@ using System.IO.Compression;
using System.IO.Packaging; using System.IO.Packaging;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@@ -12,6 +13,7 @@ using System.Threading.Tasks;
using NLog; using NLog;
using Torch.API; using Torch.API;
using Torch.API.WebAPI; using Torch.API.WebAPI;
using Torch.API.WebAPI.Update;
namespace Torch.Managers namespace Torch.Managers
{ {
@@ -21,8 +23,9 @@ namespace Torch.Managers
public class UpdateManager : Manager public class UpdateManager : Manager
{ {
private readonly Timer _updatePollTimer; private readonly Timer _updatePollTimer;
private string _torchDir = new FileInfo(typeof(UpdateManager).Assembly.Location).DirectoryName; private readonly string _torchDir = ApplicationContext.Current.TorchDirectory.FullName;
private Logger _log = LogManager.GetCurrentClassLogger(); private readonly Logger _log = LogManager.GetCurrentClassLogger();
[Dependency] [Dependency]
private FilesystemManager _fsManager = null!; private FilesystemManager _fsManager = null!;
@@ -50,26 +53,22 @@ namespace Torch.Managers
try try
{ {
var job = await JenkinsQuery.Instance.GetLatestVersion(Torch.TorchVersion.Build); var updateSource = Torch.Config.UpdateSource;
if (job == null)
{
_log.Info("Failed to fetch latest version.");
return;
}
if (job.Version > Torch.TorchVersion) IUpdateQuery source = updateSource.SourceType switch
{ {
_log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {job.Version}"); UpdateSourceType.Github => new GithubQuery(updateSource.Url),
var updateName = Path.Combine(_fsManager.TempDirectory, "torchupdate.zip"); UpdateSourceType.Jenkins => new JenkinsQuery(updateSource.Url),
//new WebClient().DownloadFile(new Uri(releaseInfo.Item2), updateName); _ => throw new ArgumentOutOfRangeException()
if (!await JenkinsQuery.Instance.DownloadRelease(job, updateName)) };
{
_log.Warn("Failed to download new release!"); var release = await source.GetLatestReleaseAsync(updateSource.Repository, updateSource.Branch);
return;
} if (release.Version > Torch.TorchVersion)
UpdateFromZip(updateName, _torchDir); {
File.Delete(updateName); _log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {release.Version}");
_log.Warn($"Torch version {job.Version} has been installed, please restart Torch to finish the process."); await UpdateAsync(release, _torchDir);
_log.Warn($"Torch version {release.Version} has been installed, please restart Torch to finish the process.");
} }
else else
{ {
@@ -83,23 +82,45 @@ namespace Torch.Managers
} }
} }
private void UpdateFromZip(string zipFile, string extractPath) private async Task UpdateAsync(UpdateRelease release, string extractPath)
{ {
using (var zip = ZipFile.OpenRead(zipFile)) using var client = new HttpClient();
await using var stream = await client.GetStreamAsync(release.ArtifactUrl);
UpdateFromZip(stream, extractPath);
}
private void UpdateFromZip(Stream zipStream, string extractPath)
{
using var zip = new ZipArchive(zipStream, ZipArchiveMode.Read, true);
foreach (var file in zip.Entries)
{ {
foreach (var file in zip.Entries) if(file.Name == "NLog-user.config" && File.Exists(Path.Combine(extractPath, file.FullName)))
continue;
_log.Debug($"Unzipping {file.FullName}");
var targetFile = Path.Combine(extractPath, file.FullName);
// if its a directory
if (Path.GetFileName(targetFile).Length == 0)
{ {
if(file.Name == "NLog-user.config" && File.Exists(Path.Combine(extractPath, file.FullName))) Directory.CreateDirectory(targetFile);
continue; }
else
_log.Debug($"Unzipping {file.FullName}"); {
var targetFile = Path.Combine(extractPath, file.FullName); _fsManager.SoftDelete(extractPath, file.FullName);
_fsManager.SoftDelete(extractPath, file.FullName); try
file.ExtractToFile(targetFile, true); {
file.ExtractToFile(targetFile, true);
}
catch (Exception e)
{
_log.Warn(e, "unable to extract {0}", targetFile);
}
} }
//zip.ExtractToDirectory(extractPath); //throws exceptions sometimes?
} }
//zip.ExtractToDirectory(extractPath); //throws exceptions sometimes?
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@@ -0,0 +1,162 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using Torch.API;
using Torch.API.Managers;
using Torch.API.WebAPI.Plugins;
using Torch.Managers;
using Torch.Utils;
namespace Torch.Packages;
public class PackageManager : Manager, IPackageManager
{
private readonly PluginManager _pluginManager;
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
private IPackageResolver? _packageResolver;
public PackageManager(ITorchBase torchInstance, PluginManager pluginManager) : base(torchInstance)
{
_pluginManager = pluginManager;
}
private AssemblyLoadContext? _loadContext;
private ImmutableHashSet<Package> _packages = ImmutableHashSet<Package>.Empty;
private ImmutableDictionary<Package,Assembly[]> _packageAssemblies = ImmutableDictionary<Package, Assembly[]>.Empty;
private DirectoryInfo? _packagesDirectory;
public IReadOnlySet<Package> Packages => _packages;
public bool TryGetPackageAssemblies(Package package, out Assembly[]? assemblies)
{
if (!Packages.Contains(package))
throw new InvalidOperationException($"Package {package.Name} does not exist");
return _packageAssemblies.TryGetValue(package, out assemblies);
}
internal async void LoadAsync(SemaphoreSlim semaphore)
{
_packagesDirectory = Directory.CreateDirectory(Path.Combine(Torch.InstancePath, "Packages"));
_packageResolver = new PackageResolver(new[]
{ // TODO make this configurable
new PackageSource("nuget", "https://api.nuget.org/v3/index.json", new[] { "*" }, PackageSourceType.NuGet)
}, _packagesDirectory);
try
{
var packages =
Torch.Config.Packages.ToImmutableDictionary(b => b[..b.IndexOf(':')], b => b[(b.IndexOf(':') + 1)..]);
await ResolvePackagesAsync(packages);
var resolvedPackages = await _packages.ToAsyncEnumerable()
.SelectAwait(async b =>
{
var reader = await _packageResolver!.GetPackageAsync(b);
var items = await reader.GetItemsAsync();
return (b, items);
}).ToDictionaryAsync(b => b.b.Name, b => b,
StringComparer.OrdinalIgnoreCase);
var dependencies = new Dictionary<PackageDependency, IEnumerable<IPackageItem>>(
resolvedPackages.Values.SelectMany(b => b.items.Dependencies)
.Where(b => !resolvedPackages.ContainsKey(b.Key.Name))
.GroupBy(b => b.Key.Name)
.Select(b => b.MaxBy(c => c.Key.Version)));
_loadContext = new("PackageManager Context");
var dependencyAssemblies = await dependencies.ToAsyncEnumerable().SelectAwait(async pair =>
{
var (dep, items) = pair;
var assemblies = await LoadAssembliesFromItems(items);
return (dep, assemblies);
}).ToDictionaryAsync(b => b.dep, b => b.assemblies);
var packageAssemblies = await resolvedPackages.Values.ToAsyncEnumerable().SelectAwait(async b =>
{
var assemblies = await LoadAssembliesFromItems(b.items.Root);
return (b.b, assemblies);
}).ToDictionaryAsync(b => b.b, b => b.assemblies);
foreach (var assembly in dependencyAssemblies.Values.Concat(packageAssemblies.Values).SelectMany(b => b))
{
TorchLauncher.RegisterAssembly(assembly);
}
foreach (var (package, assemblies) in packageAssemblies)
{
using var hash = MD5.Create();
hash.Initialize();
var guid = new Guid(hash.ComputeHash(Encoding.UTF8.GetBytes(package.Name)));
_pluginManager.InstantiatePlugin(new()
{
Name = package.Name,
Version = package.Version.ToString(),
Guid = guid
}, assemblies);
}
_packageAssemblies = packageAssemblies.ToImmutableDictionary();
}
finally
{
semaphore.Release();
}
}
private async Task<Assembly[]> LoadAssembliesFromItems(IEnumerable<IPackageItem> items)
{
var dictionary = items.ToImmutableDictionary(b => b.FileName, StringComparer.OrdinalIgnoreCase);
var assemblies = await dictionary
.Where(b => Path.GetExtension(b.Key)
.Equals("dll", StringComparison.OrdinalIgnoreCase))
.ToAsyncEnumerable()
.SelectAwait(async b =>
{
dictionary.TryGetValue(
Path.ChangeExtension(b.Key, "pdb"), out var pdbItem);
return await LoadFromStream(b.Key, b.Value, pdbItem);
}).ToArrayAsync();
return assemblies;
}
private async ValueTask<Assembly> LoadFromStream(string key, IPackageItem dllItem, IPackageItem? pdbItem)
{
if (_loadContext is null)
throw new InvalidOperationException("Load Context should be initialized before calling the method");
Log.Trace("Loading {0}", key);
await using var dll = await dllItem.OpenFileAsync();
if (pdbItem is null)
return _loadContext.LoadFromStream(dll);
await using var pdb = await pdbItem.OpenFileAsync();
return _loadContext.LoadFromStream(dll, pdb);
}
private async Task ResolvePackagesAsync(IReadOnlyDictionary<string, string> requestedPackages)
{
Log.Info("Resolving packages");
var packages = await _packageResolver!.ResolvePackagesAsync(requestedPackages);
_packages = packages.ToImmutableHashSet();
Log.Info("Got {0} packages", _packages.Count);
}
}

View File

@@ -1,4 +1,5 @@
using System; #if false
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
@@ -50,4 +51,5 @@ internal static class GcCollectPatch
yield return instruction; yield return instruction;
} }
} }
} }
#endif

View File

@@ -10,6 +10,7 @@ using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL; using Torch.Managers.PatchManager.MSIL;
using Torch.Utils; using Torch.Utils;
using VRage.Game; using VRage.Game;
using VRage.GameServices;
namespace Torch.Patches namespace Torch.Patches
{ {
@@ -31,7 +32,7 @@ namespace Torch.Patches
} }
public static void Postfix(MyWorkshop.ResultData __result, List<MyObjectBuilder_Checkpoint.ModItem> mods) public static void Postfix(MyWorkshop.ResultData __result, List<MyObjectBuilder_Checkpoint.ModItem> mods)
{ {
if (__result.Success) return; if (__result.Result is MyGameServiceCallResult.OK) return;
_log.Warn("Missing Mods:"); _log.Warn("Missing Mods:");
var mismatchMods = mods.Where(b => __result.Mods.All(c => b.PublishedFileId != c.Id)); var mismatchMods = mods.Where(b => __result.Mods.All(c => b.PublishedFileId != c.Id));
foreach (var mod in mismatchMods) foreach (var mod in mismatchMods)

View File

@@ -1,140 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Sandbox;
using Sandbox.Game.Entities;
using Torch.Utils;
using VRage.Game.Common;
using VRage.Game.Components;
using VRage.Game.Entity;
using VRage.ObjectBuilders;
using VRage.Plugins;
using VRage.Utils;
namespace Torch.Patches
{
/// <summary>
/// There are places in static ctors where the registered assembly depends on the <see cref="Assembly.GetCallingAssembly"/>
/// or <see cref="MyPlugins"/>. Here we force those registrations with the proper assemblies to ensure they work correctly.
/// </summary>
internal static class ObjectFactoryInitPatch
{
#pragma warning disable 649
[ReflectedGetter(Name = "m_objectFactory", TypeName = "Sandbox.Game.Entities.MyEntityFactory, Sandbox.Game")]
private static Func<MyObjectFactory<MyEntityTypeAttribute, MyEntity>> EntityFactoryObjectFactory;
#pragma warning restore 649
internal static void ForceRegisterAssemblies()
{
var userAssemblies = MyPlugins.UserAssemblies;
// static MyEntities() called by MySandboxGame.ForceStaticCtor
RuntimeHelpers.RunClassConstructor(typeof(MyEntities).TypeHandle);
{
MyObjectFactory<MyEntityTypeAttribute, MyEntity> factory = EntityFactoryObjectFactory();
ObjectFactory_RegisterFromAssemblySafe(factory, typeof(MySandboxGame).Assembly); // calling assembly
ObjectFactory_RegisterFromAssemblySafe(factory, MyPlugins.GameAssembly);
ObjectFactory_RegisterFromAssemblySafe(factory, MyPlugins.SandboxAssembly);
//ObjectFactory_RegisterFromAssemblySafe(factory, MyPlugins.UserAssembly);
if (userAssemblies != null)
{
foreach (var assembly in userAssemblies)
{
ObjectFactory_RegisterFromAssemblySafe(factory, assembly);
}
}
}
// static MyGuiManager():
// MyGuiControlsFactory.RegisterDescriptorsFromAssembly();
// static MyComponentTypeFactory() called by MyComponentContainer.Add
RuntimeHelpers.RunClassConstructor(typeof(MyComponentTypeFactory).TypeHandle);
{
ComponentTypeFactory_RegisterFromAssemblySafe(typeof(MyComponentContainer).Assembly); // calling assembly
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.SandboxAssembly);
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.GameAssembly);
ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.SandboxGameAssembly);
//ComponentTypeFactory_RegisterFromAssemblySafe(MyPlugins.UserAssembly);
if (userAssemblies != null)
{
foreach (var assembly in userAssemblies)
{
ComponentTypeFactory_RegisterFromAssemblySafe(assembly);
}
}
}
// static MyObjectPoolManager()
// Render, so should be fine.
}
#region MyObjectFactory Adders
private static void ObjectFactory_RegisterDescriptorSafe<TAttribute, TCreatedObjectBase>(
MyObjectFactory<TAttribute, TCreatedObjectBase> factory, TAttribute descriptor, Type type) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
{
if (factory.Attributes.TryGetValue(type, out _))
return;
if (descriptor.ObjectBuilderType != null && factory.TryGetProducedType(descriptor.ObjectBuilderType) != null)
return;
if (typeof(MyObjectBuilder_Base).IsAssignableFrom(descriptor.ProducedType) &&
factory.TryGetProducedType(descriptor.ProducedType) != null)
return;
factory.RegisterDescriptor(descriptor, type);
}
private static void ObjectFactory_RegisterFromAssemblySafe<TAttribute, TCreatedObjectBase>(MyObjectFactory<TAttribute, TCreatedObjectBase> factory, Assembly assembly) where TAttribute : MyFactoryTagAttribute where TCreatedObjectBase : class
{
if (assembly == null)
{
return;
}
foreach (Type type in assembly.GetTypes())
{
foreach (TAttribute descriptor in type.GetCustomAttributes<TAttribute>())
{
ObjectFactory_RegisterDescriptorSafe(factory, descriptor, type);
}
}
}
#endregion
#region MyComponentTypeFactory Adders
[ReflectedGetter(Name = "m_idToType", Type = typeof(MyComponentTypeFactory))]
private static Func<Dictionary<MyStringId, Type>> ComponentTypeFactoryIdToType = null!;
[ReflectedGetter(Name = "m_typeToId", Type = typeof(MyComponentTypeFactory))]
private static Func<Dictionary<Type, MyStringId>> ComponentTypeFactoryTypeToId = null!;
[ReflectedGetter(Name = "m_typeToContainerComponentType", Type = typeof(MyComponentTypeFactory))]
private static Func<Dictionary<Type, Type>> ComponentTypeFactoryContainerComponentType = null!;
private static void ComponentTypeFactory_RegisterFromAssemblySafe(Assembly assembly)
{
if (assembly == null)
return;
foreach (Type type in assembly.GetTypes())
if (typeof(MyComponentBase).IsAssignableFrom(type))
{
ComponentTypeFactory_AddIdSafe(type, MyStringId.GetOrCompute(type.Name));
ComponentTypeFactory_RegisterComponentTypeAttributeSafe(type);
}
}
private static void ComponentTypeFactory_RegisterComponentTypeAttributeSafe(Type type)
{
Type componentType = type.GetCustomAttribute<MyComponentTypeAttribute>(true)?.ComponentType;
if (componentType != null)
ComponentTypeFactoryContainerComponentType()[type] = componentType;
}
private static void ComponentTypeFactory_AddIdSafe(Type type, MyStringId id)
{
ComponentTypeFactoryIdToType()[id] = type;
ComponentTypeFactoryTypeToId()[type] = id;
}
#endregion
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Reflection;
using Sandbox.Game.Entities.Blocks;
using Sandbox.Game.World;
using Torch.API.Managers;
using Torch.Managers;
using Torch.Managers.PatchManager;
using Torch.Utils;
namespace Torch.Patches;
[PatchShim]
public static class ProgramableBlockPatch
{
[ReflectedMethodInfo(typeof(MyProgrammableBlock), "Compile")]
private static MethodInfo CompileMethod = null!;
public static void Patch(PatchContext context)
{
context.GetPattern(CompileMethod).AddPrefix();
}
private static bool Prefix(MyProgrammableBlock __instance, string program, string storage, bool instantiate)
{
if (!MySession.Static.EnableIngameScripts || __instance.CubeGrid is {IsPreview: true} or {CreatePhysics: false})
return false;
#pragma warning disable CS0618
TorchBase.Instance.CurrentSession.Managers.GetManager<ScriptCompilationManager>().CompileAsync(__instance, program, storage, instantiate);
#pragma warning restore CS0618
return false;
}
}

View File

@@ -12,6 +12,7 @@ using System.Reflection.Emit;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using ParallelTasks;
using ProtoBuf; using ProtoBuf;
using Torch.Managers.PatchManager; using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL; using Torch.Managers.PatchManager.MSIL;
@@ -57,10 +58,10 @@ namespace Torch.Patches
typeof(TypeConverter).Assembly.Location, typeof(TypeConverter).Assembly.Location,
typeof(System.Diagnostics.TraceSource).Assembly.Location, typeof(System.Diagnostics.TraceSource).Assembly.Location,
typeof(System.Security.Policy.Evidence).Assembly.Location, typeof(System.Security.Policy.Evidence).Assembly.Location,
typeof(ProtoBuf.Meta.RuntimeTypeModel).Assembly.Location, Path.Combine(baseDir, "System.Xml.ReaderWriter.dll"),
typeof(ProtoContractAttribute).Assembly.Location,
Path.Combine(baseDir, "netstandard.dll"), Path.Combine(baseDir, "netstandard.dll"),
Path.Combine(baseDir, "System.Runtime.dll"), Path.Combine(baseDir, "System.Runtime.dll"),
Path.Combine(MyFileSystem.ExePath, "ProtoBuf.Net.Core.dll"),
Path.Combine(MyFileSystem.ExePath, "Sandbox.Game.dll"), Path.Combine(MyFileSystem.ExePath, "Sandbox.Game.dll"),
Path.Combine(MyFileSystem.ExePath, "Sandbox.Common.dll"), Path.Combine(MyFileSystem.ExePath, "Sandbox.Common.dll"),
Path.Combine(MyFileSystem.ExePath, "Sandbox.Graphics.dll"), Path.Combine(MyFileSystem.ExePath, "Sandbox.Graphics.dll"),
@@ -103,7 +104,7 @@ namespace Torch.Patches
do do
{ {
ins[i] = new(OpCodes.Nop); ins[i] = new(OpCodes.Nop);
} while (ins[--i].OpCode.OperandType != OperandType.ShortInlineBrTarget); } while (ins[--i].OpCode.FlowControl != FlowControl.Cond_Branch);
ins[index] = new(OpCodes.Ret); ins[index] = new(OpCodes.Ret);
} }

View File

@@ -62,7 +62,7 @@ internal static class AssemblyRewriter
{ {
using var module = ModuleDefinition.ReadModule(inputStream, new() using var module = ModuleDefinition.ReadModule(inputStream, new()
{ {
AssemblyResolver = _zipResolver AssemblyResolver = resolver
}); });
foreach (var fieldDefinition in FindAllToRewrite(module)) foreach (var fieldDefinition in FindAllToRewrite(module))
{ {

View File

@@ -16,6 +16,7 @@ using Torch.API.Managers;
using Torch.API.Plugins; using Torch.API.Plugins;
using Torch.API.Session; using Torch.API.Session;
using Torch.API.WebAPI; using Torch.API.WebAPI;
using Torch.API.WebAPI.Plugin;
using Torch.Collections; using Torch.Collections;
using Torch.Commands; using Torch.Commands;
using Torch.Plugins; using Torch.Plugins;
@@ -306,7 +307,7 @@ namespace Torch.Managers
return; return;
} }
item.Manifest.Version.TryExtractVersion(out Version currentVersion); item.Manifest.Version.TryExtractVersion(out Version currentVersion);
var latest = await PluginQuery.Instance.QueryOne(item.Manifest.Guid); var latest = await LegacyPluginQuery.Instance.QueryOne(item.Manifest.Guid);
if (latest?.LatestVersion == null) if (latest?.LatestVersion == null)
{ {
@@ -329,7 +330,7 @@ namespace Torch.Managers
} }
_log.Info($"Updating plugin '{item.Manifest.Name}' from {currentVersion} to {newVersion}."); _log.Info($"Updating plugin '{item.Manifest.Name}' from {currentVersion} to {newVersion}.");
await PluginQuery.Instance.DownloadPlugin(latest, item.Path); await LegacyPluginQuery.Instance.DownloadPlugin(latest, item.Path);
Interlocked.Increment(ref count); Interlocked.Increment(ref count);
} }
catch (Exception e) catch (Exception e)
@@ -385,12 +386,8 @@ namespace Torch.Managers
} }
var harmonyAssembly = assemblies.FirstOrDefault(b => b.FullName?.StartsWith("0Harmony") == true); var harmonyAssembly = assemblies.FirstOrDefault(b => b.FullName?.StartsWith("0Harmony") == true);
if (harmonyAssembly is { }) if (harmonyAssembly is { }) assemblies.Remove(harmonyAssembly);
{
_log.Warn($"Plugin {item.Manifest.Name} is using harmony library, logic collision between plugins could be encountered!");
assemblies.Remove(harmonyAssembly);
}
RegisterAllAssemblies(assemblies); RegisterAllAssemblies(assemblies);
InstantiatePlugin(item.Manifest, assemblies); InstantiatePlugin(item.Manifest, assemblies);
} }
@@ -428,10 +425,10 @@ namespace Torch.Managers
if (a.Version is null || b.Version is null) if (a.Version is null || b.Version is null)
return a.Name == b.Name; return a.Name == b.Name;
return a.Name == b.Name && a.Version.Major == b.Version.Major && a.Version.Minor == b.Version.Minor; return a.Name == b.Name && b.Version >= a.Version;
} }
private void InstantiatePlugin(PluginManifest manifest, IEnumerable<Assembly> assemblies) internal void InstantiatePlugin(PluginManifest manifest, IEnumerable<Assembly> assemblies)
{ {
Type pluginType = null; Type pluginType = null;
bool mult = false; bool mult = false;

View File

@@ -50,7 +50,7 @@ namespace Torch.Session
{ {
_overrideMods = new Dictionary<ulong, MyObjectBuilder_Checkpoint.ModItem>(); _overrideMods = new Dictionary<ulong, MyObjectBuilder_Checkpoint.ModItem>();
if (Torch.Config.UgcServiceType == UGCServiceType.Steam) if (Torch.Config.UgcServiceType == UGCServiceType.Steam)
_overrideMods.Add(TorchModCore.MOD_ID, ModItemUtils.Create(TorchModCore.MOD_ID)); _overrideMods.Add(ModCommunication.MOD_ID, ModItemUtils.Create(ModCommunication.MOD_ID));
} }
/// <inheritdoc/> /// <inheritdoc/>

View File

@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6-windows</TargetFramework> <TargetFramework>net7-windows</TargetFramework>
<LangVersion>10</LangVersion>
<AssemblyTitle>Torch</AssemblyTitle> <AssemblyTitle>Torch</AssemblyTitle>
<Product>Torch</Product> <Product>Torch</Product>
<Copyright>Copyright © Torch API 2017</Copyright> <Copyright>Copyright © Torch API 2017</Copyright>
@@ -12,6 +11,9 @@
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms> <Platforms>AnyCPU</Platforms>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<DefineConstants>TRACE;TORCH</DefineConstants>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="$(Configuration) == 'Release'"> <PropertyGroup Condition="$(Configuration) == 'Release'">
@@ -21,19 +23,19 @@
<Import Project="..\Torch.Mod\Torch.Mod.projitems" Label="Shared" /> <Import Project="..\Torch.Mod\Torch.Mod.projitems" Label="Shared" />
<ItemGroup> <ItemGroup>
<PackageReference Include="ControlzEx" Version="5.0.1" /> <PackageReference Include="ControlzEx" Version="5.0.2" />
<PackageReference Include="HarmonyX" Version="2.10.0" /> <PackageReference Include="HarmonyX" Version="2.10.2-prerelease.4" />
<PackageReference Include="InfoOf.Fody" Version="2.1.1" PrivateAssets="all" /> <PackageReference Include="InfoOf.Fody" Version="2.1.1" PrivateAssets="all" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" /> <PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.3.1" /> <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" /> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageReference Include="MonoMod.RuntimeDetour" Version="22.5.1.1" /> <PackageReference Include="MonoModReorg.RuntimeDetour" Version="23.3.22.1" />
<PackageReference Include="NLog" Version="5.0.4" /> <PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="PropertyChanged.Fody" Version="4.0.3" PrivateAssets="all" /> <PackageReference Include="PropertyChanged.Fody" Version="4.1.0" PrivateAssets="all" />
<PackageReference Include="protobuf-net" Version="2.4.7" /> <PackageReference Include="protobuf-net" Version="3.1.26" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="Torch.SixLabors.ImageSharp" Version="1.0.0-beta6" /> <PackageReference Include="Torch.SixLabors.ImageSharp" Version="1.0.0-beta6" />
<PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.201.13"> <PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.202.120">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile</IncludeAssets> <IncludeAssets>compile</IncludeAssets>
</PackageReference> </PackageReference>

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Reflection; using System.Reflection;
@@ -11,7 +10,6 @@ using Sandbox;
using Sandbox.Game; using Sandbox.Game;
using Sandbox.Game.Multiplayer; using Sandbox.Game.Multiplayer;
using Sandbox.Game.Screens.Helpers; using Sandbox.Game.Screens.Helpers;
using SpaceEngineers.Game;
using Torch.API; using Torch.API;
using Torch.API.Managers; using Torch.API.Managers;
using Torch.API.ModAPI; using Torch.API.ModAPI;
@@ -21,10 +19,10 @@ using Torch.Event;
using Torch.Managers; using Torch.Managers;
using Torch.Managers.ChatManager; using Torch.Managers.ChatManager;
using Torch.Managers.PatchManager; using Torch.Managers.PatchManager;
using Torch.Packages;
using Torch.Patches; using Torch.Patches;
using Torch.Utils; using Torch.Utils;
using Torch.Session; using Torch.Session;
using VRage.Platform.Windows;
using VRage.Plugins; using VRage.Plugins;
using VRage.Utils; using VRage.Utils;
@@ -92,6 +90,7 @@ namespace Torch
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="InvalidOperationException">Thrown if a TorchBase instance already exists.</exception> /// <exception cref="InvalidOperationException">Thrown if a TorchBase instance already exists.</exception>
protected TorchBase(ITorchConfig config) protected TorchBase(ITorchConfig config)
{ {
@@ -103,14 +102,8 @@ namespace Torch
#pragma warning restore CS0618 #pragma warning restore CS0618
Config = config; Config = config;
var versionString = GetType().Assembly var assemblyVersion = GetType().Assembly.GetName().Version ?? new Version();
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()! TorchVersion = new(assemblyVersion.Major, assemblyVersion.Minor, assemblyVersion.Build);
.InformationalVersion;
if (!SemanticVersioning.Version.TryParse(versionString, out var version))
throw new TypeLoadException("Unable to parse the Torch version from the assembly.");
TorchVersion = version;
RunArgs = Array.Empty<string>(); RunArgs = Array.Empty<string>();
@@ -120,32 +113,36 @@ namespace Torch
Plugins = new PluginManager(this); Plugins = new PluginManager(this);
#pragma warning restore CS0618 #pragma warning restore CS0618
var sessionManager = new TorchSessionManager(this);
sessionManager.AddFactory((x) => Sync.IsServer ? new ChatManagerServer(this) : new ChatManagerClient(this));
sessionManager.AddFactory((x) => Sync.IsServer ? new CommandManager(this) : null);
sessionManager.AddFactory((x) => new EntityManager(this));
Managers.AddManager(sessionManager);
Managers.AddManager(new PatchManager(this)); Managers.AddManager(new PatchManager(this));
Managers.AddManager(new FilesystemManager(this)); Managers.AddManager(new FilesystemManager(this));
// Managers.AddManager(new UpdateManager(this)); Managers.AddManager(new UpdateManager(this));
Managers.AddManager(new EventManager(this)); Managers.AddManager(new EventManager(this));
#pragma warning disable CS0618 #pragma warning disable CS0618
Managers.AddManager(Plugins); Managers.AddManager(Plugins);
Managers.AddManager(new PackageManager(this, (PluginManager)Plugins));
#pragma warning restore CS0618 #pragma warning restore CS0618
Managers.AddManager(new ScriptCompilationManager(this));
TorchAPI.Instance = this; TorchAPI.Instance = this;
GameStateChanged += (game, state) => GameStateChanged += (game, state) =>
{ {
if (state == TorchGameState.Created) if (state == TorchGameState.Created)
{
// If the attached assemblies change (MySandboxGame.ctor => MySandboxGame.ParseArgs => MyPlugins.RegisterFromArgs)
// attach assemblies to object factories again.
ObjectFactoryInitPatch.ForceRegisterAssemblies();
// safe to commit here; all important static ctors have run
PatchManager.CommitInternal(); PatchManager.CommitInternal();
}
}; };
var harmonyLog = LogManager.GetLogger("HarmonyX");
HarmonyLib.Tools.Logger.ChannelFilter = HarmonyLib.Tools.Logger.LogChannel.Debug;
HarmonyLib.Tools.Logger.MessageReceived += (_, args) => harmonyLog.Log(args.LogChannel switch
{
HarmonyLib.Tools.Logger.LogChannel.None => LogLevel.Off,
HarmonyLib.Tools.Logger.LogChannel.Info => LogLevel.Info,
HarmonyLib.Tools.Logger.LogChannel.IL => LogLevel.Trace,
HarmonyLib.Tools.Logger.LogChannel.Warn => LogLevel.Warn,
HarmonyLib.Tools.Logger.LogChannel.Error => LogLevel.Error,
HarmonyLib.Tools.Logger.LogChannel.Debug => LogLevel.Debug,
HarmonyLib.Tools.Logger.LogChannel.All => LogLevel.Debug,
_ => throw new ArgumentOutOfRangeException()
}, args.Message);
} }
[Obsolete("Prefer using Managers.GetManager for global managers")] [Obsolete("Prefer using Managers.GetManager for global managers")]
@@ -250,7 +247,6 @@ namespace Torch
public virtual void Init() public virtual void Init()
{ {
Debug.Assert(!_init, "Torch instance is already initialized."); Debug.Assert(!_init, "Torch instance is already initialized.");
ObjectFactoryInitPatch.ForceRegisterAssemblies();
VRageGame.SetupVersionInfo(); VRageGame.SetupVersionInfo();
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null"); Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
@@ -276,9 +272,23 @@ namespace Torch
Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}"); Log.Info($"Executing directory: {AppDomain.CurrentDomain.BaseDirectory}");
Managers.GetManager<PluginManager>().LoadPlugins(); Managers.GetManager<PluginManager>().LoadPlugins();
var semaphore = new SemaphoreSlim(0, 1);
Managers.GetManager<PackageManager>().LoadAsync(semaphore);
semaphore.Wait();
Game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, InstancePath, RunArgs); Game = new VRageGame(this, TweakGameSettings, SteamAppName, SteamAppId, InstancePath, RunArgs);
if (!Game.WaitFor(VRageGame.GameState.Stopped)) if (!Game.WaitFor(VRageGame.GameState.Stopped))
Log.Warn("Failed to wait for game to be initialized"); Log.Warn("Failed to wait for game to be initialized");
var sessionManager = new TorchSessionManager(this);
sessionManager.AddFactory((x) => Sync.IsServer ? new ChatManagerServer(this) : new ChatManagerClient(this));
sessionManager.AddFactory((x) => Sync.IsServer ? new CommandManager(this) : null);
sessionManager.AddFactory((x) => new EntityManager(this));
Managers.AddManager(sessionManager);
Managers.Attach(); Managers.Attach();
_init = true; _init = true;

View File

@@ -47,7 +47,7 @@ namespace Torch.Utils
private static void CopyNative() private static void CopyNative()
{ {
if (ApplicationContext.Current.GameFilesDirectory.Attributes.HasFlag(FileAttributes.ReadOnly)) if (ApplicationContext.Current.IsService || ApplicationContext.Current.GameFilesDirectory.Attributes.HasFlag(FileAttributes.ReadOnly))
{ {
Log.Warn("Torch directory is readonly. You should copy steam_api64.dll, Havok.dll from bin manually"); Log.Warn("Torch directory is readonly. You should copy steam_api64.dll, Havok.dll from bin manually");
return; return;

View File

@@ -152,6 +152,7 @@ namespace Torch
Environment.SetEnvironmentVariable("SteamAppId", _appSteamId.ToString()); Environment.SetEnvironmentVariable("SteamAppId", _appSteamId.ToString());
MyVRageWindows.Init("SpaceEngineersDedicated", MySandboxGame.Log, null, false); MyVRageWindows.Init("SpaceEngineersDedicated", MySandboxGame.Log, null, false);
SpaceEngineersGame.SetupPerGameSettings(); SpaceEngineersGame.SetupPerGameSettings();
SpaceEngineersGame.SetupBasicGameInfo();
MySessionComponentExtDebug.ForceDisable = true; MySessionComponentExtDebug.ForceDisable = true;
MyPerGameSettings.SendLogToKeen = false; MyPerGameSettings.SendLogToKeen = false;
// SpaceEngineersGame.SetupAnalytics(); // SpaceEngineersGame.SetupAnalytics();
@@ -165,7 +166,7 @@ namespace Torch
_tweakGameSettings(); _tweakGameSettings();
MyFileSystem.Reset(); MyFileSystem.Reset();
MyInitializer.InvokeBeforeRun(_appSteamId, _appName, _userDataPath); MyInitializer.InvokeBeforeRun(_appSteamId, _appName, MyVRage.Platform.System.GetRootPath(), _userDataPath);
_log.Info("Loading Dedicated Config"); _log.Info("Loading Dedicated Config");
// object created in SpaceEngineersGame.SetupPerGameSettings() // object created in SpaceEngineersGame.SetupPerGameSettings()
@@ -193,7 +194,7 @@ namespace Torch
{ {
service = MyEOSService.Create(); service = MyEOSService.Create();
MyEOSService.InitNetworking(dedicated, MyEOSService.InitNetworking(dedicated, true, //true because using Eos
"Space Engineers", "Space Engineers",
service, service,
"xyza7891A4WeGrpP85BTlBa3BSfUEABN", "xyza7891A4WeGrpP85BTlBa3BSfUEABN",
@@ -225,7 +226,8 @@ namespace Torch
MyGameService.WorkshopService.AddAggregate(MyModIoService.Create(service, "spaceengineers", "264", MyGameService.WorkshopService.AddAggregate(MyModIoService.Create(service, "spaceengineers", "264",
"1fb4489996a5e8ffc6ec1135f9985b5b", "331", "f2b64abe55452252b030c48adc0c1f0e", "1fb4489996a5e8ffc6ec1135f9985b5b", "331", "f2b64abe55452252b030c48adc0c1f0e",
MyPlatformGameSettings.UGC_TEST_ENVIRONMENT, true)); MyPlatformGameSettings.UGC_TEST_ENVIRONMENT, true, MyPlatformGameSettings.MODIO_PLATFORM,
MyPlatformGameSettings.MODIO_PORTAL));
if (!isEos && !MyGameService.HasGameServer) if (!isEos && !MyGameService.HasGameServer)
{ {
@@ -240,6 +242,7 @@ namespace Torch
_log.Info("Services initialized"); _log.Info("Services initialized");
MySandboxGame.InitMultithreading(); MySandboxGame.InitMultithreading();
MyVRage.Platform.System.OnThreadpoolInitialized();
// MyInitializer.InitCheckSum(); // MyInitializer.InitCheckSum();
@@ -309,6 +312,7 @@ namespace Torch
_getVRagePluginList().Remove(_torch); _getVRagePluginList().Remove(_torch);
MyInitializer.InvokeAfterRun(); MyInitializer.InvokeAfterRun();
MyVRage.Done();
} }
private void DoStart() private void DoStart()

View File

@@ -151,7 +151,7 @@ namespace Torch.Views
valueControl = (FrameworkElement)Activator.CreateInstance(descriptor.EditorType); valueControl = (FrameworkElement)Activator.CreateInstance(descriptor.EditorType);
valueControl.SetBinding(FrameworkElement.DataContextProperty, property.Name); valueControl.SetBinding(FrameworkElement.DataContextProperty, property.Name);
} }
else if (property.GetSetMethod() == null && !(propertyType.IsGenericType && typeof(ICollection).IsAssignableFrom(propertyType.GetGenericTypeDefinition()))|| descriptor?.ReadOnly == true) else if (property.GetSetMethod() == null && !(propertyType.IsGenericType && typeof(ICollection).IsAssignableFrom(propertyType.GetGenericTypeDefinition())))
{ {
valueControl = new TextBlock(); valueControl = new TextBlock();
var binding = new Binding(property.Name) var binding = new Binding(property.Name)
@@ -234,7 +234,10 @@ namespace Torch.Views
} }
else if (propertyType.IsPrimitive) else if (propertyType.IsPrimitive)
{ {
valueControl = new TextBox(); valueControl = new TextBox
{
IsReadOnly = descriptor?.ReadOnly == true
};
valueControl.SetBinding(TextBox.TextProperty, property.Name); valueControl.SetBinding(TextBox.TextProperty, property.Name);
} }
else if (propertyType == typeof(string)) else if (propertyType == typeof(string))
@@ -244,6 +247,7 @@ namespace Torch.Views
tb.AcceptsReturn = true; tb.AcceptsReturn = true;
tb.AcceptsTab = true; tb.AcceptsTab = true;
tb.SpellCheck.IsEnabled = true; tb.SpellCheck.IsEnabled = true;
tb.IsReadOnly = descriptor?.ReadOnly == true;
tb.SetBinding(TextBox.TextProperty, property.Name); tb.SetBinding(TextBox.TextProperty, property.Name);
valueControl = tb; valueControl = tb;
} }

395
Torch/packages.lock.json Normal file
View File

@@ -0,0 +1,395 @@
{
"version": 1,
"dependencies": {
"net7.0-windows7.0": {
"ControlzEx": {
"type": "Direct",
"requested": "[5.0.2, )",
"resolved": "5.0.2",
"contentHash": "f724LoDJ36LxaLR62G4ek9ZAJI8BiiYRJJ04furC/qjXSeIwU0qmHFIe19xB1/FwxyZjevdFguEr9ZUjf3dZgw==",
"dependencies": {
"Microsoft.Xaml.Behaviors.Wpf": "1.1.31",
"System.Text.Json": "5.0.1"
}
},
"HarmonyX": {
"type": "Direct",
"requested": "[2.10.2-prerelease.4, )",
"resolved": "2.10.2-prerelease.4",
"contentHash": "M/44/DhSu1WR8gL62aLPGKqAJ0rrgkw38nT3DgEvLAvyIoQLJmL2Jsn4/d4CC0eDT1Vv82cB9Mxgg0sFD3/qrQ==",
"dependencies": {
"MonoModReorg.RuntimeDetour": "23.3.22.1"
}
},
"InfoOf.Fody": {
"type": "Direct",
"requested": "[2.1.1, )",
"resolved": "2.1.1",
"contentHash": "o87i6Aab7fVKbiEbRySH/IY14BytH/I63Yv0G3KK4xcAsQa759RrFyLB9JhpJMilN99ioJHvYqybZNvpN6FGDQ==",
"dependencies": {
"Fody": "6.6.0"
}
},
"MahApps.Metro": {
"type": "Direct",
"requested": "[2.4.9, )",
"resolved": "2.4.9",
"contentHash": "eMTkg6TBnCwHzszw7CP+pxsBeB4ZMsJFiTJJoifUVBysRyEenzzR+TKQJuMvvKK6KzvLxwHmJsFhi9o5p4vxhQ==",
"dependencies": {
"ControlzEx": "[4.4.0, 6.0.0)"
}
},
"Microsoft.CodeAnalysis.Common": {
"type": "Direct",
"requested": "[4.4.0, )",
"resolved": "4.4.0",
"contentHash": "JfHupS/B7Jb5MZoYkFFABn3mux0wQgxi2D8F/rJYZeRBK2ZOyk7TjQ2Kq9rh6W/DCh0KNbbSbn5qoFar+ueHqw==",
"dependencies": {
"Microsoft.CodeAnalysis.Analyzers": "3.3.3",
"System.Collections.Immutable": "6.0.0",
"System.Memory": "4.5.5",
"System.Reflection.Metadata": "5.0.0",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encoding.CodePages": "6.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.CodeAnalysis.CSharp": {
"type": "Direct",
"requested": "[4.4.0, )",
"resolved": "4.4.0",
"contentHash": "eD2w0xHRoaqK07hjlOKGR9eLNy3nimiGNeCClNax1NDgS/+DBtBqCjXelOa+TNy99kIB3nHhUqDmr46nDXy/RQ==",
"dependencies": {
"Microsoft.CodeAnalysis.Common": "[4.4.0]"
}
},
"MonoModReorg.RuntimeDetour": {
"type": "Direct",
"requested": "[23.3.22.1, )",
"resolved": "23.3.22.1",
"contentHash": "cUES3Hi7QJtBNe0qwVdjZOCDkZDaCpdqS6Au0RaCyQ1PDIR4av13E9qBNAEYew46sgPe0fOrU334md6DLDGHlQ==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "23.3.22.1",
"MonoModReorg.Core": "23.3.22.1",
"MonoModReorg.ILHelpers": "23.3.22.1",
"MonoModReorg.Utils": "23.3.22.1"
}
},
"NLog": {
"type": "Direct",
"requested": "[5.1.0, )",
"resolved": "5.1.0",
"contentHash": "oW7ekrkRG9okpDMUcEglunWj8Qf2RY8qkgl+/chJoavzg3dbT13y32t19R54FKkmq80fKzw4ZekZkCrRGanKgQ=="
},
"PropertyChanged.Fody": {
"type": "Direct",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "6v+f9cI8YjnZH2WBHuOqWEAo8DFFNGFIdU8xD3AsL6fhV6Y8oAmVWd7XKk49t8DpeUBwhR/X+97+6Epvek0Y3A==",
"dependencies": {
"Fody": "6.6.4"
}
},
"protobuf-net": {
"type": "Direct",
"requested": "[3.1.26, )",
"resolved": "3.1.26",
"contentHash": "iy+VROYtWZEqDvkUjS6FfzKZNpWRoVGc5h0cupGQT/37Ox0LxPblRKLnw6V0RT1MPclLo8nZp7E2M0f7PNn/Tw==",
"dependencies": {
"protobuf-net.Core": "3.1.26"
}
},
"SpaceEngineersDedicated.ReferenceAssemblies": {
"type": "Direct",
"requested": "[1.202.120, )",
"resolved": "1.202.120",
"contentHash": "HTP48NOSZY3eQPX3GJN+gjxdW83SM7DX6NcKCFqNIIvjz/J+l8uimHAQp9dnLySb0wb1K1Z1xBPIXyxMQRREIw==",
"dependencies": {
"protobuf-net": "1.0.0"
}
},
"System.ComponentModel.Annotations": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
},
"Torch.SixLabors.ImageSharp": {
"type": "Direct",
"requested": "[1.0.0-beta6, )",
"resolved": "1.0.0-beta6",
"contentHash": "WJ7ocT79HgmuKi0+ltpvXTiMI80UcI3DeS8XSfYwJtTB1tcQws6zLPGuUwra6qe6qRrFfpABeDP3xvHV1rJgfg==",
"dependencies": {
"SixLabors.Core": "1.0.0-beta0007",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Fody": {
"type": "Transitive",
"resolved": "6.6.4",
"contentHash": "vLZS+oa+ndUHYPlx/8n9bBTT3dHkCF0riml4paKq4D663+cZd47x1uagQo32D/gKFZ/sfmV1oqKaLmH0elxq4A=="
},
"JorgeSerrano.Json.JsonSnakeCaseNamingPolicy": {
"type": "Transitive",
"resolved": "0.9.0",
"contentHash": "xCqODS+wzpUXNtg4bMMvXG5PLbP0iTwRzRn2R+zWHKm83E6tbV2bCagawXp1EnZeNpd5OXpMxehulZWns8efzQ=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg=="
},
"Microsoft.CodeAnalysis.Analyzers": {
"type": "Transitive",
"resolved": "3.3.3",
"contentHash": "j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ=="
},
"Microsoft.Xaml.Behaviors.Wpf": {
"type": "Transitive",
"resolved": "1.1.31",
"contentHash": "LZpuf82ACZWldmfMuv3CTUMDh3o0xo0uHUaybR5HgqVLDBJJ9RZLykplQ/bTJd0/VDt3EhD4iDgUgbdIUAM+Kg=="
},
"Mono.Cecil": {
"type": "Transitive",
"resolved": "0.11.4",
"contentHash": "IC1h5g0NeJGHIUgzM1P82ld57knhP0IcQfrYITDPXlNpMYGUrsG5TxuaWTjaeqDNQMBDNZkB8L0rBnwsY6JHuQ=="
},
"MonoModReorg.Backports": {
"type": "Transitive",
"resolved": "23.3.22.1",
"contentHash": "nLx18prtZbS0nVO8YHElP+TUj1kkDswRgQpa9agt3tyNWke9Kqk5lVkGu87Y3cCMhG+m5Ai0ZDH0+V5B6bXF+Q==",
"dependencies": {
"MonoModReorg.ILHelpers": "23.3.22.1"
}
},
"MonoModReorg.Core": {
"type": "Transitive",
"resolved": "23.3.22.1",
"contentHash": "Q3ZOgm6AOTL0icgcHsT/6mV5kYt6meqX3DEN1p/et5PxJsXUhVkiZfAptp8y/i+cO6HNgkcESpXEQ4jUWVwsDA==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "23.3.22.1",
"MonoModReorg.ILHelpers": "23.3.22.1",
"MonoModReorg.Utils": "23.3.22.1"
}
},
"MonoModReorg.ILHelpers": {
"type": "Transitive",
"resolved": "23.3.22.1",
"contentHash": "dHE/3VZtnTUTkQ34j8Gu6CxKes1QDL8tBuF3QElxHHpPl/QabGCK938k47WWRcRBMoP7IAthkn+Bob4H97rBgw=="
},
"MonoModReorg.Utils": {
"type": "Transitive",
"resolved": "23.3.22.1",
"contentHash": "q1cX0mbYxdw2WcE/qGBB84lVY/ffobEr46VQFuwGcWhtw6f1Ql1k4TZfs8gebpesSxHnca13grLeVHrUSpy1+g==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "23.3.22.1",
"MonoModReorg.ILHelpers": "23.3.22.1"
}
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.1",
"contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A=="
},
"NuGet.Commands": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "j3ma45boGZADsHpJcu3Y2yq+n2luicC6ezd61TXTTzbOzA452oAPaSsFGUB1stIsuP/DVoqkTzjHXjaCHuJKPQ==",
"dependencies": {
"NuGet.Credentials": "6.4.0",
"NuGet.ProjectModel": "6.4.0"
}
},
"NuGet.Common": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "srECugLk+LB1bXelDCDhHoi6do/EYTXzuntKhjHraS4roVB3NfWohEdCSiAPdpSV9M40Q6jo6MV2Srml9e+jHQ==",
"dependencies": {
"NuGet.Frameworks": "6.4.0"
}
},
"NuGet.Configuration": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "vPjauG9AoacEjiZWGIs+d11FCRVmseqAw78FIApfLvZrYMEbbwc9vc0LdC3PpoW5FxYkktyZSiiXVKXGLu+gXw==",
"dependencies": {
"NuGet.Common": "6.4.0",
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
},
"NuGet.Credentials": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "tebsxclknVz3D4FrvE2MzVcsOyf6PffjGNQ77X9Yvbj9x5YpVWfumVPetqETcdsNEgiN0bBzfMre33lhrY7Itw==",
"dependencies": {
"NuGet.Protocol": "6.4.0"
}
},
"NuGet.DependencyResolver.Core": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "AKomZEKuhQlshujuiHbKvwl2cZNGq3SHsXFbpjCfCjMFMLwwA8saJGQQZp1lzsqQWcQWa6hLOcPtm7T3rd0SVg==",
"dependencies": {
"NuGet.Configuration": "6.4.0",
"NuGet.LibraryModel": "6.4.0",
"NuGet.Protocol": "6.4.0"
}
},
"NuGet.Frameworks": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "qcufbjJIDtyY/Hah7JJfcRVpRYM3scgPqYBnukjO9kfADCFGr2azvVBozuwzljA6w/cR3w8bXLq6vW5xGrsmHw=="
},
"NuGet.LibraryModel": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "K6ROQpWr34Aje81G0HfipiznLTB8vD4BO8sF6FEwx1KjJVdFkSmGZPmAhc6L1vZPs8TKY5BqoH72zG13zVzW2w==",
"dependencies": {
"NuGet.Common": "6.4.0",
"NuGet.Versioning": "6.4.0"
}
},
"NuGet.Packaging": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "aR10aYqcUMGC2mwMGH5rls/MGaz3EVH8DKTTHQ/EC91hXNtrCTTAQonaRR+v1EItcoxtQeZ/WQOorv4z270Tgg==",
"dependencies": {
"Newtonsoft.Json": "13.0.1",
"NuGet.Configuration": "6.4.0",
"NuGet.Versioning": "6.4.0",
"System.Security.Cryptography.Cng": "5.0.0",
"System.Security.Cryptography.Pkcs": "5.0.0"
}
},
"NuGet.ProjectModel": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "eW9Q7vPk8cpXDd5b+vtIPkl8dSDCPkPJPrjXPTfZGxhstldnhJrj1XPaonsDZLQ24YY7LrYCzC0BiHh3iO5zUA==",
"dependencies": {
"NuGet.DependencyResolver.Core": "6.4.0"
}
},
"NuGet.Protocol": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "KIPjsWP0P3EMsDsXaa6YBCTvYKur/zI0luS1kO5G37ci8mHs2kJFsuG5qaMhGvgyHASu54sxlic1n1oza2Pcbw==",
"dependencies": {
"NuGet.Packaging": "6.4.0"
}
},
"NuGet.Versioning": {
"type": "Transitive",
"resolved": "6.4.0",
"contentHash": "YE8p3TpX4jIw+Gb24maE8YRDoqWA4imLmCbdOj5IvslLrZJXQ8akeFOGOplxICNVevON1g1SFYT2+cq4yy0nQQ=="
},
"protobuf-net.Core": {
"type": "Transitive",
"resolved": "3.1.26",
"contentHash": "fuKoDWgAf5ju+ixbJoasVFhnUaT0oWUgrWh1AZ5D0Y5keKHpA93vQB5g6aQNtB4zgeQ4hGEeh0MY9bIbAUPgEw=="
},
"SemanticVersioning": {
"type": "Transitive",
"resolved": "2.0.2",
"contentHash": "4EQgYdNZ92SyaO7YFk6olVnebF5V+jrHyMUjvPq89tLeMo8NSfgDF+6Zwq/lgh9j/0yfQp9Lkm0ZA0rUATCZFA=="
},
"SixLabors.Core": {
"type": "Transitive",
"resolved": "1.0.0-beta0007",
"contentHash": "s9aPl6yxwcvoKRD0u0zjkCISZCCifbUi9/XVFjdvlx5Pt7vRYmGV0anq1EEftUjIEHbEu5aNBipbUSBIV2CE7w==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Formats.Asn1": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "MTvUIktmemNB+El0Fgw9egyqT9AYSIk6DTJeoDSpc3GIHxHCMo8COqkWT1mptX5tZ1SlQ6HJZ0OsSvMth1c12w=="
},
"System.Linq.Async": {
"type": "Transitive",
"resolved": "6.0.1",
"contentHash": "0YhHcaroWpQ9UCot3Pizah7ryAzQhNvobLMSxeDIGmnXfkQn8u5owvpOH0K6EVB+z9L7u6Cc4W17Br/+jyttEQ==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "6.0.0"
}
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"System.Security.Cryptography.Cng": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "jIMXsKn94T9JY7PvPq/tMfqa6GAaHpElRDpmG+SuL+D3+sTw2M8VhnibKnN8Tq+4JqbPJ/f+BwtLeDMEnzAvRg==",
"dependencies": {
"System.Formats.Asn1": "5.0.0"
}
},
"System.Security.Cryptography.Pkcs": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "9TPLGjBCGKmNvG8pjwPeuYy0SMVmGZRwlTZvyPHDbYv/DRkoeumJdfumaaDNQzVGMEmbWtg07zUpSW9q70IlDQ==",
"dependencies": {
"System.Formats.Asn1": "5.0.0",
"System.Security.Cryptography.Cng": "5.0.0"
}
},
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog=="
},
"System.Text.Encoding.CodePages": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Text.Json": {
"type": "Transitive",
"resolved": "5.0.1",
"contentHash": "/UM3UK1dXKl8Ybysg/21gM4S8DJgkR+yLU8JwqCVbuNqQNImelntgYFAN5QxR8sJJ1kMx//hOUdf0lltosi8cQ=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg=="
},
"torch.api": {
"type": "Project",
"dependencies": {
"JorgeSerrano.Json.JsonSnakeCaseNamingPolicy": "[0.9.0, )",
"NLog": "[5.1.0, )",
"NuGet.Commands": "[6.4.0, )",
"NuGet.DependencyResolver.Core": "[6.4.0, )",
"SemanticVersioning": "[2.0.2, )",
"System.Linq.Async": "[6.0.1, )"
}
}
}
}
}