Table of Contents

Getting Started

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.

Prerequisites

  • .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)

1. Create the project

dotnet new classlib -n MyGamePlugin -f net10.0
cd MyGamePlugin

Edit 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>

2. Create your IPlugin implementation

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))
        };
    }
}

3. Create your IPluginPresetConfig implementation

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; }
}

4. Register exports and expose the entry point

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.

5. Publish with NativeAOT

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 Release

The resulting .dll in the publish output directory is ready to be loaded by Collapse Launcher.


What's next?

Topic Guide
Full walkthrough with game management and self-update Creating a Plugin — full guide
API reference API docs