Compare commits

...

13 Commits

Author SHA1 Message Date
Strongleong 4f80b72603
Merge pull request #4 from Grumgog/refactor-modLauncher
refactor: modsLauncer and MainWindow
2022-02-16 19:54:54 +11:00
strongleong 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
strongleong 085320215a Removed unnecesarry stuff 2022-02-15 22:52:26 +11:00
strongleong 110029692d Fixed button 2022-02-15 22:52:11 +11:00
strongleong 8da74ffa6d Refactored methods that do filesystem stuff from `ScrapMod` to `ModsLauncher` 2022-02-15 22:51:37 +11:00
strongleong ca870eb66b Try-catched some exceptions 2022-02-15 22:50:56 +11:00
Strongleong 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
strongleong 9deb717eea Added check for minimal launcher version 2022-02-13 01:30:17 +11:00
strongleong 515aa519a4 Added support for different files for different versions in mod 2022-02-13 01:26:00 +11:00
strongleong 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.
@ -24,45 +24,68 @@ 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:
| Filename | Description |
|--------------------|----------------------------------------------|
| icon.png | Icon for mod that will show up in mod loader |
| config.xml | Information about mod |
| <filename\>.packed | Container with all mod game assets |
| Filename | Description |
|------------------------|--------------------------------------------------|
| icon.png | Icon for mod that will show up in mod loader |
| config.toml | Information about mod |
| <game_version\>\ | Folder that named as game version mod made for |
| <filename\>.packed | Container with all mod game assets |
### meta.ini sample
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ScrapMod>
<Title>Mod Title</Title>
<Description>Mod Desciption</Description>
You can have as many .packed files as you want. Mod loader will load everything.
<Category>Category</Category>
<Version>1.0</Version>
<RequiredLauncher>1.0</RequiredLauncher>
<RequiredGame>1.1</RequiredGame>
<Author name="Author1" website="https://example.com" />
<Author name="Author2" />
<Credits group="Mod author">
<Credit name="Author1" />
</Credits>
<Credits group="Some credit" >
<Credit name="Credit1" />
<Credit name="Credit2" />
<Credit name="Credit3" />
</Credits>
</ScrapMod>
.packed files in the root of mod will be copied to the `Mods` folder of Scrapland.
.pakced files under game version folder will load only to the appopriate game version.
### .sm structure sample
```
│ icon.png
│ config.toml
│ mod_assets.packed
├──1.0/
│ only_for_original.packed
└──1.1/
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:
- [X] Support for custom *.packed
- [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`)
- [ ] Recompiling *.py to *.pyc
- [ ] Mod settings.
- [ ] More meta info in `config.xml`
- [ ] More meta info in `config.toml`
- [ ] Multilanguage support
- [ ] More mods :wink:

View File

@ -18,7 +18,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="0*" />
<ColumnDefinition Name="PreviewColumn" Width="0*" />
</Grid.ColumnDefinitions>
<ListView d:ItemsSource="{d:SampleData ItemCount=5}" Name="ModsList" Initialized="ModsList_Initialized" MouseDown="ModsList_MouseDown">
<ListView.View>
@ -28,14 +28,15 @@
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" HorizontalAlignment="Left">
<CheckBox IsChecked="{Binding Checked}" VerticalAlignment="Center" HorizontalAlignment="Center" Checked="CheckBox_Checked" />
<Image Source="{Binding Icon}" Stretch="Fill" Width="16" Height="16" />
<Image Source="{Binding Icon}" Stretch="Fill" Width="16" Height="16" />
<Label Content="{Binding Name}" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Category" DisplayMemberBinding="{Binding Category}" />
<GridViewColumn Header="Mod Version" DisplayMemberBinding="{Binding Version}" />
<GridViewColumn Header="Game Version" DisplayMemberBinding="{Binding RequiredGame}" />
<GridViewColumn Header="Game Version" DisplayMemberBinding="{Binding SupportedGameVersionsDisplay}" />
</GridView>
</ListView.View>
<ListView.ItemContainerStyle>
@ -70,8 +71,8 @@
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Left">
<ComboBox Name="ScraplandVersion" Margin="0,0,10,0" SelectionChanged="ScraplandVersion_SelectionChanged">
<ComboBoxItem Content="Original" IsEnabled="False" />
<ComboBoxItem Content="Remastered" />
<ComboBoxItem Name="OriginalVersionItem" Content="Original" IsEnabled="False" />
<ComboBoxItem Name ="RemasteredVersionItem" Content="Remastered" />
</ComboBox>
<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" />

View File

@ -28,8 +28,7 @@ namespace ScrapModLoader
{
if (Settings.Default.ModsPathes.Count == 0)
{
String path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
+ Path.DirectorySeparatorChar + "Scrapland mods";
String path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Scrapland mods");
Settings.Default.ModsPathes.Add(path);
Directory.CreateDirectory(path);
}
@ -37,16 +36,24 @@ namespace ScrapModLoader
// TODO: Refactor it to separate window with pretty loading animation
if (modsLauncher.ScraplandPath == String.Empty && modsLauncher.ScraplandRemasteredPath == String.Empty)
{
Boolean isFoundScrapland = modsLauncher.SearchForScrapland();
if (!isFoundScrapland)
try
{
ButtonRunScrapland.IsEnabled = false;
MessageBox.Show("Error: unable to find Scrapland instalation. Please, specify yours game installation folder in settings.");
Boolean isFoundScrapland = modsLauncher.SearchForScrapland();
if (!isFoundScrapland)
{
ButtonRunScrapland.IsEnabled = false;
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;
((ComboBoxItem)ScraplandVersion.Items[1]).IsEnabled = modsLauncher.ScraplandRemasteredPath != String.Empty;
OriginalVersionItem.IsEnabled = modsLauncher.ScraplandPath != String.Empty;
RemasteredVersionItem.IsEnabled = modsLauncher.ScraplandRemasteredPath != String.Empty;
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_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;
MainGrid.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star);
gridLength = PreviewColumn.Width;
PreviewColumn.Width = new GridLength(0, GridUnitType.Star);
}
}
private void ListViewItem_PreviewMouseLeftButtonDown(Object sender, MouseButtonEventArgs e)
{
MainGrid.ColumnDefinitions[2].Width = gridLength;
PreviewColumn.Width = gridLength;
if (sender is ListViewItem item)
{
@ -74,7 +81,7 @@ namespace ScrapModLoader
if (selectedModName == null)
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)
throw new KeyNotFoundException(nameof(mod));
@ -92,34 +99,35 @@ namespace ScrapModLoader
private void WriteModInfo(ScrapMod mod)
{
ModInfo.Document.Blocks.Clear();
Paragraph parahraph = new Paragraph();
Paragraph paragraph = new Paragraph();
parahraph.Inlines.Add(new Bold(new Run("Description:\n")));
parahraph.Inlines.Add(new Run(mod.Description));
paragraph.Inlines.Add(new Bold(new Run("Description:\n")));
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)
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)
ModCreditsTab.Visibility = Visibility.Hidden;
else
{
ModCreditsTab.Visibility = Visibility.Visible;
ModCredits.Document.Blocks.Clear();
parahraph = new Paragraph();
paragraph = new Paragraph();
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)
parahraph.Inlines.Add(new Run(autor + "\n"));
parahraph.Inlines.Add(new Run("\n"));
paragraph.Inlines.Add(new Run(autor + "\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));
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];
String? selectedModName = label.Content.ToString();
@ -148,11 +158,16 @@ namespace ScrapModLoader
private void ButtonSettings_Click(Object sender, RoutedEventArgs e)
{
SettingsWindow settingsWindow = new SettingsWindow();
SettingsWindow settingsWindow = new SettingsWindow(modsLauncher);
settingsWindow.ShowDialog();
if (settingsWindow.Save)
modsLauncher.ScanMods();
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)
@ -166,7 +181,7 @@ namespace ScrapModLoader
String gamePath = modsLauncher.SelectedGameVersion == "1.0" ?
modsLauncher.ScraplandPath : modsLauncher.ScraplandRemasteredPath;
Process.Start(gamePath + @"\bin\Scrap.exe", args);
Process.Start(Path.Combine(gamePath, @"bin\Scrap.exe"), args);
if (CloseLauncher.IsChecked ?? false)
Close();

View File

@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Ionic.Zip;
using Microsoft.Win32;
@ -8,17 +12,17 @@ namespace ScrapModLoader
{
public class ModsLauncher
{
public List<ScrapMod> Mods { get; private set; }
public String ScraplandPath { get; set; }
public String ScraplandRemasteredPath { get; set; }
public String SelectedGameVersion { get; set; }
public List<ScrapMod> Mods { get; private set; } = new List<ScrapMod>();
public List<String> ModsPathes { get; set; } = Utils.StringCollectionToList(Settings.Default.ModsPathes);
public String ScraplandPath { get; set; } = Settings.Default.ScraplandPath;
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()
{
Mods = new List<ScrapMod>();
ScraplandPath = Settings.Default.ScraplandPath;
ScraplandRemasteredPath = Settings.Default.ScraplandRemasteredPath;
SelectedGameVersion = "0.0";
}
public void ScanMods()
@ -30,7 +34,7 @@ namespace ScrapModLoader
{
String[] files = Directory.GetFiles(folder, "*.sm", SearchOption.AllDirectories);
foreach (String file in files)
Mods.Add(new ScrapMod(file));
Mods.Add(ScrapMod.LoadFromFile(file));
}
}
}
@ -51,26 +55,32 @@ namespace ScrapModLoader
if (key == null)
continue;
foreach (String subkey_name in key.GetSubKeyNames())
foreach (String subKeyName in key.GetSubKeyNames())
{
using RegistryKey? subkey = key.OpenSubKey(subkey_name);
if (subkey == null)
using RegistryKey? subKey = key.OpenSubKey(subKeyName);
if (subKey == null)
continue;
String? displayName = subkey.GetValue("DisplayName")?.ToString();
String? displayName = subKey.GetValue("DisplayName")?.ToString();
if (displayName == null)
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;
isFound = true;
}
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;
isFound = true;
}
@ -82,22 +92,76 @@ namespace ScrapModLoader
public void LoadMods()
{
String gamePath = SelectedGameVersion == "1.0" ? ScraplandPath : ScraplandRemasteredPath;
SelectedGamePath = SelectedGameVersion == "1.0" ? ScraplandPath : ScraplandRemasteredPath;
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;
if (mod.Checked)
{
if (!mod.IsEnabled(gamePath))
mod.Enable(gamePath);
if (!IsEnabled(mod))
Enable(mod);
}
else
{
if (mod.IsEnabled(gamePath))
mod.Disable(gamePath);
if (IsEnabled(mod))
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.IO;
using System.Windows.Media.Imaging;
using System.Xml;
using Ionic.Zip;
using Tommy;
namespace ScrapModLoader
{
public class ScrapMod
{
public String Name { get; private set; }
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 String Name { get; private set; } = String.Empty;
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 Boolean IsLoaded(String gamePath) => Directory.Exists(gamePath + @"Mods\" + Name);
public Boolean IsEnabled(String gamePath)
public static ScrapMod LoadFromFile(String path)
{
if (IsLoaded(gamePath))
using ZipFile zipFile = ZipFile.Read(path);
Byte[] iconBuffer = Utils.ExtractFromZip(zipFile, "icon.png");
Byte[] confBuffer = Utils.ExtractFromZip(zipFile, "config.toml");
ScrapMod mod = new ScrapMod()
{
foreach (String file in Directory.EnumerateFiles(gamePath + @"Mods\" + Name))
{
if (Path.GetExtension(file) == ".disabled")
return false;
}
ModPath = path,
Icon = Utils.LoadImage(iconBuffer)
};
return true;
}
return false;
LoadConfig(mod, confBuffer);
return mod;
}
public void Enable(String gamePath)
private static void LoadConfig(ScrapMod mod, Byte[] buffer)
{
if (!IsLoaded(gamePath))
LoadModToGame(gamePath);
using MemoryStream sourceStream = new MemoryStream(buffer);
using StreamReader reader = new StreamReader(sourceStream);
if (IsEnabled(gamePath))
return;
TomlTable config = TOML.Parse(reader);
foreach (String file in Directory.EnumerateFiles(gamePath + @"Mods\" + Name))
{
if (Path.GetExtension(file) == ".disabled")
File.Move(file, Path.ChangeExtension(file, null));
}
}
CheckConfig(config);
public void Disable(String gamePath)
{
if (!IsEnabled(gamePath))
return;
mod.Name = config["title"];
mod.Description = config["description"];
mod.Category = config["category"];
mod.Version = config["version"];
mod.RequiredLauncher = config["requiredLauncher"];
foreach (String file in Directory.EnumerateFiles(gamePath + @"Mods\" + Name))
{
if (Path.GetExtension(file) == ".packed")
File.Move(file, file + ".disabled");
}
}
foreach (TomlNode version in config["supportedGameVersions"])
mod.SupportedGameVersions.Add(version);
private void LoadModToGame(String gamePath)
{
gamePath += @"Mods\" + Name;
Directory.CreateDirectory(gamePath);
foreach (TomlNode author in config["authors"])
mod.Authors.Add(author["name"]);
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)
foreach (TomlNode credit in config["credits"])
{
List<String> entries = new List<String>();
XmlAttribute? groupAttr = credit.Attributes?["group"];
if (groupAttr == null)
throw new FileFormatException("No 'group' attribute in 'Credits' tag in 'config.xml'");
foreach (TomlNode entry in credit["credits"])
entries.Add(entry["name"]);
foreach (XmlNode entry in credit)
{
XmlAttribute? nameAttr = entry.Attributes?["name"];
if (nameAttr == null)
throw new FileFormatException("No 'name' attribute in 'Author' tag in 'config.xml'");
entries.Add(nameAttr.InnerText);
}
Credits.Add(groupAttr.InnerText, entries);
mod.Credits.Add(credit["group"], entries);
}
}
private static void CheckConfig(TomlTable config)
{
if (!config.HasKey("title"))
throw new FileFormatException("No 'title' key in 'config.toml'");
if (!config.HasKey("description"))
throw new FileFormatException("No 'description' key in 'config.toml'");
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>
<PackageReference Include="DotNetZip" Version="1.16.0" />
<PackageReference Include="Tommy" Version="3.1.2" />
</ItemGroup>
<ItemGroup>

View File

@ -63,7 +63,7 @@
<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">
<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" />
</StackPanel>
@ -71,9 +71,10 @@
<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">
<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" />
</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>
</GroupBox>
</Grid>

View File

@ -12,27 +12,28 @@ namespace ScrapModLoader
/// </summary>
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; }
private ModsLauncher ModsLauncherInstance { get; set; }
public SettingsWindow()
public SettingsWindow(ModsLauncher modsLauncher)
{
InitializeComponent();
Save = false;
ModsLauncherInstance = modsLauncher;
ModsPathes = Utils.StringCollectionToList(Settings.Default.ModsPathes);
ScraplandPath = Settings.Default.ScraplandPath;
ScraplandRemasteredPath = Settings.Default.ScraplandRemasteredPath;
ModsPathesList.ItemsSource = ModsLauncherInstance.ModsPathes;
ScraplandPathTextBox.Text = ModsLauncherInstance.ScraplandPath;
ScraplandRemasteredPathTextBox.Text = ModsLauncherInstance.ScraplandRemasteredPath;
ModsPathesList.ItemsSource = ModsPathes;
ScraplandPathTextBox.Text = ScraplandPath;
ScraplandRemasteredPathTextBox.Text = ScraplandRemasteredPath;
Boolean enable = ModsLauncherInstance.ScraplandPath != String.Empty;
ButtonShowScrap.IsEnabled = enable;
ButtonClearScrap.IsEnabled = enable;
ButtonShowScrap.IsEnabled = ScraplandPath != String.Empty;
ButtonShowScrapRemaster.IsEnabled = ScraplandRemasteredPath != String.Empty;
enable = ModsLauncherInstance.ScraplandRemasteredPath != String.Empty;
ButtonShowScrapRemaster.IsEnabled = enable;
ButtonClearScrapRemaster.IsEnabled = enable;
}
// -------------------------------------------
@ -44,13 +45,13 @@ namespace ScrapModLoader
{
String folder = Utils.GetFolderDialog();
if (folder != String.Empty)
ModsPathes.Add(folder);
ModsLauncherInstance.ModsPathes.Add(folder);
ModsPathesList.Items.Refresh();
}
private void ButtonRemove_Click(Object sender, RoutedEventArgs e)
{
ModsPathes.Remove((String)ModsPathesList.SelectedValue);
ModsLauncherInstance.ModsPathes.Remove((String)ModsPathesList.SelectedValue);
ModsPathesList.Items.Refresh();
ButtonRemove.IsEnabled = false;
@ -64,21 +65,21 @@ namespace ScrapModLoader
if (index == 0)
return;
String? temp = ModsPathes[index - 1];
ModsPathes[index - 1] = ModsPathes[index];
ModsPathes[index] = temp;
String? temp = ModsLauncherInstance.ModsPathes[index - 1];
ModsLauncherInstance.ModsPathes[index - 1] = ModsLauncherInstance.ModsPathes[index];
ModsLauncherInstance.ModsPathes[index] = temp;
ModsPathesList.Items.Refresh();
}
private void ButtonDown_Click(Object sender, RoutedEventArgs e)
{
Int32 index = ModsPathesList.SelectedIndex;
if (index == ModsPathes.Count - 1)
if (index == ModsLauncherInstance.ModsPathes.Count - 1)
return;
String? temp = ModsPathes[index + 1];
ModsPathes[index + 1] = ModsPathes[index];
ModsPathes[index] = temp;
String? temp = ModsLauncherInstance.ModsPathes[index + 1];
ModsLauncherInstance.ModsPathes[index + 1] = ModsLauncherInstance.ModsPathes[index];
ModsLauncherInstance.ModsPathes[index] = temp;
ModsPathesList.Items.Refresh();
}
@ -108,50 +109,76 @@ namespace ScrapModLoader
private void ButtonBrowseScrap_Click(Object sender, RoutedEventArgs e)
{
String scraplandPath = Utils.GetFolderDialog();
if (scraplandPath != String.Empty)
String ScraplandPath = Utils.GetFolderDialog();
if (ScraplandPath != String.Empty)
{
ScraplandPathTextBox.Text = scraplandPath;
ScraplandPath = scraplandPath;
ScraplandPathTextBox.Text = ScraplandPath + "\\";
ModsLauncherInstance.ScraplandPath = ScraplandPath + "\\";
ButtonShowScrap.IsEnabled = true;
}
}
private void ButtonBrowseScrapRemaster_Click(Object sender, RoutedEventArgs e)
{
String scraplandRemasteredPath = Utils.GetFolderDialog();
if (scraplandRemasteredPath != String.Empty)
String ScraplandRemasteredPath = Utils.GetFolderDialog();
if (ScraplandRemasteredPath != String.Empty)
{
ScraplandRemasteredPathTextBox.Text = scraplandRemasteredPath;
ScraplandRemasteredPath = scraplandRemasteredPath;
ScraplandRemasteredPathTextBox.Text = ScraplandRemasteredPath + "\\";
ModsLauncherInstance.ScraplandRemasteredPath = ScraplandRemasteredPath + "\\";
ButtonShowScrapRemaster.IsEnabled = true;
}
}
private void ButtonClearScrap_Click(Object sender, RoutedEventArgs e)
{
ScraplandPathTextBox.Text = String.Empty;
ButtonClearScrap.IsEnabled = false;
ButtonShowScrap.IsEnabled = false;
ScraplandPath = String.Empty;
ModsLauncherInstance.ScraplandPath = String.Empty;
}
private void ButtonClearScrapRemaster_Click(Object sender, RoutedEventArgs e)
{
ScraplandRemasteredPathTextBox.Text = String.Empty;
ButtonClearScrapRemaster.IsEnabled = false;
ScraplandRemasteredPath = String.Empty;
ButtonShowScrapRemaster.IsEnabled = false;
ModsLauncherInstance.ScraplandRemasteredPath = String.Empty;
}
private void ButtonShowScrap_Click(Object sender, RoutedEventArgs e)
{
String? path = Path.GetDirectoryName(ScraplandPath);
String? path = Path.GetDirectoryName(ModsLauncherInstance.ScraplandPath);
if (path == null)
throw new DirectoryNotFoundException("Cannot find direcotry for Scrapland");
Process.Start("explorer.exe", path);
}
private void ButtonShowScrapRemaster_Click(Object sender, RoutedEventArgs e)
{
String? path = Path.GetDirectoryName(ScraplandRemasteredPath);
String? path = Path.GetDirectoryName(ModsLauncherInstance.ScraplandRemasteredPath);
if (path == null)
throw new DirectoryNotFoundException("Cannot find direcotry for Scrapland");
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
@ -161,10 +188,11 @@ namespace ScrapModLoader
private void ButtonSave_Click(Object sender, RoutedEventArgs e)
{
Settings.Default.ModsPathes.Clear();
Settings.Default.ModsPathes.AddRange(ModsPathes.ToArray());
Settings.Default.ScraplandPath = ScraplandPath;
Settings.Default.ScraplandRemasteredPath = ScraplandRemasteredPath;
Settings.Default.ModsPathes.AddRange(ModsLauncherInstance.ModsPathes.ToArray());
Settings.Default.ScraplandPath = ModsLauncherInstance.ScraplandPath;
Settings.Default.ScraplandRemasteredPath = ModsLauncherInstance.ScraplandRemasteredPath;
Settings.Default.Save();
Save = true;
Close();
}

View File

@ -1,27 +1,59 @@
using System;
using System.Collections.Generic;
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();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
return dialog.SelectedPath;
}
using System.Windows.Forms.FolderBrowserDialog dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
return dialog.SelectedPath;
}
public static List<String> StringCollectionToList(StringCollection collection)
public static List<String> StringCollectionToList(StringCollection collection)
{
List<String> list = new List<String>();
foreach (String? item in collection)
{
List<String> list = new List<String>();
foreach (String? item in collection)
{
if (item != null)
list.Add(item);
}
return list;
if (item != null)
list.Add(item);
}
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;
}
}