Skip to content

Developer Guide

tmoneymkll edited this page Dec 25, 2025 · 1 revision

Developer Guide

Technical documentation for contributing to FS Mod Downloader.


πŸ› οΈ Technology Stack

Component Technology
Framework .NET 8.0 (Windows)
UI WPF (Windows Presentation Foundation)
Pattern MVVM (Model-View-ViewModel)
MVVM Toolkit CommunityToolkit.Mvvm
Logging Serilog
HTML Parsing HtmlAgilityPack
Data Source Web scraping (mod-network.com, etc.)

πŸ“ Project Structure

FSModDownloader/
β”œβ”€β”€ Assets/                  # App icons and images
β”‚   β”œβ”€β”€ favicon.ico
β”‚   └── Logo.png
β”œβ”€β”€ Models/                  # Data models
β”‚   β”œβ”€β”€ GameInstance.cs      # Game installation representation
β”‚   β”œβ”€β”€ Mod.cs               # Mod data model
β”‚   β”œβ”€β”€ ModVersion.cs        # Mod version info
β”‚   └── ModListManifest.cs   # Modlist export format
β”œβ”€β”€ Services/                # Business logic
β”‚   β”œβ”€β”€ GamePathDetector.cs  # Auto-detect FS installations
β”‚   β”œβ”€β”€ IModDownloader.cs    # Download interface
β”‚   β”œβ”€β”€ IModManager.cs       # Mod management interface
β”‚   β”œβ”€β”€ IModRepository.cs    # Repository interface
β”‚   β”œβ”€β”€ ModDownloader.cs     # Download implementation
β”‚   β”œβ”€β”€ ModManager.cs        # Install/uninstall logic
β”‚   β”œβ”€β”€ ModRepository.cs     # Web scraper (multi-source)
β”‚   β”œβ”€β”€ ManifestService.cs   # Modlist import/export
β”‚   └── SettingsService.cs   # Settings persistence
β”œβ”€β”€ Utilities/               # Helper classes
β”‚   β”œβ”€β”€ FileHelper.cs        # File operations
β”‚   └── PathHelper.cs        # Path utilities
β”œβ”€β”€ ViewModels/              # MVVM ViewModels
β”‚   └── MainWindowViewModel.cs
β”œβ”€β”€ Views/                   # WPF UI
β”‚   β”œβ”€β”€ MainWindow.xaml/.cs
β”‚   β”œβ”€β”€ SettingsWindow.xaml/.cs
β”‚   β”œβ”€β”€ AddGameInstanceDialog.xaml/.cs
β”‚   └── ManifestInstallDialog.xaml/.cs
β”œβ”€β”€ App.xaml/.cs             # Application entry point
└── AppSettings.cs           # Settings model

πŸ”§ Building from Source

Prerequisites

  • .NET 8.0 SDK
  • Visual Studio 2022 or VS Code with C# extension
  • Windows 10/11

Build Commands

# Clone the repository
git clone https://github.com/TmoneyMKII/FS-Mod-Downloader.git
cd FS-Mod-Downloader

# Restore dependencies
dotnet restore

# Debug build
dotnet build

# Release build
dotnet build -c Release

# Run the application
dotnet run --project FSModDownloader/FSModDownloader.csproj

# Publish self-contained executable
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true

πŸ—οΈ Architecture

MVVM Pattern

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    View     │────▢│    ViewModel     │────▢│   Model    β”‚
β”‚   (XAML)    │◀────│  (Observable)    │◀────│   (Data)   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚   Services   β”‚
                    β”‚ (Business)   β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Components

ViewModels use [ObservableProperty] from CommunityToolkit.Mvvm:

public partial class MainWindowViewModel : ObservableObject
{
    [ObservableProperty]
    private List<Mod> availableMods = new();

    [ObservableProperty]
    private bool isLoading = false;
}

Services follow interface patterns for testability:

public interface IModRepository
{
    Task<List<Mod>> SearchModsAsync(string query, string? category = null);
    Task<Mod?> GetModDetailsAsync(string modId);
}

Data Flow

  1. Mod Discovery

    ModRepository.SearchModsAsync()
         ↓
    HTTP GET to mod website
         ↓
    HtmlAgilityPack parses HTML
         ↓
    Returns List<Mod>
         ↓
    ViewModel updates AvailableMods
         ↓
    UI updates via data binding
    
  2. Mod Installation

    User clicks Install
         ↓
    ModManager.InstallModAsync()
         ↓
    ModDownloader.DownloadModAsync()
         ↓
    Extract to mods folder
         ↓
    Cleanup temp files
         ↓
    Refresh installed mods list
    

🎯 Key Services

ModRepository

The heart of mod discovery. Scrapes multiple websites:

public class ModRepository : IModRepository
{
    // Cache to reduce HTTP requests
    private readonly Dictionary<string, (Mod mod, DateTime cachedAt)> _modCache = new();
    private readonly TimeSpan _cacheExpiry = TimeSpan.FromMinutes(10);

    // Configure sources per game version
    private List<ModSource> GetSourcesForGame(string gameVersion)
    {
        return gameVersion switch
        {
            "FS25" => new List<ModSource>
            {
                new("mod-network", "https://mod-network.com", ...),
                new("fs25mods", "https://farmingsimulator25mods.com", ...),
            },
            // ...
        };
    }
}

GamePathDetector

Finds FS installations across multiple locations:

public class GamePathDetector
{
    public List<GameInstance> ScanForGameInstallations()
    {
        // 1. Check Documents\My Games
        var documentsPath = GetDocumentsModsPath(gameInfo.FolderName);
        
        // 2. Check Steam library
        var steamPath = GetSteamGamePath(gameInfo.SteamAppId);
        
        // 3. Check GIANTS registry
        var giantsPath = GetGiantsRegistryPath(gameInfo.RegistryName);
    }
}

SettingsService

Persists settings to JSON:

public static class SettingsService
{
    private static readonly string SettingsPath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
        "FSModDownloader", "settings.json");

    public static AppSettings Load() { ... }
    public static void Save(AppSettings settings) { ... }
}

πŸ“ Code Conventions

Async Patterns

  • Use async Task for async methods
  • Fire-and-forget in ViewModels: _ = InitializeAsync();
  • Never use .Result or .Wait() (causes deadlocks in WPF)

Logging

Use Serilog with structured logging:

private readonly ILogger _logger = Log.ForContext<MyClass>();

_logger.Information("Installing mod {ModId} version {Version}", mod.Id, version);
_logger.Error(ex, "Failed to download {ModName}", modName);

Error Handling

  • Catch exceptions at service boundaries
  • Log errors with context
  • Return meaningful results (bool success, null on failure)
public async Task<bool> InstallModAsync(Mod mod, ...)
{
    try
    {
        // ... installation logic
        return true;
    }
    catch (Exception ex)
    {
        _logger.Error(ex, "Error installing mod {ModId}", mod.Id);
        return false;
    }
}

Null Safety

  • Use nullable annotations: string?, List<T>?
  • Check for null before operations
  • Use null-coalescing: value ?? defaultValue

πŸ§ͺ Testing

Tests live in FSModDownloader.Tests/:

# Run tests
dotnet test

What to Test

  • Service logic (ModRepository parsing, GamePathDetector)
  • Utility functions (FileHelper, PathHelper)
  • ViewModel commands (if complex logic)

Test Example

[Fact]
public void FormatFileSize_ReturnsCorrectString()
{
    Assert.Equal("1 KB", FileHelper.FormatFileSize(1024));
    Assert.Equal("1.5 MB", FileHelper.FormatFileSize(1572864));
}

🀝 Contributing

Workflow

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Make your changes
  4. Test thoroughly
  5. Commit with clear messages: git commit -m 'feat: Add amazing feature'
  6. Push: git push origin feature/amazing-feature
  7. Open a Pull Request

Commit Messages

Follow conventional commits:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation
  • refactor: Code restructuring
  • test: Tests
  • chore: Maintenance

Code Style

  • Follow Microsoft C# naming conventions
  • Use XML documentation comments on public APIs
  • Keep ViewModels thin, put logic in Services
  • Use var when type is obvious

πŸ“š Resources


❓ Questions?