This guide walks you through creating a minimal but fully functional Collapse Launcher plugin from scratch. At the end you will have a NativeAOT-compiled .dll that the launcher can load.
For a deeper look at each piece, see the full plugin creation guide.
- .NET 10 SDK or later
- A C++ build toolchain (required by NativeAOT linker): Visual Studio Build Tools with the Desktop development with C++ workload
- x64 Windows target (current supported runtime)
dotnet new classlib -n MyGamePlugin -f net10.0
cd MyGamePluginEdit the .csproj so it looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Platforms>x64</Platforms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<IsAotCompatible>true</IsAotCompatible>
<InvariantGlobalization>true</InvariantGlobalization>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Hi3Helper.Plugin.Core" Version="*-*" />
</ItemGroup>
</Project>Create MyPlugin.cs:
using Hi3Helper.Plugin.Core;
using Hi3Helper.Plugin.Core.Management.PresetConfig;
using Hi3Helper.Plugin.Core.Update;
using System;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComClass]
public partial class MyPlugin : PluginBase
{
private static DateTime _creationDate = new DateTime(2025, 6, 1, 0, 0, 0, DateTimeKind.Utc);
public override void GetPluginName(out string? result) => result = "My Game Plugin";
public override void GetPluginDescription(out string? result) => result = "Plugin for My Awesome Game";
public override void GetPluginAuthor(out string? result) => result = "YourName";
public override unsafe void GetPluginCreationDate(out DateTime* result)
{
fixed (DateTime* ptr = &_creationDate)
result = ptr;
}
public override void GetPresetConfigCount(out int count) => count = 1;
public override void GetPresetConfig(int index, out IPluginPresetConfig result)
{
result = index switch
{
0 => new MyPresetConfig(),
_ => throw new ArgumentOutOfRangeException(nameof(index))
};
}
}Create MyPresetConfig.cs:
using Hi3Helper.Plugin.Core.Management;
using Hi3Helper.Plugin.Core.Management.Api;
using Hi3Helper.Plugin.Core.Management.PresetConfig;
using System.Collections.Generic;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComClass]
public partial class MyPresetConfig : PluginPresetConfigBase
{
public override string? GameName => "My Awesome Game";
public override string GameExecutableName => "MyGame.exe";
public override string GameAppDataPath => @"%AppData%\MyGame";
public override string GameLogFileName => "output_log.txt";
public override string GameVendorName => "MyStudio";
public override string GameRegistryKeyName => @"SOFTWARE\MyStudio\MyGame";
public override string ProfileName => "Global";
public override string ZoneName => "Global";
public override string ZoneFullName => "My Awesome Game - Global";
public override string ZoneDescription => "Global server.";
public override string ZoneLogoUrl => "https://example.com/logo.png";
public override string ZonePosterUrl => "https://example.com/poster.jpg";
public override string ZoneHomePageUrl => "https://example.com/";
public override string GameMainLanguage => "en-US";
public override string LauncherGameDirectoryName => "MyGame";
public override GameReleaseChannel ReleaseChannel => GameReleaseChannel.Stable;
public override List<string> SupportedLanguages => ["en-US", "zh-CN", "ja-JP"];
// Wire up optional sub-systems after they are instantiated in InitializeAsync
public override IGameManager? GameManager { get; set; }
public override IGameInstaller? GameInstaller { get; set; }
public override ILauncherApiMedia? LauncherApiMedia { get; set; }
public override ILauncherApiNews? LauncherApiNews { get; set; }
}Create PluginExports.cs. This wires up all required function-pointer exports and exposes the single GetApiExport entry point that the launcher calls:
using Hi3Helper.Plugin.Core;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Inheriting SharedStaticV1Ext<T> registers all standard + optional v0.1 exports.
public partial class MyPluginExports : SharedStaticV1Ext<MyPluginExports>
{
[ModuleInitializer]
public static void Initialize() => Load<MyPlugin>();
}
public static partial class PluginEntryPoint
{
/// <summary>
/// Single entry point used by the launcher to resolve any plugin function by name.
/// </summary>
[UnmanagedCallersOnly(EntryPoint = "GetApiExport")]
public static unsafe int GetApiExport(char* apiName, void** outPtr)
=> SharedStatic.TryGetApiExportPointer(apiName, outPtr);
}Note
SharedStaticV1Ext<T> is a subclass of SharedStatic that also registers the optional v0.1 extension exports (Discord Rich Presence, locale, etc.). Use plain SharedStatic only if you need the absolute minimum export surface.
Add a publish profile at Properties/PublishProfiles/NativeAOT.pubxml:
<Project>
<PropertyGroup>
<PublishAot>true</PublishAot>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<!-- Keep false unless you are targeting DebugNoReflection / ReleaseNoReflection -->
<IlcDisableReflection>false</IlcDisableReflection>
</PropertyGroup>
</Project>Publish:
dotnet publish -p:PublishProfile=NativeAOT -c ReleaseThe resulting .dll in the publish output directory is ready to be loaded by Collapse Launcher.
| Topic | Guide |
|---|---|
| Full walkthrough with game management and self-update | Creating a Plugin — full guide |
| API reference | API docs |