Compare commits

..

No commits in common. "master" and "v0.0.1" have entirely different histories.

9 changed files with 303 additions and 382 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,67 +25,44 @@ 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.toml | Information about mod | | config.xml | 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 |
You can have as many .packed files as you want. Mod loader will load everything. ### meta.ini sample
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ScrapMod>
<Title>Mod Title</Title>
<Description>Mod Desciption</Description>
.packed files in the root of mod will be copied to the `Mods` folder of Scrapland. <Category>Category</Category>
<Version>1.0</Version>
<RequiredLauncher>1.0</RequiredLauncher>
<RequiredGame>1.1</RequiredGame>
.pakced files under game version folder will load only to the appopriate game version. <Author name="Author1" website="https://example.com" />
<Author name="Author2" />
### .sm structure sample <Credits group="Mod author">
``` <Credit name="Author1" />
│ icon.png </Credits>
│ config.toml <Credits group="Some credit" >
│ mod_assets.packed <Credit name="Credit1" />
├──1.0/ <Credit name="Credit2" />
│ only_for_original.packed <Credit name="Credit3" />
└──1.1/ </Credits>
only_for_remastered.packed </ScrapMod>
```
### 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.toml` - [ ] More meta info in `config.xml`
- [ ] 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 Name="PreviewColumn" Width="0*" /> <ColumnDefinition 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,9 +34,8 @@
</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 SupportedGameVersionsDisplay}" /> <GridViewColumn Header="Game Version" DisplayMemberBinding="{Binding RequiredGame}" />
</GridView> </GridView>
</ListView.View> </ListView.View>
<ListView.ItemContainerStyle> <ListView.ItemContainerStyle>
@ -71,8 +70,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 Name="OriginalVersionItem" Content="Original" IsEnabled="False" /> <ComboBoxItem Content="Original" IsEnabled="False" />
<ComboBoxItem Name ="RemasteredVersionItem" Content="Remastered" /> <ComboBoxItem 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,32 +28,25 @@ namespace ScrapModLoader
{ {
if (Settings.Default.ModsPathes.Count == 0) if (Settings.Default.ModsPathes.Count == 0)
{ {
String path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Scrapland mods"); String path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
+ 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("Unable to find Scrapland instalation. Please, specify yours game installation folder in settings.", MessageBox.Show("Error: 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);
} }
} }
OriginalVersionItem.IsEnabled = modsLauncher.ScraplandPath != String.Empty; ((ComboBoxItem)ScraplandVersion.Items[0]).IsEnabled = modsLauncher.ScraplandPath != String.Empty;
RemasteredVersionItem.IsEnabled = modsLauncher.ScraplandRemasteredPath != String.Empty; ((ComboBoxItem)ScraplandVersion.Items[1]).IsEnabled = modsLauncher.ScraplandRemasteredPath != String.Empty;
ScraplandVersion.SelectedIndex = modsLauncher.ScraplandRemasteredPath != String.Empty ? 1 : 0; ScraplandVersion.SelectedIndex = modsLauncher.ScraplandRemasteredPath != String.Empty ? 1 : 0;
@ -62,18 +55,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, MouseButtonEventArgs e) private void ModsList_MouseDown(Object sender, System.Windows.Input.MouseButtonEventArgs e)
{ {
if (PreviewColumn.Width.Value != 0) if (MainGrid.ColumnDefinitions[2].Width.Value != 0)
{ {
gridLength = PreviewColumn.Width; gridLength = MainGrid.ColumnDefinitions[2].Width;
PreviewColumn.Width = new GridLength(0, GridUnitType.Star); MainGrid.ColumnDefinitions[2].Width = new GridLength(0, GridUnitType.Star);
} }
} }
private void ListViewItem_PreviewMouseLeftButtonDown(Object sender, MouseButtonEventArgs e) private void ListViewItem_PreviewMouseLeftButtonDown(Object sender, MouseButtonEventArgs e)
{ {
PreviewColumn.Width = gridLength; MainGrid.ColumnDefinitions[2].Width = gridLength;
if (sender is ListViewItem item) if (sender is ListViewItem item)
{ {
@ -81,7 +74,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));
@ -99,35 +92,34 @@ namespace ScrapModLoader
private void WriteModInfo(ScrapMod mod) private void WriteModInfo(ScrapMod mod)
{ {
ModInfo.Document.Blocks.Clear(); ModInfo.Document.Blocks.Clear();
Paragraph paragraph = new Paragraph(); Paragraph parahraph = new Paragraph();
paragraph.Inlines.Add(new Bold(new Run("Description:\n"))); parahraph.Inlines.Add(new Bold(new Run("Description:\n")));
paragraph.Inlines.Add(new Run(mod.Description)); parahraph.Inlines.Add(new Run(mod.Description));
paragraph.Inlines.Add(new Bold(new Run("\n\nAuthors:\n"))); parahraph.Inlines.Add(new Bold(new Run("\n\nAuthors:\n")));
foreach (String autor in mod.Authors) foreach (String autor in mod.Authors)
paragraph.Inlines.Add(new Run(autor + "\n")); parahraph.Inlines.Add(new Run(autor + "\n"));
ModInfo.Document.Blocks.Add(paragraph); ModInfo.Document.Blocks.Add(parahraph);
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();
paragraph = new Paragraph(); parahraph = new Paragraph();
foreach (KeyValuePair<String, List<String>> credit in mod.Credits) foreach (KeyValuePair<String, List<String>> credit in mod.Credits)
{ {
paragraph.Inlines.Add(new Bold(new Run(credit.Key + "\n"))); parahraph.Inlines.Add(new Bold(new Run(credit.Key + "\n")));
foreach (String autor in credit.Value) foreach (String autor in credit.Value)
paragraph.Inlines.Add(new Run(autor + "\n")); parahraph.Inlines.Add(new Run(autor + "\n"));
paragraph.Inlines.Add(new Run("\n")); parahraph.Inlines.Add(new Run("\n"));
} }
ModCredits.Document.Blocks.Add(paragraph); ModCredits.Document.Blocks.Add(parahraph);
} }
} }
@ -140,8 +132,6 @@ 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();
@ -158,16 +148,11 @@ namespace ScrapModLoader
private void ButtonSettings_Click(Object sender, RoutedEventArgs e) private void ButtonSettings_Click(Object sender, RoutedEventArgs e)
{ {
SettingsWindow settingsWindow = new SettingsWindow(modsLauncher); SettingsWindow settingsWindow = new SettingsWindow();
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)
@ -181,7 +166,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(Path.Combine(gamePath, @"bin\Scrap.exe"), args); Process.Start(gamePath + @"\bin\Scrap.exe", args);
if (CloseLauncher.IsChecked ?? false) if (CloseLauncher.IsChecked ?? false)
Close(); Close();

View file

@ -1,10 +1,6 @@
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;
@ -12,17 +8,17 @@ namespace ScrapModLoader
{ {
public class ModsLauncher public class ModsLauncher
{ {
public List<ScrapMod> Mods { get; private set; } = new List<ScrapMod>(); public List<ScrapMod> Mods { get; private set; }
public List<String> ModsPathes { get; set; } = Utils.StringCollectionToList(Settings.Default.ModsPathes); public String ScraplandPath { get; set; }
public String ScraplandPath { get; set; } = Settings.Default.ScraplandPath; public String ScraplandRemasteredPath { get; set; }
public String ScraplandRemasteredPath { get; set; } = Settings.Default.ScraplandRemasteredPath; public String SelectedGameVersion { get; set; }
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()
@ -34,7 +30,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(ScrapMod.LoadFromFile(file)); Mods.Add(new ScrapMod(file));
} }
} }
} }
@ -55,32 +51,26 @@ namespace ScrapModLoader
if (key == null) if (key == null)
continue; continue;
foreach (String subKeyName in key.GetSubKeyNames()) foreach (String subkey_name in key.GetSubKeyNames())
{ {
using RegistryKey? subKey = key.OpenSubKey(subKeyName); using RegistryKey? subkey = key.OpenSubKey(subkey_name);
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" || displayName == "American McGee presents Scrapland") if (displayName == "Scrapland")
{ {
ScraplandPath = subKey.GetValue("InstallLocation")?.ToString() ?? String.Empty; ScraplandPath = subkey.GetValue("InstallLocation")?.ToString() ?? "";
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() ?? String.Empty; ScraplandRemasteredPath = subkey.GetValue("InstallLocation")?.ToString() ?? "";
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;
} }
@ -92,76 +82,22 @@ namespace ScrapModLoader
public void LoadMods() public void LoadMods()
{ {
SelectedGamePath = SelectedGameVersion == "1.0" ? ScraplandPath : ScraplandRemasteredPath; String gamePath = SelectedGameVersion == "1.0" ? ScraplandPath : ScraplandRemasteredPath;
foreach (ScrapMod mod in Mods) foreach (ScrapMod mod in Mods)
{ {
// TODO: Warning about not loading mods that not supports selected version if (mod.RequiredGame != SelectedGameVersion)
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 (!IsEnabled(mod)) if (!mod.IsEnabled(gamePath))
Enable(mod); mod.Enable(gamePath);
} }
else else
{ {
if (IsEnabled(mod)) if (mod.IsEnabled(gamePath))
Disable(mod); mod.Disable(gamePath);
}
}
}
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,113 +2,199 @@
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; } = String.Empty; 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 Description { get; private set; } = String.Empty; public ScrapMod(String path)
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)
public Boolean IsLoaded(String gamePath) => Directory.Exists(gamePath + @"Mods\" + Name);
public Boolean IsEnabled(String gamePath)
{ {
using ZipFile zipFile = ZipFile.Read(path); if (IsLoaded(gamePath))
Byte[] iconBuffer = Utils.ExtractFromZip(zipFile, "icon.png");
Byte[] confBuffer = Utils.ExtractFromZip(zipFile, "config.toml");
ScrapMod mod = new ScrapMod()
{ {
ModPath = path, foreach (String file in Directory.EnumerateFiles(gamePath + @"Mods\" + Name))
Icon = Utils.LoadImage(iconBuffer) {
}; if (Path.GetExtension(file) == ".disabled")
return false;
LoadConfig(mod, confBuffer);
return mod;
} }
private static void LoadConfig(ScrapMod mod, Byte[] buffer) return true;
}
return false;
}
public void Enable(String gamePath)
{ {
using MemoryStream sourceStream = new MemoryStream(buffer); if (!IsLoaded(gamePath))
using StreamReader reader = new StreamReader(sourceStream); LoadModToGame(gamePath);
TomlTable config = TOML.Parse(reader); if (IsEnabled(gamePath))
return;
CheckConfig(config); foreach (String file in Directory.EnumerateFiles(gamePath + @"Mods\" + Name))
{
if (Path.GetExtension(file) == ".disabled")
File.Move(file, Path.ChangeExtension(file, null));
}
}
mod.Name = config["title"]; public void Disable(String gamePath)
mod.Description = config["description"]; {
mod.Category = config["category"]; if (!IsEnabled(gamePath))
mod.Version = config["version"]; return;
mod.RequiredLauncher = config["requiredLauncher"];
foreach (TomlNode version in config["supportedGameVersions"]) foreach (String file in Directory.EnumerateFiles(gamePath + @"Mods\" + Name))
mod.SupportedGameVersions.Add(version); {
if (Path.GetExtension(file) == ".packed")
File.Move(file, file + ".disabled");
}
}
foreach (TomlNode author in config["authors"]) private void LoadModToGame(String gamePath)
mod.Authors.Add(author["name"]); {
gamePath += @"Mods\" + Name;
Directory.CreateDirectory(gamePath);
foreach (TomlNode credit in config["credits"]) 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>();
foreach (TomlNode entry in credit["credits"]) XmlAttribute? groupAttr = credit.Attributes?["group"];
entries.Add(entry["name"]); if (groupAttr == null)
throw new FileFormatException("No 'group' attribute in 'Credits' tag in 'config.xml'");
mod.Credits.Add(credit["group"], entries); foreach (XmlNode entry in credit)
}
}
private static void CheckConfig(TomlTable config)
{ {
if (!config.HasKey("title")) XmlAttribute? nameAttr = entry.Attributes?["name"];
throw new FileFormatException("No 'title' key in 'config.toml'"); if (nameAttr == null)
throw new FileFormatException("No 'name' attribute in 'Author' tag in 'config.xml'");
if (!config.HasKey("description")) entries.Add(nameAttr.InnerText);
throw new FileFormatException("No 'description' key in 'config.toml'"); }
if (!config.HasKey("category")) Credits.Add(groupAttr.InnerText, entries);
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,7 +16,6 @@
<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" IsEnabled="False"/> <Button Content=" Clear " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonClearScrap" Click="ButtonClearScrap_Click"/>
<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,10 +71,9 @@
<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" IsEnabled="False"/> <Button Content=" Clear " HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Name="ButtonClearScrapRemaster" Click="ButtonClearScrapRemaster_Click"/>
<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,28 +12,27 @@ 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 Boolean Save { get; set; }
private ModsLauncher ModsLauncherInstance { get; set; }
public SettingsWindow(ModsLauncher modsLauncher) public SettingsWindow()
{ {
InitializeComponent(); InitializeComponent();
Save = false; Save = false;
ModsLauncherInstance = modsLauncher;
ModsPathesList.ItemsSource = ModsLauncherInstance.ModsPathes; ModsPathes = Utils.StringCollectionToList(Settings.Default.ModsPathes);
ScraplandPathTextBox.Text = ModsLauncherInstance.ScraplandPath; ScraplandPath = Settings.Default.ScraplandPath;
ScraplandRemasteredPathTextBox.Text = ModsLauncherInstance.ScraplandRemasteredPath; ScraplandRemasteredPath = Settings.Default.ScraplandRemasteredPath;
Boolean enable = ModsLauncherInstance.ScraplandPath != String.Empty; ModsPathesList.ItemsSource = ModsPathes;
ButtonShowScrap.IsEnabled = enable; ScraplandPathTextBox.Text = ScraplandPath;
ButtonClearScrap.IsEnabled = enable; ScraplandRemasteredPathTextBox.Text = ScraplandRemasteredPath;
enable = ModsLauncherInstance.ScraplandRemasteredPath != String.Empty; ButtonShowScrap.IsEnabled = ScraplandPath != String.Empty;
ButtonShowScrapRemaster.IsEnabled = enable; ButtonShowScrapRemaster.IsEnabled = ScraplandRemasteredPath != String.Empty;
ButtonClearScrapRemaster.IsEnabled = enable;
} }
// ------------------------------------------- // -------------------------------------------
@ -45,13 +44,13 @@ namespace ScrapModLoader
{ {
String folder = Utils.GetFolderDialog(); String folder = Utils.GetFolderDialog();
if (folder != String.Empty) if (folder != String.Empty)
ModsLauncherInstance.ModsPathes.Add(folder); 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)
{ {
ModsLauncherInstance.ModsPathes.Remove((String)ModsPathesList.SelectedValue); ModsPathes.Remove((String)ModsPathesList.SelectedValue);
ModsPathesList.Items.Refresh(); ModsPathesList.Items.Refresh();
ButtonRemove.IsEnabled = false; ButtonRemove.IsEnabled = false;
@ -65,21 +64,21 @@ namespace ScrapModLoader
if (index == 0) if (index == 0)
return; return;
String? temp = ModsLauncherInstance.ModsPathes[index - 1]; String? temp = ModsPathes[index - 1];
ModsLauncherInstance.ModsPathes[index - 1] = ModsLauncherInstance.ModsPathes[index]; ModsPathes[index - 1] = ModsPathes[index];
ModsLauncherInstance.ModsPathes[index] = temp; 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 == ModsLauncherInstance.ModsPathes.Count - 1) if (index == ModsPathes.Count - 1)
return; return;
String? temp = ModsLauncherInstance.ModsPathes[index + 1]; String? temp = ModsPathes[index + 1];
ModsLauncherInstance.ModsPathes[index + 1] = ModsLauncherInstance.ModsPathes[index]; ModsPathes[index + 1] = ModsPathes[index];
ModsLauncherInstance.ModsPathes[index] = temp; ModsPathes[index] = temp;
ModsPathesList.Items.Refresh(); ModsPathesList.Items.Refresh();
} }
@ -109,76 +108,50 @@ 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;
ModsLauncherInstance.ScraplandPath = ScraplandPath + "\\"; 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;
ModsLauncherInstance.ScraplandRemasteredPath = ScraplandRemasteredPath + "\\"; 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;
ModsLauncherInstance.ScraplandPath = String.Empty; 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;
ButtonShowScrapRemaster.IsEnabled = false; ScraplandRemasteredPath = String.Empty;
ModsLauncherInstance.ScraplandRemasteredPath = String.Empty;
} }
private void ButtonShowScrap_Click(Object sender, RoutedEventArgs e) private void ButtonShowScrap_Click(Object sender, RoutedEventArgs e)
{ {
String? path = Path.GetDirectoryName(ModsLauncherInstance.ScraplandPath); String? path = Path.GetDirectoryName(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(ModsLauncherInstance.ScraplandRemasteredPath); String? path = Path.GetDirectoryName(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
@ -188,11 +161,10 @@ 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(ModsLauncherInstance.ModsPathes.ToArray()); Settings.Default.ModsPathes.AddRange(ModsPathes.ToArray());
Settings.Default.ScraplandPath = ModsLauncherInstance.ScraplandPath; Settings.Default.ScraplandPath = ScraplandPath;
Settings.Default.ScraplandRemasteredPath = ModsLauncherInstance.ScraplandRemasteredPath; Settings.Default.ScraplandRemasteredPath = ScraplandRemasteredPath;
Settings.Default.Save(); Settings.Default.Save();
Save = true; Save = true;
Close(); Close();
} }

View file

@ -1,19 +1,14 @@
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;
using Ionic.Zip; namespace ScrapModLoader
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;
} }
@ -28,32 +23,5 @@ internal static class Utils
} }
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;
} }
} }