Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions AdvancedPortals/AdvancedPortals.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Common;
using HarmonyLib;
using JetBrains.Annotations;
using Jotunn.Configs;
Expand Down Expand Up @@ -105,6 +106,13 @@ private void Awake()
true, true, ref BlackMarblePortalAllowPreviousPortalItems);

AssetBundle assetBundle = LoadAssetBundle("advancedportals");
if (assetBundle == null)
{
// Everything below needs the bundle. Bailing here reports one clear cause instead of a
// cascade of null-reference errors from the piece registrations that follow.
APLogger.LogError("Failed to load the 'advancedportals' asset bundle. Advanced Portals is disabled for this session.");
return;
}

LoadBuildPiece(assetBundle, "portal_ancient", new PieceConfig()
{
Expand Down Expand Up @@ -235,12 +243,14 @@ public string GetExtendedDescription(string description, bool synchronizedSettin
return description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]");
}

// The assembly is named explicitly rather than taken from Assembly.GetCallingAssembly(). When
// another mod hooks Awake, MonoMod recompiles it as a dynamic method (DMD<...::Awake>), and the
// "calling assembly" is then that dynamic assembly, not this one. The resource lookup misses,
// LoadFromStream(null) throws "ArgumentNullException: stream", and the mod fails to load —
// intermittently, since it depends on which other mods are present.
public static AssetBundle LoadAssetBundle(string filename)
{
Assembly assembly = Assembly.GetCallingAssembly();
AssetBundle assetBundle = AssetBundle.LoadFromStream(assembly.GetManifestResourceStream($"{assembly.GetName().Name}.{filename}"));

return assetBundle;
return AssetBundleLoader.LoadFromResources(filename, typeof(AdvancedPortals).Assembly);
}

private static void LoadBuildPiece(AssetBundle assetBundle, string assetName, PieceConfig piececonfig)
Expand Down
57 changes: 25 additions & 32 deletions AdvancedPortals/AdvancedPortals.csproj
Original file line number Diff line number Diff line change
@@ -1,59 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Paths.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0DDDE55C-5DAF-4B53-B5E2-83B5C44C4682}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<TargetFramework>net472</TargetFramework>
<RootNamespace>AdvancedPortals</RootNamespace>
<AssemblyName>AdvancedPortals</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<LangVersion>10.0</LangVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- Properties\AssemblyInfo.cs carries the assembly attributes; don't let the SDK
generate a second set. -->
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<!-- Keep the legacy bin\Debug\ / bin\Release\ layout that release.yml and mod-tools expect. -->
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<!-- Source files are discovered by the SDK's default **\*.cs glob; new folders under src\
need no csproj edit. -->
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="src\*.cs" />
<Compile Include="AdvancedPortals.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="..\ValheimUnity\AssetBundles\advancedportals">
<Link>advancedportals</Link>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Include="thunderstore\manifest.json" />
<None Include="README.md" />
<None Include="CHANGELOG.md" />
</ItemGroup>
<Import Project="..\Common\Common.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

<Target Name="CopyAssembly" AfterTargets="Build">
<Copy SourceFiles="$(TargetDir)$(TargetFileName)" DestinationFolder="$(SolutionDir)ValheimUnity\Assets\ExternalLibraries" />
</Target>

<Import Project="..\Common\Common.projitems" Label="Shared" />

<!-- BEGIN mod-tools (managed block; replaced by `mod-tools setup`) -->
<PropertyGroup>
<RepoRoot Condition=" '$(RepoRoot)' == '' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/'))</RepoRoot>
Expand Down Expand Up @@ -94,4 +87,4 @@
<Exec Command="$(ModTools) deploy --root &quot;$(RepoRoot).&quot; --configuration $(Configuration)" />
</Target>
<!-- END mod-tools -->
</Project>
</Project>
42 changes: 19 additions & 23 deletions ConsoleHelper/ConsoleHelper.csproj
Original file line number Diff line number Diff line change
@@ -1,41 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Paths.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{815F37D9-CEEA-4CDA-ABFB-C62FEA26B5F1}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<TargetFramework>net472</TargetFramework>
<RootNamespace>ConsoleHelper</RootNamespace>
<AssemblyName>ConsoleHelper</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<LangVersion>10.0</LangVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<!-- Properties\AssemblyInfo.cs carries the assembly attributes; don't let the SDK
generate a second set. -->
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<!-- Keep the legacy bin\Debug\ / bin\Release\ layout that release.yml and mod-tools expect. -->
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<!-- Source files are discovered by the SDK's default **\*.cs glob; new folders under src\
need no csproj edit. -->
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>DEBUG;TRACE</DefineConstants>
<Optimize>false</Optimize>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="ConsoleHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>
96 changes: 96 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Build wiring shared by every project in this solution.

Paths.props already resolves GamePath and derives the reference paths from it, but it is
imported from inside each .csproj. This file is imported before that - and before NuGet's
generated props - which is what makes it the right place to probe for things Paths.props
then consumes. Everything here only fills in values that are still empty, so an explicit
environment variable, an Environment.props, or -p:NAME=... always wins.
-->
<Project>

<!-- ==================== machine-local overrides (gitignored) ==================== -->
<!-- Copy Environment.props.example to Environment.props to point the build at a different
game install or a specific BepInEx. -->
<Import Project="$(MSBuildThisFileDirectory)Environment.props"
Condition="Exists('$(MSBuildThisFileDirectory)Environment.props')" />

<!-- ==================== solution root ==================== -->
<!-- Paths.props resolves Jotunn.dll, the API shims and the NuGet-restored LitJSON /
Newtonsoft.Json out of $(SolutionDir)Libs and $(SolutionDir)Packages. MSBuild only defines
SolutionDir when the build goes through the .sln; a direct `dotnet build Foo.csproj` leaves
it undefined and every one of those references silently fails to resolve. This file sits at
the solution root, so its own directory is the right answer. When the .sln is driving,
SolutionDir is a global property and this assignment is ignored, which is what we want. -->
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)' == '' Or !Exists('$(SolutionDir)')">$(MSBuildThisFileDirectory)</SolutionDir>
</PropertyGroup>

<!-- ==================== Valheim install ==================== -->
<!-- Same probe order Paths.props uses, hoisted here because BepinexPath below needs the
answer first. Paths.props sees GamePath already set and skips its own Choose. -->
<PropertyGroup Condition="'$(GamePath)' == '' And '$(OS)' == 'Windows_NT'">
<GamePath>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 892970', 'InstallLocation', null, RegistryView.Registry64, RegistryView.Registry32))</GamePath>
<_SteamLibraryPath>$([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Valve\Steam', 'SteamPath', null, RegistryView.Registry32))</_SteamLibraryPath>
<GamePath Condition="!Exists('$(GamePath)') And '$(_SteamLibraryPath)' != ''">$(_SteamLibraryPath)\steamapps\common\Valheim</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files\Steam\steamapps\common\Valheim</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Valheim</GamePath>
</PropertyGroup>

<PropertyGroup Condition="'$(GamePath)' == '' And '$(OS)' != 'Windows_NT'">
<GamePath>$(HOME)/.steam/steam/steamapps/common/Valheim</GamePath>
<GamePath Condition="!Exists('$(GamePath)')">$(HOME)/Library/Application Support/Steam/steamapps/common/Valheim/Contents/MacOS</GamePath>
</PropertyGroup>

<!-- ==================== BepInEx ==================== -->
<!-- Paths.props assumes BepInEx sits in the game folder. It does not when the game is modded
through Gale / r2modman / Thunderstore MM, which keep a BepInEx per profile and leave the
install vanilla - and then every BepInEx, Harmony, Mono.Cecil and MonoMod reference fails
to resolve and the compile dies in hundreds of CS0246s. Fall back to the first profile
that actually has a BepInEx core; any BepInEx 5 is fine to compile against, and
Environment.props can pin a specific one.

Note the trailing separator: Paths.props builds its HintPaths as $(BepinexPath)core\x.dll. -->
<PropertyGroup Condition="'$(BepinexPath)' == ''">
<BepinexPath Condition="Exists('$(GamePath)\BepInEx\core\BepInEx.dll')">$(GamePath)\BepInEx\</BepinexPath>
</PropertyGroup>

<PropertyGroup Condition="'$(BepinexPath)' == '' And '$(OS)' == 'Windows_NT'">
<_VhGaleProfiles>$(AppData)\com.kesomannen.gale\valheim\profiles</_VhGaleProfiles>
<_VhR2Profiles>$(AppData)\r2modmanPlus-local\Valheim\profiles</_VhR2Profiles>
<_VhTsmmProfiles>$(AppData)\Thunderstore Mod Manager\DataFolder\Valheim\profiles</_VhTsmmProfiles>

<!-- GetFiles returns absolute paths joined by ';', and only profiles that really have a
BepInEx show up at all, so the first hit is a usable one. Each manager is searched
only while nothing has been found yet. -->
<_VhBepInExHits Condition="'$(_VhBepInExHits)' == '' And Exists('$(_VhGaleProfiles)')">$([System.IO.Directory]::GetFiles('$(_VhGaleProfiles)', 'BepInEx.dll', SearchOption.AllDirectories))</_VhBepInExHits>
<_VhBepInExHits Condition="'$(_VhBepInExHits)' == '' And Exists('$(_VhR2Profiles)')">$([System.IO.Directory]::GetFiles('$(_VhR2Profiles)', 'BepInEx.dll', SearchOption.AllDirectories))</_VhBepInExHits>
<_VhBepInExHits Condition="'$(_VhBepInExHits)' == '' And Exists('$(_VhTsmmProfiles)')">$([System.IO.Directory]::GetFiles('$(_VhTsmmProfiles)', 'BepInEx.dll', SearchOption.AllDirectories))</_VhBepInExHits>
<_VhBepInExDll Condition="'$(_VhBepInExHits)' != ''">$(_VhBepInExHits.Split(';')[0])</_VhBepInExDll>
<BepinexPath Condition="'$(_VhBepInExDll)' != ''">$([System.IO.Path]::GetFullPath($([System.IO.Path]::Combine($([System.IO.Path]::GetDirectoryName('$(_VhBepInExDll)')), '..'))))\</BepinexPath>
<!-- A BepInEx.dll somewhere other than a core folder is not a BepInEx install. -->
<BepinexPath Condition="'$(BepinexPath)' != '' And !Exists('$(BepinexPath)core\0Harmony.dll')"></BepinexPath>
</PropertyGroup>

<!-- ==================== JotunnLib package aliases ==================== -->
<!-- EpicLoot pulls the JotunnLib NuGet package, whose props build their own reference list
from VALHEIM_INSTALL / BEPINEX_PATH - names this solution does not otherwise use, so they
fell back to the game folder and left BepInEx.Preloader and HarmonyXInterop unresolved.
Point them at what was resolved above. Set here because the package props are imported
before the .csproj body. -->
<PropertyGroup>
<VALHEIM_INSTALL Condition="'$(VALHEIM_INSTALL)' == ''">$(GamePath)</VALHEIM_INSTALL>
<VALHEIM_MANAGED Condition="'$(VALHEIM_MANAGED)' == '' And '$(GamePathManaged)' != ''">$(GamePathManaged.TrimEnd('\'))</VALHEIM_MANAGED>
<BEPINEX_PATH Condition="'$(BEPINEX_PATH)' == '' And '$(BepinexPath)' != ''">$(BepinexPath.TrimEnd('\'))</BEPINEX_PATH>
</PropertyGroup>

<!-- ==================== diagnostics ==================== -->
<Target Name="CheckValheimBuildPaths" BeforeTargets="BeforeBuild">
<Warning Condition="!Exists('$(GamePath)\valheim_Data\Managed')"
Text="Valheim's Managed folder was not found (GamePath='$(GamePath)'). Vanilla references will not resolve. Set GamePath in Environment.props - see Environment.props.example." />
<Warning Condition="!Exists('$(BepinexPath)core\BepInEx.dll')"
Text="BepInEx was not found (BepinexPath='$(BepinexPath)'). BepInEx, Harmony, Mono.Cecil and MonoMod references will not resolve. Set BepinexPath in Environment.props - see Environment.props.example." />
</Target>

</Project>
35 changes: 35 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Imported after every .csproj body, which is what makes it the place for items the projects and
their packages have already declared, and for anything keyed off a property a project sets.
Directory.Build.props is too early for both.
-->
<Project>

<ItemGroup>
<!-- JotunnLib's reference list (EpicLoot pulls the package) still carries assemblies that
Valheim's Unity version dropped. Nothing here uses them, and leaving them in warns
MSB3245 on every build. -->
<Reference Remove="UnityEngine.ProfilerModule" Condition="!Exists('$(GamePathManaged)UnityEngine.ProfilerModule.dll')" />
</ItemGroup>

<!-- ==================== publicized game assemblies ====================
Paths.props references the vanilla assemblies as the game ships them. Every mod here
except EquipmentAndQuickSlotsAPI patches or reads non-public Valheim members, so those
references have to be publicized first. Rather than depend on a publicized_assemblies
folder someone generated by hand at some point, BepInEx.AssemblyPublicizer.MSBuild
rewrites them into obj/ at build time - no writes to the game install, and a game update
is picked up on the next build.

Opt out per project with <PublicizeGameAssemblies>false</PublicizeGameAssemblies>; the
default is on because 11 of the 12 projects need it. The package requires an SDK-style
project, which is now true of all of them. -->
<ItemGroup Condition="'$(PublicizeGameAssemblies)' != 'false'">
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.3" PrivateAssets="all" />
<Reference Update="assembly_valheim" Publicize="true" />
<Reference Update="assembly_utils" Publicize="true" />
<Reference Update="assembly_guiutils" Publicize="true" />
<Reference Update="Splatform" Publicize="true" />
</ItemGroup>

</Project>
Loading
Loading