mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-08-22 06:57:42 -05:00
gen5: fix dex skin footer, fix musical terminator
Closes #4603 Add some filename import length sanitization
This commit is contained in:
@@ -213,6 +213,13 @@ protected ushort WriteExtSection(ReadOnlySpan<byte> data, int offset, int size,
|
||||
ArgumentOutOfRangeException.ThrowIfNotEqual(data.Length, size);
|
||||
SetData(data, offset);
|
||||
|
||||
return RefreshExtSectionFooter(offset, size, count);
|
||||
}
|
||||
|
||||
private ushort RefreshExtSectionFooter(int offset, int size, ushort count)
|
||||
{
|
||||
var data = Data.Slice(offset, size);
|
||||
|
||||
// Update Tail Section
|
||||
ushort chk = Checksums.CRC16_CCITT(data);
|
||||
var tail = Data[(offset + size)..];
|
||||
@@ -292,8 +299,9 @@ public void SetMusical(ReadOnlySpan<byte> data, ushort count = 1)
|
||||
public void SetPokeDexSkin(ReadOnlySpan<byte> data, ushort count = 1)
|
||||
{
|
||||
WriteExtSection(data, ExtPokeDexSkinOffset, PokeDexSkin5.SIZE, count);
|
||||
IsAvailablePokedexSkin = true; // checksum might be changed via this, need to refresh footer to be safe
|
||||
RefreshExtSectionFooter(ExtPokeDexSkinOffset, PokeDexSkin5.SIZE, count);
|
||||
PlayerData.UpdateExtData(ExtDataSectionNote5.PokedexSkin, count);
|
||||
IsAvailablePokedexSkin = true;
|
||||
}
|
||||
|
||||
private Span<byte> DexSkinFooter => Data.Slice(ExtPokeDexSkinOffset + PokeDexSkin5.SIZE - 4, 4);
|
||||
|
||||
@@ -6,12 +6,12 @@ public sealed class Musical5(SAV5 SAV, Memory<byte> raw) : SaveBlock<SAV5>(SAV,
|
||||
{
|
||||
public const int MusicalNameMaxLength = 20;
|
||||
|
||||
private Span<byte> MusicalTrash => Data.Slice(0x208, MusicalNameMaxLength * 2);
|
||||
private Span<byte> MusicalTrash => Data.Slice(0x208, MusicalNameMaxLength * 2 + 2); // require terminator, so +2 for 0xFFFF
|
||||
|
||||
public string MusicalName
|
||||
{
|
||||
get => StringConverter5.GetString(MusicalTrash);
|
||||
set => StringConverter5.SetString(MusicalTrash, value, MusicalNameMaxLength, 0, StringConverterOption.ClearZero);
|
||||
set => StringConverter5.SetString(MusicalTrash, value, MusicalNameMaxLength, SAV.Language, StringConverterOption.ClearZero);
|
||||
}
|
||||
|
||||
private const int PropOffset = 0x258;
|
||||
|
||||
@@ -180,6 +180,38 @@ private static void ExportFile(string extension, string name, ReadOnlySpan<byte>
|
||||
File.WriteAllBytes(sfd.FileName, data);
|
||||
}
|
||||
|
||||
private static string GetImportedMusicalName(string path)
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(path).Trim();
|
||||
|
||||
var split = name.LastIndexOf(" - ", StringComparison.Ordinal);
|
||||
if (split >= 0 && split + 3 < name.Length)
|
||||
name = name[(split + 3)..].Trim();
|
||||
|
||||
var suffix = name.LastIndexOf(" (", StringComparison.Ordinal);
|
||||
if (suffix > 0 && name[^1] == ')' && IsLikelyLanguageTag(name[(suffix + 2)..^1]))
|
||||
name = name[..suffix].TrimEnd();
|
||||
|
||||
if (name.Length > Musical5.MusicalNameMaxLength)
|
||||
name = name[..Musical5.MusicalNameMaxLength].TrimEnd();
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static bool IsLikelyLanguageTag(ReadOnlySpan<char> value)
|
||||
{
|
||||
if (value.Length is < 2 or > 5)
|
||||
return false;
|
||||
|
||||
foreach (var c in value)
|
||||
{
|
||||
if ((uint)(c - 'A') > 'Z' - 'A')
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void B_ImportPNGCGear_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var ofd = new OpenFileDialog();
|
||||
@@ -355,7 +387,7 @@ private void B_MusicalImport_Click(object sender, EventArgs e)
|
||||
var musical = new MusicalShow5(data);
|
||||
SAV.SetMusical(data);
|
||||
if (LastImportedFile is { } name)
|
||||
SAV.Musical.MusicalName = musical.IsUninitialized ? "" : Path.GetFileNameWithoutExtension(name).Trim();
|
||||
SAV.Musical.MusicalName = musical.IsUninitialized ? "" : GetImportedMusicalName(name);
|
||||
}
|
||||
|
||||
private void B_MusicalExport_Click(object sender, EventArgs e)
|
||||
|
||||
29
Tests/PKHeX.Core.Tests/Saves/SAV5DLC.cs
Normal file
29
Tests/PKHeX.Core.Tests/Saves/SAV5DLC.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core.Tests.Saves;
|
||||
|
||||
public class SAV5DLC
|
||||
{
|
||||
[Fact]
|
||||
public void SetPokeDexSkin_RecomputesChecksumAfterSettingAvailabilityFlag()
|
||||
{
|
||||
var sav = new SAV5B2W2();
|
||||
|
||||
// Create a random garbage PokeDex skin data with the last 4 bytes reserved for the availability flag and checksum
|
||||
byte[] data = new byte[PokeDexSkin5.SIZE];
|
||||
for (int i = 0; i < data.Length - sizeof(uint); i++)
|
||||
data[i] = (byte)i;
|
||||
|
||||
sav.SetPokeDexSkin(data);
|
||||
|
||||
sav.IsAvailablePokedexSkin.Should().BeTrue();
|
||||
ReadUInt32LittleEndian(sav.PokedexSkinData.Span[^sizeof(uint)..]).Should().Be(1u);
|
||||
|
||||
const int offset = 0x6D800;
|
||||
var tail = sav.Data[(offset + PokeDexSkin5.SIZE)..];
|
||||
ReadUInt16LittleEndian(tail).Should().Be(1);
|
||||
ReadUInt16LittleEndian(tail[2..]).Should().Be(Checksums.CRC16_CCITT(sav.PokedexSkinData.Span));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user