Skip to content

Commit

Permalink
fix(HSReplay): invalid deck ids when using Zilliax Deluxe 3000 cosmetics
Browse files Browse the repository at this point in the history
  • Loading branch information
beheh committed Apr 23, 2024
1 parent 45af072 commit 65ae843
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 54 deletions.
19 changes: 14 additions & 5 deletions HDTTests/HsReplay/Utility/ShortIdHelperTests.cs
Expand Up @@ -18,7 +18,7 @@ public void BasicMageDeck_ValidShortId()
var hearthDbDeck = DeckSerializer.Deserialize("AAECAf0EAA9NWtgBtAK7AosDqwS0BOAE+wSWBYoH7AeeCZYNAA==");
var deck = HearthDbConverter.FromHearthDbDeck(hearthDbDeck);
var shortid = ShortIdHelper.GetShortId(deck);
Assert.AreEqual(shortid, "KtYDbAkM8IjSSyLMWQn5zb");
Assert.AreEqual("KtYDbAkM8IjSSyLMWQn5zb", shortid);
}

[TestMethod]
Expand All @@ -27,7 +27,7 @@ public void ComboPriest_ValidShortId()
var hearthDbDeck = DeckSerializer.Deserialize("AAECAa0GBqUJ+wzl9wLQ/gKnhwOppQMM+ALlBPYH1QjRCtIK8gz3DK+lA9KlA/2nA4SoAwA=");
var deck = HearthDbConverter.FromHearthDbDeck(hearthDbDeck);
var shortid = ShortIdHelper.GetShortId(deck);
Assert.AreEqual(shortid, "0gsPp02q8ajKsGVstRwLpb");
Assert.AreEqual("0gsPp02q8ajKsGVstRwLpb", shortid);
}

[TestMethod]
Expand All @@ -36,7 +36,7 @@ public void HighlanderHunter_ValidShortId()
var hearthDbDeck = DeckSerializer.Deserialize("AAECAR8engGoArUDxwOHBMkErgbFCNsJ7Qn+DJjwAp7wAu/xAqCAA6eCA5uFA/WJA+aWA/mWA76YA7acA56dA/yjA+WkA5+lA6KlA6alA4SnA5+3AwAA");
var deck = HearthDbConverter.FromHearthDbDeck(hearthDbDeck);
var shortid = ShortIdHelper.GetShortId(deck);
Assert.AreEqual(shortid, "O2VpFDQokIrwww8iOmE3Lc");
Assert.AreEqual("O2VpFDQokIrwww8iOmE3Lc", shortid);
}

[TestMethod]
Expand All @@ -45,7 +45,7 @@ public void CardIdCollisionDeck_ValidShortId()
var hearthDbDeck = DeckSerializer.Deserialize("AAEBAf0EHnGeAcABwwG7Au4CqwSWBewF9w2JDroWwxbXtgLrugLYuwLZuwKHvQLBwQKP0wKi0wLu9gKnggP1iQP8owOSpAO+pAO/pAPdqQP0qwMAAA==");
var deck = HearthDbConverter.FromHearthDbDeck(hearthDbDeck);
var shortid = ShortIdHelper.GetShortId(deck);
Assert.AreEqual(shortid, "mA9O7wXvzxMsx1KO7EuFve");
Assert.AreEqual("mA9O7wXvzxMsx1KO7EuFve", shortid);
}

[TestMethod]
Expand All @@ -54,7 +54,16 @@ public void ETCDeck_ValidShortId()
var hearthDbDeck = DeckSerializer.Deserialize("AAECAfHhBAjlsASJ5gSP7QSX7wSE9gTipAX9xAXIxwUQlrcE9OME/eMEieQElOQEh/YEsvcEs/cEtvoEq4AFopkF8cIF4MgF1c4Fj+QF0Z4GAAEDuNkE/cQF/+EE/cQF76IF/cQFAAA=");
var deck = HearthDbConverter.FromHearthDbDeck(hearthDbDeck);
var shortid = ShortIdHelper.GetShortId(deck);
Assert.AreEqual(shortid, "QqdKc0MXat74LUQguHM3N");
Assert.AreEqual("QqdKc0MXat74LUQguHM3N", shortid);
}

[TestMethod]
public void ZilliaxDeluxe3000Cosmetic_ValidShortId()
{
var hearthDbDeck = DeckSerializer.Deserialize("AAECAYO6AgLawwXHpAYOkZ8E958E2dAFv/cFpvgF5voFofwFyYAGyJQGvZ4G7p4G2aIGracGkuYGAAED8rMGx6QG9LMGx6QG6N4Gx6QGAAA=");
var deck = HearthDbConverter.FromHearthDbDeck(hearthDbDeck);
var shortid = ShortIdHelper.GetShortId(deck);
Assert.AreEqual("Wk9FoQAUWvzTh5KbKEE0d", shortid);
}

[TestMethod]
Expand Down
106 changes: 57 additions & 49 deletions Hearthstone Deck Tracker/HsReplay/Utility/ShortIdHelper.cs
Expand Up @@ -8,69 +8,77 @@
using Hearthstone_Deck_Tracker.Hearthstone;
using Hearthstone_Deck_Tracker.Utility.Logging;

namespace Hearthstone_Deck_Tracker.HsReplay.Utility
namespace Hearthstone_Deck_Tracker.HsReplay.Utility;

public static class ShortIdHelper
{
public static class ShortIdHelper
private const string Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static readonly int AlphabetLength = Alphabet.Length;

private static string MapSideboardCardToId(Card card)
{
private const string Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static readonly int AlphabetLength = Alphabet.Length;
// We always consider Zilliax Deluxe 3000 with his default cosmetic
if(card.ZilliaxCustomizableCosmeticModule)
return "TOY_330t5";

public static string GetShortId(Deck deck)
return card.Id;
}

public static string GetShortId(Deck? deck)
{
if(deck == null || deck.Cards.Count == 0)
return string.Empty;
try
{
if(deck == null || deck.Cards.Count == 0)
return string.Empty;
try
var ids = deck.Cards.SelectMany(c => Enumerable.Repeat(c.Id, c.Count));
var idString = string.Join(",", ids.OrderBy(x => x, new Utf8StringComperer()));
foreach (var sideboard in deck.Sideboards.OrderBy(c => c.OwnerCardId))
{
var ids = deck.Cards.SelectMany(c => Enumerable.Repeat(c.Id, c.Count));
var idString = string.Join(",", ids.OrderBy(x => x, new Utf8StringComperer()));
foreach (var sideboard in deck.Sideboards.OrderBy(c => c.OwnerCardId))
{
var sideboardIds = sideboard.Cards.SelectMany(c => Enumerable.Repeat(c.Id, c.Count));
var sideboardCardsIdString = string.Join(",", sideboardIds.OrderBy(x => x, new Utf8StringComperer()));
idString += $"/{sideboard.OwnerCardId}:{sideboardCardsIdString}";
}
var bytes = Encoding.UTF8.GetBytes(idString);
var hash = MD5.Create().ComputeHash(bytes);
var hex = BitConverter.ToString(hash).Replace("-", string.Empty);
return IntToString(BigInteger.Parse("00" + hex, NumberStyles.HexNumber));
}
catch(Exception e)
{
Log.Error(e);
return string.Empty;
var sideboardIds = sideboard.Cards.SelectMany(c => Enumerable.Repeat(MapSideboardCardToId(c), c.Count));
var sideboardCardsIdString = string.Join(",", sideboardIds.OrderBy(x => x, new Utf8StringComperer()));
idString += $"/{sideboard.OwnerCardId}:{sideboardCardsIdString}";
}
var bytes = Encoding.UTF8.GetBytes(idString);
var hash = MD5.Create().ComputeHash(bytes);
var hex = BitConverter.ToString(hash).Replace("-", string.Empty);
return IntToString(BigInteger.Parse("00" + hex, NumberStyles.HexNumber));
}

private static string IntToString(BigInteger number)
catch(Exception e)
{
var sb = new StringBuilder();
while(number > 0)
{
var mod = number % AlphabetLength;
sb.Append(Alphabet[(int)mod]);
number = number / AlphabetLength;
}
return sb.ToString();
Log.Error(e);
return string.Empty;
}
}

public class Utf8StringComperer : IComparer<string>
private static string IntToString(BigInteger number)
{
private const string Chars = "_0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";

public int Compare(string x, string y)
var sb = new StringBuilder();
while(number > 0)
{
if(x == y)
return 0;
var val1 = x.ToLower().Zip(y.ToLower(), Utf8Compare).FirstOrDefault(v => v != 0);
if(val1 != 0)
return val1;
var val2 = x.Zip(y, Utf8Compare).FirstOrDefault(v => v != 0);
if(val2 != 0)
return val2;
return x.Length - y.Length;
var mod = number % AlphabetLength;
sb.Append(Alphabet[(int)mod]);
number = number / AlphabetLength;
}
return sb.ToString();
}
}

private int Utf8Compare(char x, char y) => Chars.IndexOf(x) - Chars.IndexOf(y);
public class Utf8StringComperer : IComparer<string>
{
private const string Chars = "_0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";

public int Compare(string x, string y)
{
if(x == y)
return 0;
var val1 = x.ToLower().Zip(y.ToLower(), Utf8Compare).FirstOrDefault(v => v != 0);
if(val1 != 0)
return val1;
var val2 = x.Zip(y, Utf8Compare).FirstOrDefault(v => v != 0);
if(val2 != 0)
return val2;
return x.Length - y.Length;
}

private int Utf8Compare(char x, char y) => Chars.IndexOf(x) - Chars.IndexOf(y);
}

0 comments on commit 65ae843

Please sign in to comment.