Table of Contents

Troubleshooting

Start with the exact error text. The first sections cover installation and scaffold builds; later sections cover generated code and migrations.

"s1interop" is not recognized

Cause: The global .NET tool is not installed, or the current terminal has not picked up the global tools path.

Fix: Check the installed tools:

dotnet tool list --global

If s1interop is listed, close and reopen PowerShell. If it is not listed, repeat Install the command.

"Missing MelonLoader at ..."

Cause: MonoGamePath is empty or points below the game root. The scaffold expects a folder containing Schedule I.exe, Schedule I_Data, and MelonLoader.

Fix: Open local.build.props and correct MonoGamePath. Do not point it at Schedule I_Data, Managed, or MelonLoader directly.

The same path mistake can produce Missing Unity assemblies, Missing Schedule One game assembly, or Missing ScheduleOne.Core. See Local game paths for the expected folder layout.

Runtime shows Unknown

Cause: A backend-neutral build could not find the well-known Schedule I type or assembly names used by the generated runtime probe.

Fix: Confirm the DLL is running under MelonLoader inside Schedule I, not in a standalone test process. Check MelonLoader\Latest.log for assembly-load failures and make sure the DLL came from the normal bin\Single build. If the game or generated IL2CPP assembly names changed, keep the log because the probe list may need a S1Interop update.

"type or namespace 'S1InteropType' could not be found"

Cause: Your project does not reference S1Interop.Generators. The declaration attributes (S1InteropType, S1InteropNamespace, S1InteropMember) and all generated facades are emitted by the Roslyn generator package during compilation. Without the package reference, the compiler has no knowledge of those symbols.

Fix: Add the PackageReference to your .csproj and rebuild:

<PackageReference Include="S1Interop.Generators" Version="0.1.0-alpha.1" />

After adding the reference, run a full build so the generator emits the declaration attributes and your project can recognise them.

Note

If you are using an unpublished local build, you also need S1InteropGeneratorPackageSource configured in local.build.props so the package restore can find the local feed. See package restore failure below.

Generated type or member missing from IntelliSense

Cause: No design-time build has run since you added or changed a declaration. The Roslyn generator emits symbols during compilation, not at restore time. If you edit S1Interop.BackendNeutral.cs and immediately try to use the new facade in another file, the IDE may not yet know about it.

Fix: Build the project once, either via your IDE or dotnet build. After the build (or after the IDE runs its own design-time build), the new symbols appear in IntelliSense and are compiled into the assembly.

Note

Generated symbols are emitted into the same compilation as the rest of your project. They are not a separate assembly and are not referenced from a runtime package. The S1Interop.Generators package ships only the generator DLL under analyzers/dotnet/cs.

Generated code does not match the current source

Cause: You rebuilt a local alpha package without changing its version, but NuGet reused the older S1Interop.Generators package already stored on your machine. This can make a newly packed generator behave differently from the source you just built.

Fix: Close the IDE, remove only the cached copy of this alpha package, then restore it from the local feed:

$cachedPackage = Join-Path $env:USERPROFILE ".nuget\packages\s1interop.generators\0.1.0-alpha.1"
Remove-Item -LiteralPath $cachedPackage -Recurse
dotnet restore

This deletes generated package-cache files, not your mod source. dotnet restore recreates them from S1InteropGeneratorPackageSource. Do not clear the entire NuGet cache for this problem.

Generated helper returns null or false

Cause: The helper compiled, but the active backend could not complete the runtime lookup or call. Common cases are a method renamed by a game update, an IL2CPP wrapper member that is not present, an overloaded method missing ParameterTypeNames, a parameter type that does not resolve on IL2CPP, or a value that cannot be converted to the runtime signature.

Fix: Inspect S1Interop.Generated.S1InteropMemberRegistry.Reports after the failed call. Check the latest report's Status, OwnerTypeName, MemberName, and ParameterTypeNames.

  • MissingMember means the current backend type does not expose that member. Verify the current Mono and IL2CPP reference assemblies.
  • AmbiguousMember means you probably need ParameterTypeNames.
  • MissingParameterType means a declared overload parameter does not map to a runtime type.
  • ArgumentConversionFailed means the target resolved, but the value you passed could not cross the backend boundary.

On IL2CPP, do not assume a Mono member still exists or still gets called. If the wrapper metadata is missing or the method is tiny enough to inline, patch or call a higher-level method that you can verify in the IL2CPP branch.

S1I001: game type not found

Cause: The generator validated an S1InteropType declaration against an available Mono or IL2CPP reference surface and could not locate the type. The most common reasons are:

  • The type name in the declaration does not match the actual Mono runtime type name.
  • The selected reference surface is older or newer than the game version the declaration was written against.

Fix:

  1. Open local.build.props (copy from local.build.props.example if it does not exist yet) and verify the path for the build you are running points to the correct Schedule I install:
<Project>
  <PropertyGroup>
    <MonoGamePath>C:\Program Files (x86)\Steam\steamapps\common\Schedule I</MonoGamePath>
    <Il2CppGamePath>C:\Program Files (x86)\Steam\steamapps\common\Schedule I IL2CPP</Il2CppGamePath>
  </PropertyGroup>
</Project>
  1. Double-check the fully-qualified type name in your declaration matches what is in the game assembly (for example ScheduleOne.Vehicles.LandVehicle, not ScheduleOne.Vehicle.LandVehicle).
Note

Declaration diagnostics S1I001-S1I003 stay quiet when no game reference surface is available, so package-restore and docs-only builds never fail just because local game paths are missing.

Package restore fails for S1Interop.Generators

Cause: NuGet cannot find S1Interop.Generators because the local alpha package is not in any configured feed and S1InteropGeneratorPackageSource is not set in local.build.props.

Fix:

  1. Pack both packages into the same output folder:
dotnet pack .\src\S1Interop.Cli\S1Interop.Cli.csproj -c Release -o .\artifacts\packages
dotnet pack .\src\S1Interop.Generators\S1Interop.Generators.csproj -c Release -o .\artifacts\packages
  1. Add S1InteropGeneratorPackageSource to your local.build.props pointing at that folder:
<S1InteropGeneratorPackageSource>..\S1Interop\artifacts\packages</S1InteropGeneratorPackageSource>

Generated and migrated projects already bridge that property into RestoreAdditionalProjectSources, so Visual Studio, Rider, and dotnet build pick up the local package without committing a machine-specific NuGet source to version control.

Warning

Do not commit local.build.props. It holds machine-specific paths that differ between developers. Commit only local.build.props.example.

Migration applied but something went wrong

Cause: A migration wrote unexpected changes: source rewrites, project edits, or solution updates that need to be undone.

Fix: Every applied migration writes a rollback manifest and file backups under s1interop-runs/<run-id>/. Run the rollback command to restore all backed-up files:

s1interop migrate rollback .\s1interop-runs\<run-id>\manifest.json

Replace <run-id> with the specific run directory created by --apply.

Tip

Always review --dry-run output before using --apply. The dry-run shows every operation S1Interop would perform without writing any files, giving you a chance to catch unexpected rewrites before they happen.

IDE still shows old build configurations after dual-runtime migration

Cause: Your IDE cached the old solution configuration. After a dual-runtime migration, S1Interop updates the .sln file to add the new configurations, but the IDE may not reload the solution automatically.

Fix: Verify the .sln file was updated (look for the new Mono and IL2CPP configuration entries), then close and reopen the solution in your IDE. The new configurations will be available after the reload.

verify-migration fails to build

Cause: Either the game paths are not available in the sandbox environment, or the default build timeout is too short for your project.

Fix: Pass the game paths directly as flags and increase the timeout as needed:

s1interop verify-migration . --dual-runtime --build `
  --mono-game-path "C:\Program Files (x86)\Steam\steamapps\common\Schedule I" `
  --il2cpp-game-path "C:\Program Files (x86)\Steam\steamapps\common\Schedule I IL2CPP" `
  --build-timeout-seconds 120

The --mono-game-path and --il2cpp-game-path flags override local.build.props values for the duration of the sandbox build, so you can verify on machines or CI environments where the props file is not present.

S1I007 warning on a cast

Cause: You have a plain C# cast from object or Il2CppObjectBase to a Unity object type. These casts compile fine but fail at runtime on IL2CPP because the IL2CPP proxy boundary requires going through TryCast<T> rather than a direct CLR cast. S1I007 fires only when the compilation targets IL2CPP, so the warning surfaces the problem at build time rather than as a runtime surprise.

Fix: Replace the plain cast with S1InteropObjectCast.As<T>(value):

// Before - triggers S1I007 on IL2CPP builds
var obj = (MyUnityType)rawObject;

// After - routes through TryCast<T> on IL2CPP, safe on both runtimes
var obj = S1InteropObjectCast.As<MyUnityType>(rawObject);

S1InteropObjectCast.As<T> is generated by S1Interop.Generators and handles the proxy dispatch automatically. The call is a no-op on Mono, so the same code runs correctly on both backends.

S1I008 warning on a patch target

Cause: A backend-neutral S1InteropPatch target resolved, but the target needs IL2CPP review. Common cases are overloaded methods without ParameterTypeNames, property/event accessors, operator methods, and methods marked for aggressive inlining or optimization.

Fix: Add ParameterTypeNames for overloaded methods. For accessor-like or aggressively inlined targets, patch a higher-level method when possible. If you intentionally keep the target, validate that the handler fires on the IL2CPP branch you support.