Compare commits

...

13 commits

Author SHA1 Message Date
4f80b72603
Merge pull request #4 from Grumgog/refactor-modLauncher
refactor: modsLauncer and MainWindow
2022-02-16 19:54:54 +11:00
deffd05581 Bug fixes 2022-02-16 19:52:38 +11:00
Никонов Василий
25c3fd0425 refactor: modsLauncer and MainWindow
refactor some of code of this classes
2022-02-16 00:30:28 +11:00
085320215a Removed unnecesarry stuff 2022-02-15 22:52:26 +11:00
110029692d Fixed button 2022-02-15 22:52:11 +11:00
8da74ffa6d Refactored methods that do filesystem stuff from ScrapMod to ModsLauncher 2022-02-15 22:51:37 +11:00
ca870eb66b Try-catched some exceptions 2022-02-15 22:50:56 +11:00
75333e8f68
Merge pull request #1 from Grumgog/refactor-Scrap-mod
(refactor): ScrapMod class code refactoring
2022-02-15 21:43:52 +11:00
Никонов Василий
90764c8636 do PR note 2022-02-14 09:46:59 +11:00
Никонов Василий
1e25aa210a Method of loading data from archive moved to Utils class.
Re-write property supportedGameVersionsDisplay more shortly and optimized.
Used var declaration where type is  obvious.

Property initialized in declaration.
constructor ScrapMod change to private, but LoadFromFile change to public.
LoadIcon moved to Utils and renamed to LoadImage.
2022-02-14 01:15:13 +11:00
9deb717eea Added check for minimal launcher version 2022-02-13 01:30:17 +11:00
515aa519a4 Added support for different files for different versions in mod 2022-02-13 01:26:00 +11:00
02d9df8160 Changed config format from xml to toml 2022-02-13 00:09:27 +11:00
9 changed files with 383 additions and 304 deletions

View file

@ -1,4 +1,4 @@
WIP ScrapModLoader WIP ScrapModLoader
============== ==============
This applications is for managing mods for Scrapland. This applications is for managing mods for Scrapland.
@ -25,44 +25,67 @@ ScrapModLoader supports both original and remastered versions of Scrapland.
For now mod for Scrapland is a *.sm file that basically is a zip arhive with following content: For now mod for Scrapland is a *.sm file that basically is a zip arhive with following content:
| Filename | Description | | Filename | Description |
|--------------------|----------------------------------------------| |------------------------|--------------------------------------------------|
| icon.png | Icon for mod that will show up in mod loader | | icon.png | Icon for mod that will show up in mod loader |
| config.xml | Information about mod | | config.toml | Information about mod |
| <game_version\>\ | Folder that named as game version mod made for |
| <filename\>.packed | Container with all mod game assets | | <filename\>.packed | Container with all mod game assets |
### meta.ini sample You can have as many .packed files as you want. Mod loader will load everything.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ScrapMod>
<Title>Mod Title</Title>
<Description>Mod Desciption</Description>
<Category>Category</Category> .packed files in the root of mod will be copied to the `Mods` folder of Scrapland.
<Version>1.0</Version>
<RequiredLauncher>1.0</RequiredLauncher>
<RequiredGame>1.1</RequiredGame>
<Author name="Author1" website="https://example.com" /> .pakced files under game version folder will load only to the appopriate game version.
<Author name="Author2" />
<Credits group="Mod author"> ### .sm structure sample
<Credit name="Author1" /> ```
</Credits> │ icon.png
<Credits group="Some credit" > │ config.toml
<Credit name="Credit1" /> │ mod_assets.packed
<Credit name="Credit2" /> ├──1.0/
<Credit name="Credit3" /> │ only_for_original.packed
</Credits> └──1.1/
</ScrapMod> only_for_remastered.packed
```
### config.toml sample
```toml
title = "Mod title"
description = "Mod description"
category = "Mod category"
version = "1.0"
requiredLauncher = "1.0"
supportedGameVersions = ["1.0", "1.1"]
authors = [
{ name = "Author 1" },
{ name = "Author 2" }
]
[[credits]]
group = "Group 1"
credits = [
{ name = "Author 1" },
{ name = "Author 2" },
{ name = "Author 3" }
]
[[credits]]
group = "Group 2"
credits = [
{ name = "Author 3" },
{ name = "Author 4" }
]
``` ```
## TODO: ## TODO:
- [X] Support for custom *.packed - [X] Support for custom *.packed
- [X] Supoprt for Scrapland Remastered - [X] Supoprt for Scrapland Remastered
- [ ] Support for both Scrapland versions in single .sm file
- [ ] Support for custom game files (i.e. `\Traslation\` files or custom `QuickConsole.py`) - [ ] Support for custom game files (i.e. `\Traslation\` files or custom `QuickConsole.py`)
- [ ] Recompiling *.py to *.pyc - [ ] Recompiling *.py to *.pyc
- [ ] Mod settings. - [ ] Mod settings.
- [ ] More meta info in `config.xml` - [ ] More meta info in `config.toml`
- [ ] Multilanguage support - [ ] Multilanguage support
- [ ] More mods :wink: - [ ] More mods :wink:

View file

@ -18,7 +18,7 @@
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" /> <ColumnDefinition Width="2*" />
<ColumnDefinition Width="5" /> <ColumnDefinition Width="5" />
<ColumnDefinition Width="0*" /> <ColumnDefinition Name="PreviewColumn" Width="0*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ListView d:ItemsSource="{d:SampleData ItemCount=5}" Name="ModsList" Initialized="ModsList_Initialized" MouseDown="ModsList_MouseDown"> <ListView d:ItemsSource="{d:SampleData ItemCount=5}" Name="ModsList" Initialized="ModsList_Initialized" MouseDown="ModsList_MouseDown">
<ListView.View> <ListView.View>
@ -34,8 +34,9 @@
</DataTemplate> </DataTemplate>
</GridViewColumn.CellTemplate> </GridViewColumn.CellTemplate>
</GridViewColumn> </GridViewColumn>
<GridViewColumn Header="Category" DisplayMemberBinding="{Binding Category}" />
<GridViewColumn Header="Mod Version" DisplayMemberBinding="{Binding Version}" /> <GridViewColumn Header="Mod Version" DisplayMemberBinding="{Binding Version}" />
<GridViewColumn Header="Game Version" DisplayMemberBinding="{Binding RequiredGame}" /> <GridViewColumn Header="Game Version" DisplayMemberBinding="{Binding SupportedGameVersionsDisplay}" />
</GridView> </GridView>
</ListView.View> </ListView.View>
<ListView.ItemContainerStyle> <ListView.ItemContainerStyle>
@ -70,8 +71,8 @@
</StackPanel> </StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Left"> <StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Left">
<ComboBox Name="ScraplandVersion" Margin="0,0,10,0" SelectionChanged="ScraplandVersion_SelectionChanged"> <ComboBox Name="ScraplandVersion" Margin="0,0,10,0" SelectionChanged="ScraplandVersion_SelectionChanged">
<ComboBoxItem Content="Original" IsEnabled="False" /> <ComboBoxItem Name="OriginalVersionItem" Content="Original" IsEnabled="False" />
<ComboBoxItem Content="Remastered" /> <ComboBoxItem Name ="RemasteredVersionItem" Content="Remastered" />
</ComboBox> </ComboBox>
<CheckBox Name="Windowed" Content=" Windowed " Margin="0,0,10,0" HorizontalAlignment="Center" VerticalAlignment="Center" /> <CheckBox Name="Windowed" Content=" Windowed " Margin="0,0,10,0" HorizontalAlignment="Center" VerticalAlignment="Center" />
<CheckBox Name="CloseLauncher" Content=" Close launcher " Margin="0,0,10,0" HorizontalAlignment="Center" VerticalAlignment="Center" /> <CheckBox Name="CloseLauncher" Content=" Close launcher " Margin="0,0,10,0" HorizontalAlignment="Center" VerticalAlignment="Center" />

View file

@ -28,25 +28,32 @@ namespace ScrapModLoader
{ {
if (Settings.Default.ModsPathes.Count == 0) if (Settings.Default.ModsPathes.Count == 0)
{ {
String path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) String path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Scrapland mods");
+ Path.DirectorySeparatorChar + "Scrapland mods";
Settings.Default.ModsPathes.Add(path); Settings.Default.ModsPathes.Add(path);
Directory.CreateDirectory(path); Directory.CreateDirectory(path);
} }
// TODO: Refactor it to separate window with pretty loading animation // TODO: Refactor it to separate window with pretty loading animation
if (modsLauncher.ScraplandPath == String.Empty && modsLauncher.ScraplandRemasteredPath == String.Empty) if (modsLauncher.ScraplandPath == String.Empty && modsLauncher.ScraplandRemasteredPath == String.Empty)
{
try
{ {
Boolean isFoundScrapland = modsLauncher.SearchForScrapland(); Boolean isFoundScrapland = modsLauncher.SearchForScrapland();
if (!isFoundScrapland) if (!isFoundScrapland)
{ {
ButtonRunScrapland.IsEnabled = false; ButtonRunScrapland.IsEnabled = false;
MessageBox.Show("Error: unable to find Scrapland instalation. Please, specify yours game installation folder in settings."); MessageBox.Show("Unable to find Scrapland instalation. Please, specify yours game installation folder in settings.",
"Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
catch (KeyNotFoundException ex)
{
MessageBox.Show(ex.Message);
} }
} }
((ComboBoxItem)ScraplandVersion.Items[0]).IsEnabled = modsLauncher.ScraplandPath != String.Empty; OriginalVersionItem.IsEnabled = modsLauncher.ScraplandPath != String.Empty;
((ComboBoxItem)ScraplandVersion.Items[1]).IsEnabled = modsLauncher.ScraplandRemasteredPath != String.Empty; RemasteredVersionItem.IsEnabled = modsLauncher.ScraplandRemasteredPath != String.Empty;
ScraplandVersion.SelectedIndex = modsLauncher.ScraplandRemasteredPath != String.Empty ? 1 : 0; ScraplandVersion.SelectedIndex = modsLauncher.ScraplandRemasteredPath != String.Empty ? 1 : 0;
@ -55,18 +62,18 @@ namespace ScrapModLoader
private void ModsList_Initialized(Object sender, EventArgs e) => ModsList.ItemsSource = modsLauncher.Mods; private void ModsList_Initialized(Object sender, EventArgs e) => ModsList.ItemsSource = modsLauncher.Mods;
private void ModsList_MouseDown(Object sender, System.Windows.Input.MouseButtonEventArgs e) private void ModsList_MouseDown(Object sender, MouseButtonEventArgs e)
{ {
if (MainGrid.ColumnDefinitions[2].Width.Value != 0) if (PreviewColumn.Width.Value != 0)
{ {
gridLength = MainGrid.ColumnDefinitions[2].Width; gridLength = PreviewColumn.Width;
MainGrid.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star); PreviewColumn.Width = new GridLength(0, GridUnitType.Star);
} }
} }
private void ListViewItem_PreviewMouseLeftButtonDown(Object sender, MouseButtonEventArgs e) private void ListViewItem_PreviewMouseLeftButtonDown(Object sender, MouseButtonEventArgs e)
{ {
MainGrid.ColumnDefinitions[2].Width = gridLength; PreviewColumn.Width = gridLength;
if (sender is ListViewItem item) if (sender is ListViewItem item)
{ {
@ -74,7 +81,7 @@ namespace ScrapModLoader
if (selectedModName == null) if (selectedModName == null)
throw new KeyNotFoundException(nameof(selectedModName)); throw new KeyNotFoundException(nameof(selectedModName));
ScrapMod ? mod = modsLauncher.Mods.Find(mod => mod.Name == selectedModName); ScrapMod? mod = modsLauncher.Mods.Find(mod => mod.Name == selectedModName);
if (mod == null) if (mod == null)
throw new KeyNotFoundException(nameof(mod)); throw new KeyNotFoundException(nameof(mod));
@ -92,34 +99,35 @@ namespace ScrapModLoader
private void WriteModInfo(ScrapMod mod) private void WriteModInfo(ScrapMod mod)
{ {
ModInfo.Document.Blocks.Clear(); ModInfo.Document.Blocks.Clear();
Paragraph parahraph = new Paragraph(); Paragraph paragraph = new Paragraph();
parahraph.Inlines.Add(new Bold(new Run("Description:\n"))); paragraph.Inlines.Add(new Bold(new Run("Description:\n")));
parahraph.Inlines.Add(new Run(mod.Description)); paragraph.Inlines.Add(new Run(mod.Description));
parahraph.Inlines.Add(new Bold(new Run("\n\nAuthors:\n"))); paragraph.Inlines.Add(new Bold(new Run("\n\nAuthors:\n")));
foreach (String autor in mod.Authors) foreach (String autor in mod.Authors)
parahraph.Inlines.Add(new Run(autor + "\n")); paragraph.Inlines.Add(new Run(autor + "\n"));
ModInfo.Document.Blocks.Add(parahraph); ModInfo.Document.Blocks.Add(paragraph);
ModCreditsTab.Visibility = Visibility.Visible;
if (mod.Credits.Count == 0) if (mod.Credits.Count == 0)
ModCreditsTab.Visibility = Visibility.Hidden; ModCreditsTab.Visibility = Visibility.Hidden;
else else
{ {
ModCreditsTab.Visibility = Visibility.Visible;
ModCredits.Document.Blocks.Clear(); ModCredits.Document.Blocks.Clear();
parahraph = new Paragraph(); paragraph = new Paragraph();
foreach (KeyValuePair<String, List<String>> credit in mod.Credits) foreach (KeyValuePair<String, List<String>> credit in mod.Credits)
{ {
parahraph.Inlines.Add(new Bold(new Run(credit.Key + "\n"))); paragraph.Inlines.Add(new Bold(new Run(credit.Key + "\n")));
foreach (String autor in credit.Value) foreach (String autor in credit.Value)
parahraph.Inlines.Add(new Run(autor + "\n")); paragraph.Inlines.Add(new Run(autor + "\n"));
parahraph.Inlines.Add(new Run("\n")); paragraph.Inlines.Add(new Run("\n"));
} }
ModCredits.Document.Blocks.Add(parahraph); ModCredits.Document.Blocks.Add(paragraph);
} }
} }
@ -132,6 +140,8 @@ namespace ScrapModLoader
throw new NullReferenceException(nameof(isChecked)); throw new NullReferenceException(nameof(isChecked));
StackPanel parent = (StackPanel)checkbox.Parent; StackPanel parent = (StackPanel)checkbox.Parent;
// TODO: replace by find template
// https://docs.microsoft.com/ru-ru/dotnet/desktop/wpf/data/how-to-find-datatemplate-generated-elements?view=netframeworkdesktop-4.8
Label label = (Label)parent.Children[2]; Label label = (Label)parent.Children[2];
String? selectedModName = label.Content.ToString(); String? selectedModName = label.Content.ToString();
@ -148,11 +158,16 @@ namespace ScrapModLoader
private void ButtonSettings_Click(Object sender, RoutedEventArgs e) private void ButtonSettings_Click(Object sender, RoutedEventArgs e)
{ {
SettingsWindow settingsWindow = new SettingsWindow(); SettingsWindow settingsWindow = new SettingsWindow(modsLauncher);
settingsWindow.ShowDialog(); settingsWindow.ShowDialog();
if (settingsWindow.Save) if (settingsWindow.Save)
modsLauncher.ScanMods(); modsLauncher.ScanMods();
ModsList.Items.Refresh(); ModsList.Items.Refresh();
OriginalVersionItem.IsEnabled = modsLauncher.ScraplandPath != String.Empty;
RemasteredVersionItem.IsEnabled = modsLauncher.ScraplandRemasteredPath != String.Empty;
ScraplandVersion.SelectedIndex = modsLauncher.ScraplandRemasteredPath != String.Empty ? 1 : 0;
} }
private void ButtonRunScrapland_Click(Object sender, RoutedEventArgs e) private void ButtonRunScrapland_Click(Object sender, RoutedEventArgs e)
@ -166,7 +181,7 @@ namespace ScrapModLoader
String gamePath = modsLauncher.SelectedGameVersion == "1.0" ? String gamePath = modsLauncher.SelectedGameVersion == "1.0" ?
modsLauncher.ScraplandPath : modsLauncher.ScraplandRemasteredPath; modsLauncher.ScraplandPath : modsLauncher.ScraplandRemasteredPath;
Process.Start(gamePath + @"\bin\Scrap.exe", args); Process.Start(Path.Combine(gamePath, @"bin\Scrap.exe"), args);
if (CloseLauncher.IsChecked ?? false) if (CloseLauncher.IsChecked ?? false)
Close(); Close();

View file

@ -1,6 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using Ionic.Zip;
using Microsoft.Win32; using Microsoft.Win32;
@ -8,17 +12,17 @@ namespace ScrapModLoader
{ {
public class ModsLauncher public class ModsLauncher
{ {
public List<ScrapMod> Mods { get; private set; } public List<ScrapMod> Mods { get; private set; } = new List<ScrapMod>();
public String ScraplandPath { get; set; } public List<String> ModsPathes { get; set; } = Utils.StringCollectionToList(Settings.Default.ModsPathes);
public String ScraplandRemasteredPath { get; set; } public String ScraplandPath { get; set; } = Settings.Default.ScraplandPath;
public String SelectedGameVersion { get; set; } public String ScraplandRemasteredPath { get; set; } = Settings.Default.ScraplandRemasteredPath;
public String SelectedGameVersion { get; set; } = "0.0";
public String LauncherVersion { get; set; } = "0.3";
public String SelectedGamePath { get; set; } = String.Empty;
public ModsLauncher() public ModsLauncher()
{ {
Mods = new List<ScrapMod>();
ScraplandPath = Settings.Default.ScraplandPath;
ScraplandRemasteredPath = Settings.Default.ScraplandRemasteredPath;
SelectedGameVersion = "0.0";
} }
public void ScanMods() public void ScanMods()
@ -30,7 +34,7 @@ namespace ScrapModLoader
{ {
String[] files = Directory.GetFiles(folder, "*.sm", SearchOption.AllDirectories); String[] files = Directory.GetFiles(folder, "*.sm", SearchOption.AllDirectories);
foreach (String file in files) foreach (String file in files)
Mods.Add(new ScrapMod(file)); Mods.Add(ScrapMod.LoadFromFile(file));
} }
} }
} }
@ -51,26 +55,32 @@ namespace ScrapModLoader
if (key == null) if (key == null)
continue; continue;
foreach (String subkey_name in key.GetSubKeyNames()) foreach (String subKeyName in key.GetSubKeyNames())
{ {
using RegistryKey? subkey = key.OpenSubKey(subkey_name); using RegistryKey? subKey = key.OpenSubKey(subKeyName);
if (subkey == null) if (subKey == null)
continue; continue;
String? displayName = subkey.GetValue("DisplayName")?.ToString(); String? displayName = subKey.GetValue("DisplayName")?.ToString();
if (displayName == null) if (displayName == null)
continue; continue;
if (displayName == "Scrapland") if (displayName == "Scrapland" || displayName == "American McGee presents Scrapland")
{ {
ScraplandPath = subkey.GetValue("InstallLocation")?.ToString() ?? ""; ScraplandPath = subKey.GetValue("InstallLocation")?.ToString() ?? String.Empty;
if (String.IsNullOrEmpty(ScraplandPath))
throw new KeyNotFoundException("Installed Scrapland found, but unable to locate the instalation folder");
Settings.Default.ScraplandPath = ScraplandPath; Settings.Default.ScraplandPath = ScraplandPath;
isFound = true; isFound = true;
} }
if (displayName == "Scrapland Remastered") if (displayName == "Scrapland Remastered")
{ {
ScraplandRemasteredPath = subkey.GetValue("InstallLocation")?.ToString() ?? ""; ScraplandRemasteredPath = subKey.GetValue("InstallLocation")?.ToString() ?? String.Empty;
if (String.IsNullOrEmpty(ScraplandRemasteredPath))
throw new KeyNotFoundException("Installed Scrapland Remastered found, but unable to locate the instalation folder");
Settings.Default.ScraplandRemasteredPath = ScraplandRemasteredPath; Settings.Default.ScraplandRemasteredPath = ScraplandRemasteredPath;
isFound = true; isFound = true;
} }
@ -82,22 +92,76 @@ namespace ScrapModLoader
public void LoadMods() public void LoadMods()
{ {
String gamePath = SelectedGameVersion == "1.0" ? ScraplandPath : ScraplandRemasteredPath; SelectedGamePath = SelectedGameVersion == "1.0" ? ScraplandPath : ScraplandRemasteredPath;
foreach (ScrapMod mod in Mods) foreach (ScrapMod mod in Mods)
{ {
if (mod.RequiredGame != SelectedGameVersion) // TODO: Warning about not loading mods that not supports selected version
if (!mod.SupportedGameVersions.Contains(SelectedGameVersion) ||
Single.Parse(mod.RequiredLauncher, CultureInfo.InvariantCulture) < Single.Parse(LauncherVersion, CultureInfo.InvariantCulture))
continue; continue;
if (mod.Checked) if (mod.Checked)
{ {
if (!mod.IsEnabled(gamePath)) if (!IsEnabled(mod))
mod.Enable(gamePath); Enable(mod);
} }
else else
{ {
if (mod.IsEnabled(gamePath)) if (IsEnabled(mod))
mod.Disable(gamePath); Disable(mod);
}
}
}
private String ModPath(ScrapMod mod) =>
Path.Combine(SelectedGamePath, "Mods", mod.Name);
public Boolean IsLoaded(ScrapMod mod) =>
Directory.Exists(ModPath(mod));
public Boolean IsEnabled(ScrapMod mod)
{
if (!IsLoaded(mod))
return false;
return Directory.EnumerateFiles(ModPath(mod), "*.disabled", SearchOption.AllDirectories).FirstOrDefault() == null;
}
public void Enable(ScrapMod mod)
{
if (!IsLoaded(mod))
LoadModToGame(mod);
if (IsEnabled(mod))
return;
foreach (String file in Directory.EnumerateFiles(ModPath(mod), "*.disabled", SearchOption.AllDirectories))
File.Move(file, Path.ChangeExtension(file, null));
}
public void Disable(ScrapMod mod)
{
if (!IsEnabled(mod))
return;
foreach (String file in Directory.EnumerateFiles(ModPath(mod), "*.packed", SearchOption.AllDirectories))
File.Move(file, file + ".disabled");
}
private void LoadModToGame(ScrapMod mod)
{
Directory.CreateDirectory(ModPath(mod));
using (ZipFile zipFile = ZipFile.Read(mod.ModPath))
{
foreach (ZipEntry zipEntry in zipFile)
{
if (!Path.GetFullPath(zipEntry.FileName).Contains(SelectedGameVersion))
continue;
if (Path.GetExtension(zipEntry.FileName) == ".packed")
zipEntry.Extract(ModPath(mod));
} }
} }
} }

View file

@ -2,199 +2,113 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Xml;
using Ionic.Zip; using Ionic.Zip;
using Tommy;
namespace ScrapModLoader namespace ScrapModLoader
{ {
public class ScrapMod public class ScrapMod
{ {
public String Name { get; private set; } public String Name { get; private set; } = String.Empty;
public String Description { get; private set; }
public String ModPath { get; private set; }
public BitmapImage Icon { get; private set; }
public Boolean Checked { get; set; }
public String Category { get; private set; }
public String Version { get; private set; }
public String RequiredLauncher { get; private set; }
public String RequiredGame { get; private set; }
public List<String> Authors { get; private set; }
public Dictionary<String, List<String>> Credits { get; private set; }
public ScrapMod(String path) public String Description { get; private set; } = String.Empty;
public String ModPath { get; private set; } = String.Empty;
public BitmapImage Icon { get; private set; } = new BitmapImage();
public Boolean Checked { get; set; } = false;
public String Category { get; private set; } = String.Empty;
public String Version { get; private set; } = String.Empty;
public String RequiredLauncher { get; private set; } = String.Empty;
public List<String> SupportedGameVersions { get; private set; } = new List<String>();
public String SupportedGameVersionsDisplay => String.Join(", ", SupportedGameVersions);
public List<String> Authors { get; private set; } = new List<String>();
public Dictionary<String, List<String>> Credits { get; private set; } = new Dictionary<String, List<String>>();
private ScrapMod()
{ {
ModPath = path;
Name = Path.GetFileNameWithoutExtension(path);
Description = String.Empty;
Icon = new BitmapImage();
Checked = false;
Category = String.Empty;
Version = String.Empty;
RequiredLauncher = String.Empty;
RequiredGame = String.Empty;
Authors = new List<String>();
Credits = new Dictionary<String, List<String>>();
LoadFromFile(path);
} }
public static ScrapMod LoadFromFile(String path)
{
using ZipFile zipFile = ZipFile.Read(path);
public Boolean IsLoaded(String gamePath) => Directory.Exists(gamePath + @"Mods\" + Name); Byte[] iconBuffer = Utils.ExtractFromZip(zipFile, "icon.png");
Byte[] confBuffer = Utils.ExtractFromZip(zipFile, "config.toml");
public Boolean IsEnabled(String gamePath) ScrapMod mod = new ScrapMod()
{ {
if (IsLoaded(gamePath)) ModPath = path,
{ Icon = Utils.LoadImage(iconBuffer)
foreach (String file in Directory.EnumerateFiles(gamePath + @"Mods\" + Name)) };
{
if (Path.GetExtension(file) == ".disabled") LoadConfig(mod, confBuffer);
return false;
return mod;
} }
return true; private static void LoadConfig(ScrapMod mod, Byte[] buffer)
} {
return false; using MemoryStream sourceStream = new MemoryStream(buffer);
} using StreamReader reader = new StreamReader(sourceStream);
public void Enable(String gamePath) TomlTable config = TOML.Parse(reader);
{
if (!IsLoaded(gamePath))
LoadModToGame(gamePath);
if (IsEnabled(gamePath)) CheckConfig(config);
return;
foreach (String file in Directory.EnumerateFiles(gamePath + @"Mods\" + Name)) mod.Name = config["title"];
{ mod.Description = config["description"];
if (Path.GetExtension(file) == ".disabled") mod.Category = config["category"];
File.Move(file, Path.ChangeExtension(file, null)); mod.Version = config["version"];
} mod.RequiredLauncher = config["requiredLauncher"];
}
public void Disable(String gamePath) foreach (TomlNode version in config["supportedGameVersions"])
{ mod.SupportedGameVersions.Add(version);
if (!IsEnabled(gamePath))
return;
foreach (String file in Directory.EnumerateFiles(gamePath + @"Mods\" + Name)) foreach (TomlNode author in config["authors"])
{ mod.Authors.Add(author["name"]);
if (Path.GetExtension(file) == ".packed")
File.Move(file, file + ".disabled");
}
}
private void LoadModToGame(String gamePath) foreach (TomlNode credit in config["credits"])
{
gamePath += @"Mods\" + Name;
Directory.CreateDirectory(gamePath);
using (ZipFile zipFile = ZipFile.Read(ModPath))
{
foreach (ZipEntry zipEntry in zipFile)
{
if (Path.GetExtension(zipEntry.FileName) == ".packed")
{
zipEntry.Extract(gamePath);
}
}
}
}
private void LoadFromFile(String path)
{
using (ZipFile zipFile = ZipFile.Read(path))
{
Byte[] iconBuffer = ExtractFromZip(zipFile, "icon.png");
Byte[] confBuffer = ExtractFromZip(zipFile, "config.xml");
LoadIcon(iconBuffer);
LoadConfig(confBuffer);
}
}
private Byte[] ExtractFromZip(ZipFile zip, String entry_path)
{
ZipEntry? entry = zip[entry_path];
if (entry == null)
throw new FileFormatException($"No '{entry}' in {Name} found");
Byte[] buffer = new Byte[entry.UncompressedSize];
using (MemoryStream zipStream = new MemoryStream(buffer))
entry.Extract(zipStream);
return buffer;
}
private void LoadIcon(Byte[] buffer)
{
using (MemoryStream sourceStream = new MemoryStream(buffer))
{
Icon.BeginInit();
Icon.CacheOption = BitmapCacheOption.OnLoad;
Icon.StreamSource = sourceStream;
Icon.EndInit();
}
}
private void LoadConfig(Byte[] buffer)
{
using (MemoryStream sourceStream = new MemoryStream(buffer))
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(sourceStream);
ParseModInfoFromXml(xmlDocument);
ParseAuthorsFromXml(xmlDocument);
ParseCreditsFromXml(xmlDocument);
}
}
private void ParseModInfoFromXml(XmlDocument xmlDocument)
{
Description = xmlDocument.GetElementsByTagName("Description").Item(0)?.InnerText ??
throw new FileFormatException("No 'Description' tag in 'config.xml'");
Category = xmlDocument.GetElementsByTagName("Category").Item(0)?.InnerText ??
throw new FileFormatException("No 'Category' tag in 'config.xml'");
Version = xmlDocument.GetElementsByTagName("Version").Item(0)?.InnerText ??
throw new FileFormatException("No 'Version' tag in 'config.xml'");
RequiredLauncher = xmlDocument.GetElementsByTagName("RequiredLauncher").Item(0)?.InnerText ??
throw new FileFormatException("No 'RequiredLauncher' tag in 'config.xml'");
RequiredGame = xmlDocument.GetElementsByTagName("RequiredGame").Item(0)?.InnerText ??
throw new FileFormatException("No 'RequiredGame' tag in 'config.xml'");
}
private void ParseAuthorsFromXml(XmlDocument xmlDocument)
{
XmlNodeList authors = xmlDocument.GetElementsByTagName("Author");
foreach (XmlNode author in authors)
{
XmlAttribute? nameAttr = author.Attributes?["name"];
if (nameAttr == null)
throw new FileFormatException("No 'name' attribute in 'Author' tag in 'config.xml'");
Authors.Add(nameAttr.InnerText);
}
}
private void ParseCreditsFromXml(XmlDocument xmlDocument)
{
XmlNodeList credits = xmlDocument.GetElementsByTagName("Credits");
foreach (XmlNode credit in credits)
{ {
List<String> entries = new List<String>(); List<String> entries = new List<String>();
XmlAttribute? groupAttr = credit.Attributes?["group"]; foreach (TomlNode entry in credit["credits"])
if (groupAttr == null) entries.Add(entry["name"]);
throw new FileFormatException("No 'group' attribute in 'Credits' tag in 'config.xml'");
foreach (XmlNode entry in credit) mod.Credits.Add(credit["group"], entries);
}
}
private static void CheckConfig(TomlTable config)
{ {
XmlAttribute? nameAttr = entry.Attributes?["name"]; if (!config.HasKey("title"))
if (nameAttr == null) throw new FileFormatException("No 'title' key in 'config.toml'");
throw new FileFormatException("No 'name' attribute in 'Author' tag in 'config.xml'");
entries.Add(nameAttr.InnerText); if (!config.HasKey("description"))
} throw new FileFormatException("No 'description' key in 'config.toml'");
Credits.Add(groupAttr.InnerText, entries); if (!config.HasKey("category"))
} throw new FileFormatException("No 'category' key in 'config.toml'");
if (!config.HasKey("version"))
throw new FileFormatException("No 'version' key in 'config.toml'");
if (!config.HasKey("requiredLauncher"))
throw new FileFormatException("No 'name' key in 'config.toml'");
if (!config.HasKey("supportedGameVersions"))
throw new FileFormatException("No 'supportedGameVersions' key in 'config.toml'");
} }
} }
} }

View file

@ -16,6 +16,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DotNetZip" Version="1.16.0" /> <PackageReference Include="DotNetZip" Version="1.16.0" />
<PackageReference Include="Tommy" Version="3.1.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -63,7 +63,7 @@
<TextBox Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" x:Name="ScraplandPathTextBox" IsEnabled="False" /> <TextBox Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" x:Name="ScraplandPathTextBox" IsEnabled="False" />
<StackPanel Grid.Row="0" Grid.Column="2" Orientation="Horizontal"> <StackPanel Grid.Row="0" Grid.Column="2" Orientation="Horizontal">
<Button Content=" Browse... " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonBrowseScrap" Click="ButtonBrowseScrap_Click"/> <Button Content=" Browse... " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonBrowseScrap" Click="ButtonBrowseScrap_Click"/>
<Button Content=" Clear " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonClearScrap" Click="ButtonClearScrap_Click"/> <Button Content=" Clear " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonClearScrap" Click="ButtonClearScrap_Click" IsEnabled="False"/>
<Button Content=" Show In Explorer " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonShowScrap" IsEnabled="False" Click="ButtonShowScrap_Click" /> <Button Content=" Show In Explorer " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonShowScrap" IsEnabled="False" Click="ButtonShowScrap_Click" />
</StackPanel> </StackPanel>
@ -71,9 +71,10 @@
<TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" x:Name="ScraplandRemasteredPathTextBox" IsEnabled="False" /> <TextBox Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" x:Name="ScraplandRemasteredPathTextBox" IsEnabled="False" />
<StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal"> <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
<Button Content=" Browse... " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonBrowseScrapRemaster" Click="ButtonBrowseScrapRemaster_Click"/> <Button Content=" Browse... " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonBrowseScrapRemaster" Click="ButtonBrowseScrapRemaster_Click"/>
<Button Content=" Clear " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonClearScrapRemaster" Click="ButtonClearScrapRemaster_Click"/> <Button Content=" Clear " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonClearScrapRemaster" Click="ButtonClearScrapRemaster_Click" IsEnabled="False"/>
<Button Content=" Show In Explorer " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonShowScrapRemaster" IsEnabled="False" Click="ButtonShowScrapRemaster_Click" /> <Button Content=" Show In Explorer " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonShowScrapRemaster" IsEnabled="False" Click="ButtonShowScrapRemaster_Click" />
</StackPanel> </StackPanel>
<Button Grid.Column="2" Content=" Auto find " HorizontalAlignment="Right" Grid.Row="2" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonAutoFind" Click="ButtonAutoFind_Click" />
</Grid> </Grid>
</GroupBox> </GroupBox>
</Grid> </Grid>

View file

@ -12,27 +12,28 @@ namespace ScrapModLoader
/// </summary> /// </summary>
public partial class SettingsWindow : Window public partial class SettingsWindow : Window
{ {
public List<String> ModsPathes { get; set; }
public String ScraplandPath { get; set; }
public String ScraplandRemasteredPath { get; set; }
public Boolean Save { get; set; }
public SettingsWindow() public Boolean Save { get; set; }
private ModsLauncher ModsLauncherInstance { get; set; }
public SettingsWindow(ModsLauncher modsLauncher)
{ {
InitializeComponent(); InitializeComponent();
Save = false; Save = false;
ModsLauncherInstance = modsLauncher;
ModsPathes = Utils.StringCollectionToList(Settings.Default.ModsPathes); ModsPathesList.ItemsSource = ModsLauncherInstance.ModsPathes;
ScraplandPath = Settings.Default.ScraplandPath; ScraplandPathTextBox.Text = ModsLauncherInstance.ScraplandPath;
ScraplandRemasteredPath = Settings.Default.ScraplandRemasteredPath; ScraplandRemasteredPathTextBox.Text = ModsLauncherInstance.ScraplandRemasteredPath;
ModsPathesList.ItemsSource = ModsPathes; Boolean enable = ModsLauncherInstance.ScraplandPath != String.Empty;
ScraplandPathTextBox.Text = ScraplandPath; ButtonShowScrap.IsEnabled = enable;
ScraplandRemasteredPathTextBox.Text = ScraplandRemasteredPath; ButtonClearScrap.IsEnabled = enable;
ButtonShowScrap.IsEnabled = ScraplandPath != String.Empty; enable = ModsLauncherInstance.ScraplandRemasteredPath != String.Empty;
ButtonShowScrapRemaster.IsEnabled = ScraplandRemasteredPath != String.Empty; ButtonShowScrapRemaster.IsEnabled = enable;
ButtonClearScrapRemaster.IsEnabled = enable;
} }
// ------------------------------------------- // -------------------------------------------
@ -44,13 +45,13 @@ namespace ScrapModLoader
{ {
String folder = Utils.GetFolderDialog(); String folder = Utils.GetFolderDialog();
if (folder != String.Empty) if (folder != String.Empty)
ModsPathes.Add(folder); ModsLauncherInstance.ModsPathes.Add(folder);
ModsPathesList.Items.Refresh(); ModsPathesList.Items.Refresh();
} }
private void ButtonRemove_Click(Object sender, RoutedEventArgs e) private void ButtonRemove_Click(Object sender, RoutedEventArgs e)
{ {
ModsPathes.Remove((String)ModsPathesList.SelectedValue); ModsLauncherInstance.ModsPathes.Remove((String)ModsPathesList.SelectedValue);
ModsPathesList.Items.Refresh(); ModsPathesList.Items.Refresh();
ButtonRemove.IsEnabled = false; ButtonRemove.IsEnabled = false;
@ -64,21 +65,21 @@ namespace ScrapModLoader
if (index == 0) if (index == 0)
return; return;
String? temp = ModsPathes[index - 1]; String? temp = ModsLauncherInstance.ModsPathes[index - 1];
ModsPathes[index - 1] = ModsPathes[index]; ModsLauncherInstance.ModsPathes[index - 1] = ModsLauncherInstance.ModsPathes[index];
ModsPathes[index] = temp; ModsLauncherInstance.ModsPathes[index] = temp;
ModsPathesList.Items.Refresh(); ModsPathesList.Items.Refresh();
} }
private void ButtonDown_Click(Object sender, RoutedEventArgs e) private void ButtonDown_Click(Object sender, RoutedEventArgs e)
{ {
Int32 index = ModsPathesList.SelectedIndex; Int32 index = ModsPathesList.SelectedIndex;
if (index == ModsPathes.Count - 1) if (index == ModsLauncherInstance.ModsPathes.Count - 1)
return; return;
String? temp = ModsPathes[index + 1]; String? temp = ModsLauncherInstance.ModsPathes[index + 1];
ModsPathes[index + 1] = ModsPathes[index]; ModsLauncherInstance.ModsPathes[index + 1] = ModsLauncherInstance.ModsPathes[index];
ModsPathes[index] = temp; ModsLauncherInstance.ModsPathes[index] = temp;
ModsPathesList.Items.Refresh(); ModsPathesList.Items.Refresh();
} }
@ -108,50 +109,76 @@ namespace ScrapModLoader
private void ButtonBrowseScrap_Click(Object sender, RoutedEventArgs e) private void ButtonBrowseScrap_Click(Object sender, RoutedEventArgs e)
{ {
String scraplandPath = Utils.GetFolderDialog(); String ScraplandPath = Utils.GetFolderDialog();
if (scraplandPath != String.Empty) if (ScraplandPath != String.Empty)
{ {
ScraplandPathTextBox.Text = scraplandPath; ScraplandPathTextBox.Text = ScraplandPath + "\\";
ScraplandPath = scraplandPath; ModsLauncherInstance.ScraplandPath = ScraplandPath + "\\";
ButtonShowScrap.IsEnabled = true; ButtonShowScrap.IsEnabled = true;
} }
} }
private void ButtonBrowseScrapRemaster_Click(Object sender, RoutedEventArgs e) private void ButtonBrowseScrapRemaster_Click(Object sender, RoutedEventArgs e)
{ {
String scraplandRemasteredPath = Utils.GetFolderDialog(); String ScraplandRemasteredPath = Utils.GetFolderDialog();
if (scraplandRemasteredPath != String.Empty) if (ScraplandRemasteredPath != String.Empty)
{ {
ScraplandRemasteredPathTextBox.Text = scraplandRemasteredPath; ScraplandRemasteredPathTextBox.Text = ScraplandRemasteredPath + "\\";
ScraplandRemasteredPath = scraplandRemasteredPath; ModsLauncherInstance.ScraplandRemasteredPath = ScraplandRemasteredPath + "\\";
ButtonShowScrapRemaster.IsEnabled = true; ButtonShowScrapRemaster.IsEnabled = true;
} }
} }
private void ButtonClearScrap_Click(Object sender, RoutedEventArgs e) private void ButtonClearScrap_Click(Object sender, RoutedEventArgs e)
{ {
ScraplandPathTextBox.Text = String.Empty; ScraplandPathTextBox.Text = String.Empty;
ButtonClearScrap.IsEnabled = false;
ButtonShowScrap.IsEnabled = false; ButtonShowScrap.IsEnabled = false;
ScraplandPath = String.Empty; ModsLauncherInstance.ScraplandPath = String.Empty;
} }
private void ButtonClearScrapRemaster_Click(Object sender, RoutedEventArgs e) private void ButtonClearScrapRemaster_Click(Object sender, RoutedEventArgs e)
{ {
ScraplandRemasteredPathTextBox.Text = String.Empty; ScraplandRemasteredPathTextBox.Text = String.Empty;
ButtonClearScrapRemaster.IsEnabled = false; ButtonClearScrapRemaster.IsEnabled = false;
ScraplandRemasteredPath = String.Empty; ButtonShowScrapRemaster.IsEnabled = false;
ModsLauncherInstance.ScraplandRemasteredPath = String.Empty;
} }
private void ButtonShowScrap_Click(Object sender, RoutedEventArgs e) private void ButtonShowScrap_Click(Object sender, RoutedEventArgs e)
{ {
String? path = Path.GetDirectoryName(ScraplandPath); String? path = Path.GetDirectoryName(ModsLauncherInstance.ScraplandPath);
if (path == null) if (path == null)
throw new DirectoryNotFoundException("Cannot find direcotry for Scrapland"); throw new DirectoryNotFoundException("Cannot find direcotry for Scrapland");
Process.Start("explorer.exe", path); Process.Start("explorer.exe", path);
} }
private void ButtonShowScrapRemaster_Click(Object sender, RoutedEventArgs e) private void ButtonShowScrapRemaster_Click(Object sender, RoutedEventArgs e)
{ {
String? path = Path.GetDirectoryName(ScraplandRemasteredPath); String? path = Path.GetDirectoryName(ModsLauncherInstance.ScraplandRemasteredPath);
if (path == null) if (path == null)
throw new DirectoryNotFoundException("Cannot find direcotry for Scrapland"); throw new DirectoryNotFoundException("Cannot find direcotry for Scrapland");
Process.Start("explorer.exe", path); Process.Start("explorer.exe", path);
} }
private void ButtonAutoFind_Click(Object sender, RoutedEventArgs e)
{
try
{
Boolean isFoundScrapland = ModsLauncherInstance.SearchForScrapland();
if (!isFoundScrapland)
MessageBox.Show("Error: unable to find Scrapland instalation. Please, specify yours game installation folder in settings.");
}
catch (KeyNotFoundException ex)
{
MessageBox.Show(ex.Message);
}
ScraplandPathTextBox.Text = ModsLauncherInstance.ScraplandPath;
ScraplandRemasteredPathTextBox.Text = ModsLauncherInstance.ScraplandRemasteredPath;
Boolean enable = ModsLauncherInstance.ScraplandPath != String.Empty;
ButtonShowScrap.IsEnabled = enable;
ButtonClearScrap.IsEnabled = enable;
enable = ModsLauncherInstance.ScraplandRemasteredPath != String.Empty;
ButtonShowScrapRemaster.IsEnabled = enable;
ButtonClearScrapRemaster.IsEnabled = enable;
}
// ------------------------------------------- // -------------------------------------------
// Window contols buttons // Window contols buttons
@ -161,10 +188,11 @@ namespace ScrapModLoader
private void ButtonSave_Click(Object sender, RoutedEventArgs e) private void ButtonSave_Click(Object sender, RoutedEventArgs e)
{ {
Settings.Default.ModsPathes.Clear(); Settings.Default.ModsPathes.Clear();
Settings.Default.ModsPathes.AddRange(ModsPathes.ToArray()); Settings.Default.ModsPathes.AddRange(ModsLauncherInstance.ModsPathes.ToArray());
Settings.Default.ScraplandPath = ScraplandPath; Settings.Default.ScraplandPath = ModsLauncherInstance.ScraplandPath;
Settings.Default.ScraplandRemasteredPath = ScraplandRemasteredPath; Settings.Default.ScraplandRemasteredPath = ModsLauncherInstance.ScraplandRemasteredPath;
Settings.Default.Save(); Settings.Default.Save();
Save = true; Save = true;
Close(); Close();
} }

View file

@ -1,14 +1,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.IO;
using System.Windows.Media.Imaging;
namespace ScrapModLoader using Ionic.Zip;
namespace ScrapModLoader;
internal static class Utils
{ {
internal static class Utils
{
public static String GetFolderDialog() public static String GetFolderDialog()
{ {
using System.Windows.Forms.FolderBrowserDialog? dialog = new System.Windows.Forms.FolderBrowserDialog(); using System.Windows.Forms.FolderBrowserDialog dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog(); System.Windows.Forms.DialogResult result = dialog.ShowDialog();
return dialog.SelectedPath; return dialog.SelectedPath;
} }
@ -23,5 +28,32 @@ namespace ScrapModLoader
} }
return list; return list;
} }
public static Byte[] ExtractFromZip(ZipFile zip, String entry_path)
{
ZipEntry? entry = zip[entry_path];
if (entry == null)
throw new FileFormatException($"No '{entry_path}' in {zip.Name} found");
Byte[] buffer = new Byte[entry.UncompressedSize];
using (MemoryStream zipStream = new MemoryStream(buffer))
entry.Extract(zipStream);
return buffer;
}
public static BitmapImage LoadImage(Byte[] buffer)
{
using MemoryStream sourceStream = new MemoryStream(buffer);
BitmapImage? image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = sourceStream;
image.EndInit();
return image;
} }
} }