| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- ''' <summary>
- ''' 本地的社区资源文件。
- ''' </summary>
- Public Class LocalResourceFile
- #Region "文件信息"
- ''' <summary>
- ''' 该文件的文件信息。
- ''' </summary>
- Public ReadOnly File As FileInfo
- Public Sub New(FileInfo As FileInfo)
- Me.File = FileInfo
- End Sub
- Public Sub New(FullName As String)
- Me.New(FileUtils.GetInfo(FullName))
- End Sub
- ''' <summary>
- ''' 该文件是否没有被禁用。
- ''' </summary>
- Public ReadOnly Property IsEnabled As Boolean
- Get
- Return {".jar", ".zip", ".litemod"}.Contains(File.Extension.Lower)
- End Get
- End Property
- ''' <summary>
- ''' 假若该资源未被禁用时,文件的文件名。包含 .jar/.litemod 等后缀。
- ''' </summary>
- Public ReadOnly Property EnabledName As String
- Get
- Return File.Name.Replace(".disabled", "").Replace(".old", "")
- End Get
- End Property
- ''' <summary>
- ''' 假若该资源未被禁用时,文件的完整路径。
- ''' </summary>
- Public ReadOnly Property EnabledFullName As String
- Get
- Return $"{File.DirectoryName}\{EnabledName}"
- End Get
- End Property
- #End Region
- #Region "信息项"
- ''' <summary>
- ''' 在初始化结束之后,任何信息被后续更新时触发此事件。
- ''' </summary>
- Public Event OnUpdate(sender As LocalResourceFile)
- ''' <summary>
- ''' 可读名称。
- ''' 可能为无扩展的文件名,但不会是 Nothing。
- ''' </summary>
- Public Property Display As String
- Get
- If _Display Is Nothing Then _Display = ProjectVersion?.Display
- If _Display Is Nothing Then _Display = PathUtils.GetFileNameWithoutExtension(EnabledName)
- Return _Display
- End Get
- Set(value As String)
- If _Display Is Nothing AndAlso value IsNot Nothing AndAlso Not value.Contains("modname") AndAlso
- value.Lower <> "name" AndAlso value.Count > 1 AndAlso Val(value).ToString <> value Then
- _Display = value
- End If
- End Set
- End Property
- Private _Display As String = Nothing
- ''' <summary>
- ''' Mod 的描述信息。
- ''' 可能为 Nothing。
- ''' </summary>
- Public Property Description As String
- Get
- Return _Description
- End Get
- Set(value As String)
- If _Description Is Nothing AndAlso value IsNot Nothing AndAlso value.Count > 2 Then
- _Description = value.ToString.Trim(vbLf)
- '优化显示:若以 [a-zA-Z0-9] 结尾,加上小数点句号
- If _Description.Lower.LastIndexOfAny("qwertyuiopasdfghjklzxcvbnm0123456789") = _Description.Count - 1 Then _Description += "."
- End If
- End Set
- End Property
- Private _Description As String = Nothing
- ''' <summary>
- ''' Mod 的版本。
- ''' 不保证符合版本格式规范,且可能为 Nothing。
- ''' </summary>
- Public Property Version As String
- Get
- If _Version Is Nothing Then _Version = ProjectVersion?.Version
- Return _Version
- End Get
- Set(value As String)
- If _Version IsNot Nothing AndAlso _Version.RegexCheck("[0-9.\-]+") Then Return
- If value IsNot Nothing AndAlso value.ContainsIgnoreCase("version") Then value = "version" '需要修改的标识
- _Version = value
- End Set
- End Property
- Public _Version As String = Nothing
- #End Region
- #Region "网络信息"
- ''' <summary>
- ''' 该 Mod 关联的网络项目。
- ''' </summary>
- Public Property Project As ResourceProject
- Get
- Return _Project
- End Get
- Set(value As ResourceProject)
- _Project = value
- RaiseEvent OnUpdate(Me)
- End Set
- End Property
- Private _Project As ResourceProject
- ''' <summary>
- ''' 本地文件对应的联网文件信息。
- ''' </summary>
- Public ProjectVersion As ResourceVersion
- ''' <summary>
- ''' 该 Mod 对应的联网最新版本。
- ''' </summary>
- Public Property UpdateFile As ResourceVersion
- Get
- Return _UpdateFile
- End Get
- Set(value As ResourceVersion)
- _UpdateFile = value
- RaiseEvent OnUpdate(Me)
- End Set
- End Property
- Private _UpdateFile As ResourceVersion
- ''' <summary>
- ''' 该 Mod 的更新日志网址。
- ''' </summary>
- Public ChangelogUrls As New List(Of String)
- ''' <summary>
- ''' 所有网络信息是否已成功加载。
- ''' </summary>
- Public OnlineDataLoaded As Boolean = False
- ''' <summary>
- ''' 将网络信息保存为 JSON。
- ''' </summary>
- Public Function ToJson() As JObject
- Dim Json As New JObject
- If Project IsNot Nothing Then Json.Add("Project", Project.ToJson())
- Json.Add("ChangelogUrls", New JArray(ChangelogUrls))
- Json.Add("OnlineDataLoaded", OnlineDataLoaded)
- If ProjectVersion IsNot Nothing Then Json.Add("Version", ProjectVersion.ToCacheJson())
- If UpdateFile IsNot Nothing Then Json.Add("UpdateFile", UpdateFile.ToCacheJson())
- Return Json
- End Function
- ''' <summary>
- ''' 从 JSON 中读取网络信息。
- ''' </summary>
- Public Sub FromJson(Json As JObject)
- OnlineDataLoaded = Json("OnlineDataLoaded")
- If Json.ContainsKey("Project") Then Project = New ResourceProject(Json("Project"))
- If Json.ContainsKey("ChangelogUrls") Then ChangelogUrls = Json("ChangelogUrls").ToObject(Of List(Of String))
- If Json.ContainsKey("Version") Then ProjectVersion = ResourceVersion.FromCacheJson(Json("Version"))
- If Json.ContainsKey("UpdateFile") Then UpdateFile = ResourceVersion.FromCacheJson(Json("UpdateFile"))
- End Sub
- ''' <summary>
- ''' 该文件是否可以更新。
- ''' </summary>
- Public ReadOnly Property HasUpdate As Boolean
- Get
- Return Not Settings.Get(Of Boolean)("UiHiddenFunctionModUpdate") AndAlso Not Settings.Get(Of Boolean)("VersionAdvanceDisableModUpdate", Instance:=PageInstanceLeft.Instance) AndAlso UpdateFile IsNot Nothing
- End Get
- End Property
- ''' <summary>
- ''' 获取用于 CurseForge 信息获取的 Hash 值(MurmurHash2)。
- ''' </summary>
- Public ReadOnly Property CurseForgeHash As UInteger
- Get
- If _CurseForgeHash Is Nothing Then
- '读取缓存
- Dim CacheKey As String = $"{EnabledFullName}-{File.LastWriteTime.ToLongTimeString}-{File.Length}-C".GetStableHashCode()
- Dim Cached As String = ReadIni(PathTemp & "Cache\ModHash.ini", CacheKey)
- If Cached <> "" AndAlso Cached.RegexCheck("^\d+$") Then '#5062
- _CurseForgeHash = Cached
- Return _CurseForgeHash
- End If
- '读取文件
- Dim data As New List(Of Byte)
- For Each b As Byte In FileUtils.ReadAsBytes(File.FullName)
- If b = 9 OrElse b = 10 OrElse b = 13 OrElse b = 32 Then Continue For
- data.Add(b)
- Next
- '计算 MurmurHash2
- Dim length As Integer = data.Count
- Dim h As UInteger = 1 Xor length '1 是种子
- Dim i As Integer
- For i = 0 To length - 4 Step 4
- Dim k As UInteger = data(i) Or CUInt(data(i + 1)) << 8 Or CUInt(data(i + 2)) << 16 Or CUInt(data(i + 3)) << 24
- k = (k * &H5BD1E995L) And &HFFFFFFFFL
- k = k Xor (k >> 24)
- k = (k * &H5BD1E995L) And &HFFFFFFFFL
- h = (h * &H5BD1E995L) And &HFFFFFFFFL
- h = h Xor k
- Next
- Select Case length - i
- Case 3
- h = h Xor (data(i) Or CUInt(data(i + 1)) << 8)
- h = h Xor (CUInt(data(i + 2)) << 16)
- h = (h * &H5BD1E995L) And &HFFFFFFFFL
- Case 2
- h = h Xor (data(i) Or CUInt(data(i + 1)) << 8)
- h = (h * &H5BD1E995L) And &HFFFFFFFFL
- Case 1
- h = h Xor data(i)
- h = (h * &H5BD1E995L) And &HFFFFFFFFL
- End Select
- h = h Xor (h >> 13)
- h = (h * &H5BD1E995L) And &HFFFFFFFFL
- h = h Xor (h >> 15)
- _CurseForgeHash = h
- '写入缓存
- WriteIni(PathTemp & "Cache\ModHash.ini", CacheKey, h.ToString)
- End If
- Return _CurseForgeHash
- End Get
- End Property
- Private _CurseForgeHash As UInteger?
- ''' <summary>
- ''' 获取用于 Modrinth 信息获取的 Hash 值(SHA1)。固定为小写。
- ''' </summary>
- Public ReadOnly Property ModrinthHash As String
- Get
- If _ModrinthHash Is Nothing Then
- '读取缓存
- Dim CacheKey As String = $"{EnabledFullName}-{File.LastWriteTime.ToLongTimeString}-{File.Length}-M".GetStableHashCode()
- Dim Cached As String = ReadIni(PathTemp & "Cache\ModHash.ini", CacheKey)
- If Cached <> "" Then
- _ModrinthHash = Cached
- Return _ModrinthHash
- End If
- '计算 SHA1
- _ModrinthHash = CryptographyUtils.ComputeFileHash(File.FullName, CryptographyUtils.HashMethod.Sha1)
- '写入缓存
- WriteIni(PathTemp & "Cache\ModHash.ini", CacheKey, _ModrinthHash)
- End If
- Return _ModrinthHash
- End Get
- End Property
- Private _ModrinthHash As String
- #End Region
- #Region "API"
- Public Overrides Function ToString() As String
- Return File.FullName
- End Function
- Public Overrides Function Equals(obj As Object) As Boolean
- Dim Target = TryCast(obj, LocalResourceFile)
- Return Target IsNot Nothing AndAlso PathUtils.IsSame(File.FullName, Target.File.FullName)
- End Function
- #End Region
- End Class
|