3 Commits
Author SHA1 Message Date
ds5678 0718c9c7c0 Update version to 1.1.0 in project properties
Bump the version number from 1.0.0 to 1.1.0 in the
project's configuration to reflect the latest changes.
2025-07-23 13:20:50 -07:00
ds5678 dda1975aa8 Reversal for UTF8 2025-07-23 13:20:12 -07:00
ds5678 b72e7ba10b Update dependencies and refactor test structure
- Upgraded NuGet packages in `AssetRipper.Checksum.Tests.csproj`, including `Microsoft.NET.Test.Sdk`, `NUnit`, and `coverlet.collector`.
- Changed `ReverseOutputRegex` from a method to a property in `ReversalTests.cs` for improved syntax.
- Replaced `Assert.Multiple` with `using (Assert.EnterMultipleScope())` for better assertion handling.
- Updated `System.IO.Hashing` package version in `AssetRipper.Checksum.csproj`.
- Changed target framework from `net8.0` to `net9.0` in `Directory.Build.props`.
2025-07-23 13:02:48 -07:00
6 changed files with 92 additions and 24 deletions

No files matched your search

@@ -1,18 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.2.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.9.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
+25 -11
View File
@@ -5,7 +5,7 @@ namespace AssetRipper.Checksum.Tests;
public partial class ReversalTests
{
[GeneratedRegex("[H-Wh-w]{6}[HJLN]")]
private static partial Regex ReverseOutputRegex();
private static partial Regex ReverseOutputRegex { get; }
[Test]
public void TestReversalSymmetryAndRegexMatching()
@@ -13,33 +13,47 @@ public partial class ReversalTests
for (int i = 0; i < 50; i++)
{
uint hash = RandomHash;
string reversed = Crc32Algorithm.ReverseAscii(hash);
Assert.Multiple(() =>
string reversed = Crc32Algorithm.Reverse(hash);
using (Assert.EnterMultipleScope())
{
Assert.That(Crc32Algorithm.HashAscii(reversed), Is.EqualTo(hash));
Assert.That(ReverseOutputRegex().IsMatch(reversed));
});
Assert.That(ReverseOutputRegex.IsMatch(reversed));
}
}
}
[Test]
public void ReversalLengthIsSeven()
{
string reversed = Crc32Algorithm.ReverseAscii(RandomHash);
string reversed = Crc32Algorithm.Reverse(RandomHash);
Assert.That(reversed, Has.Length.EqualTo(7));
}
[Test]
public void PrefixedReversal()
[TestCase("Prefix_")]
public void PrefixedReversal_Ascii(string prefix)
{
const string prefix = "Prefix_";
uint hash = RandomHash;
string reversed = Crc32Algorithm.ReverseAscii(hash, prefix);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(Crc32Algorithm.HashAscii(reversed), Is.EqualTo(hash));
Assert.That(reversed, Does.StartWith(prefix));
});
Assert.That(reversed, Has.Length.EqualTo(7 + prefix.Length));
}
}
[TestCase("Prefix_")]
[TestCase("\u60A8\u597D", Description = "Hello in Chinese")]
public void PrefixedReversal_UTF8(string prefix)
{
uint hash = RandomHash;
string reversed = Crc32Algorithm.ReverseUTF8(hash, prefix);
using (Assert.EnterMultipleScope())
{
Assert.That(Crc32Algorithm.HashUTF8(reversed), Is.EqualTo(hash));
Assert.That(reversed, Does.StartWith(prefix));
Assert.That(reversed, Has.Length.EqualTo(7 + prefix.Length));
}
}
private static uint RandomHash => TestContext.CurrentContext.Random.NextUInt();
@@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
<PackageReference Include="System.IO.Hashing" Version="9.0.7" />
</ItemGroup>
</Project>
+20 -4
View File
@@ -13,9 +13,9 @@ static partial class Crc32Algorithm
/// </remarks>
/// <param name="hash">A CRC32 checksum</param>
/// <returns>An Ascii string of length 7 with that <paramref name="hash"/></returns>
public static string ReverseAscii(uint hash)
public static string Reverse(uint hash)
{
return ReverseAscii(hash, default, defaultInitialMask);
return Reverse(hash, default, defaultInitialMask);
}
/// <summary>
@@ -31,10 +31,26 @@ static partial class Crc32Algorithm
public static string ReverseAscii(uint hash, ReadOnlySpan<char> prefix)
{
uint reverseInitialMask = HashAscii(prefix, HHHHHHH);
return ReverseAscii(hash, prefix, reverseInitialMask);
return Reverse(hash, prefix, reverseInitialMask);
}
private static string ReverseAscii(uint hash, ReadOnlySpan<char> prefix, uint reverseInitialMask)
/// <summary>
/// Find a conflicting UTF8 string with Gaussian Elimination
/// </summary>
/// <remarks>
/// The output is one-to-one and starts with <paramref name="prefix"/>. The suffix matches [H-Wh-w]{6}[HJLN] <br />
/// Based on <see href="https://gitlab.com/-/snippets/2369762">work</see> by <see href="https://gitlab.com/lox9973">lox9973</see> licensed under MIT.
/// </remarks>
/// <param name="hash">A CRC32 checksum</param>
/// <param name="prefix">A span of characters that will be prepended to the output</param>
/// <returns>An UTF8 string of length <paramref name="prefix"/>.Length + 7 with that <paramref name="hash"/></returns>
public static string ReverseUTF8(uint hash, ReadOnlySpan<char> prefix)
{
uint reverseInitialMask = HashUTF8(prefix, HHHHHHH);
return Reverse(hash, prefix, reverseInitialMask);
}
private static string Reverse(uint hash, ReadOnlySpan<char> prefix, uint reverseInitialMask)
{
uint b = hash ^ reverseInitialMask;
+38
View File
@@ -72,6 +72,16 @@ public static partial class Crc32Algorithm
return result;
}
private static uint HashUTF8(ReadOnlySpan<char> str1, ReadOnlySpan<char> str2)
{
crc ??= new();
crc.Reset();
crc.UpdateUTF8(str1);
crc.UpdateUTF8(str2);
return crc.FinishHash();
}
public static bool MatchAscii(ReadOnlySpan<char> data, uint hash)
{
return HashAscii(data) == hash;
@@ -91,6 +101,34 @@ public static partial class Crc32Algorithm
}
}
private static void UpdateUTF8(this Crc32 @this, ReadOnlySpan<char> data)
{
int count = (int)(uint)Encoding.UTF8.GetByteCount(data);
byte[]? rentedArray;
scoped Span<byte> buffer;
if (count > 1024)
{
rentedArray = ArrayPool<byte>.Shared.Rent(count);
buffer = new Span<byte>(rentedArray, 0, count);
}
else
{
rentedArray = null;
buffer = stackalloc byte[count];
}
int encoded = Encoding.UTF8.GetBytes(data, buffer);
Debug.Assert(encoded == count);
@this.Append(buffer);
if (rentedArray is not null)
{
ArrayPool<byte>.Shared.Return(rentedArray);
}
}
private static uint FinishHash(this Crc32 @this)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
+2 -2
View File
@@ -1,13 +1,13 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>Latest</LangVersion>
<IsAotCompatible>true</IsAotCompatible>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<Version>1.0.0</Version>
<Version>1.1.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<Copyright>Copyright © ds5678</Copyright>
<Authors>ds5678</Authors>