LocalResourceFile.vb 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. ''' <summary>
  2. ''' 本地的社区资源文件。
  3. ''' </summary>
  4. Public Class LocalResourceFile
  5. #Region "文件信息"
  6. ''' <summary>
  7. ''' 该文件的文件信息。
  8. ''' </summary>
  9. Public ReadOnly File As FileInfo
  10. Public Sub New(FileInfo As FileInfo)
  11. Me.File = FileInfo
  12. End Sub
  13. Public Sub New(FullName As String)
  14. Me.New(FileUtils.GetInfo(FullName))
  15. End Sub
  16. ''' <summary>
  17. ''' 该文件是否没有被禁用。
  18. ''' </summary>
  19. Public ReadOnly Property IsEnabled As Boolean
  20. Get
  21. Return {".jar", ".zip", ".litemod"}.Contains(File.Extension.Lower)
  22. End Get
  23. End Property
  24. ''' <summary>
  25. ''' 假若该资源未被禁用时,文件的文件名。
  26. ''' </summary>
  27. Public ReadOnly Property EnabledName As String
  28. Get
  29. Return File.Name.Replace(".disabled", "").Replace(".old", "")
  30. End Get
  31. End Property
  32. ''' <summary>
  33. ''' 假若该资源未被禁用时,文件的完整路径。
  34. ''' </summary>
  35. Public ReadOnly Property EnabledFullName As String
  36. Get
  37. Return $"{File.DirectoryName}\{EnabledName}"
  38. End Get
  39. End Property
  40. #End Region
  41. #Region "信息项"
  42. ''' <summary>
  43. ''' 在初始化结束之后,任何信息被后续更新时触发此事件。
  44. ''' </summary>
  45. Public Event OnUpdate(sender As LocalResourceFile)
  46. ''' <summary>
  47. ''' 可读名称。
  48. ''' 可能为无扩展的文件名,但不会是 Nothing。
  49. ''' </summary>
  50. Public Property Display As String
  51. Get
  52. If _Display Is Nothing Then _Display = ProjectVersion?.Display
  53. If _Display Is Nothing Then _Display = PathUtils.GetFileNameWithoutExtension(File.FullName)
  54. Return _Display
  55. End Get
  56. Set(value As String)
  57. If _Display Is Nothing AndAlso value IsNot Nothing AndAlso Not value.Contains("modname") AndAlso
  58. value.Lower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then
  59. _Display = value
  60. End If
  61. End Set
  62. End Property
  63. Private _Display As String = Nothing
  64. ''' <summary>
  65. ''' Mod 的描述信息。
  66. ''' 可能为 Nothing。
  67. ''' </summary>
  68. Public Property Description As String
  69. Get
  70. Return _Description
  71. End Get
  72. Set(value As String)
  73. If _Description Is Nothing AndAlso value IsNot Nothing AndAlso value.Count > 2 Then
  74. _Description = value.ToString.Trim(vbLf)
  75. '优化显示:若以 [a-zA-Z0-9] 结尾,加上小数点句号
  76. If _Description.Lower.LastIndexOfAny("qwertyuiopasdfghjklzxcvbnm0123456789") = _Description.Count - 1 Then _Description += "."
  77. End If
  78. End Set
  79. End Property
  80. Private _Description As String = Nothing
  81. ''' <summary>
  82. ''' Mod 的版本。
  83. ''' 不保证符合版本格式规范,且可能为 Nothing。
  84. ''' </summary>
  85. Public Property Version As String
  86. Get
  87. If _Version Is Nothing Then _Version = ProjectVersion?.Version
  88. Return _Version
  89. End Get
  90. Set(value As String)
  91. If _Version IsNot Nothing AndAlso _Version.RegexCheck("[0-9.\-]+") Then Return
  92. If value IsNot Nothing AndAlso value.ContainsIgnoreCase("version") Then value = "version" '需要修改的标识
  93. _Version = value
  94. End Set
  95. End Property
  96. Public _Version As String = Nothing
  97. #End Region
  98. #Region "网络信息"
  99. ''' <summary>
  100. ''' 该 Mod 关联的网络项目。
  101. ''' </summary>
  102. Public Property Project As ResourceProject
  103. Get
  104. Return _Project
  105. End Get
  106. Set(value As ResourceProject)
  107. _Project = value
  108. RaiseEvent OnUpdate(Me)
  109. End Set
  110. End Property
  111. Private _Project As ResourceProject
  112. ''' <summary>
  113. ''' 本地文件对应的联网文件信息。
  114. ''' </summary>
  115. Public ProjectVersion As ResourceVersion
  116. ''' <summary>
  117. ''' 该 Mod 对应的联网最新版本。
  118. ''' </summary>
  119. Public Property UpdateFile As ResourceVersion
  120. Get
  121. Return _UpdateFile
  122. End Get
  123. Set(value As ResourceVersion)
  124. _UpdateFile = value
  125. RaiseEvent OnUpdate(Me)
  126. End Set
  127. End Property
  128. Private _UpdateFile As ResourceVersion
  129. ''' <summary>
  130. ''' 该 Mod 的更新日志网址。
  131. ''' </summary>
  132. Public ChangelogUrls As New List(Of String)
  133. ''' <summary>
  134. ''' 所有网络信息是否已成功加载。
  135. ''' </summary>
  136. Public OnlineDataLoaded As Boolean = False
  137. ''' <summary>
  138. ''' 将网络信息保存为 JSON。
  139. ''' </summary>
  140. Public Function ToJson() As JObject
  141. Dim Json As New JObject
  142. If Project IsNot Nothing Then Json.Add("Project", Project.ToJson())
  143. Json.Add("ChangelogUrls", New JArray(ChangelogUrls))
  144. Json.Add("OnlineDataLoaded", OnlineDataLoaded)
  145. If ProjectVersion IsNot Nothing Then Json.Add("Version", ProjectVersion.ToCacheJson())
  146. If UpdateFile IsNot Nothing Then Json.Add("UpdateFile", UpdateFile.ToCacheJson())
  147. Return Json
  148. End Function
  149. ''' <summary>
  150. ''' 从 JSON 中读取网络信息。
  151. ''' </summary>
  152. Public Sub FromJson(Json As JObject)
  153. OnlineDataLoaded = Json("OnlineDataLoaded")
  154. If Json.ContainsKey("Project") Then Project = New ResourceProject(Json("Project"))
  155. If Json.ContainsKey("ChangelogUrls") Then ChangelogUrls = Json("ChangelogUrls").ToObject(Of List(Of String))
  156. If Json.ContainsKey("Version") Then ProjectVersion = ResourceVersion.FromCacheJson(Json("Version"))
  157. If Json.ContainsKey("UpdateFile") Then UpdateFile = ResourceVersion.FromCacheJson(Json("UpdateFile"))
  158. End Sub
  159. ''' <summary>
  160. ''' 该文件是否可以更新。
  161. ''' </summary>
  162. Public ReadOnly Property HasUpdate As Boolean
  163. Get
  164. Return Not Settings.Get(Of Boolean)("UiHiddenFunctionModUpdate") AndAlso Not Settings.Get(Of Boolean)("VersionAdvanceDisableModUpdate", Instance:=PageInstanceLeft.Instance) AndAlso UpdateFile IsNot Nothing
  165. End Get
  166. End Property
  167. ''' <summary>
  168. ''' 获取用于 CurseForge 信息获取的 Hash 值(MurmurHash2)。
  169. ''' </summary>
  170. Public ReadOnly Property CurseForgeHash As UInteger
  171. Get
  172. If _CurseForgeHash Is Nothing Then
  173. '读取缓存
  174. Dim CacheKey As String = $"{EnabledFullName}-{File.LastWriteTime.ToLongTimeString}-{File.Length}-C".GetStableHashCode()
  175. Dim Cached As String = ReadIni(PathTemp & "Cache\ModHash.ini", CacheKey)
  176. If Cached <> "" AndAlso Cached.RegexCheck("^\d+$") Then '#5062
  177. _CurseForgeHash = Cached
  178. Return _CurseForgeHash
  179. End If
  180. '读取文件
  181. Dim data As New List(Of Byte)
  182. For Each b As Byte In FileUtils.ReadAsBytes(File.FullName)
  183. If b = 9 OrElse b = 10 OrElse b = 13 OrElse b = 32 Then Continue For
  184. data.Add(b)
  185. Next
  186. '计算 MurmurHash2
  187. Dim length As Integer = data.Count
  188. Dim h As UInteger = 1 Xor length '1 是种子
  189. Dim i As Integer
  190. For i = 0 To length - 4 Step 4
  191. Dim k As UInteger = data(i) Or CUInt(data(i + 1)) << 8 Or CUInt(data(i + 2)) << 16 Or CUInt(data(i + 3)) << 24
  192. k = (k * &H5BD1E995L) And &HFFFFFFFFL
  193. k = k Xor (k >> 24)
  194. k = (k * &H5BD1E995L) And &HFFFFFFFFL
  195. h = (h * &H5BD1E995L) And &HFFFFFFFFL
  196. h = h Xor k
  197. Next
  198. Select Case length - i
  199. Case 3
  200. h = h Xor (data(i) Or CUInt(data(i + 1)) << 8)
  201. h = h Xor (CUInt(data(i + 2)) << 16)
  202. h = (h * &H5BD1E995L) And &HFFFFFFFFL
  203. Case 2
  204. h = h Xor (data(i) Or CUInt(data(i + 1)) << 8)
  205. h = (h * &H5BD1E995L) And &HFFFFFFFFL
  206. Case 1
  207. h = h Xor data(i)
  208. h = (h * &H5BD1E995L) And &HFFFFFFFFL
  209. End Select
  210. h = h Xor (h >> 13)
  211. h = (h * &H5BD1E995L) And &HFFFFFFFFL
  212. h = h Xor (h >> 15)
  213. _CurseForgeHash = h
  214. '写入缓存
  215. WriteIni(PathTemp & "Cache\ModHash.ini", CacheKey, h.ToString)
  216. End If
  217. Return _CurseForgeHash
  218. End Get
  219. End Property
  220. Private _CurseForgeHash As UInteger?
  221. ''' <summary>
  222. ''' 获取用于 Modrinth 信息获取的 Hash 值(SHA1)。
  223. ''' </summary>
  224. Public ReadOnly Property ModrinthHash As String
  225. Get
  226. If _ModrinthHash Is Nothing Then
  227. '读取缓存
  228. Dim CacheKey As String = $"{EnabledFullName}-{File.LastWriteTime.ToLongTimeString}-{File.Length}-M".GetStableHashCode()
  229. Dim Cached As String = ReadIni(PathTemp & "Cache\ModHash.ini", CacheKey)
  230. If Cached <> "" Then
  231. _ModrinthHash = Cached
  232. Return _ModrinthHash
  233. End If
  234. '计算 SHA1
  235. _ModrinthHash = CryptographyUtils.ComputeFileHash(File.FullName, CryptographyUtils.HashMethod.Sha1)
  236. '写入缓存
  237. WriteIni(PathTemp & "Cache\ModHash.ini", CacheKey, _ModrinthHash)
  238. End If
  239. Return _ModrinthHash
  240. End Get
  241. End Property
  242. Private _ModrinthHash As String
  243. #End Region
  244. #Region "API"
  245. Public Overrides Function ToString() As String
  246. Return File.FullName
  247. End Function
  248. Public Overrides Function Equals(obj As Object) As Boolean
  249. Dim Target = TryCast(obj, LocalResourceFile)
  250. Return Target IsNot Nothing AndAlso File.FullName = Target.File.FullName
  251. End Function
  252. #End Region
  253. End Class