Instance System (MVP):
- Instance model (
GameInstance.cs) and service (InstanceService.cs) - Instance folder initialization (Mods, Saves, ModConfig, Logs, Cache)
- Instance configuration persistence (
instances-config.json) - Menu-based instance selector (File > Instances)
- Create/Delete/Open folder for instances
- Mod discovery per instance (via
_dataDirectoryswap) - Game launch per instance (with
--dataPath) - Install/delete mods per instance
- Player session (login) copied to new instances
Instance Browser UI (5.1): ✅ COMPLETE
- New "Instances" tab in main window (default tab on launch)
- Card-based UI with image/icon area and details area
- Click image to launch game, click details to view info
- Create/Delete/Open folder actions
- Right-click context menu (themed properly)
- Empty state with "Create Your First Instance" prompt
- Buttons follow app theme (IMM.ButtonBaseStyle)
Instance Edit Dialog: ✅ COMPLETE (2026-01-14)
- Edit instance name
- Edit custom icon (browse/clear)
- Edit notes
- Auto-detected game version display (read-only)
- Open instance folder button
Instance Duplication: ✅ COMPLETE (2026-01-14)
- Full folder copy (mods, saves, configs)
- New instance with "(Copy)" suffix
- Runs on background thread
- Resets playtime/last played for new copy
Automatic Dependency Resolution (G.2): ✅ COMPLETE
DependencyResolverServicefor recursive dependency resolution- Auto-prompt after installing mod: "Install dependencies?"
- "Fix all missing dependencies" menu option (Mods menu)
- Recursive resolution (A→B→C all at once)
- Nested dependency check after install
Mod Description Modal: ✅ COMPLETE (2026-01-14)
- Click any mod card in browser to view full HTML description
- Resizable dialog (800x600) with dark theme styling
- Renders HTML content from API with proper formatting
- External links open in default browser
- "View on Website" button for full mod page
Mod Browser Filter Improvements: ✅ COMPLETE (2026-01-14)
- Default filter changed to "Not Installed" (hides already-installed mods)
- Per-instance aware (filters based on current instance's mods)
Portable Paths: ✅ COMPLETE (2026-01-16)
- Instance paths stored as relative when under app folder
- Moving app folder auto-detects and uses new location
- Instance.json path overridden at load time with actual directory location
-
Creating new instance does not set it as active✅ FIXED -
Deleting active instance doesn't switch away properly✅ FIXED (2026-01-16)- ✅ Instance Browser delete (
DeleteInstanceAsync) - properly switches back to profile - ✅ Menu delete (
DeleteInstanceMenuItem_OnClick) - now switches back to profile after delete
- ✅ Instance Browser delete (
-
App startup doesn't load last active instance✅ FIXED -
Base Mods Dialog UX is poor - Currently requires picking zip files manually
- Should allow selecting from already-installed mods in current instance
- Should have access to mod browser to download directly to base mods
- Status: Low priority, workaround is to use "Open Folder" and copy files manually
Transform the current "profile" system into a full Instance-based architecture (similar to Minecraft launchers like MultiMC/Prism). Each instance is a completely isolated VS environment with its own mods, saves, and configurations.
- Single shared
Modsfolder per DataDirectory - Profiles enable/disable mods via
clientsettings.jsondisabled list - All profiles share the same physical mod files
- Launch already uses
--dataPathargument
- Each instance = isolated DataPath folder
- Mods physically present in instance = enabled
- No enable/disable tracking needed per instance
- Full isolation: mods, saves, configs, worlds
{InstancesRoot}/ # User-configurable, default: app folder
├── VS Hardcore/
│ ├── Mods/ # Mods for this instance only
│ ├── Saves/ # Worlds for this instance
│ ├── ModConfig/ # Mod configurations
│ ├── clientsettings.json # Instance-specific settings
│ ├── Logs/
│ └── instance.json # Instance metadata (managed by app)
├── VS Exploration/
│ └── ...
└── Vanilla/
└── ...
{
"name": "VS Hardcore",
"created": "2024-01-15T10:30:00Z",
"lastPlayed": "2024-01-20T18:45:00Z",
"vsVersion": "1.20.0", // Target VS version (optional)
"gameDirectory": "C:\\Program Files\\Vintagestory", // Can differ per instance
"iconPath": "icon.png", // Optional custom icon
"notes": "My hardcore survival playthrough",
"totalPlaytime": 3600 // Seconds (optional tracking)
}Goal: Create the data model and service for managing instances
Files to create/modify:
-
Models/GameInstance.cs- Instance data model -
Services/InstanceService.cs- CRUD operations for instances
Model properties:
public class GameInstance
{
public string Id { get; set; } // GUID
public string Name { get; set; }
public string Path { get; set; } // Full path to instance folder
public string? GameDirectory { get; set; } // VS installation path
public string? TargetVsVersion { get; set; }
public DateTime Created { get; set; }
public DateTime? LastPlayed { get; set; }
public string? IconPath { get; set; }
public string? Notes { get; set; }
}Service methods:
public interface IInstanceService
{
string InstancesRootPath { get; set; }
IReadOnlyList<GameInstance> GetAllInstances();
GameInstance? GetInstance(string id);
GameInstance CreateInstance(string name, string? vsVersion = null);
void DeleteInstance(string id, bool deleteFiles = false);
void RenameInstance(string id, string newName);
string GetInstanceModsPath(string id);
string GetInstanceSavesPath(string id);
string GetInstanceConfigPath(string id);
}Test:
- Create instance via service
- Verify folder structure created
- Read instance back
- Delete instance
Goal: Save/load instances root path and instance list
Files to modify:
-
- Handled in InstanceService viaServices/UserConfigurationService.csinstances-config.json -
- Default path isDevConfig.cs{AppDirectory}/Instances
Settings to add:
-
InstancesRootPath- where instances are stored (in InstanceService) -
ActiveInstanceId- currently selected instance (in InstanceService) -
InstancesEnabled- feature toggle during development (NOT IMPLEMENTED - instances always available)
Test:
- Change instances root path
- Restart app, verify path persisted
- Switch active instance, verify persisted
Goal: Create proper VS data folder structure when creating instance
Required subfolders:
Mods/
Saves/
ModConfig/
Logs/
Cache/
Also create:
-
clientsettings.jsonwith player session data copied from source profile -
instance.jsonmetadata file
Test:
- Create new instance
- Verify all folders exist
- Verify VS can launch with
--dataPathpointing to instance
Goal: Replace profile dropdown with instance selector
Options (pick one):
- Option A: Sidebar with instance list (like Prism Launcher)
- Option B: Menu in File menu (implemented as submenu, not dropdown in toolbar)
- Option C: Separate "Instances" tab/view
Suggested: Start with Option B (dropdown), upgrade to Option A later
UI elements needed:
- Instance selector (as menu items under File > Instances)
- "New Instance" button (Create Instance menu item)
- "Manage Instances" dialog (NOT IMPLEMENTED - no dedicated dialog)
- Instance context menu items: delete, open folder
- Instance rename (NOT IMPLEMENTED from UI)
- Instance duplicate (NOT IMPLEMENTED)
Test:
- Create instance from UI
- Switch between instances
- Verify mod list updates to show instance's mods
Goal: Dialog for creating/editing instances
Fields:
- Instance name (only via simple InputBox on create)
- Target VS version (dropdown of installed versions?)
- Game directory (optional override)
- Notes/description
- Icon selection (optional)
Actions:
- Create new instance (simple InputBox prompt)
- Duplicate existing instance
- Delete instance (with confirmation)
- Open instance folder in explorer
Test:
- Create instance with custom settings
- Edit instance name/notes
- Duplicate instance with all mods
Goal: ModDiscoveryService reads from active instance's Mods folder
Files to modify:
-
- Works via MainWindow updatingServices/ModDiscoveryService.cs_dataDirectoryand callingReloadViewModelAsync() -
- Works automatically since data directory changesServices/ClientSettingsStore.cs
Implementation approach (different from plan):
Instead of modifying ModDiscoveryService, MainWindow swaps _dataDirectory to the instance path and reloads the ViewModel, which reinitializes ModDiscoveryService with the new path.
// In MainWindow.InstanceMenuItem_OnClick:
_dataDirectory = instance.Path;
await ReloadViewModelAsync();Test:
- Add mod to Instance A
- Switch to Instance B
- Verify mod not visible
- Switch back, verify mod visible
Goal: Launch button uses active instance's path
Implementation (MainWindow.xaml.cs:8354-8391):
var launchDataPath = activeInstance?.Path ?? _dataDirectory;
var launchGameDirectory = activeInstance?.GameDirectory ?? _gameDirectory;
// ...
startInfo.ArgumentList.Add("--dataPath");
startInfo.ArgumentList.Add(launchDataPath);Also updates LastPlayed timestamp on the instance when launching.
Test:
- Launch game from Instance A
- Verify correct mods loaded in-game
- Verify saves are in Instance A folder
Goal: Downloaded mods go to active instance's Mods folder
Files to modify:
-
- Uses callback to MainWindowViewModels/ModBrowserViewModel.cs -
- MainWindow provides target pathServices/ModApiService.cs
Implementation:
MainWindow's TryGetInstallTargetPath() uses _dataDirectory which is set to the active instance's path when an instance is selected. No changes needed to ModApiService.
New flow:
- User clicks "Install" on mod
- Mod downloaded to
_dataDirectory/Mods/(which is instance path when active) - Only that instance has the mod
Test:
- Install mod to Instance A
- Verify mod file in
{InstanceA}/Mods/ - Switch to Instance B, mod not visible
Goal: Deleting a mod only affects current instance
Implementation: Delete operates on current _dataDirectory/Mods/ which points to instance folder when active.
Test:
- Install same mod to Instance A and B
- Delete from Instance A
- Verify still exists in Instance B
Goal: Each instance can have different mod versions
This is automatic with instance isolation:
- Instance A has ModX v1.0
- Instance B has ModX v2.0
- No conflict, different folders
UI consideration:
- When installing, show "Install to [Instance Name]" (NOT IMPLEMENTED)
- Warn if mod version incompatible with instance's target VS version (NOT IMPLEMENTED)
Test:
- Install ModX v1.0 to Instance A (VS 1.19)
- Install ModX v2.0 to Instance B (VS 1.20)
- Verify each instance has correct version
Goal: Cache downloaded mods centrally, copy/link to instances
Structure:
{AppData}/SimpleVSManager/
├── ModLibrary/ # Shared download cache
│ ├── modx/
│ │ ├── 1.0.0/
│ │ │ └── modx_v1.0.0.zip
│ │ └── 2.0.0/
│ │ └── modx_v2.0.0.zip
│ └── mody/
│ └── ...
└── Instances/ # Instance folders
Benefits:
- Faster installs (copy from cache)
- Less disk space (if using symlinks)
- Track all downloaded versions
Installation flow:
- Download mod to library (if not cached)
- Copy (or symlink) to instance Mods folder
Goal: Share instances as modpacks
Export options:
- Full export: Mods + Configs + Saves (large)
- Modpack export: Mods + Configs only
- Manifest export: Just mod list (smallest, requires re-download)
Export format (modpack.json):
{
"name": "VS Hardcore Pack",
"version": "1.0.0",
"author": "YourName",
"vsVersion": "1.20.0",
"mods": [
{ "modId": "wildcraftmod", "version": "1.5.0", "required": true },
{ "modId": "xskills", "version": "0.8.0", "required": true }
],
"optionalMods": [
{ "modId": "vtmlib", "version": "1.2.0" }
]
}Import flow:
- User selects modpack file
- Create new instance
- Download mods from mod database (with progress)
- Copy configs if included
Goal: Support different VS installations per instance
Instance setting:
-
gameDirectory- path to specific VS installation (property exists in model) - Launch uses
activeInstance?.GameDirectory ?? _gameDirectory(implemented)
Considerations:
- Auto-detect installed VS versions (NOT IMPLEMENTED)
- Warn on mod version incompatibility (NOT IMPLEMENTED)
- Different exe paths for different versions (works via GameDirectory property)
Goal: Clone an instance with all its content
Options on duplicate:
- Copy mods only
- Copy mods + configs
- Copy mods + configs + saves
- Copy everything
Option A: Clean Start ✅ CURRENT APPROACH
- Instances are separate from profiles
- Users manually create instances and install mods
- Profiles continue to work for legacy users ("Use Profile" option)
Option B: Migration Wizard ❌ NOT IMPLEMENTED
- Detect existing profile with mods
- Offer to create instance from profile
- Copy enabled mods to new instance folder
- Copy relevant configs
Migration steps:
- For each profile with
disabledModslist:- Create instance folder
- Copy all mods NOT in disabled list
- Copy ModConfig folder
- Set instance vsVersion from profile
- Mark migration complete in config
- Instance model and service (1.1)
- Instance folder creation (1.3)
- Basic instance selector UI (2.1 - menu-based)
- Mod discovery per instance (2.3)
- Game launch per instance (2.4)
- Install mods to instance (3.1)
- Instance management dialog (2.2) - ❌ NOT DONE
- Instance configuration persistence (1.2)
- Delete mods per instance (3.2)
- Instance duplication (4.4) - ❌ NOT DONE
- Shared mod library (4.1)
- Import/Export modpacks (4.2)
- Multiple VS versions (4.3) - Model ready, no UI
- Migration wizard
-
Instances root default location: ✅ DECIDED
- App folder (
./Instances/) - IMPLEMENTED - AppData (
%AppData%/SimpleVSManager/Instances/) - User Documents
- Prompt on first run
- App folder (
-
What happens to existing profiles?: ✅ DECIDED
- Keep both systems (profiles + instances) - IMPLEMENTED ("Use Profile" option)
- Migrate profiles to instances
- Deprecate profiles, instances only
-
Mod library caching: ✅ DECIDED
- Always copy mods to instances (simple, more disk space) - IMPLEMENTED
- Use symlinks/hardlinks (complex, saves space)
- Optional per-user setting
-
Enable/disable mods within instance?: ✅ DECIDED
- No - mod present = enabled (like Minecraft) - IMPLEMENTED (full isolation)
- Yes - keep disabled list per instance
- Hybrid - can disable but discouraged
-
Saves management: ✅ DECIDED
- Saves stay in instance (default) - IMPLEMENTED
- Option to share saves between instances
- Import/export individual saves
Goal: Dedicated tab/view for browsing and managing instances with card-based UI
UI Design - Card Layout:
┌─────────────────────────────┐
│ │
│ INSTANCE IMAGE/ICON │ ← Click here = LAUNCH GAME
│ (large, top area) │
│ │
├─────────────────────────────┤
│ Instance Name │ ← Click here = OPEN DETAILS/EDIT
│ 🕐 12.5 hrs │ 🎮 1.20.3 │
│ 📦 24 mods │
└─────────────────────────────┘
Card Top Area (Image/Icon):
- Large instance icon or custom image
- Click action: Launch game with this instance immediately
- Default icon if none set (VS logo or generated from name)
Card Bottom Area (Details):
- Instance name (prominent)
- Time played (from
TotalPlaytimeSeconds) - Game version (from
TargetVsVersionor detected) - Mod count (count files in instance's Mods folder)
- Click action: Open instance details/edit panel or dialog
Details Panel/Dialog (when clicking bottom area):
- Edit instance name
- Edit notes/description
- Set/change target VS version
- Set/change game directory override
- Set/change icon image
- View full stats (created date, last played, total playtime)
- Actions: Duplicate, Delete, Open Folder
Tab Placement:
- New "Instances" tab alongside "Mods" and "Database" tabs
- Or: Replace current profile system entirely with instance cards
Additional Features:
- "Create New Instance" card/button (+ icon)
- Grid layout, responsive to window size
- Right-click context menu as fallback
- Keyboard navigation support
Implementation notes:
- Use
ItemsControlorListBoxwithWrapPanelfor card grid - Each card is a custom
UserControl - ViewModel:
InstanceBrowserViewModelwithObservableCollection<InstanceCardViewModel> - Need to calculate mod count on demand (or cache it)
- Need to track playtime (currently model has field but not populated)
Goal: Optional shared "base" folder with mods that auto-copy to new instances
Concept:
{InstancesRoot}/
├── _BaseMods/ # Special folder for shared mods
│ ├── essential-mod.zip
│ └── utility-lib.zip
├── Instance A/
│ └── Mods/ # Gets copies of _BaseMods on creation
└── Instance B/
└── Mods/
Implemented:
- ✅ When creating a new instance, copies all mods from
_BaseMods/to the new instance'sMods/folder - ✅ Deleting a mod from an instance does NOT affect
_BaseMods/ - ✅
_BaseMods/is managed separately via dedicated dialog - ✅ Checkbox to enable/disable auto-copy on instance creation (defaults to enabled)
UI Elements:
- ✅ "Manage Base Mods..." menu item in File > Instances menu
- ✅ Dialog shows list of mods in
_BaseMods/ - ✅ Add from file (supports multi-select), remove selected, open folder buttons
- "Add to Base Mods" context menu on installed mods (not implemented - users can manually add)
Configuration:
- ✅
copyBaseModsOnCreatesetting ininstances-config.json - ✅ Default path:
{InstancesRoot}/_BaseMods/
Files created/modified:
Services/InstanceService.cs- AddedBaseModsPath,CopyBaseModsOnCreate,CopyBaseModsToInstance(),GetBaseModFiles(),AddModToBaseMods(),RemoveModFromBaseMods(),OpenBaseModsFolder()Views/Dialogs/BaseModsDialog.xaml- New dialog for managing base modsViews/Dialogs/BaseModsDialog.xaml.cs- Dialog code-behindViews/MainWindow.xaml- Added "Manage base mods..." menu itemViews/MainWindow.xaml.cs- AddedManageBaseModsMenuItem_OnClickhandler
Use cases:
- Library mods everyone needs (VTMLlib, etc.)
- Personal "must-have" QoL mods
- Modpack creators maintaining a base set
Goal: Automatically resolve dependencies when installing mods (like Prism Launcher)
Implemented (2026-01-13):
- ✅ DependencyResolverService - New service for recursive dependency resolution
- ✅ On install: After installing a mod, automatically detects missing dependencies and prompts to install them
- ✅ Recursive: Resolves entire dependency tree - if A needs B and B needs C, all are detected
- ✅ "Fix All Dependencies" menu option: Mods menu > Fix all missing dependencies
- ✅ Confirmation dialog: Shows list of dependencies to install with version info
- ✅ Nested check: After installing deps, checks if new deps have their own missing deps
Files created/modified:
Services/DependencyResolverService.cs- New service withExtractDependenciesFromZip()andResolveAllDependenciesAsync()MainWindow.xaml.cs-CheckAndOfferDependencyInstallAsync(),InstallResolvedDependenciesAsync(),FixAllDependenciesAsync()MainWindow.xaml- Added "Fix all missing dependencies" menu item under Mods menu
How it works:
- After mod is installed via Mod Browser,
CheckAndOfferDependencyInstallAsync()is called - Extracts
modinfo.jsonfrom the installed zip to find dependencies - Uses
DependencyResolverService.ResolveAllDependenciesAsync()to check what's missing - Shows dialog: "This mod requires X, Y, Z. Install them?"
- If user accepts, installs all missing dependencies
- After installation, checks for nested dependencies and offers to fix those too
How it works:
- Each mod's
modinfo.jsoncontains adependenciesobject:"dependencies": { "game": "1.20.0", "vslib": "1.5.0", "someothermod": "" }
- The app already parses this in
ModDiscoveryService.GetDependencies()(line 953) - The mod database API (
/api/mod/{modid}) can be queried by modId string - Existing "Fix" code in
MainWindow.xaml.cs:5490-5600handles single-mod repair
What Rustique does (for reference):
- Extracts dependencies from
modinfo.jsoninside downloaded zips - Recursively resolves entire dependency chains in one pass
install --missing-dependenciesfixes all at once- The mod database API does NOT return dependency data directly - must extract from mod files
Current limitation in your app:
- Manual per-mod "Fix" action only
- Non-recursive: if mod A needs B, and B needs C, user must fix twice
- No prompt on install
Recommendation:
- Add recursive dependency resolution (follow chain until all resolved)
- Trigger on install, not just as repair action
- Add "Fix All Dependencies" batch operation
Research findings:
- Vintage Story uses
auth.vintagestory.atfor authentication - No public OAuth API or documented authentication flow for third-party apps
- Session tokens are stored in
clientsettings.jsonunderstringSettings:sessionkeysessionsignatureuseremailplayeruidplayernamemptoken
Current workaround (already implemented):
InstanceService.CopyPlayerSessionToInstance()copies these fields from source data directory- This means: user logs in once via the game, then session is shared to instances
Why no app-level auth:
- VS auth server is not documented for third-party use
- No OAuth endpoints or API tokens available
- The game client handles login internally
- Other mod managers (Rustique, Vintage Launcher) also don't implement direct auth
Alternatives considered:
- Browser-based login - Would need to scrape/intercept (fragile, potentially against ToS)
- Ask user to paste session token - Poor UX, security concerns
- Current approach - Copy session from existing game data (what you're doing now)
Recommendation: Keep current session-copying approach. It's the most reliable and what other tools do.
- The app already launches with
--dataPath, so the core mechanism exists ModDiscoveryServicealready supports multiple search pathsDownloadModAsyncinModApiServicealready handles mod downloads- Instance system can coexist with profiles during transition
| Component | File | Relevance |
|---|---|---|
| Launch with dataPath | MainWindow.xaml.cs:8139 |
Already uses --dataPath |
| Mod discovery | ModDiscoveryService.cs:540-559 |
BuildSearchPaths() needs instance path |
| Download mods | ModApiService.cs:55-59 |
DownloadModAsync destination |
| Profile state | UserConfigurationService.cs:3955 |
GameProfileState as reference |
| Mod browser install | ModBrowserViewModel.cs |
Install destination path |
Fix instance activation bug✅Fix app startup not loading last instance✅Instance Browser UI (5.1)✅ - Card-based view with launch/detailsAutomatic Dependency Resolution (G.2)✅ - Auto-install deps, "Fix All" menuInstance Edit Dialog (2.2)✅ - Edit name, icon, notes, view game versionInstance Duplication✅ - Full folder copy with background threadContext menu theming✅ - Override ModernWpf stylesButton theming✅ - Use IMM.ButtonBaseStyleInstances tab as default✅ - Opens on launchFix: Deleting active instance✅ (2026-01-16) - Both browser and menu delete now switch away properlyBase Mods Directory (5.2)✅ (2026-01-16) - Shared mods auto-copied to new instancesPortable Paths✅ (2026-01-16) - App folder can be moved without breaking instances