'''
''' 本地的社区资源文件。
'''
Public Class LocalResourceFile
#Region "文件信息"
'''
''' 该文件的文件信息。
'''
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
'''
''' 该文件是否没有被禁用。
'''
Public ReadOnly Property IsEnabled As Boolean
Get
Return {".jar", ".zip", ".litemod"}.Contains(File.Extension.Lower)
End Get
End Property
'''
''' 假若该资源未被禁用时,文件的文件名。
'''
Public ReadOnly Property EnabledName As String
Get
Return File.Name.Replace(".disabled", "").Replace(".old", "")
End Get
End Property
'''
''' 假若该资源未被禁用时,文件的完整路径。
'''
Public ReadOnly Property EnabledFullName As String
Get
Return $"{File.DirectoryName}\{EnabledName}"
End Get
End Property
#End Region
#Region "信息项"
'''
''' 在初始化结束之后,任何信息被后续更新时触发此事件。
'''
Public Event OnUpdate(sender As LocalResourceFile)
'''
''' 可读名称。
''' 可能为无扩展的文件名,但不会是 Nothing。
'''
Public Property Display As String
Get
If _Display Is Nothing Then _Display = ProjectVersion?.Display
If _Display Is Nothing Then _Display = PathUtils.GetFileNameWithoutExtension(File.FullName)
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
'''
''' Mod 的描述信息。
''' 可能为 Nothing。
'''
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
'''
''' Mod 的版本。
''' 不保证符合版本格式规范,且可能为 Nothing。
'''
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 "网络信息"
'''
''' 该 Mod 关联的网络项目。
'''
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
'''
''' 本地文件对应的联网文件信息。
'''
Public ProjectVersion As ResourceVersion
'''
''' 该 Mod 对应的联网最新版本。
'''
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
'''
''' 该 Mod 的更新日志网址。
'''
Public ChangelogUrls As New List(Of String)
'''
''' 所有网络信息是否已成功加载。
'''
Public OnlineDataLoaded As Boolean = False
'''
''' 将网络信息保存为 JSON。
'''
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
'''
''' 从 JSON 中读取网络信息。
'''
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
'''
''' 该文件是否可以更新。
'''
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
'''
''' 获取用于 CurseForge 信息获取的 Hash 值(MurmurHash2)。
'''
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?
'''
''' 获取用于 Modrinth 信息获取的 Hash 值(SHA1)。
'''
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 File.FullName = Target.File.FullName
End Function
#End Region
End Class