Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SFS Analyzer

A cross-platform desktop application for browsing and editing Kerbal Space Program save files (.sfs), craft files (.craft), and configuration files (.cfg).

Existing save editors are either Windows-only, or abandoned years ago, or limited to specific tasks like editing ships. KML (Kerbal Markup Lister) — the most recent .NET option — can be run on Linux via the terminal only, with no GUI. SFS Analyzer aims to be a modern alternative that runs on both Linux and Windows without compromises.

Main Window

Features

Open

Open

Click the toolbar button to select a file. The same action can be performed with Ctrl+O. On Linux the KSP installation directory is often a hidden folder (~/.steam/...). Press Ctrl+H in the file dialog to show hidden directories.

Save

Save

Writes changes back to the file. Before every save a .bakN copy (next free number) is created automatically in the same directory, so earlier versions are never lost. The same action can be performed with Ctrl+S.

Tree View

Tree View

Hierarchical tree of all nodes in the file. Select any node to inspect and edit its parameters in the right panel. Nodes can be expanded and collapsed to navigate the structure.

Copy

Copy

Copies the selected node to the system clipboard as SFS source text. The node (including all parameters, comments, and child nodes) is serialised into standard SFS format, so it can be pasted into any text editor or into another instance of SFS Analyzer. Ctrl+C copies the selected node.

Paste

Paste

Reads the system clipboard and parses the content as an SFS node definition. Click Paste to insert as a child of the selected node. Shift+click Paste inserts as a sibling after the copied source node, grouped by type name. Keyboard shortcuts: Ctrl+V pastes as child, Ctrl+Shift+V pastes as sibling. When pasting as sibling, if the node has a parent parameter referencing a sibling index, its value is recalculated for the new context (matching the behaviour of Clone Node).

Paste works with any valid single-node SFS text — nodes copied from SFS Analyzer, typed manually, or copied from a .craft/.sfs file.

Copy/Paste is well suited for cloning launched vessels or moving vessels between save files. When transferring a crewed vessel, either also transfer the relevant kerbals from the ROSTER section, or remove the crew parameters from all inhabited parts (PART nodes) before loading the vessel — otherwise the game will hang.

Move Node

Move Up Move Down

Reorders a sibling node within its parent. Click Move Up or Move Down to shift by one position. Shift+click moves by five. Ctrl+click moves to the top or bottom. Keyboard shortcuts: Alt+Up / Alt+Down (one), Alt+Shift+Up / Alt+Shift+Down (five), Alt+Home / Alt+End (top/bottom).

Clone Node

Clone Node

Creates a deep copy of the selected node and inserts it as a sibling in the same parent. The cloned subtree includes all parameters and child nodes. If the source has a parent parameter referencing a sibling index, the clone's parent is updated to point to the original node's index.

Create Node

Create Node

Creates a new empty child node under the selected node. The node name is provided in a dialog. The new node is immediately added to the tree and expanded for visibility.

Delete Node

Delete Node

Removes the selected node and its entire subtree. A confirmation dialog prevents accidental deletion.

Add Parameter

Add Parameter

Adds a new key-value pair to the selected node. A dialog prompts for the key and value. The new entry is appended at the end of the node's entry list and appears immediately in the parameters panel.

Node Parameters

Node Parameters

The right panel shows all key-value pairs of the selected node. Values are edited inline in a text field — press Enter or focus away to confirm the change. Entries are displayed in file order, preserving the original layout.

At the top of the panel, a breadcrumb path shows the full ancestor chain of the selected node. Intermediate nodes are clickable — clicking one navigates the tree to that node.

Nodes with a parent parameter (integer index referencing a sibling of the same type) show a jump button in the top-left corner of the parameters panel, next to the breadcrumbs. Clicking it selects and scrolls to the referenced node.

Existing node parameters can be renamed or deleted.

Color Groups

Color Group

RGB triples ending in *R, *G, *B with float values in [0, 1] are automatically grouped into a single row with a ColorPicker. Changes update all three fields simultaneously.

Search

Search

Full-text search across all node names and parameter values. Results are highlighted in the tree, and the status bar shows the current match number. Arrow buttons cycle through matches.

Status Bar

The bottom bar shows the current file name, node count, and file size after loading. During search it displays match progress; after saving it confirms the operation.

Settings

Settings Button

The toolbar's cog icon opens the settings dialog with several tabs (built-in tabs plus any added by plugins). The same action can be performed with Ctrl+P.

General

Settings Window

Controls core application settings:

  • Indent mode — Tab or Spaces (when Spaces is selected, indent width can be adjusted from 1 to 8)
  • Create backup before saving — when checked, a .bakN copy (next free number) is created before every save
  • GameData path — path to the KSP GameData directory, used by plugins to load part configs and other game assets. Click Browse… to select a directory
  • Theme — UI theme; select a theme and click OK to apply.

Themes

Themes

Choose from several built-in themes via the Theme dropdown on the General tab. Themes are implemented with AtlantaFX and include:

  • Default — JavaFX Modena (the standard look)
  • Cupertino Light / Cupertino Dark — macOS-inspired appearance
  • Dracula — a dark theme with a purple tint
  • Nord Light / Nord Dark — based on the Nord color palette
  • Primer Light / Primer Dark — GitHub-style design

Select a theme from the dropdown and click OK to apply it. The choice is persisted and restored on next launch.

Node Display

Node Display Tab

Configures how nodes are displayed in the tree view. The default parameter (default: name) is the key whose value is shown alongside the node name — e.g. a VESSEL node with name = My Ship renders as VESSEL: My Ship. Per-type overrides can set a different default parameter and extra parameters for specific paths; extra values appear as key = value after a comma.

Plugins

Plugins Tab

Lists all discovered plugins with their metadata (name, author, version). Each plugin can be enabled or disabled via a checkbox — changes take effect after restart. Disabled plugins are never loaded into the JVM.

Bundled Plugins

Kerbal List

Kerbal List

Lists all kerbals from the loaded save file's ROSTER section in a detailed table.

Kerbal List Tab

  • 12 columns: Name, Gender (♂/♀), Type, Trait, Courage/Stupidity (progress bars), Badass/Veteran/Hero (checkmarks), State, Suit, Vessel.
  • Bidirectional selection: click a name => select KERBAL node; click a vessel name => select VESSEL node; select a KERBAL/VESSEL node in the tree => highlight the matching row.
  • Vessel column shows the name of the vessel the kerbal is currently assigned to.

Vessel List

Vessel List

Lists all vessels from the loaded save file in a table view.

Vessel List Tab

  • Name column always visible; Type, Situation, SOI, On Board, and crew breakdown columns toggleable via Settings.

  • Crew breakdown: separate columns for Pilots, Engineers, Scientists, Tourists — with per-kerbal tooltips.

  • Context menu on crew count cells — right-click a number to jump to the corresponding kerbal node in the ROSTER tree.

    Vessel List Context Menu

  • Bidirectional selection: click a table row to select the VESSEL node in the tree; select a VESSEL in the tree => highlight the matching row.

  • SOI resolved from ORBIT/REF using stock celestial body indices.

  • Settings page under Plugins => Vessel List.

Vessel Exporter

Vessel Exporter

Exports a launched vessel from a save file as a .craft file loadable in the VAB/SPH.

  • Selection-driven: the toolbar button is enabled only when a VESSEL node is selected.
  • Part tree reconstruction with parent-relative position/rotation transforms.
  • Symmetry handling: save-file index references converted to part-name references.
  • Surface attachment (srfN) resolution from index-based to part-name-based format.
  • Node stack geometry discovered dynamically by scanning GameData/*.cfg files for node_stack_* definitions.
  • Deterministic DFS output order for reproducible exports.
  • ⚠️ GameData path: for correct node-stack discovery the path to the KSP installation's GameData/ directory must be set in File => Settings => General tab ("GameData path:" field). Without it, part attachment nodes are resolved by name only, which may produce incorrect .craft output on parts with non-standard attach-node layouts.
  • ⚠️ Known limitation: may produce incorrect .craft output on complex vessels, especially those with docking-node-attached subassemblies. Work in progress.

Docking Inspector

Docking Inspector

Detects and fixes stuck docking ports in the save file.

Docking Inspector Tab

  • Scan-driven: click the toolbar button to scan all vessels for docking port pairs.
  • Stuck detection: flags pairs where ModuleDockingNode.state deviates from Docked (dockee) / Docked (docker).
  • Per-pair Fix button and Fix All Stuck button — rewrites state to the correct values.
  • Auto-refresh: re-scans on document change (debounced 500ms) when the tab is open.
  • Vessel column with hyperlink navigation.
  • Settings page with "Show OK ports" toggle (default on).
  • Sort order preserved across rescans.

Command-Line Argument

Pass a file path as the first argument to open it directly on startup:

java -jar sfsanalyzer-app-<version>.jar path/to/quicksave.sfs

Supported File Types

  • *.sfs — KSP save files
  • *.cfg — KSP configuration files
  • *.craft — KSP craft files

Installation

  1. Make sure you have Java 21 (or later) installed (a JRE is sufficient for running).

  2. Download the latest release ZIP and extract it to a directory of your choice.

  3. Run:

    java -jar sfsanalyzer-app-<version>.jar

    For convenience, create a launcher:

    Windows — a shortcut with Target set to javaw.exe -jar sfsanalyzer-app-<version>.jar, Start in set to the installation directory, and Change Icon... pointing to sfsanalyzer.ico (included in the release ZIP).

    Linux — a .desktop file referencing the JAR and icon.png from the release ZIP:

    [Desktop Entry]
    Type=Application
    Name=SFS Analyzer
    Exec=java -jar sfsanalyzer-app-<version>.jar
    Path=/path/to/installation
    Icon=/path/to/icon.png
    Terminal=false

    Place the file at ~/.local/share/applications/sfsanalyzer.desktop for the current user or /usr/share/applications/ system-wide.

Building from Source

Prerequisites

  • Java 21 JDK
  • Apache Maven 3.8+

Build

mvn clean package

Produces:

  • app/target/sfsanalyzer-app-<version>.jar — runnable application (fat JAR)
  • api/target/sfsanalyzer-plugin-api-<version>.jar — plugin API library
  • demo-plugins/*/target/sfsanalyzer-demo-*.jar — demo plugins
  • plugins/*/target/*.jar — bundled plugins (see Bundled Plugins)

Run

Build produces app/target/sfsanalyzer-app-<version>.jar. To run:

java -jar app/target/sfsanalyzer-app-<version>.jar

Optionally create a plugins/ directory next to the JAR to enable the plugin system.

Plugin API

Plugins are discovered by scanning JARs in the plugins/ directory; disabled plugins are never loaded into the JVM.

If you plan to use an AI assistant to write plugins, see PLUGIN_AGENTS.md. It covers the full API guide, model classes, project structure, build setup, and deployment. To make an AI assistant pick up the instructions automatically, rename PLUGIN_AGENTS.md to the filename your assistant expects (e.g. AGENTS.md or CLAUDE.md) and place it in your plugin project root.

Getting Started

  1. Add the API as a dependency:

    <dependency>
        <groupId>io.github.crvx</groupId>
        <artifactId>sfsanalyzer-plugin-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
  2. Implement SfsAnalyzerPlugin:

    package com.example.myplugin;
    
    import io.github.crvx.sfsanalyzer.api.*;
    
    public class MyPlugin implements SfsAnalyzerPlugin {
        @Override
        public String getName() { return "My Plugin"; }
    
          @Override
          public void init(PluginContext ctx) {
              ToolbarButton btn = ctx.addToolbarButton(
                  "Do Something", "fas-star",
                  (node, mods) -> System.out.println("Selected: " + node.getName()));
              btn.setEnabled(false);
              ctx.addSelectionListener(n -> btn.setEnabled(n != null));
          }
    }
  3. Create a service descriptor at META-INF/services/io.github.crvx.sfsanalyzer.api.SfsAnalyzerPlugin:

    com.example.myplugin.MyPlugin
    
  4. Build your plugin as a JAR and place it in the plugins/ directory next to sfsanalyzer-app.jar.

  5. On next launch the plugin appears in the Plugins tab of the settings window (enabled by default).

Plugin Configuration

Each plugin gets its own config namespace persisted in sfsanalyzer.cfg:

CONFIG > PLUGINS > PLUGIN {
    name = com.example.myplugin.MyPlugin

    disabled = false    -- toggled via the Plugins tab
    META
    {
        name,
        author,
        version,
        description
    }
    SETTINGS            -- ctx.getConfig() returns only this
    {
        ...
    }
}

Metadata (name, author, version, description) is extracted automatically from the plugin class on first discovery. The plugin's custom configuration lives inside the PLUGIN > SETTINGS sub-node — only this subtree is exposed to ctx.getConfig().

To persist config changes call ctx.markModified(DirtyFlag.CONFIG). A custom settings tab is added when createSettingsNode() returns a non-null node.

Lifecycle

  1. Discovery — all JARs in plugins/ are scanned. New plugins are added to sfsanalyzer.cfg with metadata.
  2. Filtering — plugins marked disabled = true in the config are skipped.
  3. Initialisationinit(PluginContext) is called for each enabled plugin.
  4. SettingscreateSettingsNode() is called lazily when the user opens settings.

Demo Plugins

Two demo plugins are included in the source repository under demo-plugins/:

  • demo-toolbar-button (ToolbarButtonDemo) — adds a toolbar button that counts parameters in the selected node. Demonstrates addToolbarButton(), addSelectionListener(), addShortcut(), getConfig(), markModified(), and createSettingsNode().
  • demo-tab (SfsViewDemo) — adds a custom tab showing the selected node as formatted SFS source text, controlled by a toggleable toolbar button. Demonstrates addTab(), TabControl, addToolbarButton() with setToggleable(), and selection-driven visibility.

Both are built as part of the Maven reactor (mvn clean package from the project root). Their source code is heavily commented — browse it for practical API usage examples with explanations.

API Reference

Interface Purpose
SfsAnalyzerPlugin Entry point. init(PluginContext) is called at startup.
PluginContext Access to the document model, selection events, configuration, dirty tracking, and UI refresh.
ToolbarButton Control a toolbar button.
ToolbarAction Functional interface for the button action ((SfsNode, int modifiers) -> void).
DirtyFlag Flags for markModified(): DOCUMENT, CONFIG.
RefreshFlag Flags for refreshUI(): PARAMETERS, TREE.
ModifierFlag Flags for toolbar click modifiers: CTRL, SHIFT, ALT.
TabControl Control a plugin tab (title, content, visibility).

SfsAnalyzerPlugin

The entry point. Every plugin must implement this interface.

Method Returns Description
getId() String Unique plugin identifier. Default: getClass().getName().
getName() String Human-readable name shown in settings and tab titles.
getAuthor() String Author name (default: "").
getVersion() String Version string (default: "").
getDescription() String One-line description (default: "").
init(PluginContext) void Initialise the plugin (toolbar buttons, tabs, listeners).
createSettingsNode() Node or null Return a custom settings node, or null to omit the tab.

PluginContext

The main interaction point between a plugin and the application. Injected via SfsAnalyzerPlugin.init(PluginContext).

Method Returns Description
addToolbarButton(label, icon, action) ToolbarButton Add a button to the toolbar before the search field. icon is an Ikonli icon literal (e.g. "fas-star").
addTab(title, content) TabControl Add a tab to the right panel's tab pane below the parameters panel.
getDocument() SfsDocument The current document (read/write), or null if no file is open.
getSelectedNode() SfsNode or null The currently selected tree node, or null if nothing is selected.
selectNode(node) void Selects the given node in the tree and scrolls to it. Does nothing if the node is not found or no document is loaded.
addShortcut(keyCombo, action) void Register a global keyboard shortcut. keyCombo is a KeyCombination string, e.g. "Ctrl+Shift+R". Shortcut fires on KEY_RELEASED and takes priority over built-in shortcuts.
addSelectionListener(consumer) void Subscribe to selection changes. The consumer receives the new selection or null.
getConfig() SfsNode The plugin's private SETTINGS node (persisted in sfsanalyzer.cfg). Empty on first access.
markModified(flags) void Signal changes. Use DirtyFlag.DOCUMENT (enables Save) and/or DirtyFlag.CONFIG (persists plugin config and fires listeners).
isDocumentModified() boolean Whether the document has unsaved changes.
refreshUI(flags) void Request UI repaint. Use RefreshFlag.PARAMETERS (reload parameter panel) and/or RefreshFlag.TREE (rebuild tree).
addConfigChangeListener(runnable) void Subscribe to config changes (fires when another source modifies the plugin config).
removeConfigChangeListener(runnable) void Unsubscribe a previously registered listener.
getGameDataPath() String The configured GameData path from settings, or "" if not set.
parseFile(file) SfsDocument Parse any KSP file (.sfs, .craft, .cfg) and return its document model. The document is independent of the currently open file. Throws IOException on read errors.

ToolbarButton

Handle for controlling a plugin toolbar button after creation.

Method Returns Description
setEnabled(boolean) void Enable or disable the button. Disabled buttons are greyed out and do not respond to clicks.
setVisible(boolean) void Show or hide the button. Hidden buttons do not take up toolbar space.
setText(String) void Change the button's display text.
setTooltip(String) void Set tooltip text shown on hover.
setToggleable(boolean) void Make the button toggleable (click toggles selected state) or non-toggleable (auto-deselects after every click). Default: non-toggleable.
isSelected() boolean Returns whether the toggle button is currently selected (pressed).
setSelected(boolean) void Programmatically press or release the button. Only meaningful when toggleable.

ToolbarAction

Functional interface for the toolbar button callback:

@FunctionalInterface
public interface ToolbarAction {
    void run(SfsNode selectedNode, int modifiers);
}

selectedNode is the currently selected tree node at the moment of the click, or null if nothing is selected. modifiers is a bitmask of ModifierFlag values active when the button was clicked (Ctrl, Shift, Alt).

ModifierFlag

Bitmask flags for toolbar click modifiers.

Constant Value Description
CTRL 1 Control key was held
SHIFT 2 Shift key was held
ALT 4 Alt key was held

Check flags:

if (ModifierFlag.has(mods, ModifierFlag.SHIFT)) {
    // Shift was held during click
}

TabControl

Handle for managing a plugin-created tab. Returned by addTab().

Method Returns Description
setTitle(String) void Change the tab title.
setContent(Node) void Replace the tab content.
setVisible(boolean) void Show or hide the tab. When all tabs are hidden the tab pane is removed from the layout entirely.

RefreshFlag

Bitmask flags for refreshUI().

Constant Value Effect
PARAMETERS 1 Reload the parameter panel for the current node
TREE 2 Rebuild the entire tree from the document model

Combine flags:

ctx.refreshUI(RefreshFlag.of(RefreshFlag.PARAMETERS, RefreshFlag.TREE));

DirtyFlag

Bitmask flags for markModified().

Constant Value Effect
DOCUMENT 1 Marks the document as having unsaved changes (enables Save, shows * in title)
CONFIG 2 Marks the plugin configuration as dirty (persisted on exit) and fires config change listeners

Combine flags:

ctx.markModified(DirtyFlag.of(DirtyFlag.DOCUMENT, DirtyFlag.CONFIG));

Model Classes

All model classes live in io.github.crvx.sfsanalyzer.model.

SfsNode

A named node with an ordered list of entries (parameters, comments, children).

Method Description
getName() Node name (e.g. "VESSEL")
getEntries() Mutable List<SfsEntry> (params, comments, children)
addEntry(SfsEntry) Append an entry
getChildren() List of direct child SfsNodes (filtered from entries)
getFlatValues() LinkedHashMap<String, String> of key-value parameters
getComment() / setComment(String) Optional node comment
deepClone() Deep copy of the node and subtree
queryNode(path) Navigate a "/"-separated path to the first matching descendant, or null. Empty segments are ignored.
queryAllNodes(path) Navigate a "/"-separated path and return all descendants matching the final segment (never null).

SfsEntry (sealed interface)

Record Fields
SfsEntry.Parameter String key, String value
SfsEntry.Comment String text (includes // prefix from the parser)
SfsEntry.Child SfsNode node

SfsDocument

Method Description
getSourceFile() The source file (or null)
getRoot() / setRoot(SfsNode) Root node of the tree
isVirtualRoot() / setVirtualRoot(boolean) Whether root is a synthetic wrapper
isModified() / markModified() / clearModified() Dirty-tracking flag
unwrapIfNeeded() Normalise virtual-root wrapper (called automatically after parsing)

Icons

Toolbar and UI icons use Ikonli with the FontAwesome 6 set. Each icon is referenced by its Ikonli literal string (e.g. "fas-star") which maps to a constant in org.kordamp.ikonli.fontawesome6.FontAwesomeSolid.

Browse all available icons at the FontAwesome 6 Cheat Sheet.

License

MIT License. Copyright © Sergey Kuznetsov (crvx).

About

A cross-platform desktop application for browsing and editing Kerbal Space Program save files.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages