Hashing.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. using SongCore.Data;
  2. using System;
  3. using System.Collections.Concurrent;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Security.Cryptography;
  8. using System.Text;
  9. using SongCore.Arcache;
  10. namespace SongCore.Utilities
  11. {
  12. public class Hashing
  13. {
  14. internal static ConcurrentDictionary<string, SongHashData> cachedSongHashData = new ConcurrentDictionary<string, SongHashData>();
  15. internal static ConcurrentDictionary<string, AudioCacheData> cachedAudioData = new ConcurrentDictionary<string, AudioCacheData>();
  16. public static readonly string cachedHashDataPath = Path.Combine(IPA.Utilities.UnityGame.InstallPath, "UserData", "SongCore", "SongHashData.dat");
  17. public static readonly string cachedAudioDataPath = Path.Combine(IPA.Utilities.UnityGame.InstallPath, "UserData", "SongCore", "SongDurationCache.dat");
  18. public static void ReadCachedSongHashes()
  19. {
  20. //if (File.Exists(cachedHashDataPath))
  21. //{
  22. // cachedSongHashData = Newtonsoft.Json.JsonConvert.DeserializeObject<ConcurrentDictionary<string, SongHashData>>(File.ReadAllText(cachedHashDataPath));
  23. // if (cachedSongHashData == null) cachedSongHashData = new ConcurrentDictionary<string, SongHashData>();
  24. // Logging.Log($"Finished reading cached hashes for {cachedSongHashData.Count} songs!");
  25. //}
  26. }
  27. public static void UpdateCachedHashes(HashSet<string> currentSongPaths)
  28. {
  29. //UpdateCachedHashesInternal(currentSongPaths);
  30. }
  31. /// <summary>
  32. /// Intended for use in the Loader
  33. /// </summary>
  34. /// <param name="currentSongPaths"></param>
  35. internal static void UpdateCachedHashesInternal(ICollection<string> currentSongPaths)
  36. {
  37. foreach (var hashData in cachedSongHashData.ToArray())
  38. {
  39. if (!currentSongPaths.Contains(hashData.Key))
  40. cachedSongHashData.TryRemove(hashData.Key, out _);
  41. }
  42. Logging.Log($"Updating cached hashes for {cachedSongHashData.Count} songs!");
  43. File.WriteAllText(cachedHashDataPath, Newtonsoft.Json.JsonConvert.SerializeObject(cachedSongHashData));
  44. }
  45. public static void ReadCachedAudioData()
  46. {
  47. //if (File.Exists(cachedAudioDataPath))
  48. //{
  49. // cachedAudioData = Newtonsoft.Json.JsonConvert.DeserializeObject<ConcurrentDictionary<string, AudioCacheData>>(File.ReadAllText(cachedAudioDataPath));
  50. // if (cachedAudioData == null) cachedAudioData = new ConcurrentDictionary<string, AudioCacheData>();
  51. // Logging.Log($"Finished reading cached Durations for {cachedAudioData.Count} songs!");
  52. //}
  53. }
  54. public static void UpdateCachedAudioData(HashSet<string> currentSongPaths)
  55. {
  56. UpdateCachedAudioDataInternal(currentSongPaths);
  57. }
  58. /// <summary>
  59. /// Intended for use in the Loader
  60. /// </summary>
  61. /// <param name="currentSongPaths"></param>
  62. internal static void UpdateCachedAudioDataInternal(ICollection<string> currentSongPaths)
  63. {
  64. //foreach (var hashData in cachedAudioData.ToArray())
  65. //{
  66. // if (!currentSongPaths.Contains(hashData.Key))
  67. // cachedAudioData.TryRemove(hashData.Key, out _);
  68. //}
  69. //Logging.Log($"Updating cached Map Lengths for {cachedAudioData.Count} songs!");
  70. //File.WriteAllText(cachedAudioDataPath, Newtonsoft.Json.JsonConvert.SerializeObject(cachedAudioData));
  71. }
  72. private static long GetDirectoryHash(string directory)
  73. {
  74. long hash = 0;
  75. var directoryInfo = new DirectoryInfo(directory);
  76. foreach (var f in directoryInfo.GetFiles())
  77. {
  78. hash ^= f.CreationTimeUtc.ToFileTimeUtc();
  79. hash ^= f.LastWriteTimeUtc.ToFileTimeUtc();
  80. hash ^= f.Name.GetHashCode();
  81. hash ^= f.Length;
  82. }
  83. return hash;
  84. }
  85. private static bool GetCachedSongData(string customLevelPath, out long directoryHash, out string cachedSongHash)
  86. {
  87. directoryHash = GetDirectoryHash(customLevelPath);
  88. cachedSongHash = string.Empty;
  89. if (cachedSongHashData.TryGetValue(customLevelPath, out var cachedSong))
  90. {
  91. if (cachedSong.directoryHash == directoryHash)
  92. {
  93. cachedSongHash = cachedSong.songHash;
  94. return true;
  95. }
  96. }
  97. return false;
  98. }
  99. public static string GetCustomLevelHash(CustomPreviewBeatmapLevel level)
  100. {
  101. //if (GetCachedSongData(level.customLevelPath, out var directoryHash, out var songHash))
  102. // return songHash;
  103. var combinedBytes = new List<byte>();
  104. combinedBytes.AddRange(ArcacheLoader.ReadAllBytes(level.customLevelPath + '/' + "info.dat"));
  105. for (var i = 0; i < level.standardLevelInfoSaveData.difficultyBeatmapSets.Length; i++)
  106. {
  107. for (var i2 = 0; i2 < level.standardLevelInfoSaveData.difficultyBeatmapSets[i].difficultyBeatmaps.Length; i2++)
  108. if (ArcacheLoader.ArchiveExists(level.customLevelPath + '/' + level.standardLevelInfoSaveData.difficultyBeatmapSets[i].difficultyBeatmaps[i2].beatmapFilename))
  109. {
  110. combinedBytes.AddRange(ArcacheLoader.ReadAllBytes(level.customLevelPath + '/' + level.standardLevelInfoSaveData.difficultyBeatmapSets[i].difficultyBeatmaps[i2].beatmapFilename));
  111. }
  112. }
  113. var hash = CreateSha1FromBytes(combinedBytes.ToArray());
  114. //cachedSongHashData[level.customLevelPath] = new SongHashData(directoryHash, hash);
  115. return hash;
  116. }
  117. public static string GetCustomLevelHash(StandardLevelInfoSaveData level, string customLevelPath)
  118. {
  119. //if (GetCachedSongData(customLevelPath, out var directoryHash, out var songHash))
  120. // return songHash;
  121. var combinedBytes = new byte[0];
  122. combinedBytes = combinedBytes.Concat(ArcacheLoader.ReadAllBytes(customLevelPath + '/' + "info.dat")).ToArray();
  123. for (var i = 0; i < level.difficultyBeatmapSets.Length; i++)
  124. {
  125. for (var i2 = 0; i2 < level.difficultyBeatmapSets[i].difficultyBeatmaps.Length; i2++)
  126. if (ArcacheLoader.ArchiveExists(customLevelPath + '/' + level.difficultyBeatmapSets[i].difficultyBeatmaps[i2].beatmapFilename))
  127. combinedBytes = combinedBytes.Concat(ArcacheLoader.ReadAllBytes(customLevelPath + '/' + level.difficultyBeatmapSets[i].difficultyBeatmaps[i2].beatmapFilename)).ToArray();
  128. }
  129. var hash = CreateSha1FromBytes(combinedBytes.ToArray());
  130. //cachedSongHashData[customLevelPath] = new SongHashData(directoryHash, hash);
  131. return hash;
  132. }
  133. public static string GetCustomLevelHash(CustomBeatmapLevel level)
  134. {
  135. //if (GetCachedSongData(level.customLevelPath, out var directoryHash, out var songHash))
  136. // return songHash;
  137. var combinedBytes = new byte[0];
  138. combinedBytes = combinedBytes.Concat(ArcacheLoader.ReadAllBytes(level.customLevelPath + '/' + "info.dat")).ToArray();
  139. for (var i = 0; i < level.standardLevelInfoSaveData.difficultyBeatmapSets.Length; i++)
  140. {
  141. for (var i2 = 0; i2 < level.standardLevelInfoSaveData.difficultyBeatmapSets[i].difficultyBeatmaps.Length; i2++)
  142. if (File.Exists(level.customLevelPath + '/' + level.standardLevelInfoSaveData.difficultyBeatmapSets[i].difficultyBeatmaps[i2].beatmapFilename))
  143. combinedBytes = combinedBytes.Concat(ArcacheLoader.ReadAllBytes(level.customLevelPath + '/' + level.standardLevelInfoSaveData.difficultyBeatmapSets[i].difficultyBeatmaps[i2].beatmapFilename)).ToArray();
  144. }
  145. var hash = CreateSha1FromBytes(combinedBytes.ToArray());
  146. //cachedSongHashData[level.customLevelPath] = new SongHashData(directoryHash, hash);
  147. return hash;
  148. }
  149. public static string CreateSha1FromString(string input)
  150. {
  151. // Use input string to calculate MD5 hash
  152. using (var sha1 = SHA1.Create())
  153. {
  154. var inputBytes = Encoding.ASCII.GetBytes(input);
  155. var hashBytes = sha1.ComputeHash(inputBytes);
  156. return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
  157. }
  158. }
  159. public static string CreateSha1FromBytes(byte[] input)
  160. {
  161. // Use input string to calculate MD5 hash
  162. using (var sha1 = SHA1.Create())
  163. {
  164. var inputBytes = input;
  165. var hashBytes = sha1.ComputeHash(inputBytes);
  166. return BitConverter.ToString(hashBytes).Replace("-", string.Empty);
  167. }
  168. }
  169. public static bool CreateSha1FromFile(string path, out string hash)
  170. {
  171. hash = "";
  172. if (!File.Exists(path)) return false;
  173. using (var sha1 = SHA1.Create())
  174. {
  175. using (var stream = File.OpenRead(path))
  176. {
  177. var hashBytes = sha1.ComputeHash(stream);
  178. hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
  179. return true;
  180. }
  181. }
  182. }
  183. }
  184. }