Migrating from stock KSP2
There have been many significant changes to SpaceWarp and the general modding architecture from stock KSP2 to KSP2 Redux, and old code mods will not work without some modifications. This page serves as a general overview of the changes and how to upgrade your mods.
Overview
BepInEx removal: BepInEx has been completely removed. Equivalents for most of its functionality are now located in the
ReduxLiborSpaceWarp2namespaces.Harmony version: HarmonyX has been replaced with Harmony2. A
CreateAndPatchAllutility method is now provided within the mod classes.Part modules: Classes extending
PartComponentModulenow need to explicitly subscribe to updates if they implement theOnUpdatemethod.API cleanup: Deprecated APIs from SpaceWarp 1 have been removed. A legacy package for porting old mods is available at https://github.com/KSP2Community/SpaceWarpLegacy, though it is not recommended for new projects.
Namespace changes: The namespace
SpaceWarphas been replaced withSpaceWarp2. Additionally, namespaces previously named asSpaceWarp.API.<Module>are now namedSpaceWarp2.<Module>.API.Campaign features: An installed mod is not on in all campaigns. Each campaign has its own feature set of features. If the default feature of a mod is off in a campaign, the mod must do nothing in that campaign. See Mods that are off in a campaign.
BepInEx API
BaseUnityPlugin and BaseSpaceWarpPlugin
The base mod classes BaseUnityPlugin and BaseSpaceWarpPlugin have been replaced with 3 classes, along with an interface that can be used to define more.
For all of these types, the mod lifecycle virtual methods from SpaceWarp 1 are the same, so OnPreInitialized, OnInitialized, and OnPostInitialized remain the same with the same semantics.
SpaceWarp2.API.Mods.ISpaceWarpMod
This is the base mod interface that can be used to define more mod types if desired. It provides three common properties:
**SWLogger**- logging interface for your mod,**SWConfiguration**- adding and reading mod configuration values,**SWMetadata**- provides metadata about your mod.
SpaceWarp2.API.Mods.GeneralMod
This is the recommended mod class for mods that don’t need the Unity lifecycle (e.g. for mods that only run some initialization code or provide part component classes, and don’t need Update or FixedUpdate).
SpaceWarp2.API.Mods.MonoBehaviourMod
This is the closest equivalent to BaseUnityPlugin. It is an ISpaceWarpMod that is also a MonoBehaviour, and as such, has the entire Unity component lifecycle. It gets instantiated as soon as possible upon loading the game.
Redux.ExtraModTypes.KerbalMod
This is the closest equivalent to BaseSpaceWarpPlugin. It includes Game, Assets, Messages and CFXSystem accessors in addition to everything provided by MonoBehaviourMod.
Mods that are off in a campaign
Each Redux campaign uses a feature set of campaign features. Each mod has a default feature. This feature is on when the mod is on.
A mod that is off in the loaded campaign is still loaded:
Its assembly loads.
OnPreInitialized,OnInitializedandOnPostInitializedrun as before.
For a mod that is off, Redux disables its patches, assets, campaign hooks and app bar buttons. Code that your mod runs directly continues to run, for example Harmony patches.
All three base classes supply these members:
Enabledistruewhile the default feature of your mod is on in the active feature set. Check it before code that must run only while your mod is on.OnCampaignLoaded()andOnCampaignUnloading()run only for campaigns that have your mod on. Set up per-campaign behaviour inOnCampaignLoaded(). Remove it inOnCampaignUnloading().[Feature("Id")]on aboolfield makes the field show if one of the features of your mod is on.
For all of the members, see Code modding guides.
Logging
The BepInEx logging system has been replaced with the ReduxLib.Logging namespace. To access your mod’s provided logger, use the SWLogger property in your mod class. To create a new logger, you can call ReduxLib.ReduxLib.GetLogger(name).
Log output from here goes to redux.log in the base folder of the install. There is an option in the game’s Settings → Mods → Redux to also mirror the output into Unity’s logs (and console).
Configuration
The BepInEx configuration system has been replaced with the ReduxLib.Configuration namespace. It functions slightly differently than BepInEx’s, the following snippet illustrates a general use case:
/* This is a set of code snippets for defining a config value */
// Define a config section and value in your mod class.
// The "string" type can be replaced by any type that can be serialized to a JSON
[ConfigSection("My Section", loc: "MyMod/Settings/MySection")]
[ConfigValue(
"Some Config Name",
"Some Config Description",
nameLoc: "MyMod/Settings/Name/SomeConfigValue",
descLoc: "MyMod/Settings/Desc/SomeConfigValue"
)]
public ConfigValue<string> SomeConfigValue = new ConfigDescription<string>("DEFAULT VALUE");
// If you only need a one-way binding (no callback on value change, etc), you can
// directly use the raw type for the config value object
[ConfigValue(
"Some Other Config Name",
"Some Other Config Description",
nameLoc: "MyMod/Settings/Name/SomeOtherConfigValue",
descLoc: "MyMod/Settings/Desc/SomeOtherConfigValue"
)]
public bool SomeOtherConfigValue = true;
// ...
public override void OnPreInitialized()
{
// We bind the configs defined in the class to the SWConfiguration object
// (inherited from the Mod base class)
SWConfiguration.Bind(this);
// Register an action to call when the value changes
SomeConfigValue.RegisterCallback(SomeConfigCallback);
}
private static void SomeConfigCallback(string oldValue, string newValue)
{
SWLogger.LogInfo($"I changed from {oldValue} to {newValue}");
}Keybinds
New in KSP2 Redux, ReduxLib provides classes for binding KeyboardShortcuts and Keybinds. When you create config values with these types, they will be automatically added into the Settings menu.
ReduxLib.Input.Keybind
Represents a single key action
ReduxLib.Input.KeyboardShortcut
Represents a keyboard shortcut consisting of modifier(s) and a single key
HarmonyX
HarmonyX has been replaced with a modified version of Harmony2 in order to allow it to run in the Unity Editor. Unfortunately, this means that some features were removed.
CreateHarmonyAndPatchAll is a helper method provided in most mod classes, which replaces Harmony.CreateAndPatchAll. It will create a harmony instance and apply all patches from your mod’s assembly.
Part Modules
The OnUpdate method in classes extending KSP.Sim.impl.PartComponentModule no longer runs automatically for performance reasons. If you need it to run, you will now need to explicitly register and unregister for updates using KSP.Sim.impl.PartComponent’s RegisterModuleForUpdates and UnregisterModuleForUpdates methods. Following is an example:
public class PartComponentModule_MyModule : PartComponentModule
{
public override void OnStart(double universalTime)
{
Part.RegisterModuleForUpdates(this);
}
public override void OnUpdate(double universalTime, double deltaUniversalTime)
{
// My update code will run with this setup
}
public override void OnShutdown()
{
Part.UnregisterModuleForUpdates(this);
}
}Deprecations
The following APIs have been deprecated and removed in Redux:
The
AssetManagerAPI from SpaceWarp 1 has been deprecated and removed in favor of Unity’s Addressables, which the Redux SDK can automatically build.The IMGUI API and skin from SpaceWarp 1 has been deprecated and removed in favor of UI Toolkit, which the Unity Editor has a convenient editor for. The UitkForKsp2.Unity Unity package provides a KSP2 theme, fonts and other assets.
If you need either of the two above deprecated APIs, a legacy port is available in the SpaceWarpLegacy Unity package.
The Lua modding support from SpaceWarp 1 was completely removed without any replacements, as it never got used by any mods.
Namespace Changes
The following namespaces have been renamed or moved:
SpaceWarp→SpaceWarp2SpaceWarp.API.Configuration→ReduxLib.ConfigurationSpaceWarp.API.Loading→SpaceWarp2.API.LoadingSpaceWarp.API.Logging→ReduxLib.LoggingSpaceWarp.API.Mods→SpaceWarp2.API.ModsSpaceWarp.API.Parts→SpaceWarp2.API.PartsSpaceWarp.API.SaveGameManager→SpaceWarp2.API.SaveGameManagerSpaceWarp.API.Versions→SpaceWarp2.API.VersionsSpaceWarp.Patching.LoadingActions→SpaceWarp2.API.Loading.LoadingActionsSpaceWarp.API.Game.Messages→Redux.MessagesSpaceWarp.API.Game.Extensions→ integrated into the target classesSpaceWarp.API.Game.Waypoints→SpaceWarp2.Game.API.WaypointsSpaceWarp.API.Messaging→ReduxLib.MessagingSpaceWarp.API.Sound→SpaceWarp2.Sound.APISpaceWarp.API.UI→SpaceWarp2.UI.API
UI Toolkit
From Unity 6.6 on, creating custom controls using the UxmlFactory and UxmlTraits pattern is no longer possible, so you will need to migrate to the newer way of using [UxmlElement] and [UxmlAttribute] attributes. For more info, see Unity’s documentation.
There have also been many changes made to the KerbalUI theme, so make sure your UI still looks good with these new styles.
Example before & after
Before:
using System.Reflection;
using BepInEx;
using JetBrains.Annotations;
using SpaceWarp;
using SpaceWarp.API.Assets;
using SpaceWarp.API.Mods;
using SpaceWarp.API.UI.Appbar;
using SpaceWarpModUI.UI;
using UitkForKsp2.API;
using UnityEngine;
using UnityEngine.UIElements;
namespace SpaceWarpModUI;
[BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)]
[BepInDependency(SpaceWarpPlugin.ModGuid, SpaceWarpPlugin.ModVer)]
public class SpaceWarpModUIPlugin : BaseSpaceWarpPlugin
{
// Useful in case some other mod wants to use this mod a dependency
[PublicAPI] public const string ModGuid = MyPluginInfo.PLUGIN_GUID;
[PublicAPI] public const string ModName = MyPluginInfo.PLUGIN_NAME;
[PublicAPI] public const string ModVer = MyPluginInfo.PLUGIN_VERSION;
/// Singleton instance of the plugin class
[PublicAPI] public static SpaceWarpModUIPlugin Instance { get; set; }
// AppBar button IDs
internal const string ToolbarFlightButtonID = "BTN-SpaceWarpModUIFlight";
internal const string ToolbarOabButtonID = "BTN-SpaceWarpModUIOAB";
internal const string ToolbarKscButtonID = "BTN-SpaceWarpModUIKSC";
/// <summary>
/// Runs when the mod is first initialized.
/// </summary>
public override void OnInitialized()
{
base.OnInitialized();
Instance = this;
// Load all the other assemblies used by this mod
LoadAssemblies();
// Load the UI from the asset bundle
var myFirstWindowUxml = AssetManager.GetAsset<VisualTreeAsset>(
// The case-insensitive path to the asset in the bundle is composed of:
// - The mod GUID:
$"{ModGuid}/" +
// - The name of the asset bundle:
"SpaceWarpModUI_ui/" +
// - The path to the asset in your Unity project (without the "Assets/" part)
"ui/myfirstwindow/myfirstwindow.uxml"
);
// Create the window options object
var windowOptions = new WindowOptions
{
// The ID of the window. It should be unique to your mod.
WindowId = "SpaceWarpModUI_MyFirstWindow",
// The transform of parent game object of the window.
// If null, it will be created under the main canvas.
Parent = null,
// Whether or not the window can be hidden with F2.
IsHidingEnabled = true,
// Whether to disable game input when typing into text fields.
DisableGameInputForTextFields = true,
MoveOptions = new MoveOptions
{
// Whether or not the window can be moved by dragging.
IsMovingEnabled = true,
// Whether or not the window can only be moved within the screen bounds.
CheckScreenBounds = true
}
};
// Create the window
var myFirstWindow = Window.Create(windowOptions, myFirstWindowUxml);
// Add a controller for the UI to the window's game object
var myFirstWindowController = myFirstWindow.gameObject.AddComponent<MyFirstWindowController>();
// Register Flight AppBar button
Appbar.RegisterAppButton(
ModName,
ToolbarFlightButtonID,
AssetManager.GetAsset<Texture2D>($"{ModGuid}/images/icon.png"),
isOpen => myFirstWindowController.IsWindowOpen = isOpen
);
// Register OAB AppBar Button
Appbar.RegisterOABAppButton(
ModName,
ToolbarOabButtonID,
AssetManager.GetAsset<Texture2D>($"{ModGuid}/images/icon.png"),
isOpen => myFirstWindowController.IsWindowOpen = isOpen
);
// Register KSC AppBar Button
Appbar.RegisterKSCAppButton(
ModName,
ToolbarKscButtonID,
AssetManager.GetAsset<Texture2D>($"{ModGuid}/images/icon.png"),
() => myFirstWindowController.IsWindowOpen = !myFirstWindowController.IsWindowOpen
);
}
/// <summary>
/// Loads all the assemblies for the mod.
/// </summary>
private static void LoadAssemblies()
{
// Load the Unity project assembly
var currentFolder = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory!.FullName;
var unityAssembly = Assembly.LoadFrom(Path.Combine(currentFolder, "SpaceWarpModUI.Unity.dll"));
// Register any custom UI controls from the loaded assembly
CustomControls.RegisterFromAssembly(unityAssembly);
}
}After:
using JetBrains.Annotations;
using Redux.ExtraModTypes;
using SpaceWarp2.UI.API.Appbar;
using SpaceWarpModUI.UI;
using UitkForKsp2.API;
using UnityEngine;
using UnityEngine.UIElements;
namespace SpaceWarpModUI;
public abstract class SpaceWarpModUIPlugin : KerbalMod
{
[PublicAPI] public const string ModGuid = "SpaceWarpModUI"; // ModGuid here
[PublicAPI] public const string ModName = "SpaceWarp UI Mod";
//[PublicAPI] public const string ModVer = MyPluginInfo.PLUGIN_VERSION;
/// Singleton instance of the plugin class
[PublicAPI] public static SpaceWarpModUIPlugin Instance { get; set; }
// AppBar button IDs
internal const string ToolbarFlightButtonID = "BTN-SpaceWarpModUIFlight";
internal const string ToolbarOabButtonID = "BTN-SpaceWarpModUIOAB";
internal const string ToolbarKscButtonID = "BTN-SpaceWarpModUIKSC";
/// <summary>
/// Runs before the game is loaded, as early as possible.
/// You would want to use this to register loading actions (like loading JSONs from addressables).
/// </summary>
public override void OnPreInitialized()
{
Debug.Log("Pre Initialized");
}
/// <summary>
/// Runs after the game is loaded and after your mod assets are loaded.
/// </summary>
public override async void OnInitialized()
{
Debug.Log("Is Initializing"); // You can use Debug.Log to log things.
Instance = this;
// Start both asset operations before awaiting either one so that they
// can load concurrently.
var myFirstWindowUxmlHandle =
Assets.LoadAssetAsync<VisualTreeAsset>(
// The address given to the addressable asset:
"/myfirstwindow/myfirstwindow.uxml"
);
var appbarIconHandle =
Assets.LoadAssetAsync<Texture2D>(
$"{ModGuid}/images/icon.png"
);
try
{
// Await the Tasks exposed by the Unity operation handles.
// This does not block the Unity main thread.
await Task.WhenAll(
myFirstWindowUxmlHandle.Task,
appbarIconHandle.Task
);
}
catch (Exception exception)
{
Debug.LogError(
$"{ModName}: An exception occurred while loading UI assets."
);
Debug.LogException(exception);
return;
}
if (myFirstWindowUxmlHandle.Status !=
AsyncOperationStatus.Succeeded)
{
Debug.LogError(
$"{ModName}: Failed to load the window UXML asset. " +
$"Status: {myFirstWindowUxmlHandle.Status}. " +
$"Exception: {myFirstWindowUxmlHandle.OperationException}"
);
return;
}
if (appbarIconHandle.Status != AsyncOperationStatus.Succeeded)
{
Debug.LogError(
$"{ModName}: Failed to load the AppBar icon. " +
$"Status: {appbarIconHandle.Status}. " +
$"Exception: {appbarIconHandle.OperationException}"
);
return;
}
// Both operations have completed successfully, so we can access Result now.
var myFirstWindowUxml = myFirstWindowUxmlHandle.Result;
var appbarIcon = appbarIconHandle.Result;
// Create the window options object
var windowOptions = new WindowOptions
{
// The ID of the window. It should be unique to your mod.
WindowId = "SpaceWarpModUI_MyFirstWindow",
// The transform of parent game object of the window.
// If null, it will be created under the main canvas.
Parent = null,
// Whether the window can be hidden with F2.
IsHidingEnabled = true,
// Whether the UI will use the "Scale" option in the settings.
UseStockScale = true,
// Whether to disable game input when typing into text fields.
DisableGameInputForTextFields = true,
MoveOptions = new MoveOptions
{
// Whether the window can be moved by dragging.
IsMovingEnabled = true,
// Whether the window can only be moved within the screen bounds.
CheckScreenBounds = true
}
};
// Create the window
var myFirstWindow = Window.Create(windowOptions, myFirstWindowUxml);
// Add a controller for the UI to the window's game object
var myFirstWindowController = myFirstWindow.gameObject.AddComponent<MyFirstWindowController>();
// Register Flight AppBar button
Appbar.RegisterAppButton(
ModName,
ToolbarFlightButtonID,
appbarIcon,
isOpen => myFirstWindowController.IsWindowOpen = isOpen
);
// Register OAB AppBar Button
Appbar.RegisterOABAppButton(
ModName,
ToolbarOabButtonID,
appbarIcon,
isOpen => myFirstWindowController.IsWindowOpen = isOpen
);
// Register KSC AppBar Button
Appbar.RegisterKSCAppButton(
ModName,
ToolbarKscButtonID,
appbarIcon,
() => myFirstWindowController.IsWindowOpen = !myFirstWindowController.IsWindowOpen
);
}
/// <summary>
/// Runs after all mods have done their 2nd stage initialization.
/// You would want to use this to interact with game systems, since they're guaranteed to be loaded on this stage.
/// </summary>
public override void OnPostInitialized()
{
Debug.Log("Post Initialized");
}
// You can create controls directly in Unity. No need for this method anymore.
//private static void LoadAssemblies()
//{
// // Load the Unity project assembly
// var currentFolder = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory!.FullName;
// var unityAssembly = Assembly.LoadFrom(Path.Combine(currentFolder, "SpaceWarpModUI.Unity.dll"));
// // Register any custom UI controls from the loaded assembly
// CustomControls.RegisterFromAssembly(unityAssembly);
//}
}For more examples you can look at some of the "Reduxed" mods we've made for Redux here: https://github.com/orgs/KSP2Community/repositories?type=all