diff --git a/NHSE.Core/Resources/text/de/MessageStrings_de.txt b/NHSE.Core/Resources/text/de/MessageStrings_de.txt index 1724f0d..ecf9a3f 100644 --- a/NHSE.Core/Resources/text/de/MessageStrings_de.txt +++ b/NHSE.Core/Resources/text/de/MessageStrings_de.txt @@ -1,6 +1,7 @@ MsgBackupCreateLocation=NHSE can perform automatic backups if you create a folder with the name '{0}' in the same folder as the executable. MsgBackupCreateQuestion=Would you like NHSE to automatically keep a backup of your save data? MsgDataSizeMismatchImport=The size of the imported file (0x{0:X}) does not match the required size (0x{1:X}). +MsgDataTrimmedWarning=It will be trimmed to fit. MsgDataSizeMismatchRAM=Read size (0x{0:X}) != Write size (0x{1:X}). MsgDataDidNotOriginateFromHost_0=Imported data did not originate from Villager0 ({0})'s data. MsgAskUpdateValues=Update values? diff --git a/NHSE.Core/Resources/text/en/MessageStrings_en.txt b/NHSE.Core/Resources/text/en/MessageStrings_en.txt index 5a1891d..27fcd1b 100644 --- a/NHSE.Core/Resources/text/en/MessageStrings_en.txt +++ b/NHSE.Core/Resources/text/en/MessageStrings_en.txt @@ -1,6 +1,7 @@ MsgBackupCreateLocation=NHSE can perform automatic backups if you create a folder with the name '{0}' in the same folder as the executable. MsgBackupCreateQuestion=Would you like NHSE to automatically keep a backup of your save data? MsgDataSizeMismatchImport=The size of the imported file (0x{0:X}) does not match the required size (0x{1:X}). +MsgDataTrimmedWarning=It will be trimmed to fit. MsgDataSizeMismatchRAM=Read size (0x{0:X}) != Write size (0x{1:X}). MsgDataDidNotOriginateFromHost_0=Imported data did not originate from Villager0 ({0})'s data. MsgAskUpdateValues=Update values? diff --git a/NHSE.Core/Resources/text/es/MessageStrings_es.txt b/NHSE.Core/Resources/text/es/MessageStrings_es.txt index 2cf0094..fbdaae5 100644 --- a/NHSE.Core/Resources/text/es/MessageStrings_es.txt +++ b/NHSE.Core/Resources/text/es/MessageStrings_es.txt @@ -1,6 +1,7 @@ MsgBackupCreateLocation=NHSE puede crear un respaldos automáticos si crea una carpeta con el nombre '{0}' en la misma carpeta del ejecutable. MsgBackupCreateQuestion=¿Quisiera que NHSE cree automáticamente un respaldo de sus datos de guardado? MsgDataSizeMismatchImport=El tamaño del archivo importado (0x{0:X}) no coincide con el tamaño requerido (0x{1:X}). +MsgDataTrimmedWarning=Se recortará para que encaje. MsgDataSizeMismatchRAM=Tamaño de lectura (0x{0:X}) != Tamaño de Escritura (0x{1:X}). MsgDataDidNotOriginateFromHost_0=Los datos importados no se originaron de los datos del Aldeano ({0}). MsgAskUpdateValues=¿Actualizar valores? diff --git a/NHSE.Core/Resources/text/fr/MessageStrings_fr.txt b/NHSE.Core/Resources/text/fr/MessageStrings_fr.txt index 0e700b9..fdfe264 100644 --- a/NHSE.Core/Resources/text/fr/MessageStrings_fr.txt +++ b/NHSE.Core/Resources/text/fr/MessageStrings_fr.txt @@ -1,6 +1,7 @@ MsgBackupCreateLocation=NHSE peut effectuer des sauvegardes automatiques si vous créez un dossier portant le nom «{0}» dans le même dossier que l'exécutable. MsgBackupCreateQuestion=Souhaitez-vous que NHSE conserve automatiquement une sauvegarde de vos données de sauvegarde? MsgDataSizeMismatchImport=La taille du fichier importé (0x{0:X}) ne correspond pas à la taille requise (0x{1:X}). +MsgDataTrimmedWarning=Il sera découpé aux dimensions voulues. MsgDataSizeMismatchRAM=Taille de lecture (0x{0:X})! = Taille d'écriture (0x{1:X}). MsgDataDidNotOriginateFromHost_0=Les données importées ne proviennent pas des données de Villager0({0}). MsgAskUpdateValues=Mettre à jour les valeurs? diff --git a/NHSE.Core/Resources/text/it/MessageStrings_it.txt b/NHSE.Core/Resources/text/it/MessageStrings_it.txt index 34a43aa..ea58d59 100644 --- a/NHSE.Core/Resources/text/it/MessageStrings_it.txt +++ b/NHSE.Core/Resources/text/it/MessageStrings_it.txt @@ -1,6 +1,7 @@ MsgBackupCreateLocation=NHSE pu� fare backup automatici se crei una cartella chiamata '{0}' nella stessa cartella dell'eseguibile. MsgBackupCreateQuestion=Vuoi che NHSE faccia automaticamente un backup dei tuoi dati di salvataggio? MsgDataSizeMismatchImport=La dimensione del file importato (0x{0:X}) non corrisponde alla dimensione richiesta (0x{1:X}). +MsgDataTrimmedWarning=Verrà ritagliato per adattarlo. MsgDataSizeMismatchRAM=Dimensione letta (0x{0:X}) != Dimensione da scrivere (0x{1:X}). MsgDataDidNotOriginateFromHost_0=I dati importati non derivano dai dati del Villager0 ({0}). MsgAskUpdateValues=Aggiornare i valori? diff --git a/NHSE.Core/Resources/text/jp/MessageStrings_jp.txt b/NHSE.Core/Resources/text/jp/MessageStrings_jp.txt index 48294c8..10b3260 100644 --- a/NHSE.Core/Resources/text/jp/MessageStrings_jp.txt +++ b/NHSE.Core/Resources/text/jp/MessageStrings_jp.txt @@ -1,6 +1,7 @@ MsgBackupCreateLocation=実行可能ファイルと同じフォルダー内に '{0}' という名前のフォルダーを作成すると、NHSE は自動バックアップを実行できます。 MsgBackupCreateQuestion=NHSE がセーブ データのバックアップを自動的に保存することを希望しますか? MsgDataSizeMismatchImport=インポートされたファイル (0x{0:X}) のサイズが必要なサイズ (0x{1:X}) と一致しません。 +MsgDataTrimmedWarning=フィットするようにトリミングされます。 MsgDataSizeMismatchRAM=読み取りサイズ (0x{0:X}) は書き込みサイズ (0x{1:X}) と等しくありません。 MsgDataDidNotOriginateFromHost_0=インポートされたデータは Villager0 ({0}) のデータからのものではありませんでした。 MsgAskUpdateValues=値を更新しますか? diff --git a/NHSE.Core/Resources/text/ko/MessageStrings_ko.txt b/NHSE.Core/Resources/text/ko/MessageStrings_ko.txt index d493b20..28cd35e 100644 --- a/NHSE.Core/Resources/text/ko/MessageStrings_ko.txt +++ b/NHSE.Core/Resources/text/ko/MessageStrings_ko.txt @@ -1,6 +1,7 @@ MsgBackupCreateLocation=실행 파일과 같은 폴더에 '{0}' 이름으로 폴더를 만들면 NHSE가 자동으로 백업을 수행합니다. MsgBackupCreateQuestion=NHSE가 세이브 데이터를 자동으로 백업하도록 설정하시겠습니까? MsgDataSizeMismatchImport=가져온 파일의 크기(0x{0:X})가 필요한 크기(0x{1:X})와 일치하지 않습니다. +MsgDataTrimmedWarning=크기에 맞춰 다듬어질 것입니다. MsgDataSizeMismatchRAM=읽기 크기(0x{0:X})와 쓰기 크기(0x{1:X})가 다릅니다. MsgDataDidNotOriginateFromHost_0=가져온 데이터는 플레이어0({0})의 데이터가 아닙니다. MsgAskUpdateValues=값들을 업데이트하시겠습니까? diff --git a/NHSE.Core/Resources/text/zhs/MessageStrings_zhs.txt b/NHSE.Core/Resources/text/zhs/MessageStrings_zhs.txt index 8ccfbf7..a476edb 100644 --- a/NHSE.Core/Resources/text/zhs/MessageStrings_zhs.txt +++ b/NHSE.Core/Resources/text/zhs/MessageStrings_zhs.txt @@ -1,6 +1,7 @@ MsgBackupCreateLocation=如果您在本程序根目录创建 '{0}' 文件夹,NHSE可以帮您自动备份存档。 MsgBackupCreateQuestion=您希望NHSE自动备份您的存档吗? MsgDataSizeMismatchImport=导入的文件尺寸(0x{0:X})和必要的文件尺寸(0x{1:X})不匹配。 +MsgDataTrimmedWarning=它会被修剪到合适的尺寸。 MsgDataSizeMismatchRAM=读取大小 (0x{0:X}) != 写入大小 (0x{1:X})。 MsgDataDidNotOriginateFromHost_0=导入的数据不是来自Villager0 ({0})的数据。 MsgAskUpdateValues=更新数值? diff --git a/NHSE.Core/Resources/text/zht/MessageStrings_zht.txt b/NHSE.Core/Resources/text/zht/MessageStrings_zht.txt index 4be2e88..a8c38af 100644 --- a/NHSE.Core/Resources/text/zht/MessageStrings_zht.txt +++ b/NHSE.Core/Resources/text/zht/MessageStrings_zht.txt @@ -1,6 +1,7 @@ MsgBackupCreateLocation=NHSE can perform automatic backups if you create a folder with the name '{0}' in the same folder as the executable. MsgBackupCreateQuestion=Would you like NHSE to automatically keep a backup of your save data? MsgDataSizeMismatchImport=The size of the imported file (0x{0:X}) does not match the required size (0x{1:X}). +MsgDataTrimmedWarning=It will be trimmed to fit. MsgDataSizeMismatchRAM=Read size (0x{0:X}) != Write size (0x{1:X}). MsgDataDidNotOriginateFromHost_0=Imported data did not originate from Villager0 ({0})'s data. MsgAskUpdateValues=Update values? diff --git a/NHSE.Core/Save/Files/MainSave.cs b/NHSE.Core/Save/Files/MainSave.cs index a47e8dc..b16a9a3 100644 --- a/NHSE.Core/Save/Files/MainSave.cs +++ b/NHSE.Core/Save/Files/MainSave.cs @@ -217,15 +217,15 @@ public void SetAcreBytes(ReadOnlySpan data) public const ushort MapDesignNone = 0xF800; - public Memory MapDesignTileData => Raw.Slice(Offsets.MyDesignMap, (AcreWidth * LayerFieldItem.TilesPerAcreDim) * (AcreHeight * LayerFieldItem.TilesPerAcreDim) * sizeof(ushort)); - public ushort[] GetMapDesignTiles() => MemoryMarshal.Cast(MapDesignTileData.Span).ToArray(); - public void SetMapDesignTiles(ReadOnlySpan value) => MemoryMarshal.Cast(value).CopyTo(MapDesignTileData.Span); + public Memory MapDesignTileData(int w, int h) => Raw.Slice(Offsets.MyDesignMap, (w * 16) * (h * 16) * sizeof(ushort)); + public ushort[] GetMapDesignTiles(int w, int h) => MemoryMarshal.Cast(MapDesignTileData(w, h).Span).ToArray(); + public void SetMapDesignTiles(ReadOnlySpan value, int w, int h) => MemoryMarshal.Cast(value).CopyTo(MapDesignTileData(w, h).Span); - public void ClearDesignTiles() + public void ClearDesignTiles(int w, int h) { - var tiles = GetMapDesignTiles(); + var tiles = GetMapDesignTiles(w, h); tiles.AsSpan().Fill(MapDesignNone); - SetMapDesignTiles(tiles); + SetMapDesignTiles(tiles, w, h); } private int FieldItemLayerSize => TotalFieldItemTileCount * Item.SIZE; diff --git a/NHSE.Core/Util/ArrayUtil.cs b/NHSE.Core/Util/ArrayUtil.cs index 76f5475..aa2a789 100644 --- a/NHSE.Core/Util/ArrayUtil.cs +++ b/NHSE.Core/Util/ArrayUtil.cs @@ -28,4 +28,64 @@ public static int ReplaceOccurrences(this Span array, ReadOnlySpan p ++count; } } + + /// + /// Pads the specified byte array on the left side with the given padding bytes until it reaches the desired total length. + /// + /// The original byte array to pad. + /// The desired total length of the resulting array. + /// The byte pattern to use for padding. If multiple bytes are provided, they will repeat as needed. + /// + /// A new byte array of with padding on the left and the original content on the right. + /// If is already equal to or longer than , returns the original array unchanged. + /// + public static byte[] PadLeft(byte[] original, int totalLength, byte[] paddingBytes) + { + int bytesNeeded = totalLength - original.Length; + if (bytesNeeded <= 0) return original; + + byte[] paddedArray = new byte[totalLength]; + int padBytesCount = paddingBytes.Length; + + // Fill the left with padding bytes + for (int i = 0; i < bytesNeeded; i += padBytesCount) + { + for (int j = 0; j < padBytesCount && i + j < bytesNeeded; j++) + paddedArray[i + j] = paddingBytes[j]; + } + + // Copy the original array to the right of the padding + Buffer.BlockCopy(original, 0, paddedArray, bytesNeeded, original.Length); + return paddedArray; + } + /// + /// Pads the specified byte array on the right side with the given padding bytes until it reaches the desired total length. + /// + /// The original byte array to pad. + /// The desired total length of the resulting array. + /// The byte pattern to use for padding. If multiple bytes are provided, they will repeat as needed. + /// + /// A new byte array of with the original content on the left and padding on the right. + /// If is already equal to or longer than , returns the original array unchanged. + /// + public static byte[] PadRight(byte[] original, int totalLength, byte[] paddingBytes) + { + int bytesNeeded = totalLength - original.Length; + if (bytesNeeded <= 0) return original; + + byte[] paddedArray = new byte[totalLength]; + // Copy the original array to the left + Buffer.BlockCopy(original, 0, paddedArray, 0, original.Length); + + int padBytesCount = paddingBytes.Length; + + // Fill the right with padding bytes + for (int i = 0; i < bytesNeeded; i += padBytesCount) + { + for (int j = 0; j < padBytesCount && original.Length + i + j < totalLength; j++) + paddedArray[original.Length + i + j] = paddingBytes[j]; + } + + return paddedArray; + } } \ No newline at end of file diff --git a/NHSE.WinForms/Subforms/Map/FieldItemEditor.cs b/NHSE.WinForms/Subforms/Map/FieldItemEditor.cs index 284c8f7..e199f7f 100644 --- a/NHSE.WinForms/Subforms/Map/FieldItemEditor.cs +++ b/NHSE.WinForms/Subforms/Map/FieldItemEditor.cs @@ -7,8 +7,11 @@ using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Drawing.Imaging; +using System.Drawing.Imaging.Effects; using System.IO; using System.Linq; +using System.Numerics; +using System.Reflection; using System.Windows.Forms; namespace NHSE.WinForms; @@ -34,6 +37,8 @@ public sealed partial class FieldItemEditor : Form, IItemLayerEditor private int DragY = -1; private bool IsDragOperationActive; + private Tuple MapDesignGridSize = new(7, 6); + private bool IsMenuHasActivate = true; public ItemEditor ItemProvider => ItemEdit; @@ -62,6 +67,7 @@ public FieldItemEditor(MainSave sav) Loading = true; + LoadMapDesignSize(); LoadComboBoxes(); LoadBuildings(sav); ReloadMapBackground(); @@ -111,6 +117,12 @@ private void LoadEditors() PG_TerrainTile.SelectedObject = new TerrainTile(); } + private void LoadMapDesignSize() + { + if (SAV.Info.GetKnownRevisionIndex() >= 31) + MapDesignGridSize = new (9, 6); + } + private int ExteriorAcreIndex => CB_Acre.SelectedIndex; private void ChangeAcre(object sender, EventArgs e) @@ -1228,35 +1240,21 @@ private void B_SetAllRoadTiles_Click(object sender, EventArgs e) private void B_ClearPlacedDesigns_Click(object sender, EventArgs e) { - SAV.ClearDesignTiles(); + SAV.ClearDesignTiles(MapDesignGridSize.Item1, MapDesignGridSize.Item2); System.Media.SystemSounds.Asterisk.Play(); } private void B_ExportPlacedDesigns_Click(object sender, EventArgs e) { - using var sfd = new SaveFileDialog(); - sfd.Filter = "nhmd file (*.nhmd)|*.nhmd"; - sfd.FileName = "Island MyDesignMap.nhmd"; - if (sfd.ShowDialog() != DialogResult.OK) + if (!MapDumpHelper.DumpMapDesigns(SAV, MapDesignGridSize)) return; - - string path = sfd.FileName; - var tiles = SAV.MapDesignTileData.Span; - File.WriteAllBytes(path, tiles); System.Media.SystemSounds.Asterisk.Play(); } private void B_ImportPlacedDesigns_Click(object sender, EventArgs e) { - using var ofd = new OpenFileDialog(); - ofd.Filter = "nhmd file (*.nhmd)|*.nhmd"; - ofd.FileName = "Island MyDesignMap.nhmd"; - if (ofd.ShowDialog() != DialogResult.OK) + if (!MapDumpHelper.ImportMapDesigns(SAV, MapDesignGridSize)) return; - - string path = ofd.FileName; - var tiles = File.ReadAllBytes(path); - tiles.CopyTo(SAV.MapDesignTileData.Span); System.Media.SystemSounds.Asterisk.Play(); } diff --git a/NHSE.WinForms/Subforms/Map/FieldItemEditor.resx b/NHSE.WinForms/Subforms/Map/FieldItemEditor.resx index c600636..08f9278 100644 --- a/NHSE.WinForms/Subforms/Map/FieldItemEditor.resx +++ b/NHSE.WinForms/Subforms/Map/FieldItemEditor.resx @@ -129,12 +129,12 @@ 465, 17 - - 930, 17 - 620, 17 + + 930, 17 + 17, 56 diff --git a/NHSE.WinForms/Subforms/Map/MapDumpHelper.cs b/NHSE.WinForms/Subforms/Map/MapDumpHelper.cs index 7ccfe14..14702ed 100644 --- a/NHSE.WinForms/Subforms/Map/MapDumpHelper.cs +++ b/NHSE.WinForms/Subforms/Map/MapDumpHelper.cs @@ -8,6 +8,9 @@ namespace NHSE.WinForms; public static class MapDumpHelper { + private const int DesignDumpSizeAbove31 = 0x6C00; + private const int DesignDumpSizeBelow31 = 0x5400; + public static bool ImportToLayerAcreSingle(LayerFieldItem layerField, string acre, int layerIndex, int relX, int relY) { var name = GetBaseFileName(acre, "acre"); @@ -276,4 +279,116 @@ public static bool ImportToLayerAllFlag(ILayerFieldItemFlag layer, uint layerInd layer.Import(data); return true; } + + public static bool ImportMapDesigns(MainSave sav, Tuple size) + { + using var ofd = new OpenFileDialog(); + ofd.Filter = "nhmd file (*.nhmd)|*.nhmd"; + ofd.FileName = "Island MyDesignMap.nhmd"; + if (ofd.ShowDialog() != DialogResult.OK) + return false; + + string path = ofd.FileName; + + var tiles = File.ReadAllBytes(path); + + var rel = size; + + if (rel.Item1 == 9) + { + if (tiles.Length == DesignDumpSizeAbove31) + { + tiles.CopyTo(sav.MapDesignTileData(rel.Item1, rel.Item2).Span); + } + else if (tiles.Length == DesignDumpSizeBelow31) + { + byte[] padBytes = BitConverter.GetBytes(MainSave.MapDesignNone); + int len = (DesignDumpSizeAbove31 - DesignDumpSizeBelow31) / 2; + + tiles = ArrayUtil.PadLeft(tiles, tiles.Length + len, padBytes); + tiles = ArrayUtil.PadRight(tiles, tiles.Length + len, padBytes); + + using var sfd = new SaveFileDialog(); + sfd.Filter = "nhmd file (*.nhmd)|*.nhmd"; + sfd.FileName = "Island MyDesignMap.nhmd"; + if (sfd.ShowDialog() != DialogResult.OK) + return false; + + string path2 = sfd.FileName; + + File.WriteAllBytes(path2, tiles); + + tiles.CopyTo(sav.MapDesignTileData(rel.Item1, rel.Item2).Span); + } + // account for previous dumps that included extra data after the tile data (oops) + // this can be removed in the future once enough time has passed that everyone has + // re-dumped with the fixed version, but for now it prevents data loss + else if (tiles.Length > DesignDumpSizeAbove31) + { + Span trimTiles = tiles.AsSpan().Slice(0, DesignDumpSizeAbove31); + tiles = trimTiles.ToArray(); + var msg1 = MessageStrings.MsgDataSizeMismatchImport; + var msg2 = MessageStrings.MsgDataTrimmedWarning; + WinFormsUtil.Alert(string.Format(msg1, tiles.Length, DesignDumpSizeAbove31) + " " + msg2); + + using var sfd = new SaveFileDialog(); + sfd.Filter = "nhmd file (*.nhmd)|*.nhmd"; + sfd.FileName = "Island MyDesignMap.nhmd"; + if (sfd.ShowDialog() != DialogResult.OK) + return false; + + string path2 = sfd.FileName; + + File.WriteAllBytes(path2, tiles); + + tiles.CopyTo(sav.MapDesignTileData(rel.Item1, rel.Item2).Span); + } + else + { + var msg = MessageStrings.MsgDataSizeMismatchImport; + WinFormsUtil.Error(string.Format(msg, tiles.Length, DesignDumpSizeAbove31)); + return false; + } + } + else + { + if (tiles.Length == DesignDumpSizeBelow31) + { + tiles.CopyTo(sav.MapDesignTileData(rel.Item1, rel.Item2).Span); + } + else if (tiles.Length == DesignDumpSizeAbove31) + { + int trimAmount = (DesignDumpSizeAbove31 - DesignDumpSizeBelow31) / 2; + int newLength = tiles.Length - (2 * trimAmount); + + tiles = tiles.AsSpan(trimAmount, newLength).ToArray(); + + tiles.CopyTo(sav.MapDesignTileData(rel.Item1, rel.Item2).Span); + } + else + { + var msg = MessageStrings.MsgDataSizeMismatchImport; + WinFormsUtil.Error(string.Format(msg, tiles.Length, DesignDumpSizeBelow31)); + return false; + } + } + + return true; + } + public static bool DumpMapDesigns(MainSave sav, Tuple size) + { + using var sfd = new SaveFileDialog(); + sfd.Filter = "nhmd file (*.nhmd)|*.nhmd"; + sfd.FileName = "Island MyDesignMap.nhmd"; + if (sfd.ShowDialog() != DialogResult.OK) + return false; + + string path = sfd.FileName; + + var tiles = sav.MapDesignTileData(size.Item1, size.Item2).Span; + + File.WriteAllBytes(path, tiles); + + return true; + } } \ No newline at end of file diff --git a/NHSE.WinForms/Util/MessageStrings.cs b/NHSE.WinForms/Util/MessageStrings.cs index 5653d4c..c949c11 100644 --- a/NHSE.WinForms/Util/MessageStrings.cs +++ b/NHSE.WinForms/Util/MessageStrings.cs @@ -7,6 +7,7 @@ public static class MessageStrings public static string MsgBackupCreateQuestion { get; set; } = "Would you NHSE to automatically keep a backup of your save data?"; public static string MsgDataSizeMismatchImport { get; set; } = "The size of the imported file (0x{0:X}) does not match the required size (0x{1:X})."; + public static string MsgDataTrimmedWarning { get; set; } = "It will be trimmed to fit."; public static string MsgDataSizeMismatchRAM { get; set; } = "Read size (0x{0:X}) != Write size (0x{1:X})."; public static string MsgDataDidNotOriginateFromHost_0 { get; set; } = "Imported data did not originate from Villager0 ({0})'s data.";