-
Notifications
You must be signed in to change notification settings - Fork 0
Developer Guide
tmoneymkll edited this page Dec 25, 2025
·
1 revision
Technical documentation for contributing to FS Mod Downloader.
| 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.) |
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
- .NET 8.0 SDK
- Visual Studio 2022 or VS Code with C# extension
- Windows 10/11
# 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βββββββββββββββ ββββββββββββββββββββ ββββββββββββββ
β View ββββββΆβ ViewModel ββββββΆβ Model β
β (XAML) βββββββ (Observable) βββββββ (Data) β
βββββββββββββββ ββββββββββββββββββββ ββββββββββββββ
β
βΌ
ββββββββββββββββ
β Services β
β (Business) β
ββββββββββββββββ
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);
}-
Mod Discovery
ModRepository.SearchModsAsync() β HTTP GET to mod website β HtmlAgilityPack parses HTML β Returns List<Mod> β ViewModel updates AvailableMods β UI updates via data binding -
Mod Installation
User clicks Install β ModManager.InstallModAsync() β ModDownloader.DownloadModAsync() β Extract to mods folder β Cleanup temp files β Refresh installed mods list
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", ...),
},
// ...
};
}
}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);
}
}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) { ... }
}- Use
async Taskfor async methods - Fire-and-forget in ViewModels:
_ = InitializeAsync(); - Never use
.Resultor.Wait()(causes deadlocks in WPF)
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);- 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;
}
}- Use nullable annotations:
string?,List<T>? - Check for null before operations
- Use null-coalescing:
value ?? defaultValue
Tests live in FSModDownloader.Tests/:
# Run tests
dotnet test- Service logic (ModRepository parsing, GamePathDetector)
- Utility functions (FileHelper, PathHelper)
- ViewModel commands (if complex logic)
[Fact]
public void FormatFileSize_ReturnsCorrectString()
{
Assert.Equal("1 KB", FileHelper.FormatFileSize(1024));
Assert.Equal("1.5 MB", FileHelper.FormatFileSize(1572864));
}- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes
- Test thoroughly
- Commit with clear messages:
git commit -m 'feat: Add amazing feature' - Push:
git push origin feature/amazing-feature - Open a Pull Request
Follow conventional commits:
-
feat:New feature -
fix:Bug fix -
docs:Documentation -
refactor:Code restructuring -
test:Tests -
chore:Maintenance
- Follow Microsoft C# naming conventions
- Use XML documentation comments on public APIs
- Keep ViewModels thin, put logic in Services
- Use
varwhen type is obvious
- CommunityToolkit.Mvvm Documentation
- WPF Documentation
- Serilog Documentation
- HtmlAgilityPack Documentation
- Open a GitHub Discussion
- Check existing Issues