mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-04-24 07:08:35 -05:00
Split PathUtil from Util, add more xmldoc
This commit is contained in:
parent
5b70ca0397
commit
ccfa58e5f1
|
|
@ -119,7 +119,7 @@ private static int GetSlotCountForBox(int boxSlotCount, int box, int total)
|
|||
private static string GetFolderName(SaveFile sav, int box, BoxExportFolderNaming mode)
|
||||
{
|
||||
var boxName = sav is IBoxDetailNameRead r ? r.GetBoxName(box) : BoxDetailNameExtensions.GetDefaultBoxName(box);
|
||||
boxName = Util.CleanFileName(boxName);
|
||||
boxName = PathUtil.CleanFileName(boxName);
|
||||
return mode switch
|
||||
{
|
||||
BoxExportFolderNaming.BoxName => boxName,
|
||||
|
|
@ -132,7 +132,7 @@ private static string GetFolderName(SaveFile sav, int box, BoxExportFolderNaming
|
|||
private static string GetFileName(PKM pk, BoxExportIndexPrefix mode, IFileNamer<PKM> namer, int box, int slot, int boxSlotCount)
|
||||
{
|
||||
var slotName = GetInnerName(namer, pk);
|
||||
var fileName = Util.CleanFileName(slotName);
|
||||
var fileName = PathUtil.CleanFileName(slotName);
|
||||
var prefix = GetPrefix(mode, box, slot, boxSlotCount);
|
||||
|
||||
return $"{prefix}{fileName}.{pk.Extension}";
|
||||
|
|
@ -152,7 +152,7 @@ private static string GetInnerName(IFileNamer<PKM> namer, PKM pk)
|
|||
try
|
||||
{
|
||||
var slotName = namer.GetName(pk);
|
||||
return Util.CleanFileName(slotName);
|
||||
return PathUtil.CleanFileName(slotName);
|
||||
}
|
||||
catch { return "Name Error"; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ public IReadOnlyList<ComboItem> GetLocationList(GameVersion version, EntityConte
|
|||
return MetGen2;
|
||||
|
||||
IReadOnlyList<ComboItem> result;
|
||||
if (egg && version < W && context.Generation() >= 5)
|
||||
if (egg && version < W && context is not (EntityContext.Gen3 or EntityContext.Gen4))
|
||||
result = MetGen4;
|
||||
else
|
||||
result = GetLocationListInternal(version, context);
|
||||
|
|
|
|||
|
|
@ -13,21 +13,26 @@ namespace PKHeX.Core;
|
|||
public sealed class EvolutionTree : EvolutionNetwork
|
||||
{
|
||||
public const int MaxEvolutions = 3;
|
||||
public static readonly EvolutionTree Evolves1 = GetViaSpecies (PersonalTable.Y, EvolutionSet.GetArray(GetReader("g1", "g1"u8)));
|
||||
public static readonly EvolutionTree Evolves2 = GetViaSpecies (PersonalTable.C, EvolutionSet.GetArray(GetReader("g2", "g2"u8)));
|
||||
public static readonly EvolutionTree Evolves3 = GetViaSpecies (PersonalTable.RS, EvolutionSet.GetArray(GetReader("g3", "g3"u8)));
|
||||
public static readonly EvolutionTree Evolves4 = GetViaSpecies (PersonalTable.DP, EvolutionSet.GetArray(GetReader("g4", "g4"u8)));
|
||||
public static readonly EvolutionTree Evolves5 = GetViaSpecies (PersonalTable.BW, EvolutionSet.GetArray(GetReader("g5", "g5"u8)));
|
||||
public static readonly EvolutionTree Evolves6 = GetViaSpecies (PersonalTable.AO, EvolutionSet.GetArray(GetReader("g6", "g6"u8)));
|
||||
public static readonly EvolutionTree Evolves7 = GetViaPersonal(PersonalTable.USUM, EvolutionSet.GetArray(GetReader("uu", "uu"u8)));
|
||||
public static readonly EvolutionTree Evolves7b = GetViaPersonal(PersonalTable.GG, EvolutionSet.GetArray(GetReader("gg", "gg"u8)));
|
||||
public static readonly EvolutionTree Evolves8 = GetViaPersonal(PersonalTable.SWSH, EvolutionSet.GetArray(GetReader("ss", "ss"u8)));
|
||||
public static readonly EvolutionTree Evolves8a = GetViaPersonal(PersonalTable.LA, EvolutionSet.GetArray(GetReader("la", "la"u8), 0));
|
||||
public static readonly EvolutionTree Evolves8b = GetViaPersonal(PersonalTable.BDSP, EvolutionSet.GetArray(GetReader("bs", "bs"u8)));
|
||||
public static readonly EvolutionTree Evolves9 = GetViaPersonal(PersonalTable.SV, EvolutionSet.GetArray(GetReader("sv", "sv"u8)));
|
||||
public static readonly EvolutionTree Evolves1 = GetViaSpecies (PersonalTable.Y, Get("g1", "g1"u8));
|
||||
public static readonly EvolutionTree Evolves2 = GetViaSpecies (PersonalTable.C, Get("g2", "g2"u8));
|
||||
public static readonly EvolutionTree Evolves3 = GetViaSpecies (PersonalTable.RS, Get("g3", "g3"u8));
|
||||
public static readonly EvolutionTree Evolves4 = GetViaSpecies (PersonalTable.DP, Get("g4", "g4"u8));
|
||||
public static readonly EvolutionTree Evolves5 = GetViaSpecies (PersonalTable.BW, Get("g5", "g5"u8));
|
||||
public static readonly EvolutionTree Evolves6 = GetViaSpecies (PersonalTable.AO, Get("g6", "g6"u8));
|
||||
public static readonly EvolutionTree Evolves7 = GetViaPersonal(PersonalTable.USUM, Get("uu", "uu"u8));
|
||||
public static readonly EvolutionTree Evolves7b = GetViaPersonal(PersonalTable.GG, Get("gg", "gg"u8));
|
||||
public static readonly EvolutionTree Evolves8 = GetViaPersonal(PersonalTable.SWSH, Get("ss", "ss"u8));
|
||||
public static readonly EvolutionTree Evolves8a = GetViaPersonal(PersonalTable.LA, Get("la", "la"u8, 0));
|
||||
public static readonly EvolutionTree Evolves8b = GetViaPersonal(PersonalTable.BDSP, Get("bs", "bs"u8));
|
||||
public static readonly EvolutionTree Evolves9 = GetViaPersonal(PersonalTable.SV, Get("sv", "sv"u8));
|
||||
|
||||
private static EvolutionMethod[][] Get([ConstantExpected] string resource, [Length(2, 2)] ReadOnlySpan<byte> identifier, [ConstantExpected] byte levelUp = 1)
|
||||
{
|
||||
var data = Util.GetBinaryResource($"evos_{resource}.pkl");
|
||||
var bla = BinLinkerAccessor16.Get(data, identifier);
|
||||
return EvolutionSet.GetArray(bla, levelUp);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> GetResource([ConstantExpected] string resource) => Util.GetBinaryResource($"evos_{resource}.pkl");
|
||||
private static BinLinkerAccessor16 GetReader([ConstantExpected] string resource, [Length(2, 2)] ReadOnlySpan<byte> identifier) => BinLinkerAccessor16.Get(GetResource(resource), identifier);
|
||||
private EvolutionTree(IEvolutionForward forward, IEvolutionReverse reverse) : base(forward, reverse) { }
|
||||
|
||||
private static EvolutionTree GetViaSpecies(IPersonalTable t, EvolutionMethod[][] entries)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace PKHeX.Core;
|
|||
/// Stores parsed data about how a move was learned.
|
||||
/// </summary>
|
||||
/// <param name="Info">Info about the game it was learned in.</param>
|
||||
/// <param name="EvoStage">Evolution stage index within the <see cref="MoveLearnInfo.Environment"/> evolution list it existed in.</param>
|
||||
/// <param name="EvoStage">Evolution stage index within the <see cref="EntityContext"/> evolution list it existed in.</param>
|
||||
/// <param name="Generation">Rough indicator of generation the <see cref="MoveLearnInfo.Environment"/> was.</param>
|
||||
/// <param name="Expect">Optional value used when the move is not legal, to indicate that another move ID should have been in that move slot instead.</param>
|
||||
public readonly record struct MoveResult(MoveLearnInfo Info, byte EvoStage = 0, byte Generation = 0, ushort Expect = 0)
|
||||
|
|
@ -15,7 +15,8 @@ namespace PKHeX.Core;
|
|||
public bool IsParsed => this != default;
|
||||
public bool Valid => Info.Method.IsValid();
|
||||
|
||||
internal MoveResult(LearnMethod method, LearnEnvironment game = 0) : this(new MoveLearnInfo(method, game), Generation: game.GetGeneration()) { }
|
||||
internal MoveResult(LearnMethod method, LearnEnvironment game) : this(new MoveLearnInfo(method, game), Generation: game.GetGeneration()) { }
|
||||
private MoveResult(LearnMethod method) : this(new MoveLearnInfo(method, LearnEnvironment.None)) { }
|
||||
|
||||
public string Summary(ISpeciesForm current, EvolutionHistory history)
|
||||
{
|
||||
|
|
@ -66,6 +67,7 @@ private EvoCriteria GetDetail(EvolutionHistory history)
|
|||
public static readonly MoveResult Duplicate = new(LearnMethod.Duplicate);
|
||||
public static readonly MoveResult EmptyInvalid = new(LearnMethod.EmptyInvalid);
|
||||
public static readonly MoveResult Sketch = new(LearnMethod.Sketch);
|
||||
|
||||
public static MoveResult Unobtainable(ushort expect) => new(LearnMethod.UnobtainableExpect) { Expect = expect };
|
||||
public static MoveResult Unobtainable() => new(LearnMethod.Unobtainable);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ namespace PKHeX.Core;
|
|||
/// </summary>
|
||||
internal static class LearnVerifier
|
||||
{
|
||||
private static readonly MoveResult Duplicate = new(LearnMethod.Duplicate);
|
||||
private static readonly MoveResult EmptyInvalid = new(LearnMethod.EmptyInvalid);
|
||||
|
||||
public static void Verify(Span<MoveResult> result, PKM pk, IEncounterTemplate enc, EvolutionHistory history)
|
||||
{
|
||||
// Clear any existing parse results.
|
||||
|
|
@ -46,7 +43,7 @@ private static void Finalize(Span<MoveResult> result, ReadOnlySpan<ushort> curre
|
|||
|
||||
// Can't have empty first move.
|
||||
if (current[0] == 0)
|
||||
result[0] = EmptyInvalid;
|
||||
result[0] = MoveResult.EmptyInvalid;
|
||||
}
|
||||
|
||||
private static void VerifyNoEmptyDuplicates(Span<MoveResult> result, ReadOnlySpan<ushort> current)
|
||||
|
|
@ -80,7 +77,7 @@ private static void FlagDuplicateMovesAfterIndex(Span<MoveResult> result, ReadOn
|
|||
{
|
||||
if (current[i] != move)
|
||||
continue;
|
||||
result[index] = Duplicate;
|
||||
result[index] = MoveResult.Duplicate;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +88,7 @@ private static void FlagEmptySlotsBeforeIndex(Span<MoveResult> result, ReadOnlyS
|
|||
{
|
||||
if (current[i] != 0)
|
||||
return;
|
||||
result[i] = EmptyInvalid;
|
||||
result[i] = MoveResult.EmptyInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,9 +114,6 @@ public static void ChangeFormArgument(this IFormArgument f, ushort species, byte
|
|||
public static uint GetFormArgumentMax(ushort species, byte form, EntityContext context)
|
||||
{
|
||||
var gen = context.Generation();
|
||||
if (gen <= 5)
|
||||
return 0;
|
||||
|
||||
return species switch
|
||||
{
|
||||
(int)Furfrou when form != 0 => 5,
|
||||
|
|
|
|||
|
|
@ -17,9 +17,12 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
|
|||
public abstract int SIZE_STORED { get; }
|
||||
public string Extension => GetType().Name.ToLowerInvariant();
|
||||
public abstract PersonalInfo PersonalInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Bytes in the data structure that are unused, either as alignment padding, or were reserved and never used.
|
||||
/// </summary>
|
||||
public virtual ReadOnlySpan<ushort> ExtraBytes => [];
|
||||
|
||||
// Internal Attributes set on creation
|
||||
public readonly byte[] Data; // Raw Storage
|
||||
|
||||
protected PKM(byte[] data) => Data = data;
|
||||
|
|
|
|||
|
|
@ -45,9 +45,22 @@ public static string[] GetFormList(ushort species, IReadOnlyList<string> types,
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether Mega Pokémon forms exist in the specified <see cref="EntityContext"/>.
|
||||
/// </summary>
|
||||
private static bool IsMegaContext(this EntityContext context) => context is Gen6 or Gen7 or Gen7b;
|
||||
|
||||
/// <summary>
|
||||
/// Used to indicate that the form list is a single form, so no name is specified.
|
||||
/// </summary>
|
||||
private static readonly string[] EMPTY = [string.Empty];
|
||||
|
||||
/// <summary>
|
||||
/// Lets Go, Pikachu! & Eevee! Starter form name.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Different from the "Partner Cap" form.
|
||||
/// </remarks>
|
||||
private const string Starter = nameof(Starter);
|
||||
|
||||
private static string[] GetFormsGen1(ushort species, IReadOnlyList<string> types, IReadOnlyList<string> forms, EntityContext context)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ public interface IRibbonSetAffixed
|
|||
|
||||
public static class AffixedRibbon
|
||||
{
|
||||
/// <summary>
|
||||
/// Value present when no ribbon is affixed.
|
||||
/// </summary>
|
||||
public const sbyte None = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the maximum allowable value for an affixed ribbon index.
|
||||
/// </summary>
|
||||
public const sbyte Max = (sbyte)RibbonIndex.MAX_COUNT - 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,17 +46,19 @@ public int MaxCount
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all ribbons available for the entity and their state.
|
||||
/// </summary>
|
||||
public static List<RibbonInfo> GetRibbonInfo(PKM pk)
|
||||
{
|
||||
// Get a list of all Ribbon Attributes in the PKM
|
||||
var riblist = new List<RibbonInfo>();
|
||||
var names = ReflectUtil.GetPropertiesStartWithPrefix(pk.GetType(), PropertyPrefix);
|
||||
foreach (var name in names)
|
||||
{
|
||||
object? RibbonValue = ReflectUtil.GetValue(pk, name);
|
||||
if (RibbonValue is bool b)
|
||||
var value = ReflectUtil.GetValue(pk, name);
|
||||
if (value is bool b)
|
||||
riblist.Add(new RibbonInfo(name, b));
|
||||
else if (RibbonValue is byte x)
|
||||
else if (value is byte x)
|
||||
riblist.Add(new RibbonInfo(name, x));
|
||||
}
|
||||
return riblist;
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ private static string GetFileName(string path, string bak)
|
|||
var fileName = Path.GetFileName(path);
|
||||
|
||||
// Trim off existing backup name if present
|
||||
var bakName = Util.CleanFileName(bak);
|
||||
var bakName = PathUtil.CleanFileName(bak);
|
||||
if (fileName.EndsWith(bakName, StringComparison.Ordinal))
|
||||
fileName = fileName[..^bakName.Length];
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ private static string TrimNames(string fileName, ReadOnlySpan<string> extensions
|
|||
|
||||
public string GetBackupFileName(string destDir)
|
||||
{
|
||||
return Path.Combine(destDir, Util.CleanFileName(BAKName));
|
||||
return Path.Combine(destDir, PathUtil.CleanFileName(BAKName));
|
||||
}
|
||||
|
||||
private void SetAsBlank()
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@ public static int DumpBoxes(this SaveFile sav, string path, bool boxFolders = fa
|
|||
if (boxFolders)
|
||||
{
|
||||
string boxName = sav is IBoxDetailName bn ? bn.GetBoxName(box) : BoxDetailNameExtensions.GetDefaultBoxName(box);
|
||||
boxName = Util.CleanFileName(boxName);
|
||||
boxName = PathUtil.CleanFileName(boxName);
|
||||
boxFolder = Path.Combine(path, boxName);
|
||||
Directory.CreateDirectory(boxFolder);
|
||||
}
|
||||
|
||||
var fileName = Util.CleanFileName(pk.FileName);
|
||||
var fileName = PathUtil.CleanFileName(pk.FileName);
|
||||
var fn = Path.Combine(boxFolder, fileName);
|
||||
if (File.Exists(fn))
|
||||
continue;
|
||||
|
|
@ -76,7 +76,7 @@ public static int DumpBox(this SaveFile sav, string path, int currentBox)
|
|||
if (pk.Species == 0 || !pk.Valid || box != currentBox)
|
||||
continue;
|
||||
|
||||
var fileName = Path.Combine(path, Util.CleanFileName(pk.FileName));
|
||||
var fileName = Path.Combine(path, PathUtil.CleanFileName(pk.FileName));
|
||||
if (File.Exists(fileName))
|
||||
continue;
|
||||
|
||||
|
|
|
|||
|
|
@ -128,15 +128,18 @@ public static ComboItem[] GetVariedCBListBall(ReadOnlySpan<string> itemNames, Re
|
|||
for (int i = 0; i < ballItemID.Length; i++)
|
||||
list[i] = new ComboItem(itemNames[ballItemID[i]], ballIndex[i]);
|
||||
|
||||
// 3 Balls are preferentially first, sort Master Ball with the rest Alphabetically.
|
||||
// First 3 Balls (Poke, Great, Ultra) are preferentially first, sort Master Ball with the rest Alphabetically.
|
||||
list.AsSpan(3).Sort(Comparer);
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Comparer for <see cref="ComboItem"/> based on the <see cref="ComboItem.Text"/> property.
|
||||
/// </summary>
|
||||
private static readonly FunctorComparer<ComboItem> Comparer =
|
||||
new((a, b) => string.CompareOrdinal(a.Text, b.Text));
|
||||
|
||||
private sealed class FunctorComparer<T>(Comparison<T> Comparison) : IComparer<T>
|
||||
private sealed class FunctorComparer<T>(Comparison<T> Comparison) : IComparer<T> where T : notnull
|
||||
{
|
||||
public int Compare(T? x, T? y)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for handling date and time values, including validation and conversion to/from seconds elapsed since a specific epoch.
|
||||
/// </summary>
|
||||
public static class DateUtil
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -102,12 +105,5 @@ public static DateOnly GetRandomDateWithin(DateOnly start, DateOnly end, Random
|
|||
/// <summary>
|
||||
/// Checks if the given time components represent a valid time.
|
||||
/// </summary>
|
||||
/// <param name="receivedHour"></param>
|
||||
/// <param name="receivedMinute"></param>
|
||||
/// <param name="receivedSecond"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsValidTime(byte receivedHour, byte receivedMinute, byte receivedSecond)
|
||||
{
|
||||
return receivedHour < 24u && receivedMinute < 60u && receivedSecond < 60u;
|
||||
}
|
||||
public static bool IsValidTime(byte hour, byte minute, byte second) => hour < 24u && minute < 60u && second < 60u;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,22 @@ public static long GetFileSize(string path)
|
|||
catch { return -1; } // Bad File / Locked
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely iterates over the elements of the specified <see cref="IEnumerable{T}"/>, handling exceptions during enumeration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method ensures that exceptions thrown during enumeration do not terminate the iteration prematurely.
|
||||
/// Instead, it logs the exception (if a <paramref name="log"/> action is provided) and continues iterating until the specified <paramref name="failOut"/> limit is reached.
|
||||
/// If the limit is exceeded, the iteration stops.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of elements in the source collection.</typeparam>
|
||||
/// <param name="source">The source collection to iterate over. Cannot be <see langword="null"/>.</param>
|
||||
/// <param name="failOut">
|
||||
/// The maximum number of consecutive exceptions allowed before the iteration is terminated.
|
||||
/// Must be greater than or equal to 0.
|
||||
/// </param>
|
||||
/// <param name="log">An optional action to log or handle exceptions that occur during enumeration. If <see langword="null"/>, exceptions are ignored.</param>
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> that yields elements from the source collection, skipping over elements that cause exceptions.</returns>
|
||||
public static IEnumerable<T> IterateSafe<T>(this IEnumerable<T> source, int failOut = 10, Action<Exception>? log = null)
|
||||
{
|
||||
using var enumerator = source.GetEnumerator();
|
||||
|
|
@ -306,7 +322,7 @@ public static string GetPKMTempFileName(PKM pk, bool encrypt)
|
|||
string fn = pk.FileNameWithoutExtension;
|
||||
string filename = fn + (encrypt ? $".ek{pk.Format}" : $".{pk.Extension}");
|
||||
|
||||
return Path.Combine(Path.GetTempPath(), Util.CleanFileName(filename));
|
||||
return Path.Combine(Path.GetTempPath(), PathUtil.CleanFileName(filename));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -342,8 +358,15 @@ public static string GetPKMTempFileName(PKM pk, bool encrypt)
|
|||
/// <param name="Count">Count of objects</param>
|
||||
public sealed record ConcatenatedEntitySet(Memory<byte> Data, int Count)
|
||||
{
|
||||
/// <summary>
|
||||
/// Size of each Entity in bytes.
|
||||
/// </summary>
|
||||
public int SlotSize => Data.Length / Count;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a specific slot of data from the concatenated set.
|
||||
/// </summary>
|
||||
/// <param name="index">Slot index to retrieve.</param>
|
||||
public Span<byte> GetSlot(int index)
|
||||
{
|
||||
var size = SlotSize;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ public static bool GetFlag(ReadOnlySpan<byte> arr, int offset, int bitIndex)
|
|||
return ((arr[offset] >> bitIndex) & 1) != 0;
|
||||
}
|
||||
|
||||
public static bool GetFlag(ReadOnlySpan<byte> arr, int index) => GetFlag(arr, index >> 3, index);
|
||||
/// <inheritdoc cref="GetFlag(ReadOnlySpan{byte}, int, int)"/>
|
||||
public static bool GetFlag(ReadOnlySpan<byte> arr, int bitIndex) => GetFlag(arr, bitIndex >> 3, bitIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the requested <see cref="bitIndex"/> value to the byte at <see cref="offset"/>.
|
||||
|
|
@ -36,8 +37,20 @@ public static void SetFlag(Span<byte> arr, int offset, int bitIndex, bool value)
|
|||
arr[offset] = (byte)newValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets or clears a specific bit in the provided byte span at the specified index.
|
||||
/// </summary>
|
||||
/// <remarks>This method modifies the byte span in place.
|
||||
/// Ensure that the span has sufficient capacity to accommodate the specified bit index.
|
||||
/// </remarks>
|
||||
/// <param name="arr">The span of bytes where the bit will be set or cleared.</param>
|
||||
/// <param name="index">The zero-based index of the bit to modify. Must be within the bounds of the span.</param>
|
||||
/// <param name="value"><see langword="true"/> to set the bit to 1; <see langword="false"/> to clear the bit to 0.</param>
|
||||
public static void SetFlag(Span<byte> arr, int index, bool value) => SetFlag(arr, index >> 3, index, value);
|
||||
|
||||
/// <inheritdoc cref="GetBitFlagArray(ReadOnlySpan{byte}, Span{bool})"/>
|
||||
/// <param name="data">The byte array containing the bit flags.</param>
|
||||
/// <param name="count">The number of bits to read from the byte array.</param>
|
||||
public static bool[] GetBitFlagArray(ReadOnlySpan<byte> data, int count)
|
||||
{
|
||||
var result = new bool[count];
|
||||
|
|
@ -45,14 +58,33 @@ public static bool[] GetBitFlagArray(ReadOnlySpan<byte> data, int count)
|
|||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a byte array into a boolean array, where each bit in the byte array corresponds to a boolean value in the result.
|
||||
/// </summary>
|
||||
/// <param name="data">The byte array containing the bit flags.</param>
|
||||
/// <param name="result">>The span to write the boolean values into.</param>
|
||||
public static void GetBitFlagArray(ReadOnlySpan<byte> data, Span<bool> result)
|
||||
{
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
result[i] = (data[i >> 3] & (1 << (i & 7))) != 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GetBitFlagArray(ReadOnlySpan{byte}, Span{bool})"/>
|
||||
public static bool[] GetBitFlagArray(ReadOnlySpan<byte> data) => GetBitFlagArray(data, data.Length << 3);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the bit flags in the specified byte array based on the provided boolean values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The method modifies the <paramref name="data"/> span in place.
|
||||
/// Each boolean value in <paramref name="value"/> corresponds to a bit in <paramref name="data"/>, with the first boolean value affecting the least significant bit of the first byte.
|
||||
/// Ensure that <paramref name="data"/> has enough bytes to store all bits from <paramref name="value"/>; otherwise, an <see cref="IndexOutOfRangeException"/> may occur.
|
||||
/// </remarks>
|
||||
/// <param name="data">A <see cref="Span{T}"/> of bytes where the bit flags will be set.</param>
|
||||
/// <param name="value">
|
||||
/// A <see cref="ReadOnlySpan{T}"/> of boolean values representing the bit flags to set.
|
||||
/// Each <see langword="true"/> value sets the corresponding bit to 1, and each <see langword="false"/> value sets it to 0.
|
||||
/// </param>
|
||||
public static void SetBitFlagArray(Span<byte> data, ReadOnlySpan<bool> value)
|
||||
{
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
|
|
|
|||
|
|
@ -42,16 +42,13 @@ private static string[] DumpStrings(Type t)
|
|||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current localization in a static class containing language-specific strings
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <inheritdoc cref="GetLocalization(Type, ReadOnlySpan{string})"/>
|
||||
public static string[] GetLocalization(Type t) => DumpStrings(t);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current localization in a static class containing language-specific strings
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <param name="t">Type of the static class containing the desired strings.</param>
|
||||
/// <param name="existingLines">Existing localization lines (if provided)</param>
|
||||
public static string[] GetLocalization(Type t, ReadOnlySpan<string> existingLines)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,8 +5,21 @@
|
|||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Logic for fetching data from the internet, such as text files or JSON data.
|
||||
/// </summary>
|
||||
public static class NetUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the content of the specified URL as a string.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method attempts to fetch the content of the specified URL and return it as a string.
|
||||
/// If the URL is inaccessible or an error occurs during the operation, the method returns <see langword="null"/>.
|
||||
/// The caller should handle the possibility of a <see langword="null"/> return value.
|
||||
/// </remarks>
|
||||
/// <param name="url">The <see cref="Uri"/> of the resource to retrieve.</param>
|
||||
/// <returns>A string containing the content of the resource, or <see langword="null"/> if the resource could not be retrieved or an error occurred.</returns>
|
||||
public static string? GetStringFromURL(Uri url)
|
||||
{
|
||||
try
|
||||
|
|
@ -26,13 +39,14 @@ public static class NetUtil
|
|||
}
|
||||
}
|
||||
|
||||
// The GitHub API will fail if no user agent is provided. Use a hardcoded one to avoid issues.
|
||||
private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
|
||||
|
||||
private static Stream? GetStreamFromURL(Uri url)
|
||||
{
|
||||
// The GitHub API will fail if no user agent is provided
|
||||
using var client = new HttpClient();
|
||||
client.Timeout = TimeSpan.FromSeconds(3);
|
||||
const string agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36";
|
||||
client.DefaultRequestHeaders.Add("User-Agent", agent);
|
||||
client.DefaultRequestHeaders.Add("User-Agent", UserAgent);
|
||||
var response = client.GetAsync(url).Result;
|
||||
return response.IsSuccessStatusCode ? response.Content.ReadAsStream() : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,16 @@
|
|||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public static partial class Util
|
||||
/// <summary>
|
||||
/// Logic for sanitizing file names and paths.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Converting raw data to file names, trusting the data can lead to filesystem issues with invalid characters.
|
||||
/// </remarks>
|
||||
public static class PathUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Cleans the local <see cref="fileName"/> by removing any invalid filename characters.
|
||||
/// Cleans the <see cref="fileName"/> by removing any invalid filename characters.
|
||||
/// </summary>
|
||||
/// <returns>New string without any invalid characters.</returns>
|
||||
public static string CleanFileName(string fileName)
|
||||
|
|
@ -28,6 +34,11 @@ public static string CleanFileName(ReadOnlySpan<char> fileName)
|
|||
return new string(result[..ctr]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wish this were a ReadOnlySpan<char> instead of a char[].
|
||||
/// </summary>
|
||||
private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars();
|
||||
|
||||
/// <summary>
|
||||
/// Removes any invalid filename characters from the input string.
|
||||
/// </summary>
|
||||
|
|
@ -36,7 +47,7 @@ public static string CleanFileName(ReadOnlySpan<char> fileName)
|
|||
/// <returns>Length of the cleaned string</returns>
|
||||
private static int GetCleanFileName(ReadOnlySpan<char> input, Span<char> output)
|
||||
{
|
||||
ReadOnlySpan<char> invalid = Path.GetInvalidFileNameChars();
|
||||
ReadOnlySpan<char> invalid = InvalidFileNameChars;
|
||||
int ctr = 0;
|
||||
foreach (var c in input)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,25 @@ public static partial class Util
|
|||
/// <inheritdoc cref="Random.Shared"/>
|
||||
public static Random Rand => Random.Shared;
|
||||
|
||||
/// <inheritdoc cref="Rand32(Random)"/>
|
||||
/// <remarks>Uses <see cref="Random.Shared"/> to generate the random number.</remarks>
|
||||
public static uint Rand32() => Rand32(Rand);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random 32-bit unsigned integer.
|
||||
/// </summary>
|
||||
/// <param name="rnd">The <see cref="Random"/> instance used to generate the random number.</param>
|
||||
/// <returns>A random 32-bit unsigned integer.</returns>
|
||||
public static uint Rand32(this Random rnd) => (uint)rnd.NextInt64();
|
||||
|
||||
/// <summary>
|
||||
/// Generates a 64-bit unsigned random number by combining two 32-bit random values.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method extends the <see cref="Random"/> class to provide a 64-bit random number by combining the results of two 32-bit random number generations.
|
||||
/// The lower 32 bits are derived from one call to <c>Rand32</c>, and the upper 32 bits are derived from another call.
|
||||
/// </remarks>
|
||||
/// <param name="rnd">The <see cref="Random"/> instance used to generate the random values.</param>
|
||||
/// <returns>A 64-bit unsigned integer representing the combined random value.</returns>
|
||||
public static ulong Rand64(this Random rnd) => rnd.Rand32() | ((ulong)rnd.Rand32() << 32);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,6 +178,19 @@ public static string GetStringResource(string name)
|
|||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the specified <see cref="ReadOnlySpan{T}"/> of characters into an array of strings, using newline characters ('\n') as delimiters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is optimized for performance and avoids unnecessary allocations by working directly with spans.
|
||||
/// It is suitable for scenarios where splitting large text data into lines is required.
|
||||
/// </remarks>
|
||||
/// <param name="s">The span of characters to split. Can include '\n' and '\r\n' as line breaks.</param>
|
||||
/// <returns>
|
||||
/// An array of strings, where each element represents a line of text from the input span.
|
||||
/// Lines ending with a carriage return ('\r') will have the '\r' removed.
|
||||
/// Returns an empty array if the input span is empty.
|
||||
/// </returns>
|
||||
private static string[] FastSplit(ReadOnlySpan<char> s)
|
||||
{
|
||||
// Get Count
|
||||
|
|
|
|||
|
|
@ -90,20 +90,15 @@ public static void LoadHexBytesTo(ReadOnlySpan<char> str, Span<byte> dest, int t
|
|||
|
||||
private static byte DecodeTuple(char _0, char _1)
|
||||
{
|
||||
byte result;
|
||||
if (char.IsAsciiDigit(_0))
|
||||
result = (byte)((_0 - '0') << 4);
|
||||
else if (char.IsAsciiHexDigitUpper(_0))
|
||||
result = (byte)((_0 - 'A' + 10) << 4);
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(_0));
|
||||
return (byte)(DecodeChar(_0) << 4 | DecodeChar(_1));
|
||||
|
||||
if (char.IsAsciiDigit(_1))
|
||||
result |= (byte)(_1 - '0');
|
||||
else if (char.IsAsciiHexDigitUpper(_1))
|
||||
result |= (byte)(_1 - 'A' + 10);
|
||||
else
|
||||
throw new ArgumentOutOfRangeException(nameof(_1));
|
||||
return result;
|
||||
static int DecodeChar(char x)
|
||||
{
|
||||
if (char.IsAsciiDigit(x))
|
||||
return (byte)(x - '0');
|
||||
if (char.IsAsciiHexDigitUpper(x))
|
||||
return (byte)(x - 'A' + 10);
|
||||
throw new ArgumentOutOfRangeException(nameof(_0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,17 +69,17 @@ public static uint GetHexValue(ReadOnlySpan<char> value)
|
|||
if (char.IsAsciiDigit(c))
|
||||
{
|
||||
result <<= 4;
|
||||
result += (uint)(c - '0');
|
||||
result |= (uint)(c - '0');
|
||||
}
|
||||
else if (char.IsAsciiHexDigitUpper(c))
|
||||
{
|
||||
result <<= 4;
|
||||
result += (uint)(c - 'A' + 10);
|
||||
result |= (uint)(c - 'A' + 10);
|
||||
}
|
||||
else if (char.IsAsciiHexDigitLower(c))
|
||||
{
|
||||
result <<= 4;
|
||||
result += (uint)(c - 'a' + 10);
|
||||
result |= (uint)(c - 'a' + 10);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
@ -101,29 +101,31 @@ public static ulong GetHexValue64(ReadOnlySpan<char> value)
|
|||
if (char.IsAsciiDigit(c))
|
||||
{
|
||||
result <<= 4;
|
||||
result += (uint)(c - '0');
|
||||
result |= (uint)(c - '0');
|
||||
}
|
||||
else if (char.IsAsciiHexDigitUpper(c))
|
||||
{
|
||||
result <<= 4;
|
||||
result += (uint)(c - 'A' + 10);
|
||||
result |= (uint)(c - 'A' + 10);
|
||||
}
|
||||
else if (char.IsAsciiHexDigitLower(c))
|
||||
{
|
||||
result <<= 4;
|
||||
result += (uint)(c - 'a' + 10);
|
||||
result |= (uint)(c - 'a' + 10);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a variable length hex string (non-spaced, bytes in order).
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="GetBytesFromHexString(ReadOnlySpan{char}, Span{byte})"/>
|
||||
public static byte[] GetBytesFromHexString(ReadOnlySpan<char> input)
|
||||
=> Convert.FromHexString(input);
|
||||
|
||||
/// <inheritdoc cref="GetBytesFromHexString(ReadOnlySpan{char})"/>
|
||||
/// <summary>
|
||||
/// Parses a variable length hex string (non-spaced, bytes in order).
|
||||
/// </summary>
|
||||
/// <param name="input">Hex string to parse</param>
|
||||
/// <param name="result">Buffer to write the result to</param>
|
||||
public static void GetBytesFromHexString(ReadOnlySpan<char> input, Span<byte> result)
|
||||
=> Convert.FromHexString(input, result, out _, out _);
|
||||
|
||||
|
|
@ -162,10 +164,7 @@ public static int GetOnlyHex(ReadOnlySpan<char> str, Span<char> result)
|
|||
return ctr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new string with each word converted to its appropriate title case.
|
||||
/// </summary>
|
||||
/// <param name="span">Input string to modify</param>
|
||||
/// <inheritdoc cref="ToTitleCase(ReadOnlySpan{char}, Span{char})"/>
|
||||
public static string ToTitleCase(ReadOnlySpan<char> span)
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
|
|
@ -176,7 +175,14 @@ public static string ToTitleCase(ReadOnlySpan<char> span)
|
|||
return new string(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ToTitleCase(ReadOnlySpan{char})"/>
|
||||
/// <summary>
|
||||
/// Returns a new string with each word converted to its appropriate title case.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Assumes that words are separated by whitespace characters. Duplicate whitespace are not skipped.
|
||||
/// </remarks>
|
||||
/// <param name="span">Input string to modify</param>
|
||||
/// <param name="result">>Buffer to write the result to</param>
|
||||
public static void ToTitleCase(ReadOnlySpan<char> span, Span<char> result)
|
||||
{
|
||||
// Add the first character as uppercase, then add each successive character as lowercase.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ public sealed class ValueTypeTypeConverter : ExpandableObjectConverter
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for converting a <see cref="uint"/> to an uppercase hex string and back.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When converting from a string, it accepts both "0x" prefixed and non-prefixed hex strings, with case insensitivity.
|
||||
/// </remarks>
|
||||
public sealed class TypeConverterU32 : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
|
|
@ -60,6 +66,12 @@ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destina
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for converting a <see cref="ulong"/> to an uppercase hex string and back.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When converting from a string, it accepts both "0x" prefixed and non-prefixed hex strings, with case insensitivity.
|
||||
/// </remarks>
|
||||
public sealed class TypeConverterU64 : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
|
|
|
|||
|
|
@ -958,7 +958,7 @@ public bool ExportBackup()
|
|||
return false;
|
||||
}
|
||||
|
||||
var suggestion = Util.CleanFileName(SAV.Metadata.BAKName);
|
||||
var suggestion = PathUtil.CleanFileName(SAV.Metadata.BAKName);
|
||||
using var sfd = new SaveFileDialog();
|
||||
sfd.FileName = suggestion;
|
||||
if (sfd.ShowDialog() != DialogResult.OK)
|
||||
|
|
|
|||
|
|
@ -901,7 +901,7 @@ private static string GetProgramTitle(SaveFile sav)
|
|||
return title + $"[{version}]";
|
||||
if (!sav.State.Exportable) // Blank save file
|
||||
return title + $"{sav.Metadata.FileName} [{sav.OT} ({version})]";
|
||||
return title + Path.GetFileNameWithoutExtension(Util.CleanFileName(sav.Metadata.BAKName)); // more descriptive
|
||||
return title + Path.GetFileNameWithoutExtension(PathUtil.CleanFileName(sav.Metadata.BAKName)); // more descriptive
|
||||
}
|
||||
|
||||
private static bool TryBackupExportCheck(SaveFile sav, string path)
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ private void ClickSet(object sender, EventArgs e)
|
|||
PKM pk = PKME_Tabs.PreparePKM();
|
||||
Directory.CreateDirectory(DatabasePath);
|
||||
|
||||
string path = Path.Combine(DatabasePath, Util.CleanFileName(pk.FileName));
|
||||
string path = Path.Combine(DatabasePath, PathUtil.CleanFileName(pk.FileName));
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
|
|
@ -510,7 +510,7 @@ private void Menu_Export_Click(object sender, EventArgs e)
|
|||
Directory.CreateDirectory(path);
|
||||
|
||||
foreach (var pk in Results.Select(z => z.Entity))
|
||||
File.WriteAllBytes(Path.Combine(path, Util.CleanFileName(pk.FileName)), pk.DecryptedPartyData);
|
||||
File.WriteAllBytes(Path.Combine(path, PathUtil.CleanFileName(pk.FileName)), pk.DecryptedPartyData);
|
||||
}
|
||||
|
||||
private void Menu_Import_Click(object sender, EventArgs e)
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ private void Menu_Export_Click(object sender, EventArgs e)
|
|||
|
||||
foreach (var gift in Results.OfType<DataMysteryGift>()) // WC3 have no data
|
||||
{
|
||||
var fileName = Util.CleanFileName(gift.FileName);
|
||||
var fileName = PathUtil.CleanFileName(gift.FileName);
|
||||
var path = Path.Combine(folder, fileName);
|
||||
var data = gift.Write();
|
||||
File.WriteAllBytes(path, data);
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ private void B_Export_Click(object sender, EventArgs e)
|
|||
tr = "Trainer";
|
||||
using var sfd = new SaveFileDialog();
|
||||
sfd.Filter = "Secret Base Data|*.sb6";
|
||||
sfd.FileName = $"{sb.BaseLocation:D2} - {Util.CleanFileName(tr)}.sb6";
|
||||
sfd.FileName = $"{sb.BaseLocation:D2} - {PathUtil.CleanFileName(tr)}.sb6";
|
||||
if (sfd.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ private void B_ExportGoFiles_Click(object sender, EventArgs e)
|
|||
|
||||
var folder = fbd.SelectedPath;
|
||||
foreach (var gpk in gofiles)
|
||||
File.WriteAllBytes(Path.Combine(folder, Util.CleanFileName(gpk.FileName)), gpk.Data);
|
||||
File.WriteAllBytes(Path.Combine(folder, PathUtil.CleanFileName(gpk.FileName)), gpk.Data);
|
||||
WinFormsUtil.Alert($"Dumped {gofiles.Length} files to {folder}");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ private async void BoxSlot_MouseDown(object? sender, MouseEventArgs e)
|
|||
// Create Temp File to Drag
|
||||
wc_slot = index;
|
||||
Cursor.Current = Cursors.Hand;
|
||||
string newfile = Path.Combine(Path.GetTempPath(), Util.CleanFileName(gift.FileName));
|
||||
string newfile = Path.Combine(Path.GetTempPath(), PathUtil.CleanFileName(gift.FileName));
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(newfile, gift.Write());
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ public static bool SavePKMDialog(PKM pk)
|
|||
using var sfd = new SaveFileDialog();
|
||||
sfd.Filter = genericFilter;
|
||||
sfd.DefaultExt = pkx;
|
||||
sfd.FileName = Util.CleanFileName(pk.FileName);
|
||||
sfd.FileName = PathUtil.CleanFileName(pk.FileName);
|
||||
if (sfd.ShowDialog() != DialogResult.OK)
|
||||
return false;
|
||||
|
||||
|
|
@ -422,7 +422,7 @@ public static bool ExportMGDialog(DataMysteryGift gift)
|
|||
{
|
||||
using var sfd = new SaveFileDialog();
|
||||
sfd.Filter = GetMysterGiftFilter(gift.Context);
|
||||
sfd.FileName = Util.CleanFileName(gift.FileName);
|
||||
sfd.FileName = PathUtil.CleanFileName(gift.FileName);
|
||||
if (sfd.ShowDialog() != DialogResult.OK)
|
||||
return false;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user