mirror of
https://github.com/kwsch/NHSE.git
synced 2026-10-02 06:46:36 -05:00
Add support for opening .zip save files
ty claude u a real g heavy legwork by claude, refinements/finish by me
This commit is contained in:
@@ -11,7 +11,7 @@ namespace NHSE.Core;
|
||||
public sealed class MainSave : EncryptedFilePair
|
||||
{
|
||||
public readonly MainSaveOffsets Offsets;
|
||||
public MainSave(string folder) : base(folder, "main") => Offsets = MainSaveOffsets.GetOffsets(Info);
|
||||
public MainSave(ISaveFileProvider provider) : base(provider, "main") => Offsets = MainSaveOffsets.GetOffsets(Info);
|
||||
|
||||
public Hemisphere Hemisphere { get => (Hemisphere)Data[Offsets.WeatherArea]; set => Data[Offsets.WeatherArea] = (byte)value; }
|
||||
public AirportColor AirportThemeColor { get => (AirportColor)Data[Offsets.AirportThemeColor]; set => Data[Offsets.AirportThemeColor] = (byte)value; }
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace NHSE.Core;
|
||||
public sealed class Personal : EncryptedFilePair, IVillagerOrigin
|
||||
{
|
||||
public readonly PersonalOffsets Offsets;
|
||||
public Personal(string folder) : base(folder, "personal") => Offsets = PersonalOffsets.GetOffsets(Info);
|
||||
public Personal(ISaveFileProvider provider) : base(provider, "personal") => Offsets = PersonalOffsets.GetOffsets(Info);
|
||||
public override string ToString() => PlayerName;
|
||||
|
||||
public uint TownID
|
||||
|
||||
@@ -3,7 +3,4 @@
|
||||
/// <summary>
|
||||
/// photo_studio_island.dat
|
||||
/// </summary>
|
||||
public sealed class PhotoStudioIsland : EncryptedFilePair
|
||||
{
|
||||
public PhotoStudioIsland(string folder) : base(folder, "photo_studio_island") { }
|
||||
}
|
||||
public sealed class PhotoStudioIsland(ISaveFileProvider provider) : EncryptedFilePair(provider, "photo_studio_island");
|
||||
@@ -3,7 +3,4 @@
|
||||
/// <summary>
|
||||
/// postbox.dat
|
||||
/// </summary>
|
||||
public sealed class PostBox : EncryptedFilePair
|
||||
{
|
||||
public PostBox(string folder) : base(folder, "postbox") { }
|
||||
}
|
||||
public sealed class PostBox(ISaveFileProvider provider) : EncryptedFilePair(provider, "postbox");
|
||||
@@ -3,9 +3,7 @@
|
||||
/// <summary>
|
||||
/// profile.dat
|
||||
/// </summary>
|
||||
public sealed class Profile : EncryptedFilePair
|
||||
{
|
||||
public Profile(string folder) : base(folder, "profile") { }
|
||||
|
||||
// pretty much just a jpeg -- which is also stored in Personal.
|
||||
}
|
||||
/// <remarks>
|
||||
/// pretty much just a jpeg -- which is also stored in Personal.
|
||||
/// </remarks>
|
||||
public sealed class Profile(ISaveFileProvider provider) : EncryptedFilePair(provider, "profile");
|
||||
@@ -1,13 +1,14 @@
|
||||
namespace NHSE.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Data structures stored for the HappyHomeDesigner DLC.
|
||||
/// </summary>
|
||||
public sealed class WhereAreN : EncryptedFilePair
|
||||
{
|
||||
public const string FileName = "wherearen";
|
||||
|
||||
public readonly WhereAreNOffsets Offsets;
|
||||
public WhereAreN(string folder) : base(folder, FileName) => Offsets = WhereAreNOffsets.GetOffsets(Info);
|
||||
public WhereAreN(ISaveFileProvider provider) : base(provider, FileName) => Offsets = WhereAreNOffsets.GetOffsets(Info);
|
||||
|
||||
public EncryptedInt32 Poki
|
||||
{
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace NHSE.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents two files -- <see cref="DataPath"/> and <see cref="HeaderPath"/> and their decrypted data.
|
||||
/// Represents two files -- <see cref="NameData"/> and <see cref="NameHeader"/> and their decrypted data.
|
||||
/// </summary>
|
||||
public abstract class EncryptedFilePair
|
||||
{
|
||||
private readonly byte[] RawData;
|
||||
private readonly byte[] RawHeader;
|
||||
private readonly ISaveFileProvider Provider;
|
||||
|
||||
protected Memory<byte> Raw => RawData;
|
||||
public Span<byte> Data => RawData;
|
||||
@@ -19,36 +19,32 @@ public abstract class EncryptedFilePair
|
||||
|
||||
public readonly FileHeaderInfo Info;
|
||||
|
||||
public readonly string DataPath;
|
||||
public readonly string HeaderPath;
|
||||
public readonly string NameData;
|
||||
public readonly string NameHeader;
|
||||
|
||||
public static bool Exists(string folder, string name)
|
||||
/// <summary>
|
||||
/// Checks if the file pair exists in the specified provider.
|
||||
/// </summary>
|
||||
public static bool Exists(ISaveFileProvider provider, string name)
|
||||
{
|
||||
var NameData = $"{name}.dat";
|
||||
var NameHeader = $"{name}Header.dat";
|
||||
var hdr = Path.Combine(folder, NameHeader);
|
||||
var dat = Path.Combine(folder, NameData);
|
||||
return File.Exists(hdr) && File.Exists(dat);
|
||||
var nameData = $"{name}.dat";
|
||||
var nameHeader = $"{name}Header.dat";
|
||||
return provider.FileExists(nameHeader) && provider.FileExists(nameData);
|
||||
}
|
||||
|
||||
protected EncryptedFilePair(string folder, string name)
|
||||
protected EncryptedFilePair(ISaveFileProvider provider, string name)
|
||||
{
|
||||
Provider = provider;
|
||||
NameData = $"{name}.dat";
|
||||
NameHeader = $"{name}Header.dat";
|
||||
var hdr = Path.Combine(folder, NameHeader);
|
||||
var dat = Path.Combine(folder, NameData);
|
||||
|
||||
var hd = File.ReadAllBytes(hdr);
|
||||
var md = File.ReadAllBytes(dat);
|
||||
var hd = provider.ReadFile(NameHeader);
|
||||
var md = provider.ReadFile(NameData);
|
||||
|
||||
Encryption.Decrypt(hd, md);
|
||||
|
||||
RawHeader = hd;
|
||||
RawData = md;
|
||||
DataPath = dat;
|
||||
HeaderPath = hdr;
|
||||
|
||||
Info = RawHeader[..FileHeaderInfo.SIZE].ToClass<FileHeaderInfo>();
|
||||
}
|
||||
@@ -56,10 +52,11 @@ protected EncryptedFilePair(string folder, string name)
|
||||
public void Save(uint seed)
|
||||
{
|
||||
var encrypt = Encryption.Encrypt(RawData, seed, RawHeader);
|
||||
File.WriteAllBytes(DataPath, encrypt.Data.Span);
|
||||
File.WriteAllBytes(HeaderPath, encrypt.Header.Span);
|
||||
Provider.WriteFile(NameData, encrypt.Data.Span);
|
||||
Provider.WriteFile(NameHeader, encrypt.Header.Span);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updates all hashes of <see cref="Data"/>.
|
||||
/// </summary>
|
||||
|
||||
52
NHSE.Core/Save/Meta/FolderSaveFileProvider.cs
Normal file
52
NHSE.Core/Save/Meta/FolderSaveFileProvider.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace NHSE.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides file access from a filesystem folder.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Creates a provider rooted at the specified folder path.
|
||||
/// </remarks>
|
||||
/// <param name="rootPath">Absolute path to the save folder.</param>
|
||||
public sealed class FolderSaveFileProvider(string rootPath) : ISaveFileProvider
|
||||
{
|
||||
public byte[] ReadFile(string relativePath)
|
||||
{
|
||||
var fullPath = Path.Combine(rootPath, relativePath);
|
||||
return File.ReadAllBytes(fullPath);
|
||||
}
|
||||
|
||||
public void WriteFile(string relativePath, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var fullPath = Path.Combine(rootPath, relativePath);
|
||||
File.WriteAllBytes(fullPath, data);
|
||||
}
|
||||
|
||||
public bool FileExists(string relativePath)
|
||||
{
|
||||
var fullPath = Path.Combine(rootPath, relativePath);
|
||||
return File.Exists(fullPath);
|
||||
}
|
||||
|
||||
public string[] GetDirectories(string searchPattern)
|
||||
{
|
||||
var dirs = Directory.GetDirectories(rootPath, searchPattern, SearchOption.TopDirectoryOnly);
|
||||
var result = new string[dirs.Length];
|
||||
for (int i = 0; i < dirs.Length; i++)
|
||||
result[i] = new DirectoryInfo(dirs[i]).Name;
|
||||
return result;
|
||||
}
|
||||
|
||||
public ISaveFileProvider GetSubdirectoryProvider(string subdirectory)
|
||||
{
|
||||
var subPath = Path.Combine(rootPath, subdirectory);
|
||||
return new FolderSaveFileProvider(subPath);
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
// No-op for folder provider; writes are immediate.
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,38 @@ namespace NHSE.Core;
|
||||
/// <summary>
|
||||
/// Represents all saved data that is stored on the device for the New Horizon's game.
|
||||
/// </summary>
|
||||
public class HorizonSave
|
||||
/// <remarks>
|
||||
/// Creates a HorizonSave from a file provider.
|
||||
/// </remarks>
|
||||
/// <param name="provider">Provider for reading/writing save files.</param>
|
||||
public class HorizonSave(ISaveFileProvider provider)
|
||||
{
|
||||
public readonly MainSave Main;
|
||||
public readonly Player[] Players;
|
||||
public readonly MainSave Main = new(provider);
|
||||
public readonly Player[] Players = Player.ReadMany(provider);
|
||||
private readonly ISaveFileProvider Provider = provider;
|
||||
|
||||
public override string ToString() => $"{Players[0].Personal.TownName} - {Players[0]}";
|
||||
|
||||
public HorizonSave(string folder)
|
||||
/// <summary>
|
||||
/// Creates a HorizonSave from an unpacked file path.
|
||||
/// </summary>
|
||||
/// <param name="folder">Path to the folder containing save files.</param>
|
||||
/// <returns>HorizonSave loaded from the folder.</returns>
|
||||
public static HorizonSave FromFolder(string folder)
|
||||
{
|
||||
Main = new MainSave(folder);
|
||||
Players = Player.ReadMany(folder);
|
||||
var provider = new FolderSaveFileProvider(folder);
|
||||
return new HorizonSave(provider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a HorizonSave from a ZIP file path.
|
||||
/// </summary>
|
||||
/// <param name="zipPath">Path to the ZIP archive containing save files.</param>
|
||||
/// <returns>HorizonSave loaded from the ZIP archive.</returns>
|
||||
public static HorizonSave FromZip(string zipPath)
|
||||
{
|
||||
var provider = new ZipSaveFileProvider(zipPath);
|
||||
return new HorizonSave(provider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,6 +57,7 @@ public void Save(uint seed)
|
||||
pair.Save(seed);
|
||||
}
|
||||
}
|
||||
Provider.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
50
NHSE.Core/Save/Meta/ISaveFileProvider.cs
Normal file
50
NHSE.Core/Save/Meta/ISaveFileProvider.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace NHSE.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction for reading and writing save files from different storage backends (folder, ZIP archive, etc.).
|
||||
/// </summary>
|
||||
public interface ISaveFileProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads all bytes from the specified file path relative to the root.
|
||||
/// </summary>
|
||||
/// <param name="relativePath">Relative path to the file (e.g., "main.dat" or "Villager0/personal.dat").</param>
|
||||
/// <returns>Byte array containing the file contents.</returns>
|
||||
byte[] ReadFile(string relativePath);
|
||||
|
||||
/// <summary>
|
||||
/// Writes all bytes to the specified file path relative to the root.
|
||||
/// </summary>
|
||||
/// <param name="relativePath">Relative path to the file.</param>
|
||||
/// <param name="data">Byte data to write.</param>
|
||||
void WriteFile(string relativePath, ReadOnlySpan<byte> data);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a file exists at the specified relative path.
|
||||
/// </summary>
|
||||
/// <param name="relativePath">Relative path to the file.</param>
|
||||
/// <returns>True if the file exists; otherwise false.</returns>
|
||||
bool FileExists(string relativePath);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all subdirectory names matching the specified pattern relative to the root.
|
||||
/// </summary>
|
||||
/// <param name="searchPattern">Search pattern (e.g., "Villager*").</param>
|
||||
/// <returns>Array of matching directory names (not full paths).</returns>
|
||||
string[] GetDirectories(string searchPattern);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a child provider scoped to the specified subdirectory.
|
||||
/// </summary>
|
||||
/// <param name="subdirectory">Subdirectory name to scope to.</param>
|
||||
/// <returns>A new provider rooted at the subdirectory.</returns>
|
||||
ISaveFileProvider GetSubdirectoryProvider(string subdirectory);
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any pending writes to the underlying storage.
|
||||
/// For folder providers, this may be a no-op. For ZIP providers, this rebuilds and saves the archive.
|
||||
/// </summary>
|
||||
void Flush();
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace NHSE.Core;
|
||||
@@ -36,29 +35,32 @@ public IEnumerator<EncryptedFilePair> GetEnumerator()
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Imports Player data from the requested <see cref="folder"/>.
|
||||
/// Imports Player data from the requested provider.
|
||||
/// </summary>
|
||||
/// <param name="folder">Folder that contains the Player Villager sub-folders.</param>
|
||||
/// <returns>Player object array loaded from the <see cref="folder"/>.</returns>
|
||||
public static Player[] ReadMany(string folder)
|
||||
/// <param name="provider">Provider that contains the Player Villager sub-folders.</param>
|
||||
/// <returns>Player object array loaded from the provider.</returns>
|
||||
public static Player[] ReadMany(ISaveFileProvider provider)
|
||||
{
|
||||
var dirs = Directory.GetDirectories(folder, "Villager*", SearchOption.TopDirectoryOnly);
|
||||
var dirs = provider.GetDirectories("Villager*");
|
||||
var result = new Player[dirs.Length];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
result[i] = new Player(dirs[i]);
|
||||
{
|
||||
var subProvider = provider.GetSubdirectoryProvider(dirs[i]);
|
||||
result[i] = new Player(subProvider, dirs[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Player(string folder)
|
||||
private Player(ISaveFileProvider provider, string directoryName)
|
||||
{
|
||||
DirectoryName = new DirectoryInfo(folder).Name;
|
||||
DirectoryName = directoryName;
|
||||
|
||||
Personal = new Personal(folder);
|
||||
Photo = new PhotoStudioIsland(folder);
|
||||
PostBox = new PostBox(folder);
|
||||
Profile = new Profile(folder);
|
||||
Personal = new Personal(provider);
|
||||
Photo = new PhotoStudioIsland(provider);
|
||||
PostBox = new PostBox(provider);
|
||||
Profile = new Profile(provider);
|
||||
|
||||
if (EncryptedFilePair.Exists(folder, WhereAreN.FileName))
|
||||
WhereAreN = new WhereAreN(folder);
|
||||
if (EncryptedFilePair.Exists(provider, WhereAreN.FileName))
|
||||
WhereAreN = new WhereAreN(provider);
|
||||
}
|
||||
}
|
||||
151
NHSE.Core/Save/Meta/ZipSaveFileProvider.cs
Normal file
151
NHSE.Core/Save/Meta/ZipSaveFileProvider.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
||||
namespace NHSE.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides file access from a ZIP archive. Loads contents into memory and rebuilds the archive on Flush.
|
||||
/// </summary>
|
||||
public sealed class ZipSaveFileProvider : ISaveFileProvider
|
||||
{
|
||||
private string ZipPath { get; }
|
||||
private string BasePath { get; } = string.Empty;
|
||||
private Dictionary<string, byte[]> Files { get; } = [];
|
||||
private ZipSaveFileProvider? Root { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a provider from a ZIP file path. The ZIP contents are loaded into memory.
|
||||
/// </summary>
|
||||
/// <param name="zipPath">Absolute path to the ZIP file.</param>
|
||||
public ZipSaveFileProvider(string zipPath)
|
||||
{
|
||||
ZipPath = zipPath;
|
||||
using var archive = ZipFile.OpenRead(ZipPath);
|
||||
LoadFromZip(archive);
|
||||
}
|
||||
|
||||
private ZipSaveFileProvider(ZipSaveFileProvider root, string basePath)
|
||||
{
|
||||
ZipPath = root.ZipPath;
|
||||
BasePath = basePath;
|
||||
Files = root.Files;
|
||||
Root = root;
|
||||
}
|
||||
|
||||
private void LoadFromZip(ZipArchive archive)
|
||||
{
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entry.Name))
|
||||
continue; // Skip directory entries
|
||||
|
||||
using var stream = entry.Open();
|
||||
using var ms = new MemoryStream((int)entry.Length);
|
||||
stream.CopyTo(ms);
|
||||
var key = NormalizePath(entry.FullName);
|
||||
Files[key] = ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFullKey(string relativePath)
|
||||
{
|
||||
var combined = string.IsNullOrEmpty(BasePath) ? relativePath : $"{BasePath}/{relativePath}";
|
||||
return NormalizePath(combined);
|
||||
}
|
||||
|
||||
private static string NormalizePath(string path)
|
||||
{
|
||||
return path.Replace('\\', '/').TrimStart('/');
|
||||
}
|
||||
|
||||
public byte[] ReadFile(string relativePath)
|
||||
{
|
||||
var key = GetFullKey(relativePath);
|
||||
if (!Files.TryGetValue(key, out var data))
|
||||
throw new FileNotFoundException($"File not found in ZIP: {key}");
|
||||
return data;
|
||||
}
|
||||
|
||||
public void WriteFile(string relativePath, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var key = GetFullKey(relativePath);
|
||||
Files[key] = data.ToArray();
|
||||
}
|
||||
|
||||
public bool FileExists(string relativePath)
|
||||
{
|
||||
var key = GetFullKey(relativePath);
|
||||
return Files.ContainsKey(key);
|
||||
}
|
||||
|
||||
public string[] GetDirectories(string searchPattern)
|
||||
{
|
||||
var prefix = string.IsNullOrEmpty(BasePath) ? string.Empty : BasePath + "/";
|
||||
var pattern = searchPattern.Replace("*", string.Empty);
|
||||
|
||||
var directories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var key in Files.Keys)
|
||||
{
|
||||
if (!key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var remainder = key[prefix.Length..];
|
||||
var slashIndex = remainder.IndexOf('/');
|
||||
if (slashIndex <= 0)
|
||||
continue;
|
||||
|
||||
var dirName = remainder[..slashIndex];
|
||||
if (dirName.Contains(pattern, StringComparison.OrdinalIgnoreCase))
|
||||
directories.Add(dirName);
|
||||
}
|
||||
|
||||
return [.. directories.Order()];
|
||||
}
|
||||
|
||||
public ISaveFileProvider GetSubdirectoryProvider(string subdirectory)
|
||||
{
|
||||
var newBase = string.IsNullOrEmpty(BasePath) ? subdirectory : $"{BasePath}/{subdirectory}";
|
||||
var rootProvider = Root ?? this;
|
||||
return new ZipSaveFileProvider(rootProvider, NormalizePath(newBase));
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
// Only the root provider can flush
|
||||
if (Root is not null)
|
||||
{
|
||||
Root.Flush();
|
||||
return;
|
||||
}
|
||||
|
||||
// Write to a temporary file first, then replace the original
|
||||
var tempPath = ZipPath + ".tmp";
|
||||
try
|
||||
{
|
||||
using (var stream = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
|
||||
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create))
|
||||
{
|
||||
foreach (var (key, data) in Files.OrderBy(x => x.Key))
|
||||
{
|
||||
var entry = archive.CreateEntry(key, CompressionLevel.Optimal);
|
||||
using var entryStream = entry.Open();
|
||||
entryStream.Write(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace original with temp
|
||||
File.Delete(ZipPath);
|
||||
File.Move(tempPath, ZipPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Clean up temp file on failure
|
||||
if (File.Exists(tempPath))
|
||||
File.Delete(tempPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,11 @@ public sealed partial class Editor : Form
|
||||
private readonly HorizonSave SAV;
|
||||
private readonly VillagerEditor Villagers;
|
||||
|
||||
/// <summary>
|
||||
/// Currently loaded player index.
|
||||
/// </summary>
|
||||
private int PlayerIndex = -1;
|
||||
|
||||
public Editor(HorizonSave file)
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -212,8 +217,6 @@ private void LoadPlayers()
|
||||
PlayerIndex = -1;
|
||||
CB_Players.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private int PlayerIndex = -1;
|
||||
private void LoadPlayer(object sender, EventArgs e) => LoadPlayer(CB_Players.SelectedIndex);
|
||||
|
||||
private void B_EditPlayerItems_Click(object sender, EventArgs e)
|
||||
@@ -313,7 +316,7 @@ private void LoadPlayer(int index)
|
||||
|
||||
if (player.WhereAreN is not null)
|
||||
{
|
||||
NUD_Poki.Value = Math.Min(int.MaxValue, player.WhereAreN.Poki.Value);
|
||||
NUD_Poki.Value = Math.Min(int.MaxValue, player.WhereAreN.Poki);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -123,6 +123,15 @@ private static void OpenFileOrPath(string path)
|
||||
return;
|
||||
}
|
||||
|
||||
// Load zip files differently
|
||||
var ext = Path.GetExtension(path);
|
||||
if (ext.Equals(".zip", StringComparison.OrdinalIgnoreCase) && new FileInfo(path).Length < 20 * 1024 * 1024) // less than 20MB
|
||||
{
|
||||
var file = HorizonSave.FromZip(path);
|
||||
Open(file);
|
||||
return;
|
||||
}
|
||||
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
if (dir is null || !Directory.Exists(dir)) // ya never know
|
||||
{
|
||||
@@ -135,7 +144,7 @@ private static void OpenFileOrPath(string path)
|
||||
|
||||
private static void OpenSaveFile(string path)
|
||||
{
|
||||
var file = new HorizonSave(path);
|
||||
var file = HorizonSave.FromFolder(path);
|
||||
Open(file);
|
||||
|
||||
var settings = Settings.Default;
|
||||
|
||||
@@ -67,7 +67,7 @@ private static void LoadSpecialForms()
|
||||
{
|
||||
// For forms that require more complete initialization (dynamically added user controls)
|
||||
var path = Settings.Default.LastFilePath;
|
||||
var sav = new HorizonSave(path);
|
||||
var sav = HorizonSave.FromFolder(path);
|
||||
using var editor = new Editor(sav);
|
||||
using var so = new SingleObjectEditor<object>(new object(), PropertySort.NoSort, false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user