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
20 changes: 9 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0

Expand All @@ -40,8 +40,7 @@ jobs:
exit 1
fi

- name: Setup Resonite environment
id: resonite
- name: Setup current Resonite environment
uses: resonite-modding-group/setup-resonite-env-action@v0.1.0
with:
steam-user: ${{ secrets.STEAMUSER }}
Expand All @@ -65,9 +64,9 @@ jobs:
https://github.com/resonite-modding-group/ResoniteModLoader/releases/latest/download/0Harmony.dll

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x

- name: Cache NuGet packages
uses: actions/cache@v4
Expand Down Expand Up @@ -112,14 +111,12 @@ jobs:
set -euo pipefail
artifacts_dir=release-artifacts
mkdir -p "$artifacts_dir"
cp src/ReferenceReplacement/bin/Release/net9.0/ReferenceReplacement.dll "$artifacts_dir/ReferenceReplacement.dll"
if [ -f src/ReferenceReplacement/bin/Release/net9.0/ReferenceReplacement.pdb ]; then
cp src/ReferenceReplacement/bin/Release/net9.0/ReferenceReplacement.pdb "$artifacts_dir/ReferenceReplacement.pdb"
fi
cp src/ReferenceReplacement/bin/Release/net10.0/ReferenceReplacement.dll "$artifacts_dir/ReferenceReplacement.dll"
cp src/ReferenceReplacement/bin/Release/net10.0/ReferenceReplacement.pdb "$artifacts_dir/ReferenceReplacement.pdb"

- name: Upload release artifacts
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: reference-replacement
path: release-artifacts
Expand All @@ -129,11 +126,12 @@ jobs:
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: reference-replacement
path: release-artifacts
Expand Down
24 changes: 24 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AnalysisLevel>latest-all</AnalysisLevel>
<Nullable>enable</Nullable>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<IsPackable>false</IsPackable>
<CopyToMods Condition="'$(CopyToMods)'==''">false</CopyToMods>
<EnableResoniteHotReloadLib Condition="'$(EnableResoniteHotReloadLib)'==''">false</EnableResoniteHotReloadLib>
</PropertyGroup>

<PropertyGroup Condition="'$(ResonitePath)'==''">
Expand All @@ -16,4 +22,22 @@
<PropertyGroup Condition="'$(ResonitePath)'!=''">
<ResolvedResonitePath>$([System.IO.Path]::GetFullPath('$(ResonitePath)'))</ResolvedResonitePath>
</PropertyGroup>

<ItemDefinitionGroup Condition="'$(ResolvedResonitePath)'!=''">
<Reference>
<Private>$([MSBuild]::ValueOrDefault('$(IsTestProject)','false'))</Private>
</Reference>
</ItemDefinitionGroup>

<ItemGroup Condition="'$(ResolvedResonitePath)'!=''">
<Reference Include="FrooxEngine">
<HintPath>$(ResolvedResonitePath)/FrooxEngine.dll</HintPath>
</Reference>
<Reference Include="Elements.Core">
<HintPath>$(ResolvedResonitePath)/Elements.Core.dll</HintPath>
</Reference>
<Reference Include="Renderite.Shared">
<HintPath>$(ResolvedResonitePath)/Renderite.Shared.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ A [ResoniteModLoader](https://github.com/resonite-modding-group/ResoniteModLoade

Launch the tool via `Create New > Editor > Reference Replacement (Mod)` in the Dev Create menu.

Reference discovery traverses nested sync members, lists, dictionaries, slot components, and child slots while avoiding duplicate visits. Matches with an incompatible target type are reported but never replaced.

## Installation

1. Install [ResoniteModLoader](https://github.com/resonite-modding-group/ResoniteModLoader).
Expand All @@ -12,7 +14,9 @@ Launch the tool via `Create New > Editor > Reference Replacement (Mod)` in the D

## Build & Hot Reload

1. Install the .NET 9 SDK.
1. Install the .NET 10 SDK.
2. `dotnet build ReferenceReplacement.sln` auto-detects the Resonite install next to this repo, the default Steam Windows path, then the default Steam Linux path. If the game lives elsewhere, pass `-p:ResonitePath="/absolute/path/to/Resonite"` so the build can find `FrooxEngine.dll`, `Elements.Core.dll`, `Libraries/ResoniteModLoader.dll`, and `rml_libs/0Harmony.dll`.
3. Set `CopyToMods=true` when invoking `dotnet build` to copy the compiled DLL into `$(ResonitePath)/rml_mods` after each build.
4. Drop `ResoniteHotReloadLib.dll` (and `ResoniteHotReloadLibCore.dll`) into `$(ResonitePath)/rml_libs` and build with `-p:EnableResoniteHotReloadLib=true` if you want the Dev Tool’s **Hot Reload Mods** panel to reload this mod without restarting Resonite. Leave the property unset on machines without the DLL.

CI provisions the current Resonite assembly set with `setup-resonite-env`, then runs restore, formatting, build, and tests against those runtime assemblies before publishing a tagged release.
57 changes: 57 additions & 0 deletions src/ReferenceReplacement/Logic/ReferenceMatchCollector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;

using FrooxEngine;

namespace ReferenceReplacement.Logic;

internal sealed class ReferenceMatchCollector
{
private readonly IWorldElement _source;
private readonly IWorldElement _target;
private readonly HashSet<ISyncRef> _visitedRefs = new();
private readonly List<SyncReferenceMatch> _matches = new();

internal ReferenceMatchCollector(IWorldElement source, IWorldElement target)
{
_source = source ?? throw new ArgumentNullException(nameof(source));
_target = target ?? throw new ArgumentNullException(nameof(target));
}

public int IncompatibleCount { get; private set; }
public string? LastHitPath { get; private set; }

public void TryCapture(ISyncRef candidate, string path)
{
if (!_visitedRefs.Add(candidate))
{
return;
}

IWorldElement? current = candidate.Target;
if (current == null)
{
return;
}

if (!ReferenceEquals(current, _source) && current.ReferenceID != _source.ReferenceID)
{
return;
}

Type? requiredType = candidate.TargetType;
if (requiredType != null && !requiredType.IsInstanceOfType(_target))
{
IncompatibleCount++;
return;
}

LastHitPath = path;
_matches.Add(new SyncReferenceMatch(candidate, path));
}

public ReferenceScanResult BuildResult(int visitedMembers)
{
return new ReferenceScanResult(_matches.ToArray(), IncompatibleCount, visitedMembers, LastHitPath);
}
}
Loading
Loading