Make your first mod

Tools, project setup and a worked IPA plugin.

Developers
Tutorial
5 min read

This section gets you from zero to a compiled, loadable plugin DLL.

3.1 Tools

Tool Purpose Link
Visual Studio 2017 Community (or newer) Write & compile your C# Class Library https://visualstudio.microsoft.com/
dnSpy Decompile & browse the game's code so you know what to call/hook https://github.com/dnSpy/dnSpy
uTinyRipper (or AssetRipper) Extract the game's assets to study/reference them https://github.com/mafaca/UtinyRipper
PlayHooky Method hooking (Jet Island's stand-in for Harmony) community / GitHub
Unity 2017.1.1 Only needed for asset mods — see §4 https://unity.com/releases/editor/archive

Optional content tools: Blender / Maya / 3ds Max (3D), GIMP / Photoshop (textures), Audacity (audio).

Why VS 2017 / .NET Framework? The game is a 2017-era Mono Unity build. Target a classic .NET Framework profile (e.g. net35 or net46) so your DLL is binary-compatible with the game's runtime. Newer Visual Studio works fine as long as you keep the target framework classic and reference the game's own assemblies (below).

3.2 Reference the game's assemblies

Create a Class Library project, then add references to these DLLs from JetIsland_Data\Managed\:

  • Assembly-CSharp.dll — the game's own code (all the Jet Island classes you'll hook into).
  • IPA.dll — gives you the IPlugin interface.
  • UnityEngine.dll — the Unity engine API (GameObject, MonoBehaviour, Input, etc.). On newer Unity layouts you may also need the split modules like UnityEngine.CoreModule.dll.

If your mod touches the UI, text, or networking, also reference what you need: TextMeshPro, the UI assemblies, and/or Photon. Pull those from Managed\ as well.

Do not commit the game's DLLs to a public repo — they're copyrighted. Reference them locally and add them to .gitignore. Tell contributors to point the references at their own Managed\ folder.

3.3 The IPlugin interface

An IPA plugin is a class that implements IPlugin. IPA discovers it, instantiates it, and calls its lifecycle methods as the game runs. The interface gives you a name/version plus hooks for app start/exit, level load, and a per-frame update.

Note: IPA upstream also has an IEnhancedPlugin interface, but Jet Island's ecosystem uses plain IPlugin — stick with that for compatibility with the patched loader.

3.4 A minimal worked plugin

Here is a complete, minimal plugin. It logs on startup and lets the player press a key to do something each frame. Use it as your template.

using System;
using IllusionPlugin;      // IPA's namespace, from IPA.dll
using UnityEngine;

namespace MyFirstJetMod
{
    public class Plugin : IPlugin
    {
        // Shown by IPA; keep these accurate — Version is your SemVer string.
        public string Name    => "My First Jet Mod";
        public string Version => "1.0.0";

        // Called once when the game process starts.
        public void OnApplicationStart()
        {
            Debug.Log("[MyFirstJetMod] Loaded! Press F8 in-game to say hi.");
        }

        // Called once when the game is shutting down.
        public void OnApplicationQuit() { }

        // Called every time a scene/level finishes loading.
        public void OnLevelWasLoaded(int level) { }

        // Called after the level was initialized.
        public void OnLevelWasInitialized(int level) { }

        // Called once per rendered frame (your main loop).
        public void OnUpdate()
        {
            if (Input.GetKeyDown(KeyCode.F8))
            {
                Debug.Log("[MyFirstJetMod] Hello from inside Jet Island!");
                // e.g. spawn something, toggle a feature, modify the player...
            }
        }

        // Called once per physics step.
        public void OnFixedUpdate() { }
    }
}

Build it, then drop the resulting .dll into the game's Plugins\ folder and launch. Watch the game's log output (in JetIsland_Data\output_log.txt or the Unity Player log) for your [MyFirstJetMod] lines to confirm it loaded.

The exact IPlugin member set comes from the IPA.dll you reference. If your IDE complains a member is missing or extra, open IPA.dll in dnSpy and match the interface exactly — that's the source of truth for your loader build.

3.5 Hooking game methods & reading private fields

IPA gives you OnUpdate/OnLevelWasLoaded etc., but to change what an existing game method does, you hook it.

  • PlayHooky is the community's method-hooking library (Jet Island's equivalent of Harmony). Use it to redirect or wrap a game method — for example, to neutralize fall damage or change turning behavior.
  • Reading private/protected fields: grab the small ReflectionUtil.cs helper (originally from the BeatSaberSongLoader project) and use reflection to read or set non-public members of the game's classes. Decompile the target class in dnSpy first to learn the exact field/property names.

There is no Harmony in this ecosystem. Don't add a Harmony NuGet package and expect the patterns from other Unity mods to apply — use PlayHooky + reflection instead.

3.6 Workflow loop

  1. Decompile with dnSpy to find the class/method/field you want to affect.
  2. Write your IPlugin plus any PlayHooky hooks.
  3. Build the Class Library → .dll.
  4. Copy to Plugins\, launch, read the log.
  5. Iterate. Keep a clean copy of the game (Alt-drag to un-patch) handy for sanity checks.

A great real reference to study is the JetIslandPowerups example repo — it's the canonical "example power-up mod" with both the Unity project and plugin side. ModifierUnlockPlugin and VoiceToggle-LagReductionPlugin are small, readable plugin-only examples.



An unhandled error has occurred. Reload 🗙

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.