ModBase.vb 133 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735
  1. Imports System.IO.Compression
  2. Imports System.Runtime.CompilerServices
  3. Imports System.Security.Cryptography
  4. Imports System.Security.Principal
  5. Imports System.Text.RegularExpressions
  6. Imports System.Windows.Markup
  7. Imports Newtonsoft.Json
  8. Public Module ModBase
  9. #Region "声明"
  10. '下列版本信息由更新器自动修改
  11. Public Const VersionBaseName As String = "2.8.4" '不含分支前缀的显示用版本名
  12. Public Const VersionStandardCode As String = "2.8.4." & VersionBranchCode '标准格式的四段式版本号
  13. 'CommitHash
  14. Public Const CommitHash As String = ""
  15. #If BETA Then
  16. Public Const VersionCode As Integer = 332 'Release
  17. #Else
  18. Public Const VersionCode As Integer = 333 'Snapshot
  19. #End If
  20. '自动生成的版本信息
  21. Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
  22. #If RELEASE Then
  23. Public Const VersionBranchName As String = "Snapshot"
  24. Public Const VersionBranchCode As String = "0"
  25. #ElseIf BETA Then
  26. Public Const VersionBranchName As String = "Release"
  27. Public Const VersionBranchCode As String = "50"
  28. #Else
  29. Public Const VersionBranchName As String = "Debug"
  30. Public Const VersionBranchCode As String = "100"
  31. #End If
  32. ''' <summary>
  33. ''' 主窗口句柄。
  34. ''' </summary>
  35. Public Handle As IntPtr
  36. ''' <summary>
  37. ''' 程序的启动路径,以“\”结尾。
  38. ''' </summary>
  39. Public Path As String = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
  40. ''' <summary>
  41. ''' 包含程序名的完整路径。
  42. ''' </summary>
  43. Public PathWithName As String = Path & AppDomain.CurrentDomain.SetupInformation.ApplicationName
  44. ''' <summary>
  45. ''' 程序内嵌图片文件夹路径,以“/”结尾。
  46. ''' </summary>
  47. Public PathImage As String = "pack://application:,,,/Plain Craft Launcher 2;component/Images/"
  48. ''' <summary>
  49. ''' 当前程序的语言。
  50. ''' </summary>
  51. Public Lang As String = "zh_CN"
  52. ''' <summary>
  53. ''' 设置对象。
  54. ''' </summary>
  55. Public Setup As New ModSetup
  56. ''' <summary>
  57. ''' 程序的打开计时。
  58. ''' </summary>
  59. Public ApplicationStartTick As Long = GetTimeTick()
  60. ''' <summary>
  61. ''' 程序打开时的时间。
  62. ''' </summary>
  63. Public ApplicationOpenTime As Date = Date.Now
  64. ''' <summary>
  65. ''' 设备唯一标识符。
  66. ''' </summary>
  67. Public UniqueAddress As String = SecretGetUniqueAddress()
  68. ''' <summary>
  69. ''' 程序是否已结束。
  70. ''' </summary>
  71. Public IsProgramEnded As Boolean = False
  72. ''' <summary>
  73. ''' 是否为 32 位系统。
  74. ''' </summary>
  75. Public Is32BitSystem As Boolean = Not Environment.Is64BitOperatingSystem
  76. ''' <summary>
  77. ''' 是否使用 GBK 编码。
  78. ''' </summary>
  79. Public IsGBKEncoding As Boolean = Encoding.Default.CodePage = 936
  80. ''' <summary>
  81. ''' 系统盘盘符,以 \ 结尾。例如 “C:\”。
  82. ''' </summary>
  83. Public OsDrive As String = Environment.GetLogicalDrives().Where(Function(p) Directory.Exists(p)).First.ToUpper.First & ":\" '#3799
  84. ''' <summary>
  85. ''' 程序的缓存文件夹路径,以 \ 结尾。
  86. ''' </summary>
  87. Public PathTemp As String = If(Setup.Get("SystemSystemCache") = "", IO.Path.GetTempPath() & "PCL\", Setup.Get("SystemSystemCache")).ToString.Replace("/", "\").TrimEnd("\") & "\"
  88. ''' <summary>
  89. ''' AppData 中的 PCL 文件夹路径,以 \ 结尾。
  90. ''' </summary>
  91. Public PathAppdata As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\PCL\"
  92. #End Region
  93. #Region "矢量图标"
  94. Public Class Logo
  95. ''' <summary>
  96. ''' 图标按钮,心(空心),1.1x
  97. ''' </summary>
  98. Public Const IconButtonLikeLine As String = "M512 896a42.666667 42.666667 0 0 1-30.293333-12.373333l-331.52-331.946667a224.426667 224.426667 0 0 1 0-315.733333 223.573333 223.573333 0 0 1 315.733333 0L512 282.026667l46.08-46.08a223.573333 223.573333 0 0 1 315.733333 0 224.426667 224.426667 0 0 1 0 315.733333l-331.52 331.946667A42.666667 42.666667 0 0 1 512 896zM308.053333 256a136.533333 136.533333 0 0 0-97.28 40.106667 138.24 138.24 0 0 0 0 194.986666L512 792.746667l301.226667-301.653334a138.24 138.24 0 0 0 0-194.986666 141.653333 141.653333 0 0 0-194.56 0l-76.373334 76.8a42.666667 42.666667 0 0 1-60.586666 0L405.333333 296.106667A136.533333 136.533333 0 0 0 308.053333 256z"
  99. ''' <summary>
  100. ''' 图标按钮,心(实心),1.1x
  101. ''' </summary>
  102. Public Const IconButtonLikeFill As String = "M700.856 155.543c-74.769 0-144.295 72.696-190.046 127.26-45.737-54.576-115.247-127.26-190.056-127.26-134.79 0-244.443 105.78-244.443 235.799 0 77.57 39.278 131.988 70.845 175.713C238.908 694.053 469.62 852.094 479.39 858.757c9.41 6.414 20.424 9.629 31.401 9.629 11.006 0 21.998-3.215 31.398-9.63 9.782-6.662 240.514-164.703 332.238-291.701 31.587-43.724 70.874-98.143 70.874-175.713-0.001-130.02-109.656-235.8-244.445-235.8z m0 0"
  103. ''' <summary>
  104. ''' 图标按钮,垃圾桶,1.1x
  105. ''' </summary>
  106. Public Const IconButtonDelete As String = "M520.192 0C408.43 0 317.44 82.87 313.563 186.734H52.736c-29.038 0-52.663 21.943-52.663 49.079s23.625 49.152 52.663 49.152h58.075v550.473c0 103.35 75.118 187.757 167.717 187.757h472.43c92.599 0 167.716-83.894 167.716-187.757V285.477h52.59c29.038 0 52.59-21.943 52.663-49.08-0.073-27.135-23.625-49.151-52.663-49.151H726.235C723.237 83.017 631.955 0 520.192 0zM404.846 177.957c3.803-50.03 50.176-89.015 107.447-89.015 57.197 0 103.57 38.985 106.788 89.015H404.92zM284.379 933.669c-33.353 0-69.997-39.351-69.997-95.525v-549.01H833.39v549.522c0 56.247-36.645 95.525-69.998 95.525H284.379v-0.512z M357.23 800.695a48.274 48.274 0 0 0 47.616-49.006V471.7a48.274 48.274 0 0 0-47.543-49.08 48.274 48.274 0 0 0-47.69 49.006V751.69c0 27.282 20.846 49.006 47.617 49.006z m166.62 0a48.274 48.274 0 0 0 47.688-49.006V471.7a48.274 48.274 0 0 0-47.689-49.08 48.274 48.274 0 0 0-47.543 49.006V751.69c0 27.282 21.431 49.006 47.543 49.006z m142.92 0a48.274 48.274 0 0 0 47.543-49.006V471.7a48.274 48.274 0 0 0-47.543-49.08 48.274 48.274 0 0 0-47.616 49.006V751.69c0 27.282 20.773 49.006 47.543 49.006z"
  107. ''' <summary>
  108. ''' 图标按钮,禁止,1x
  109. ''' </summary>
  110. Public Const IconButtonStop As String = "M508 990.4c-261.6 0-474.4-212-474.4-474.4S246.4 41.6 508 41.6s474.4 212 474.4 474.4S769.6 990.4 508 990.4zM508 136.8c-209.6 0-379.2 169.6-379.2 379.2 0 209.6 169.6 379.2 379.2 379.2s379.2-169.6 379.2-379.2C887.2 306.4 717.6 136.8 508 136.8zM697.6 563.2 318.4 563.2c-26.4 0-47.2-21.6-47.2-47.2 0-26.4 21.6-47.2 47.2-47.2l379.2 0c26.4 0 47.2 21.6 47.2 47.2C744.8 542.4 724 563.2 697.6 563.2z"
  111. ''' <summary>
  112. ''' 图标按钮,勾选,1x
  113. ''' </summary>
  114. Public Const IconButtonCheck As String = "M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m0 921.6a409.6 409.6 0 1 1 409.6-409.6 409.6 409.6 0 0 1-409.6 409.6z M716.8 339.968l-256 253.44L328.192 460.8A51.2 51.2 0 0 0 256 532.992l168.448 168.96a51.2 51.2 0 0 0 72.704 0l289.28-289.792A51.2 51.2 0 0 0 716.8 339.968z"
  115. ''' <summary>
  116. ''' 图标按钮,笔,1x
  117. ''' </summary>
  118. Public Const IconButtonEdit As String = "M732.64 64.32C688.576 21.216 613.696 21.216 569.6 64.32L120.128 499.52c-17.6 12.896-26.432 30.144-30.848 51.68L32 870.048c0 25.856 8.8 56 26.432 73.248 17.632 17.216 17.632 48.704 88.64 48.704h13.248l326.08-56c22.016-4.32 39.68-12.928 52.864-30.176l449.472-435.2c22.048-21.536 35.264-47.36 35.264-77.536 0-30.176-13.216-56-35.264-77.568l-256.096-251.2zM139.712 903.776l56-326.912 311.04-295.136 267.104 269.44-310.976 295.168-323.168 57.44zM844.576 467.84l-273.984-260.672 61.856-59.84c8.832-8.512 26.528-8.512 39.776 0l234.24 226.496c4.384 4.288 8.832 12.8 8.832 17.088s-4.416 8.544-8.864 12.8l-61.856 64.128z"
  119. ''' <summary>
  120. ''' 图标按钮,齿轮,1.1x
  121. ''' </summary>
  122. Public Const IconButtonSetup As String = "M651.946667 1001.813333c-22.186667 0-42.666667-10.24-61.44-27.306666-23.893333-23.893333-49.493333-35.84-75.093334-35.84-29.013333 0-56.32 11.946667-73.386666 30.72v3.413333c-17.066667 17.066667-42.666667 27.306667-66.56 27.306667h-6.826667c-6.826667 0-11.946667-1.706667-15.36-1.706667l-6.826667-1.706667c-64.853333-20.48-121.173333-54.613333-168.96-98.986666-29.013333-23.893333-37.546667-63.146667-25.6-95.573334 8.533333-23.893333 5.12-51.2-10.24-75.093333-15.36-27.306667-34.133333-40.96-59.733333-47.786667h-1.706667l-5.12-1.706666c-35.84-8.533333-61.44-34.133333-66.56-69.973334C1.706667 575.146667 0 537.6 0 512c0-32.426667 3.413333-63.146667 8.533333-93.866667v-6.826666l3.413334-8.533334c10.24-23.893333 23.893333-40.96 44.373333-51.2 5.12-3.413333 11.946667-6.826667 20.48-8.533333 27.306667-8.533333 51.2-25.6 63.146667-44.373333 13.653333-23.893333 17.066667-52.906667 10.24-81.92-11.946667-34.133333 0-71.68 30.72-93.866667 44.373333-37.546667 97.28-68.266667 158.72-93.866667l3.413333-1.706666c44.373333-13.653333 75.093333 3.413333 92.16 20.48 23.893333 23.893333 49.493333 35.84 75.093333 35.84 30.72 0 56.32-10.24 71.68-30.72l3.413334-3.413334c27.306667-27.306667 63.146667-35.84 93.866666-22.186666 63.146667 22.186667 117.76 54.613333 165.546667 97.28 29.013333 23.893333 37.546667 63.146667 25.6 95.573333-8.533333 23.893333-5.12 51.2 10.24 75.093333 15.36 27.306667 34.133333 40.96 59.733333 47.786667h1.706667l5.12 1.706667c35.84 8.533333 61.44 34.133333 66.56 71.68 6.826667 30.72 10.24 63.146667 11.946667 93.866666v3.413334c0 32.426667-3.413333 63.146667-8.533334 93.866666v6.826667l-3.413333 8.533333c-10.24 23.893333-23.893333 40.96-44.373333 51.2-5.12 3.413333-11.946667 6.826667-20.48 8.533334-27.306667 8.533333-51.2 25.6-63.146667 46.08-13.653333 23.893333-17.066667 52.906667-10.24 81.92 11.946667 35.84-1.706667 75.093333-30.72 95.573333-44.373333 35.84-95.573333 66.56-157.013333 92.16-15.36 3.413333-27.306667 3.413333-35.84 3.413333z m3.413333-83.626666z m1.706667 0zM517.12 853.333333c47.786667 0 93.866667 20.48 134.826667 59.733334 1.706667 1.706667 3.413333 1.706667 3.413333 3.413333 52.906667-22.186667 97.28-49.493333 136.533333-80.213333l1.706667-1.706667v-3.413333c-13.653333-52.906667-8.533333-104.106667 17.066667-148.48 23.893333-39.253333 64.853333-69.973333 114.346666-85.333334 1.706667 0 3.413333-1.706667 6.826667-6.826666 5.12-25.6 8.533333-51.2 8.533333-78.506667-1.706667-29.013333-3.413333-56.32-10.24-81.92v-5.12h-1.706666c-51.2-11.946667-90.453333-39.253333-119.466667-87.04-27.306667-44.373333-34.133333-100.693333-17.066667-148.48l-1.706666-1.706667h-3.413334c-39.253333-35.84-85.333333-63.146667-136.533333-80.213333H648.533333s-1.706667 1.706667-3.413333 1.706667c-32.426667 39.253333-80.213333 59.733333-136.533333 59.733333-47.786667 0-93.866667-20.48-134.826667-59.733333l-1.706667-1.706667h-1.706666c-54.613333 22.186667-98.986667 49.493333-136.533334 80.213333l-1.706666 1.706667v3.413333c13.653333 52.906667 8.533333 104.106667-17.066667 148.48-23.893333 39.253333-64.853333 69.973333-114.346667 85.333334-1.706667 0-3.413333 1.706667-6.826666 6.826666-6.826667 25.6-8.533333 51.2-8.533334 78.506667 0 30.72 3.413333 58.026667 6.826667 76.8l1.706667 5.12h1.706666c51.2 11.946667 90.453333 39.253333 119.466667 87.04 27.306667 44.373333 34.133333 100.693333 17.066667 148.48l1.706666 1.706667 1.706667 1.706666c37.546667 35.84 83.626667 63.146667 134.826667 80.213334 1.706667 0 3.413333 0 3.413333 1.706666h1.706667s1.706667 0 5.12-1.706666c34.133333-37.546667 81.92-59.733333 136.533333-59.733334z m-6.826667-146.773333c-110.933333 0-199.68-85.333333-199.68-196.266667 0-109.226667 87.04-196.266667 199.68-196.266666s199.68 85.333333 199.68 196.266666c-1.706667 109.226667-88.746667 196.266667-199.68 196.266667z m0-307.2c-63.146667 0-114.346667 49.493333-114.346666 110.933333 0 63.146667 49.493333 110.933333 114.346666 110.933334 30.72 0 59.733333-11.946667 80.213334-32.426667 20.48-20.48 32.426667-49.493333 32.426666-78.506667 0-63.146667-49.493333-110.933333-112.64-110.933333z"
  123. ''' <summary>
  124. ''' 图标按钮,重置,0.9x
  125. ''' </summary>
  126. Public Const IconButtonReset As String = "M667.6817627 313.65283203l-45.28564454 55.76660156L858.06933594 391.27124023 787.61950684 165.93066406l-56.01379395 69.01611328A354.47387695 354.47387695 0 0 0 520.89892578 165.93066406C324.87536621 165.93066406 165.93066406 324.43041992 165.93066406 519.91015625c0 195.52917481 158.94470215 353.97949219 354.96826172 353.97949219a355.06713867 355.06713867 0 0 0 331.73217774-227.66418458 50.52612305 50.52612305 0 0 0-29.21813966-65.25878905 50.77331543 50.77331543 0 0 0-65.50598144 29.16870117A253.61938477 253.61938477 0 0 1 520.94836426 772.78796387c-140.05920411 0-253.61938477-113.21411133-253.61938477-252.87780762 0-139.61425781 113.56018067-252.82836914 253.61938477-252.82836914 53.59130859 0 104.46350098 16.61132813 146.73339843 46.57104492"
  127. ''' <summary>
  128. ''' 图标按钮,刷新,0.85x
  129. ''' </summary>
  130. Public Const IconButtonRefresh As String = "M875.52 148.48C783.36 56.32 655.36 0 512 0 291.84 0 107.52 138.24 30.72 332.8l122.88 46.08C204.8 230.4 348.16 128 512 128c107.52 0 199.68 40.96 271.36 112.64L640 384h384V0L875.52 148.48zM512 896c-107.52 0-199.68-40.96-271.36-112.64L384 640H0v384l148.48-148.48C240.64 967.68 368.64 1024 512 1024c220.16 0 404.48-138.24 481.28-332.8L870.4 645.12C819.2 793.6 675.84 896 512 896z"
  131. ''' <summary>
  132. ''' 图标按钮,软盘,1x
  133. ''' </summary>
  134. Public Const IconButtonSave As String = "M819.392 0L1024 202.752v652.16a168.96 168.96 0 0 1-168.832 168.768h-104.192a47.296 47.296 0 0 1-10.752 0H283.776a47.232 47.232 0 0 1-10.752 0H168.832A168.96 168.96 0 0 1 0 854.912V168.768A168.96 168.96 0 0 1 168.832 0h650.56z m110.208 854.912V242.112l-149.12-147.776H168.896c-41.088 0-74.432 33.408-74.432 74.432v686.144c0 41.024 33.344 74.432 74.432 74.432h62.4v-190.528c0-33.408 27.136-60.544 60.544-60.544h440.448c33.408 0 60.544 27.136 60.544 60.544v190.528h62.4c41.088 0 74.432-33.408 74.432-74.432z m-604.032 74.432h372.864v-156.736H325.568v156.736z m403.52-596.48a47.168 47.168 0 1 1 0 94.336H287.872a47.168 47.168 0 1 1 0-94.336h441.216z m0-153.728a47.168 47.168 0 1 1 0 94.4H287.872a47.168 47.168 0 1 1 0-94.4h441.216z"
  135. ''' <summary>
  136. ''' 图标按钮,信息,1.05x
  137. ''' </summary>
  138. Public Const IconButtonInfo As String = "M512 917.333333c223.861333 0 405.333333-181.472 405.333333-405.333333S735.861333 106.666667 512 106.666667 106.666667 288.138667 106.666667 512s181.472 405.333333 405.333333 405.333333z m0 106.666667C229.226667 1024 0 794.773333 0 512S229.226667 0 512 0s512 229.226667 512 512-229.226667 512-512 512z m-32-597.333333h64a21.333333 21.333333 0 0 1 21.333333 21.333333v320a21.333333 21.333333 0 0 1-21.333333 21.333333h-64a21.333333 21.333333 0 0 1-21.333333-21.333333V448a21.333333 21.333333 0 0 1 21.333333-21.333333z m0-192h64a21.333333 21.333333 0 0 1 21.333333 21.333333v64a21.333333 21.333333 0 0 1-21.333333 21.333333h-64a21.333333 21.333333 0 0 1-21.333333-21.333333v-64a21.333333 21.333333 0 0 1 21.333333-21.333333z"
  139. ''' <summary>
  140. ''' 图标按钮,列表,1x
  141. ''' </summary>
  142. Public Const IconButtonList As String = "M384 128h640v128H384zM160 192m-96 0a96 96 0 1 0 192 0 96 96 0 1 0-192 0ZM384 448h640v128H384zM160 512m-96 0a96 96 0 1 0 192 0 96 96 0 1 0-192 0ZM384 768h640v128H384zM160 832m-96 0a96 96 0 1 0 192 0 96 96 0 1 0-192 0Z"
  143. ''' <summary>
  144. ''' 图标按钮,文件夹,1.15x
  145. ''' </summary>
  146. Public Const IconButtonOpen As String = "M889.018182 418.909091H884.363636V316.509091a93.090909 93.090909 0 0 0-99.607272-89.832727h-302.545455l-93.090909-76.334546A46.545455 46.545455 0 0 0 358.865455 139.636364H146.152727A93.090909 93.090909 0 0 0 46.545455 229.469091V837.818182a46.545455 46.545455 0 0 0 46.545454 46.545454 46.545455 46.545455 0 0 0 16.756364-3.258181 109.381818 109.381818 0 0 0 25.134545 3.258181h586.472727a85.178182 85.178182 0 0 0 87.04-63.301818l163.374546-302.545454a46.545455 46.545455 0 0 0 5.585454-21.876364A82.385455 82.385455 0 0 0 889.018182 418.909091z m-744.727273-186.181818h198.283636l93.09091 76.334545a46.545455 46.545455 0 0 0 29.323636 10.705455h319.301818a12.101818 12.101818 0 0 1 6.516364 0V418.909091H302.545455a85.178182 85.178182 0 0 0-87.04 63.301818L139.636364 622.778182V232.727273a19.549091 19.549091 0 0 1 6.516363 0z m578.094546 552.029091a27.461818 27.461818 0 0 0-2.792728 6.516363H154.530909l147.083636-272.290909a27.461818 27.461818 0 0 0 2.792728-6.981818h565.061818z"
  147. ''' <summary>
  148. ''' 图标按钮,名片,1.1x
  149. ''' </summary>
  150. Public Const IconButtonCard As String = "M834.5 684.1c-31.2-70.4-98.9-120.9-179.1-127.3 63.5-8.5 112.6-63 112.6-128.8 0-71.8-58.2-130-130-130s-130 58.2-130 130c0 65.9 49 120.3 112.6 128.8-80.2 6.4-148 57-179.1 127.3-8.7 19.7 6 42 27.6 42 12.1 0 22.7-7.5 27.7-18.5 24.3-53.9 78.5-91.5 141.3-91.5s117 37.6 141.3 91.5c5 11.1 15.6 18.5 27.7 18.5 21.4 0 36.1-22.3 27.4-42zM567.9 427.9c0-38.6 31.4-70 70-70s70 31.4 70 70-31.4 70-70 70-70-31.4-70-70zM460.3 347.9H216.9c-16.6 0-30 13.4-30 30s13.4 30 30 30h243.3c16.6 0 30-13.4 30-30 0.1-16.5-13.4-30-29.9-30zM367.4 459.6H216.9c-16.6 0-30 13.4-30 30s13.4 30 30 30h150.4c16.6 0 30-13.4 30-30 0.1-16.6-13.4-30-29.9-30zM297.4 571.2H217c-16.6 0-30 13.4-30 30s13.4 30 30 30h80.4c16.6 0 30-13.4 30-30 0-16.5-13.5-30-30-30zM900 236v552H124V236h776m0-60H124c-33.1 0-60 26.9-60 60v552c0 33.1 26.9 60 60 60h776c33.1 0 60-26.9 60-60V236c0-33.1-26.9-60-60-60z"
  151. ''' <summary>
  152. ''' 图标按钮,×,0.85x
  153. ''' </summary>
  154. Public Const IconButtonCross As String = "F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z"
  155. ''' <summary>
  156. ''' 图标按钮,Mojang,1.1x
  157. ''' </summary>
  158. Public Const IconButtonMojang As String = "M9.183,18.967c-0.109-2.239,1.336-5.119,3.92-4.96c3.657-0.027,7.319-0.044,10.977,0.005 c3.712,0.214,6.596,2.759,9.652,4.533c0.181-1.697-0.197-3.717,1.237-4.982c1.899,2.091,3.143,4.894,5.677,6.334 c1.577,0.805,2.973-0.668,4.221-1.44c1.55,2.305,2.108,5.075,2.622,7.752c0.657,3.438,0.947,6.925,1.111,10.413 c-1.734-0.733-3.355-1.708-4.971-2.671c-1.396-4.933-5.349-8.656-9.723-11.059c-3.46-1.817-7.752-2.185-11.338-0.52 c-3.761,1.593-6.285,5.666-5.891,9.75c0.121,4.577,3.17,8.765,7.391,10.456c7.746,3.285,16.407,2.234,24.521,1.243 c0.454,2.315-1.511,4.527-3.695,4.889c-10.577,0.038-21.148-0.011-31.725,0.022c-2.217,0.279-4.079-1.879-3.964-4.008 C9.167,36.141,9.216,27.556,9.183,18.967z M40.114,8.09c0.294-0.801,0.872-1.417,1.542-1.924c1.379,2.589,2.742,5.482,2.311,8.491 c-0.332,2.54-4.42,2.507-5.052,0.163C38.349,12.527,39.209,10.178,40.114,8.09z M0,53 l0.1,0 z"
  159. ''' <summary>
  160. ''' 图标按钮,用户,0.95x
  161. ''' </summary>
  162. Public Const IconButtonUser As String = "M660.338 528.065c63.61-46.825 105.131-121.964 105.131-206.83 0-141.7-115.29-256.987-256.997-256.987-141.706 0-256.998 115.288-256.998 256.987 0 85.901 42.52 161.887 107.456 208.562-152.1 59.92-260.185 207.961-260.185 381.077 0 21.276 17.253 38.53 38.53 38.53 21.278 0 38.53-17.254 38.53-38.53 0-183.426 149.232-332.671 332.667-332.671 1.589 0 3.113-0.207 4.694-0.244 0.8 0.056 1.553 0.244 2.362 0.244 183.434 0 332.664 149.245 332.664 332.671 0 21.276 17.255 38.53 38.533 38.53 21.277 0 38.53-17.254 38.53-38.53 0-174.885-110.354-324.13-264.917-382.809z m-331.803-206.83c0-99.22 80.72-179.927 179.935-179.927s179.937 80.708 179.937 179.927c0 99.203-80.721 179.91-179.937 179.91s-179.935-80.708-179.935-179.91z"
  163. ''' <summary>
  164. ''' 图标按钮,盾牌,1x
  165. ''' </summary>
  166. Public Const IconButtonShield As String = "M511.488256 95.184408c35.310345 22.516742 95.184408 55.78011 167.34033 84.437781 75.738131 29.681159 148.405797 40.93953 191.392304 45.033483v353.615193c0 73.691154-50.662669 164.781609-136.123938 244.101949C649.65917 901.181409 558.568716 942.12094 512 942.12094c-46.568716 0-137.65917-40.93953-222.096952-119.748126C204.441779 742.54073 153.77911 651.450275 153.77911 577.247376v-353.103448c42.474763-4.093953 116.165917-15.352324 191.904048-45.545227 75.226387-30.192904 133.565217-63.456272 165.805098-83.414293M512 0c-4.093953 0-8.187906 1.535232-11.258371 3.582209l-14.84058 10.234882c-1.023488 0.511744-67.550225 47.592204-170.410794 88.531735-100.813593 39.916042-198.556722 41.963018-199.58021 41.963018l-25.075462 0.511744c-10.746627 0.511744-18.934533 8.187906-18.934533 18.422789v414.000999c0 216.97951 286.064968 446.24088 440.09995 446.24088s440.09995-229.261369 440.09995-445.729136V163.758121c0-10.234883-8.69965-18.422789-18.934533-18.422789l-24.563718-0.511744c-1.023488 0-98.766617-2.046977-199.58021-41.963018-103.372314-40.93953-170.410795-88.01999-170.922538-88.531734L523.258371 3.582209c-3.070465-2.558721-7.164418-3.582209-11.258371-3.582209z M743.308346 410.930535l-260.477761 260.477761c-15.864068 15.864068-41.963018 15.864068-57.827087 0l-144.823588-144.823588c-15.864068-15.864068-15.864068-41.963018 0-57.827087 8.187906-8.187906 18.422789-11.770115 29.169415-11.770115 10.234883 0 20.981509 4.093953 29.169416 11.770115l115.654173 115.654173L685.993003 352.591704c15.864068-15.864068 41.963018-15.864068 57.827087 0 15.352324 16.375812 15.352324 42.474763-0.511744 58.338831z"
  167. ''' <summary>
  168. ''' 图标按钮,离线,0.85x
  169. ''' </summary>
  170. Public Const IconButtonOffline As String = "M533.293176 788.841412a60.235294 60.235294 0 1 1 85.202824 85.202823l-42.616471 42.586353c-129.355294 129.385412-339.124706 129.385412-468.510117 0-129.385412-129.385412-129.385412-339.124706 0-468.510117l42.586353-42.616471a60.235294 60.235294 0 1 1 85.202823 85.202824l-42.61647 42.586352a210.823529 210.823529 0 1 0 298.164706 298.164706l42.586352-42.61647z m255.548236-255.548236l42.61647-42.586352a210.823529 210.823529 0 1 0-298.164706-298.164706l-42.586352 42.61647a60.235294 60.235294 0 1 1-85.202824-85.202823l42.616471-42.586353c129.355294-129.385412 339.124706-129.385412 468.510117 0 129.385412 129.385412 129.385412 339.124706 0 468.510117l-42.586353 42.616471a60.235294 60.235294 0 1 1-85.202823-85.202824zM192.542118 192.542118a60.235294 60.235294 0 0 1 85.202823 0l553.712941 553.712941a60.235294 60.235294 0 0 1-85.202823 85.202823L192.542118 277.744941a60.235294 60.235294 0 0 1 0-85.202823z"
  171. ''' <summary>
  172. ''' 图标,音符,1x
  173. ''' </summary>
  174. Public Const IconMusic As String = "M348.293565 716.53287V254.797913c0-41.672348 28.004174-78.358261 68.919652-90.37913L815.994435 40.826435c62.775652-18.610087 125.907478 26.579478 125.907478 89.933913v539.158261c8.013913 42.25113-8.94887 89.177043-47.014956 127.109565a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c21.014261 1.024 40.514783 5.164522 57.878261 12.065391V233.338435c0-12.109913-10.551652-20.034783-20.569044-20.034783a24.620522 24.620522 0 0 0-5.787826 0.934957L439.785739 338.18713a19.545043 19.545043 0 0 0-14.825739 19.144348v438.984348H423.846957c11.53113 43.987478-5.164522 94.208-45.412174 134.322087a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c20.791652 1.024 40.069565 5.075478 57.299478 11.842783z"
  175. ''' <summary>
  176. ''' 图标,播放,0.8x
  177. ''' </summary>
  178. Public Const IconPlay As String = "M803.904 463.936a55.168 55.168 0 0 1 0 96.128l-463.616 264.448C302.848 845.888 256 819.136 256 776.448V247.616c0-42.752 46.848-69.44 84.288-48.064l463.616 264.384z"
  179. End Class
  180. #End Region
  181. #Region "自定义类"
  182. ''' <summary>
  183. ''' 支持小数与常见类型隐式转换的颜色。
  184. ''' </summary>
  185. Public Class MyColor
  186. Public A As Double = 255
  187. Public R As Double = 0
  188. Public G As Double = 0
  189. Public B As Double = 0
  190. '类型转换
  191. Public Shared Widening Operator CType(str As String) As MyColor
  192. Return New MyColor(str)
  193. End Operator
  194. Public Shared Widening Operator CType(col As Color) As MyColor
  195. Return New MyColor(col)
  196. End Operator
  197. Public Shared Widening Operator CType(conv As MyColor) As Color
  198. Return Color.FromArgb(MathByte(conv.A), MathByte(conv.R), MathByte(conv.G), MathByte(conv.B))
  199. End Operator
  200. Public Shared Widening Operator CType(conv As MyColor) As System.Drawing.Color
  201. Return System.Drawing.Color.FromArgb(MathByte(conv.A), MathByte(conv.R), MathByte(conv.G), MathByte(conv.B))
  202. End Operator
  203. Public Shared Widening Operator CType(bru As SolidColorBrush) As MyColor
  204. Return New MyColor(bru.Color)
  205. End Operator
  206. Public Shared Widening Operator CType(conv As MyColor) As SolidColorBrush
  207. Return New SolidColorBrush(Color.FromArgb(MathByte(conv.A), MathByte(conv.R), MathByte(conv.G), MathByte(conv.B)))
  208. End Operator
  209. Public Shared Widening Operator CType(bru As Brush) As MyColor
  210. Return New MyColor(bru)
  211. End Operator
  212. Public Shared Widening Operator CType(conv As MyColor) As Brush
  213. Return New SolidColorBrush(Color.FromArgb(MathByte(conv.A), MathByte(conv.R), MathByte(conv.G), MathByte(conv.B)))
  214. End Operator
  215. '颜色运算
  216. Public Shared Operator +(a As MyColor, b As MyColor) As MyColor
  217. Return New MyColor With {.A = a.A + b.A, .B = a.B + b.B, .G = a.G + b.G, .R = a.R + b.R}
  218. End Operator
  219. Public Shared Operator -(a As MyColor, b As MyColor) As MyColor
  220. Return New MyColor With {.A = a.A - b.A, .B = a.B - b.B, .G = a.G - b.G, .R = a.R - b.R}
  221. End Operator
  222. Public Shared Operator *(a As MyColor, b As Double) As MyColor
  223. Return New MyColor With {.A = a.A * b, .B = a.B * b, .G = a.G * b, .R = a.R * b}
  224. End Operator
  225. Public Shared Operator /(a As MyColor, b As Double) As MyColor
  226. Return New MyColor With {.A = a.A / b, .B = a.B / b, .G = a.G / b, .R = a.R / b}
  227. End Operator
  228. Public Shared Operator =(a As MyColor, b As MyColor) As Boolean
  229. If IsNothing(a) AndAlso IsNothing(b) Then Return True
  230. If IsNothing(a) OrElse IsNothing(b) Then Return False
  231. Return a.A = b.A AndAlso a.R = b.R AndAlso a.G = b.G AndAlso a.B = b.B
  232. End Operator
  233. Public Shared Operator <>(a As MyColor, b As MyColor) As Boolean
  234. If IsNothing(a) AndAlso IsNothing(b) Then Return False
  235. If IsNothing(a) OrElse IsNothing(b) Then Return True
  236. Return Not (a.A = b.A AndAlso a.R = b.R AndAlso a.G = b.G AndAlso a.B = b.B)
  237. End Operator
  238. '构造函数
  239. Public Sub New()
  240. End Sub
  241. Public Sub New(col As Color)
  242. Me.A = col.A
  243. Me.R = col.R
  244. Me.G = col.G
  245. Me.B = col.B
  246. End Sub
  247. Public Sub New(HexString As String)
  248. Dim StringColor As Media.Color = ColorConverter.ConvertFromString(HexString)
  249. A = StringColor.A
  250. R = StringColor.R
  251. G = StringColor.G
  252. B = StringColor.B
  253. End Sub
  254. Public Sub New(newA As Double, col As MyColor)
  255. Me.A = newA
  256. Me.R = col.R
  257. Me.G = col.G
  258. Me.B = col.B
  259. End Sub
  260. Public Sub New(newR As Double, newG As Double, newB As Double)
  261. Me.A = 255
  262. Me.R = newR
  263. Me.G = newG
  264. Me.B = newB
  265. End Sub
  266. Public Sub New(newA As Double, newR As Double, newG As Double, newB As Double)
  267. Me.A = newA
  268. Me.R = newR
  269. Me.G = newG
  270. Me.B = newB
  271. End Sub
  272. Public Sub New(brush As Brush)
  273. Dim Color As Color = CType(brush, SolidColorBrush).Color
  274. A = Color.A
  275. R = Color.R
  276. G = Color.G
  277. B = Color.B
  278. End Sub
  279. Public Sub New(brush As SolidColorBrush)
  280. Dim Color As Color = brush.Color
  281. A = Color.A
  282. R = Color.R
  283. G = Color.G
  284. B = Color.B
  285. End Sub
  286. Public Sub New(obj As Object)
  287. If obj Is Nothing Then
  288. A = 255 : R = 255 : G = 255 : B = 255
  289. Else
  290. If TypeOf obj Is SolidColorBrush Then
  291. '避免反复获取 Color 对象造成性能下降
  292. Dim Color As Color = CType(obj, SolidColorBrush).Color
  293. A = Color.A
  294. R = Color.R
  295. G = Color.G
  296. B = Color.B
  297. Else
  298. A = obj.A
  299. R = obj.R
  300. G = obj.G
  301. B = obj.B
  302. End If
  303. End If
  304. End Sub
  305. 'HSL
  306. Public Function Hue(v1 As Double, v2 As Double, vH As Double) As Double
  307. If vH < 0 Then vH += 1
  308. If vH > 1 Then vH -= 1
  309. If vH < 0.16667 Then Return v1 + (v2 - v1) * 6 * vH
  310. If vH < 0.5 Then Return v2
  311. If vH < 0.66667 Then Return v1 + (v2 - v1) * (4 - vH * 6)
  312. Return v1
  313. End Function
  314. Public Function FromHSL(sH As Double, sS As Double, sL As Double) As MyColor
  315. If sS = 0 Then
  316. R = sL * 2.55
  317. G = R
  318. B = R
  319. Else
  320. Dim H = sH / 360
  321. Dim S = sS / 100
  322. Dim L = sL / 100
  323. S = If(L < 0.5, S * L + L, S * (1.0 - L) + L)
  324. L = 2 * L - S
  325. R = 255 * Hue(L, S, H + 1 / 3)
  326. G = 255 * Hue(L, S, H)
  327. B = 255 * Hue(L, S, H - 1 / 3)
  328. End If
  329. A = 255
  330. Return Me
  331. End Function
  332. Public Function FromHSL2(sH As Double, sS As Double, sL As Double) As MyColor
  333. If sS = 0 Then
  334. R = sL * 2.55 : G = R : B = R
  335. Else
  336. '初始化
  337. sH = (sH + 3600000) Mod 360
  338. Dim cent As Double() = {
  339. +0.1, -0.06, -0.3, '0, 30, 60
  340. -0.19, -0.15, -0.24, '90, 120, 150
  341. -0.32, -0.09, +0.18, '180, 210, 240
  342. +0.05, -0.12, -0.02, '270, 300, 330
  343. +0.1, -0.06} '最后两位与前两位一致,加是变亮,减是变暗
  344. '计算色调对应的亮度片区
  345. Dim center As Double = sH / 30.0
  346. Dim intCenter As Integer = Math.Floor(center) '亮度片区编号
  347. center = 50 - (
  348. (1 - center + intCenter) * cent(intCenter) + (center - intCenter) * cent(intCenter + 1)
  349. ) * sS
  350. 'center = 50 + (cent(intCenter) + (center - intCenter) * (cent(intCenter + 1) - cent(intCenter))) * sS
  351. sL = If(sL < center, sL / center, 1 + (sL - center) / (100 - center)) * 50
  352. FromHSL(sH, sS, sL)
  353. End If
  354. A = 255
  355. Return Me
  356. End Function
  357. Public Overrides Function ToString() As String
  358. Return "(" & A & "," & R & "," & G & "," & B & ")"
  359. End Function
  360. Public Overrides Function Equals(obj As Object) As Boolean
  361. Return Me = obj
  362. End Function
  363. End Class
  364. ''' <summary>
  365. ''' 支持负数与浮点数的矩形。
  366. ''' </summary>
  367. Public Class MyRect
  368. '属性
  369. Public Property Width As Double = 0
  370. Public Property Height As Double = 0
  371. Public Property Left As Double = 0
  372. Public Property Top As Double = 0
  373. '构造函数
  374. Public Sub New()
  375. End Sub
  376. Public Sub New(left As Double, top As Double, width As Double, height As Double)
  377. Me.Left = left
  378. Me.Top = top
  379. Me.Width = width
  380. Me.Height = height
  381. End Sub
  382. End Class
  383. ''' <summary>
  384. ''' 模块加载状态枚举。
  385. ''' </summary>
  386. Public Enum LoadState
  387. Waiting
  388. Loading
  389. Finished
  390. Failed
  391. Aborted
  392. End Enum
  393. ''' <summary>
  394. ''' 执行返回值。
  395. ''' </summary>
  396. Public Enum Result
  397. ''' <summary>
  398. ''' 执行成功,或进程被中断。
  399. ''' </summary>
  400. Aborted = -1
  401. ''' <summary>
  402. ''' 执行成功。
  403. ''' </summary>
  404. Success = 0
  405. ''' <summary>
  406. ''' 执行失败。
  407. ''' </summary>
  408. Fail
  409. ''' <summary>
  410. ''' 执行时出现未经处理的异常。
  411. ''' </summary>
  412. Exception
  413. ''' <summary>
  414. ''' 执行超时。
  415. ''' </summary>
  416. Timeout
  417. ''' <summary>
  418. ''' 取消执行。可能是由于不满足执行的前置条件。
  419. ''' </summary>
  420. Cancel
  421. End Enum
  422. ''' <summary>
  423. ''' 可以使用 Equals 和等号的 List。
  424. ''' </summary>
  425. Public Class EqualableList(Of T)
  426. Inherits List(Of T)
  427. Public Overrides Function Equals(obj As Object) As Boolean
  428. If TryCast(obj, List(Of T)) Is Nothing Then
  429. '类型不同
  430. Return False
  431. Else
  432. '类型相同
  433. Dim objList As List(Of T) = obj
  434. If objList.Count <> Count Then Return False
  435. For i = 0 To objList.Count - 1
  436. If Not objList(i).Equals(Me(i)) Then Return False
  437. Next
  438. Return True
  439. End If
  440. End Function
  441. Public Shared Operator =(left As EqualableList(Of T), right As EqualableList(Of T)) As Boolean
  442. Return EqualityComparer(Of EqualableList(Of T)).Default.Equals(left, right)
  443. End Operator
  444. Public Shared Operator <>(left As EqualableList(Of T), right As EqualableList(Of T)) As Boolean
  445. Return Not left = right
  446. End Operator
  447. End Class
  448. #End Region
  449. #Region "数学"
  450. ''' <summary>
  451. ''' 2~65 进制的转换。
  452. ''' </summary>
  453. Public Function RadixConvert(Input As String, FromRadix As Integer, ToRadix As Integer) As String
  454. Const Digits As String = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/+="
  455. '零与负数的处理
  456. If String.IsNullOrEmpty(Input) Then Return "0"
  457. Dim IsNegative As Boolean = Input.StartsWithF("-")
  458. If IsNegative Then Input = Input.TrimStart("-")
  459. '转换为十进制
  460. Dim RealNum As Long = 0, Scale As Long = 1
  461. For Each Digit In Input.Reverse.Select(Function(l) Digits.IndexOfF(l))
  462. RealNum += Digit * Scale
  463. Scale *= FromRadix
  464. Next
  465. '转换为指定进制
  466. Dim Result = ""
  467. While RealNum > 0
  468. Dim NewNum As Integer = RealNum Mod ToRadix
  469. RealNum = (RealNum - NewNum) / ToRadix
  470. Result = Digits(NewNum) & Result
  471. End While
  472. '负数的结束处理与返回
  473. Return If(IsNegative, "-", "") & Result
  474. End Function
  475. ''' <summary>
  476. ''' 计算二阶贝塞尔曲线。
  477. ''' </summary>
  478. Public Function MathBezier(x As Double, x1 As Double, y1 As Double, x2 As Double, y2 As Double, Optional acc As Double = 0.01) As Double
  479. If x <= 0 OrElse Double.IsNaN(x) Then Return 0
  480. If x >= 1 Then Return 1
  481. Dim a, b
  482. a = x
  483. Do
  484. b = 3 * a * ((0.33333333 + x1 - x2) * a * a + (x2 - 2 * x1) * a + x1)
  485. a += (x - b) * 0.5
  486. Loop Until Math.Abs(b - x) < acc '精度
  487. Return 3 * a * ((0.33333333 + y1 - y2) * a * a + (y2 - 2 * y1) * a + y1)
  488. End Function
  489. ''' <summary>
  490. ''' 将一个数字限制为 0~255 的 Byte 值。
  491. ''' </summary>
  492. Public Function MathByte(d As Double) As Byte
  493. If d < 0 Then d = 0
  494. If d > 255 Then d = 255
  495. Return Math.Round(d)
  496. End Function
  497. ''' <summary>
  498. ''' 提供 MyColor 类型支持的 Math.Round。
  499. ''' </summary>
  500. Public Function MathRound(col As MyColor, Optional w As Integer = 0) As MyColor
  501. Return New MyColor With {.A = Math.Round(col.A, w), .R = Math.Round(col.R, w), .G = Math.Round(col.G, w), .B = Math.Round(col.B, w)}
  502. End Function
  503. ''' <summary>
  504. ''' 获取两数间的百分比。小数点精确到 6 位。
  505. ''' </summary>
  506. ''' <returns></returns>
  507. Public Function MathPercent(ValueA As Double, ValueB As Double, Percent As Double) As Double
  508. Return Math.Round(ValueA * (1 - Percent) + ValueB * Percent, 6) '解决 Double 计算错误
  509. End Function
  510. ''' <summary>
  511. ''' 获取两颜色间的百分比,根据 RGB 计算。小数点精确到 6 位。
  512. ''' </summary>
  513. Public Function MathPercent(ValueA As MyColor, ValueB As MyColor, Percent As Double) As MyColor
  514. Return MathRound(ValueA * (1 - Percent) + ValueB * Percent, 6) '解决Double计算错误
  515. End Function
  516. ''' <summary>
  517. ''' 将数值限定在某个范围内。
  518. ''' </summary>
  519. Public Function MathClamp(value As Double, min As Double, max As Double) As Double
  520. Return Math.Max(min, Math.Min(max, value))
  521. End Function
  522. ''' <summary>
  523. ''' 符号函数。
  524. ''' </summary>
  525. Public Function MathSgn(Value As Double) As Integer
  526. If Value = 0 Then
  527. Return 0
  528. ElseIf Value > 0 Then
  529. Return 1
  530. Else
  531. Return -1
  532. End If
  533. End Function
  534. #End Region
  535. #Region "文件"
  536. '注册表
  537. ''' <summary>
  538. ''' 重命名一个注册表子键。不可用于包含子键的子键。
  539. ''' </summary>
  540. Public Sub RenameReg(parentKey As Microsoft.Win32.RegistryKey, subKeyName As String, newSubKeyName As String)
  541. If parentKey.GetSubKeyNames().Contains(newSubKeyName) Then parentKey.DeleteSubKeyTree(newSubKeyName, False)
  542. Dim SourceKey As Microsoft.Win32.RegistryKey = parentKey.OpenSubKey(subKeyName)
  543. If IsNothing(SourceKey) Then Exit Sub '没有目标项
  544. Dim NewKey As Microsoft.Win32.RegistryKey = parentKey.CreateSubKey(newSubKeyName)
  545. If SourceKey.GetSubKeyNames().Length > 0 Then Throw New NotSupportedException("不支持对包含子键的子键进行重命名:" & SourceKey.GetSubKeyNames()(0) & "。")
  546. For Each valueName As String In SourceKey.GetValueNames()
  547. Dim objValue As Object = SourceKey.GetValue(valueName)
  548. Dim valKind As Microsoft.Win32.RegistryValueKind = SourceKey.GetValueKind(valueName)
  549. NewKey.SetValue(valueName, objValue, valKind)
  550. Next
  551. parentKey.DeleteSubKeyTree(subKeyName, False)
  552. End Sub
  553. ''' <summary>
  554. ''' 读取程序所属注册表。
  555. ''' </summary>
  556. Public Function ReadReg(Key As String, Optional DefaultValue As String = "") As String
  557. Try
  558. Dim parentKey As Microsoft.Win32.RegistryKey, softKey As Microsoft.Win32.RegistryKey
  559. parentKey = My.Computer.Registry.CurrentUser
  560. softKey = parentKey.OpenSubKey("Software\" & RegFolder, True)
  561. If softKey Is Nothing Then
  562. ReadReg = DefaultValue '不存在则返回默认值
  563. Else
  564. Dim readValue As New Text.StringBuilder
  565. readValue.AppendLine(softKey.GetValue(Key))
  566. Dim value = readValue.ToString.Replace(vbCrLf, "") '去除莫名的回车
  567. Return If(value = "", DefaultValue, value) '错误则返回默认值
  568. End If
  569. Catch ex As Exception
  570. Log(ex, "读取注册表出错:" & Key, LogLevel.Hint)
  571. Return DefaultValue
  572. End Try
  573. End Function
  574. ''' <summary>
  575. ''' 写入程序所属注册表。
  576. ''' </summary>
  577. Public Sub WriteReg(Key As String, Value As String, Optional ShowException As Boolean = False)
  578. Try
  579. Dim parentKey As Microsoft.Win32.RegistryKey, softKey As Microsoft.Win32.RegistryKey
  580. parentKey = My.Computer.Registry.CurrentUser
  581. softKey = parentKey.OpenSubKey("Software\" & RegFolder, True)
  582. If softKey Is Nothing Then softKey = parentKey.CreateSubKey("Software\" & RegFolder) '如果不存在就创建
  583. softKey.SetValue(Key, Value)
  584. Catch ex As Exception
  585. Log(ex, "写入注册表出错:" & Key, If(ShowException, LogLevel.Hint, LogLevel.Developer))
  586. If ShowException Then Throw '如果显示错误就丢一个
  587. End Try
  588. End Sub
  589. 'Ini 文件
  590. Private ReadOnly IniCache As New Dictionary(Of String, Dictionary(Of String, String))
  591. ''' <summary>
  592. ''' 清除某 ini 文件的运行时缓存。
  593. ''' </summary>
  594. ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
  595. Public Sub IniClearCache(FileName As String)
  596. If Not FileName.Contains(":\") Then FileName = $"{Path}PCL\{FileName}.ini"
  597. If IniCache.ContainsKey(FileName) Then IniCache.Remove(FileName)
  598. End Sub
  599. ''' <summary>
  600. ''' 获取 ini 文件缓存。如果没有,则新读取 ini 文件内容。
  601. ''' 在文件不存在或读取失败时返回 Nothing。
  602. ''' </summary>
  603. ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
  604. Private Function IniGetContent(FileName As String) As Dictionary(Of String, String)
  605. Try
  606. '还原文件路径
  607. If Not FileName.Contains(":\") Then FileName = $"{Path}PCL\{FileName}.ini"
  608. '检索缓存
  609. If IniCache.ContainsKey(FileName) Then Return IniCache(FileName)
  610. '读取文件
  611. If Not File.Exists(FileName) Then Return Nothing
  612. Dim Ini As New Dictionary(Of String, String)
  613. For Each Line In ReadFile(FileName).Split(vbCrLf.ToArray(), StringSplitOptions.RemoveEmptyEntries)
  614. Dim Index As Integer = Line.IndexOfF(":")
  615. If Index > 0 Then Ini(Line.Substring(0, Index)) = Line.Substring(Index + 1) '可能会有重复键,见 #3616
  616. Next
  617. IniCache(FileName) = Ini
  618. Return Ini
  619. Catch ex As Exception
  620. Log(ex, $"生成 ini 文件缓存失败({FileName})", LogLevel.Hint)
  621. Return Nothing
  622. End Try
  623. End Function
  624. ''' <summary>
  625. ''' 读取 ini 文件。这可能会使用到缓存。
  626. ''' </summary>
  627. ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
  628. ''' <param name="Key">键。</param>
  629. ''' <param name="DefaultValue">没有找到键时返回的默认值。</param>
  630. Public Function ReadIni(FileName As String, Key As String, Optional DefaultValue As String = "") As String
  631. Dim Content = IniGetContent(FileName)
  632. If Content Is Nothing OrElse Not Content.ContainsKey(Key) Then Return DefaultValue
  633. Return Content(Key)
  634. End Function
  635. ''' <summary>
  636. ''' 写入 ini 文件,这会更新缓存。
  637. ''' </summary>
  638. ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
  639. ''' <param name="Key">键。</param>
  640. ''' <param name="Value">值。</param>
  641. ''' <remarks></remarks>
  642. Public Sub WriteIni(FileName As String, Key As String, Value As String)
  643. Try
  644. '预处理
  645. Key = Key.Replace(vbCr, "").Replace(vbLf, "").Replace(":", "")
  646. Value = Value.Replace(vbCr, "").Replace(vbLf, "") '允许 Value 中包含冒号
  647. '获取目前文件
  648. Dim Content As Dictionary(Of String, String) = IniGetContent(FileName)
  649. If Content Is Nothing Then Content = New Dictionary(Of String, String)
  650. If Content.ContainsKey(Key) AndAlso Content(Key) = Value Then Exit Sub '如果值一样就不处理
  651. '更新缓存
  652. Content(Key) = Value
  653. '写入文件
  654. Dim FileContent As New StringBuilder
  655. For Each Pair In Content
  656. FileContent.Append(Pair.Key)
  657. FileContent.Append(":")
  658. FileContent.Append(Pair.Value)
  659. FileContent.Append(vbCrLf)
  660. Next
  661. If Not FileName.Contains(":\") Then FileName = $"{Path}PCL\{FileName}.ini"
  662. WriteFile(FileName, FileContent.ToString)
  663. Catch ex As Exception
  664. Log(ex, $"写入文件失败({FileName} -> {Key}:{Value})")
  665. End Try
  666. End Sub
  667. '路径处理
  668. ''' <summary>
  669. ''' 从文件路径或者 Url 获取不包含文件名的路径,或获取文件夹的父文件夹路径。
  670. ''' 取决于原路径格式,路径以 / 或 \ 结尾。
  671. ''' 不包含路径将会抛出异常。
  672. ''' </summary>
  673. Public Function GetPathFromFullPath(FilePath As String) As String
  674. If Not (FilePath.Contains("\") OrElse FilePath.Contains("/")) Then Throw New Exception("不包含路径:" & FilePath)
  675. If FilePath.EndsWithF("\") OrElse FilePath.EndsWithF("/") Then
  676. '是文件夹路径
  677. Dim IsRight As Boolean = FilePath.EndsWithF("\")
  678. FilePath = Left(FilePath, Len(FilePath) - 1)
  679. GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"})) & If(IsRight, "\", "/")
  680. If GetPathFromFullPath = "" Then Throw New Exception("不包含路径:" & FilePath)
  681. Else
  682. '是文件路径
  683. GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"}) + 1)
  684. If GetPathFromFullPath = "" Then Throw New Exception("不包含路径:" & FilePath)
  685. End If
  686. End Function
  687. ''' <summary>
  688. ''' 从文件路径或者 Url 获取不包含路径的文件名。不包含文件名将会抛出异常。
  689. ''' </summary>
  690. Public Function GetFileNameFromPath(FilePath As String) As String
  691. FilePath = FilePath.Replace("/", "\")
  692. If FilePath.EndsWithF("\") Then Throw New Exception("不包含文件名:" & FilePath)
  693. If FilePath.Contains("?") Then FilePath = FilePath.Substring(0, FilePath.IndexOfF("?")) '去掉网络参数后的 ?
  694. If FilePath.Contains("\") Then FilePath = FilePath.Substring(FilePath.LastIndexOfF("\") + 1)
  695. Dim length As Integer = FilePath.Length
  696. If length = 0 Then Throw New Exception("不包含文件名:" & FilePath)
  697. If length > 250 Then Throw New PathTooLongException("文件名过长:" & FilePath)
  698. Return FilePath
  699. End Function
  700. ''' <summary>
  701. ''' 从文件路径或者 Url 获取不包含路径与扩展名的文件名。不包含文件名将会抛出异常。
  702. ''' </summary>
  703. Public Function GetFileNameWithoutExtentionFromPath(FilePath As String) As String
  704. Dim Name As String = GetFileNameFromPath(FilePath)
  705. If Name.Contains(".") Then
  706. Return Name.Substring(0, Name.LastIndexOfF("."))
  707. Else
  708. Return Name
  709. End If
  710. End Function
  711. ''' <summary>
  712. ''' 从文件夹路径获取文件夹名。
  713. ''' </summary>
  714. Public Function GetFolderNameFromPath(FolderPath As String) As String
  715. If FolderPath.EndsWithF(":\") OrElse FolderPath.EndsWithF(":\\") Then Return FolderPath.Substring(0, 1)
  716. If FolderPath.EndsWithF("\") OrElse FolderPath.EndsWithF("/") Then FolderPath = Left(FolderPath, FolderPath.Length - 1)
  717. Return GetFileNameFromPath(FolderPath)
  718. End Function
  719. '读取、写入、复制文件
  720. Public Sub CopyFile(FromPath As String, ToPath As String)
  721. Try
  722. '还原文件路径
  723. If Not FromPath.Contains(":\") Then FromPath = Path & FromPath
  724. If Not ToPath.Contains(":\") Then ToPath = Path & ToPath
  725. '如果复制同一个文件则跳过
  726. If FromPath = ToPath Then Exit Sub
  727. '确保目录存在
  728. Directory.CreateDirectory(GetPathFromFullPath(ToPath))
  729. '复制文件
  730. File.Copy(FromPath, ToPath, True)
  731. Catch ex As Exception
  732. Throw New Exception("复制文件出错:" & FromPath & " → " & ToPath, ex)
  733. End Try
  734. End Sub
  735. ''' <summary>
  736. ''' 读取文件,如果失败则返回空字符串。
  737. ''' </summary>
  738. ''' <param name="FilePath">文件完整或相对路径。</param>
  739. Public Function ReadFile(FilePath As String, Optional Encoding As Encoding = Nothing) As String
  740. Try
  741. '还原文件路径
  742. If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
  743. If File.Exists(FilePath) Then
  744. Dim FileBytes As Byte()
  745. Using ReadStream As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) '支持读取使用中的文件
  746. ReDim FileBytes(ReadStream.Length - 1)
  747. ReadStream.Read(FileBytes, 0, ReadStream.Length)
  748. End Using
  749. ReadFile = If(Encoding Is Nothing, DecodeBytes(FileBytes), Encoding.GetString(FileBytes))
  750. Else
  751. Log("[System] 欲读取的文件不存在,已返回空字符串:" & FilePath)
  752. Return ""
  753. End If
  754. Catch ex As Exception
  755. Log(ex, "读取文件出错:" & FilePath)
  756. Return ""
  757. End Try
  758. End Function
  759. ''' <summary>
  760. ''' 读取流中的所有文本。
  761. ''' </summary>
  762. Public Function ReadFile(Stream As Stream, Optional Encoding As Encoding = Nothing) As String
  763. Try
  764. Dim srcBuf As Byte() = New Byte(16384) {}
  765. Dim DataCount As Integer = Stream.Read(srcBuf, 0, 16384)
  766. Dim Result As New List(Of Byte)
  767. While DataCount > 0
  768. If DataCount > 0 Then Result.AddRange(srcBuf.ToList.GetRange(0, DataCount))
  769. DataCount = Stream.Read(srcBuf, 0, 16384)
  770. End While
  771. Dim Bts = Result.ToArray
  772. Return If(Encoding, GetEncoding(Bts)).GetString(Bts)
  773. Catch ex As Exception
  774. Log(ex, "读取流出错")
  775. Return ""
  776. End Try
  777. End Function
  778. ''' <summary>
  779. ''' 写入文件。
  780. ''' </summary>
  781. ''' <param name="FilePath">文件完整或相对路径。</param>
  782. ''' <param name="Text">文件内容。</param>
  783. ''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
  784. Public Function WriteFile(FilePath As String, Text As String, Optional Append As Boolean = False, Optional Encoding As Encoding = Nothing) As Boolean
  785. Try
  786. '还原文件路径
  787. If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
  788. '确保目录存在
  789. Directory.CreateDirectory(GetPathFromFullPath(FilePath))
  790. '写入文件
  791. If File.Exists(FilePath) Then
  792. '如果文件存在,刷新目前文件
  793. Using writer As New StreamWriter(FilePath, Append, If(Encoding, GetEncoding(FilePath)))
  794. writer.Write(Text)
  795. writer.Flush()
  796. writer.Close()
  797. End Using
  798. Else
  799. '如果文件不存在,则新建并写入
  800. File.WriteAllText(FilePath, Text, If(Encoding, New UTF8Encoding(False)))
  801. End If
  802. Return True
  803. Catch ex As Exception
  804. Log(ex, "写入文件时出错:" & FilePath)
  805. Return False
  806. End Try
  807. End Function
  808. ''' <summary>
  809. ''' 写入文件。
  810. ''' </summary>
  811. ''' <param name="FilePath">文件完整或相对路径。</param>
  812. ''' <param name="Content">文件内容。</param>
  813. ''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
  814. Public Function WriteFile(FilePath As String, Content As Byte(), Optional Append As Boolean = False) As Boolean
  815. Try
  816. '还原文件路径
  817. If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
  818. '确保目录存在
  819. Directory.CreateDirectory(GetPathFromFullPath(FilePath))
  820. '写入文件
  821. File.WriteAllBytes(FilePath, Content)
  822. Return True
  823. Catch ex As Exception
  824. Log(ex, "写入文件时出错:" & FilePath)
  825. Return False
  826. End Try
  827. End Function
  828. ''' <summary>
  829. ''' 将流写入文件。
  830. ''' </summary>
  831. ''' <param name="FilePath">文件完整或相对路径。</param>
  832. Public Function WriteFile(FilePath As String, Stream As Stream) As Boolean
  833. Try
  834. '还原文件路径
  835. If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
  836. '确保目录存在
  837. Directory.CreateDirectory(GetPathFromFullPath(FilePath))
  838. '读取流
  839. Using fs As New FileStream(FilePath, FileMode.Create, FileAccess.Write)
  840. Dim srcBuf As Byte() = New Byte(16384) {}
  841. Dim DataCount As Integer = Stream.Read(srcBuf, 0, 16384)
  842. While DataCount > 0
  843. If DataCount > 0 Then fs.Write(srcBuf, 0, DataCount)
  844. DataCount = Stream.Read(srcBuf, 0, 16384)
  845. End While
  846. fs.Close()
  847. End Using
  848. Return True
  849. Catch ex As Exception
  850. Log(ex, "保存流出错")
  851. Return False
  852. End Try
  853. End Function
  854. '文件编码
  855. ''' <summary>
  856. ''' 获取文件编码。
  857. ''' </summary>
  858. ''' <param name="FilePath">文件完整或相对路径。</param>
  859. Public Function GetEncoding(FilePath As String) As Encoding
  860. '还原文件路径
  861. If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
  862. '获取编码
  863. GetEncoding = GetEncoding(File.ReadAllBytes(FilePath))
  864. End Function
  865. ''' <summary>
  866. ''' 获取 Bytes 的编码。
  867. ''' </summary>
  868. Public Function GetEncoding(Bytes As Byte()) As Encoding
  869. Dim Length As Integer = Bytes.Count
  870. If Length <= 2 Then Return New UTF8Encoding(False) '不带 BOM 的 UTF8
  871. '根据 BOM 判断编码
  872. If Bytes(0) >= &HEF Then
  873. '有 BOM 类型
  874. If Bytes(0) = &HEF AndAlso Bytes(1) = &HBB Then
  875. Return New UTF8Encoding(True) '带 BOM 的 UTF8
  876. ElseIf Bytes(0) = &HFE AndAlso Bytes(1) = &HFF Then
  877. Return Encoding.BigEndianUnicode
  878. ElseIf Bytes(0) = &HFF AndAlso Bytes(1) = &HFE Then
  879. Return Encoding.Unicode
  880. Else
  881. Return Encoding.GetEncoding("GB18030")
  882. End If
  883. End If
  884. '无 BOM 文件:GB18030(ANSI)或 UTF8
  885. Dim UTF8 = Encoding.UTF8.GetString(Bytes)
  886. Dim ErrorChar As Char = Encoding.UTF8.GetString({239, 191, 189}).ToCharArray()(0)
  887. If UTF8.Contains(ErrorChar) Then
  888. Return Encoding.GetEncoding("GB18030")
  889. Else
  890. Return New UTF8Encoding(False) '不带 BOM 的 UTF8
  891. End If
  892. End Function
  893. ''' <summary>
  894. ''' 解码 Bytes。
  895. ''' </summary>
  896. Public Function DecodeBytes(Bytes As Byte()) As String
  897. Dim Length As Integer = Bytes.Length
  898. If Length < 3 Then Return Encoding.UTF8.GetString(Bytes)
  899. '根据 BOM 判断编码
  900. If Bytes(0) >= &HEF Then
  901. '有 BOM 类型
  902. If Bytes(0) = &HEF AndAlso Bytes(1) = &HBB Then
  903. Return Encoding.UTF8.GetString(Bytes, 3, Length - 3)
  904. ElseIf Bytes(0) = &HFE AndAlso Bytes(1) = &HFF Then
  905. Return Encoding.BigEndianUnicode.GetString(Bytes, 3, Length - 3)
  906. ElseIf Bytes(0) = &HFF AndAlso Bytes(1) = &HFE Then
  907. Return Encoding.Unicode.GetString(Bytes, 3, Length - 3)
  908. Else
  909. Return Encoding.GetEncoding("GB18030").GetString(Bytes, 3, Length - 3)
  910. End If
  911. End If
  912. '无 BOM 文件:GB18030(ANSI)或 UTF8
  913. Dim UTF8 = Encoding.UTF8.GetString(Bytes)
  914. Dim ErrorChar As Char = Encoding.UTF8.GetString({239, 191, 189}).ToCharArray()(0)
  915. If UTF8.Contains(ErrorChar) Then
  916. Return Encoding.GetEncoding("GB18030").GetString(Bytes)
  917. Else
  918. Return UTF8
  919. End If
  920. End Function
  921. '对话框
  922. ''' <summary>
  923. ''' 弹出保存对话框并且要求保存位置,返回用户输入的完整路径。
  924. ''' </summary>
  925. ''' <param name="FileFilter">要求的格式。如:“常用图片文件(*.png;*.jpg)|*.png;*.jpg”。</param>
  926. ''' <param name="Title">弹窗的标题。</param>
  927. ''' <param name="FileName">默认的文件名。</param>
  928. Public Function SelectAs(Title As String, FileName As String, Optional FileFilter As String = Nothing, Optional DefaultDir As String = Nothing) As String
  929. Using fileDialog As New Forms.SaveFileDialog
  930. fileDialog.AddExtension = True
  931. fileDialog.AutoUpgradeEnabled = True
  932. fileDialog.Title = Title
  933. fileDialog.FileName = FileName
  934. If FileFilter IsNot Nothing Then fileDialog.Filter = FileFilter
  935. If DefaultDir IsNot Nothing Then fileDialog.InitialDirectory = DefaultDir
  936. fileDialog.ShowDialog()
  937. SelectAs = If(fileDialog.FileName.Contains(":\"), fileDialog.FileName, "")
  938. Log("[UI] 选择文件返回:" & SelectAs)
  939. End Using
  940. End Function
  941. ''' <summary>
  942. ''' 弹出选取文件对话框并且要求选取文件。
  943. ''' </summary>
  944. ''' <param name="FileFilter">要求的格式。如:“常用图片文件(*.png;*.jpg)|*.png;*.jpg”。</param>
  945. ''' <param name="Title">弹窗的标题。</param>
  946. Public Function SelectFile(FileFilter As String, Title As String) As String
  947. Using fileDialog As New Forms.OpenFileDialog
  948. fileDialog.AddExtension = True
  949. fileDialog.AutoUpgradeEnabled = True
  950. fileDialog.CheckFileExists = True
  951. fileDialog.Filter = FileFilter
  952. fileDialog.Multiselect = False
  953. fileDialog.Title = Title
  954. fileDialog.ValidateNames = True
  955. fileDialog.ShowDialog()
  956. SelectFile = fileDialog.FileName
  957. Log("[UI] 选择文件返回:" & SelectFile)
  958. End Using
  959. End Function
  960. ''' <summary>
  961. ''' 弹出选取文件夹对话框并且要求选取文件夹。如果没有选择就返回空字符串。
  962. ''' </summary>
  963. Public Function SelectFolder(Optional Title As String = "选择文件夹") As String
  964. Dim folderDialog As New Ookii.Dialogs.Wpf.VistaFolderBrowserDialog With {.ShowNewFolderButton = True, .RootFolder = Environment.SpecialFolder.Desktop, .Description = Title, .UseDescriptionForTitle = True}
  965. folderDialog.ShowDialog()
  966. SelectFolder = If(String.IsNullOrEmpty(folderDialog.SelectedPath), "", folderDialog.SelectedPath & If(folderDialog.SelectedPath.EndsWithF("\"), "", "\"))
  967. Log("[UI] 选择文件夹返回:" & SelectFolder)
  968. End Function
  969. '文件校验
  970. ''' <summary>
  971. ''' 检查是否拥有某一文件夹的 I/O 权限。如果文件夹不存在,会返回 False。
  972. ''' </summary>
  973. Public Function CheckPermission(Path As String) As Boolean
  974. Try
  975. If String.IsNullOrEmpty(Path) Then Return False
  976. If Not Path.EndsWithF("\") Then Path += "\"
  977. If Path.EndsWithF(":\System Volume Information\") OrElse Path.EndsWithF(":\$RECYCLE.BIN\") Then Return False
  978. If Not Directory.Exists(Path) Then Return False
  979. Dim FileName As String = "CheckPermission" & GetUuid()
  980. If File.Exists(Path & FileName) Then File.Delete(Path & FileName)
  981. File.Create(Path & FileName).Dispose()
  982. File.Delete(Path & FileName)
  983. Return True
  984. Catch ex As Exception
  985. Log(ex, "没有对文件夹 " & Path & " 的权限,请尝试以管理员权限运行 PCL")
  986. Return False
  987. End Try
  988. End Function
  989. ''' <summary>
  990. ''' 检查是否拥有某一文件夹的 I/O 权限。如果出错,则抛出异常。
  991. ''' </summary>
  992. Public Sub CheckPermissionWithException(Path As String)
  993. If String.IsNullOrWhiteSpace(Path) Then Throw New ArgumentNullException("文件夹名不能为空!")
  994. If Not Path.EndsWithF("\") Then Path += "\"
  995. If Not Directory.Exists(Path) Then Throw New DirectoryNotFoundException("文件夹不存在!")
  996. If File.Exists(Path & "CheckPermission") Then File.Delete(Path & "CheckPermission")
  997. File.Create(Path & "CheckPermission").Dispose()
  998. File.Delete(Path & "CheckPermission")
  999. End Sub
  1000. ''' <summary>
  1001. ''' 获取文件 MD5,若失败则返回空字符串。
  1002. ''' </summary>
  1003. Public Function GetFileMD5(FilePath As String) As String
  1004. Dim Retry As Boolean = False
  1005. Re:
  1006. Try
  1007. '获取 MD5
  1008. Dim Result As New StringBuilder()
  1009. Dim File As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
  1010. Dim MD5 As MD5 = New MD5CryptoServiceProvider()
  1011. Dim Retval As Byte() = MD5.ComputeHash(File)
  1012. File.Close()
  1013. For i = 0 To Retval.Length - 1
  1014. Result.Append(Retval(i).ToString("x2"))
  1015. Next
  1016. Return Result.ToString
  1017. Catch ex As Exception
  1018. If Retry OrElse TypeOf ex Is FileNotFoundException Then
  1019. Log(ex, "获取文件 MD5 失败:" & FilePath)
  1020. Return ""
  1021. Else
  1022. Retry = True
  1023. Log(ex, "获取文件 MD5 可重试失败:" & FilePath, LogLevel.Normal)
  1024. Thread.Sleep(RandomInteger(200, 500))
  1025. GoTo Re
  1026. End If
  1027. End Try
  1028. End Function
  1029. ''' <summary>
  1030. ''' 获取文件 SHA256,若失败则返回空字符串。
  1031. ''' </summary>
  1032. Public Function GetFileSHA256(FilePath As String) As String
  1033. Dim Retry As Boolean = False
  1034. Re:
  1035. Try
  1036. ''检测该文件是否在下载中,若在下载则放弃检测
  1037. 'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
  1038. '获取 SHA256
  1039. Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
  1040. Dim sha1 As SHA256 = New SHA256CryptoServiceProvider()
  1041. Dim retval As Byte() = sha1.ComputeHash(file)
  1042. file.Close()
  1043. Dim Result As New StringBuilder()
  1044. For i As Integer = 0 To retval.Length - 1
  1045. Result.Append(retval(i).ToString("x2"))
  1046. Next
  1047. Return Result.ToString
  1048. Catch ex As Exception
  1049. If Retry OrElse TypeOf ex Is FileNotFoundException Then
  1050. Log(ex, "获取文件 SHA256 失败:" & FilePath)
  1051. Return ""
  1052. Else
  1053. Retry = True
  1054. Log(ex, "获取文件 SHA256 可重试失败:" & FilePath, LogLevel.Normal)
  1055. Thread.Sleep(RandomInteger(200, 500))
  1056. GoTo Re
  1057. End If
  1058. End Try
  1059. End Function
  1060. ''' <summary>
  1061. ''' 获取文件 SHA1,若失败则返回空字符串。
  1062. ''' </summary>
  1063. Public Function GetFileSHA1(FilePath As String) As String
  1064. Dim Retry As Boolean = False
  1065. Re:
  1066. Try
  1067. '获取 SHA1
  1068. Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
  1069. Dim sha1 As SHA1 = New SHA1CryptoServiceProvider()
  1070. Dim retval As Byte() = sha1.ComputeHash(file)
  1071. file.Close()
  1072. Dim Result As New StringBuilder()
  1073. For i As Integer = 0 To retval.Length - 1
  1074. Result.Append(retval(i).ToString("x2"))
  1075. Next
  1076. Return Result.ToString
  1077. Catch ex As Exception
  1078. If Retry OrElse TypeOf ex Is FileNotFoundException Then
  1079. Log(ex, "获取文件 SHA1 失败:" & FilePath)
  1080. Return ""
  1081. Else
  1082. Retry = True
  1083. Log(ex, "获取文件 SHA1 可重试失败:" & FilePath, LogLevel.Normal)
  1084. Thread.Sleep(RandomInteger(200, 500))
  1085. GoTo Re
  1086. End If
  1087. End Try
  1088. End Function
  1089. ''' <summary>
  1090. ''' 获取流 SHA1,若失败则返回空字符串。
  1091. ''' </summary>
  1092. Public Function GetAuthSHA1(Stream As Stream) As String
  1093. Try
  1094. Dim sha1 As SHA1 = New SHA1CryptoServiceProvider()
  1095. Dim retval As Byte() = sha1.ComputeHash(Stream)
  1096. Dim Result As New StringBuilder()
  1097. For i As Integer = 0 To retval.Length - 1
  1098. Result.Append(retval(i).ToString("x2"))
  1099. Next
  1100. Return Result.ToString
  1101. Catch ex As Exception
  1102. Log(ex, "获取流 SHA1 失败")
  1103. Return ""
  1104. End Try
  1105. End Function
  1106. ''' <summary>
  1107. ''' 文件的校验规则。
  1108. ''' </summary>
  1109. Public Class FileChecker
  1110. ''' <summary>
  1111. ''' 文件的准确大小。
  1112. ''' </summary>
  1113. Public ActualSize As Long = -1
  1114. ''' <summary>
  1115. ''' 文件的最小大小。
  1116. ''' </summary>
  1117. Public MinSize As Long = -1
  1118. ''' <summary>
  1119. ''' 文件的 MD5、SHA1 或 SHA256。会根据输入字符串的长度自动判断种类。
  1120. ''' </summary>
  1121. Public Hash As String = Nothing
  1122. ''' <summary>
  1123. ''' 是否可以使用已经存在的文件。
  1124. ''' </summary>
  1125. Public CanUseExistsFile As Boolean = True
  1126. ''' <summary>
  1127. ''' 是否为 Json 文件。
  1128. ''' </summary>
  1129. Public IsJson As Boolean = False
  1130. Public Sub New(Optional MinSize As Long = -1, Optional ActualSize As Long = -1, Optional Hash As String = Nothing, Optional CanUseExistsFile As Boolean = True, Optional IsJson As Boolean = False)
  1131. Me.ActualSize = ActualSize
  1132. Me.MinSize = MinSize
  1133. Me.Hash = Hash
  1134. Me.CanUseExistsFile = CanUseExistsFile
  1135. Me.IsJson = IsJson
  1136. End Sub
  1137. ''' <summary>
  1138. ''' 检查文件。若成功则返回 Nothing,失败则返回错误的描述文本,描述文本不以句号结尾。不会抛出错误。
  1139. ''' </summary>
  1140. Public Function Check(LocalPath As String) As String
  1141. Try
  1142. Dim Info As New FileInfo(LocalPath)
  1143. If Not Info.Exists Then Return "文件不存在:" & LocalPath
  1144. Dim FileSize As Long = Info.Length
  1145. If ActualSize >= 0 AndAlso ActualSize <> FileSize Then Return "文件大小应为 " & ActualSize & " B,实际为 " & FileSize & " B"
  1146. If MinSize >= 0 AndAlso MinSize > FileSize Then Return "文件大小应大于 " & MinSize & " B,实际为 " & FileSize & " B"
  1147. If Not String.IsNullOrEmpty(Hash) Then
  1148. If Hash.Length < 35 Then 'MD5
  1149. If Hash.ToLowerInvariant <> GetFileMD5(LocalPath) Then Return "文件 MD5 应为 " & Hash & ",实际为 " & GetFileMD5(LocalPath)
  1150. ElseIf Hash.Length = 64 Then 'SHA256
  1151. If Hash.ToLowerInvariant <> GetFileSHA256(LocalPath) Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & GetFileSHA256(LocalPath)
  1152. Else 'SHA1 (40)
  1153. If Hash.ToLowerInvariant <> GetFileSHA1(LocalPath) Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & GetFileSHA1(LocalPath)
  1154. End If
  1155. End If
  1156. If IsJson Then
  1157. Dim Content As String = ReadFile(LocalPath)
  1158. If Content = "" Then Throw New Exception("读取到的文件为空")
  1159. Try
  1160. GetJson(Content)
  1161. Catch ex As Exception
  1162. Throw New Exception("不是有效的 json 文件", ex)
  1163. End Try
  1164. End If
  1165. Return Nothing
  1166. Catch ex As Exception
  1167. Log(ex, "检查文件出错")
  1168. Return GetExceptionSummary(ex)
  1169. End Try
  1170. End Function
  1171. End Class
  1172. ''' <summary>
  1173. ''' 尝试根据后缀名判断文件种类并解压文件,支持 gz 与 zip,会尝试将 jar 以 zip 方式解压。
  1174. ''' 会尝试创建,但不会清空目标文件夹。
  1175. ''' </summary>
  1176. Public Sub ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing)
  1177. Directory.CreateDirectory(DestDirectory)
  1178. If CompressFilePath.EndsWithF(".gz", True) Then
  1179. '以 gz 方式解压
  1180. Dim stream As New GZipStream(New FileStream(CompressFilePath, FileMode.Open, FileAccess.ReadWrite), CompressionMode.Decompress)
  1181. Dim decompressedFile As New FileStream(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", ""), FileMode.OpenOrCreate, FileAccess.Write)
  1182. Dim data As Integer = stream.ReadByte()
  1183. While data <> -1
  1184. decompressedFile.WriteByte(data)
  1185. data = stream.ReadByte()
  1186. End While
  1187. decompressedFile.Close()
  1188. stream.Close()
  1189. Else
  1190. '以 zip 方式解压
  1191. Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030")))
  1192. For Each Entry As ZipArchiveEntry In Archive.Entries
  1193. Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName)
  1194. If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then
  1195. Continue For '不创建空文件夹
  1196. Else
  1197. Directory.CreateDirectory(GetPathFromFullPath(DestinationPath))
  1198. Entry.ExtractToFile(DestinationPath, True)
  1199. End If
  1200. Next
  1201. End Using
  1202. End If
  1203. End Sub
  1204. ''' <summary>
  1205. ''' 删除文件夹,返回删除的文件个数。通过参数选择是否抛出异常。
  1206. ''' </summary>
  1207. Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer
  1208. If Not Directory.Exists(Path) Then Return 0
  1209. Dim DeletedCount As Integer = 0
  1210. Dim Temp As String()
  1211. Temp = Directory.GetFiles(Path)
  1212. For Each FilePath As String In Temp
  1213. Dim RetriedFile As Boolean = False
  1214. RetryFile:
  1215. Try
  1216. File.Delete(FilePath)
  1217. DeletedCount += 1
  1218. Catch ex As Exception
  1219. If Not RetriedFile Then
  1220. RetriedFile = True
  1221. Log(ex, $"删除文件失败,将在 0.3s 后重试({FilePath})")
  1222. Thread.Sleep(300)
  1223. GoTo RetryFile
  1224. ElseIf IgnoreIssue Then
  1225. Log(ex, "删除单个文件可忽略地失败")
  1226. Else
  1227. Throw
  1228. End If
  1229. End Try
  1230. Next
  1231. Temp = Directory.GetDirectories(Path)
  1232. For Each str As String In Temp
  1233. DeleteDirectory(str, IgnoreIssue)
  1234. Next
  1235. Dim RetriedDir As Boolean = False
  1236. RetryDir:
  1237. Try
  1238. Directory.Delete(Path, True)
  1239. Catch ex As Exception
  1240. If Not RetriedDir Then
  1241. RetriedDir = True
  1242. Log(ex, $"删除文件夹失败,将在 0.3s 后重试({Path})")
  1243. Thread.Sleep(300)
  1244. GoTo RetryDir
  1245. ElseIf IgnoreIssue Then
  1246. Log(ex, "删除单个文件夹可忽略地失败")
  1247. Else
  1248. Throw
  1249. End If
  1250. End Try
  1251. Return DeletedCount
  1252. End Function
  1253. ''' <summary>
  1254. ''' 复制文件夹,失败会抛出异常。
  1255. ''' </summary>
  1256. Public Sub CopyDirectory(FromPath As String, ToPath As String)
  1257. FromPath = FromPath.Replace("/", "\")
  1258. If Not FromPath.EndsWithF("\") Then FromPath &= "\"
  1259. ToPath = ToPath.Replace("/", "\")
  1260. If Not ToPath.EndsWithF("\") Then ToPath &= "\"
  1261. For Each File In EnumerateFiles(FromPath)
  1262. CopyFile(File.FullName, File.FullName.Replace(FromPath, ToPath))
  1263. Next
  1264. End Sub
  1265. ''' <summary>
  1266. ''' 遍历文件夹中的所有文件。
  1267. ''' </summary>
  1268. Public Function EnumerateFiles(Directory As String) As IEnumerable(Of FileInfo)
  1269. Dim Info As New DirectoryInfo(Directory)
  1270. If Not Info.Exists Then Return New List(Of FileInfo)
  1271. Return Info.EnumerateFiles("*", SearchOption.AllDirectories)
  1272. End Function
  1273. #End Region
  1274. #Region "文本"
  1275. Public vbLQ As Char = Convert.ToChar(8220)
  1276. Public vbRQ As Char = Convert.ToChar(8221)
  1277. ''' <summary>
  1278. ''' 提取 Exception 的具体描述与堆栈。
  1279. ''' </summary>
  1280. ''' <param name="ShowAllTrace">是否必须显示所有堆栈。通常用于判定堆栈信息。</param>
  1281. Public Function GetExceptionDetail(Ex As Exception, Optional ShowAllTrace As Boolean = False) As String
  1282. If Ex Is Nothing Then Return "无可用错误信息!"
  1283. '获取最底层的异常
  1284. Dim InnerEx As Exception = Ex
  1285. Do Until InnerEx.InnerException Is Nothing
  1286. InnerEx = InnerEx.InnerException
  1287. Loop
  1288. '获取各级错误的描述与堆栈信息
  1289. Dim DescList As New List(Of String)
  1290. Dim StackList As New List(Of String)
  1291. Do Until Ex Is Nothing
  1292. DescList.Add(Ex.Message.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr & vbCr, vbCr).Replace(vbCr, vbCrLf))
  1293. If Ex.StackTrace IsNot Nothing Then
  1294. For Each St As String In Ex.StackTrace.Split(vbCrLf)
  1295. If ShowAllTrace OrElse St.ToLower.Contains("pcl") Then StackList.Add(St.Replace(vbCr, String.Empty).Replace(vbLf, String.Empty))
  1296. Next
  1297. End If
  1298. Ex = Ex.InnerException
  1299. Loop
  1300. DescList = DescList.Distinct.ToList
  1301. Dim Desc As String = Join(DescList, vbCrLf & "→ ")
  1302. Dim Stack As String = If(StackList.Any, vbCrLf & Join(StackList, vbCrLf), "")
  1303. '常见错误(记得同时修改下面的)
  1304. Dim CommonReason As String = Nothing
  1305. If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
  1306. CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。"
  1307. ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
  1308. CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。"
  1309. ElseIf TypeOf InnerEx Is OutOfMemoryException Then
  1310. CommonReason = "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。"
  1311. ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
  1312. CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
  1313. ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
  1314. "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) Desc.Contains(s)) Then
  1315. CommonReason = "你的网络环境不佳,导致难以连接到服务器。请检查网络,多重试几次,或尝试使用 VPN。"
  1316. End If
  1317. '获取错误类型
  1318. Dim TypeDesc As String = If(InnerEx.GetType.FullName = "System.Exception", "", vbCrLf & "错误类型:" & InnerEx.GetType.FullName)
  1319. '构造输出信息
  1320. If CommonReason Is Nothing Then
  1321. Return Desc & Stack & TypeDesc
  1322. Else
  1323. Dim Result As String = DescList.First & vbCrLf & CommonReason & vbCrLf & "————————————" & vbCrLf
  1324. DescList(0) = "详细错误信息:"
  1325. Return Result & Join(DescList, vbCrLf & "→ ") & Stack & TypeDesc
  1326. End If
  1327. End Function
  1328. ''' <summary>
  1329. ''' 提取 Exception 描述,汇总到一行。
  1330. ''' </summary>
  1331. Public Function GetExceptionSummary(Ex As Exception) As String
  1332. If Ex Is Nothing Then Return "无可用错误信息!"
  1333. '获取最底层的异常
  1334. Dim InnerEx As Exception = Ex
  1335. Do Until InnerEx.InnerException Is Nothing
  1336. InnerEx = InnerEx.InnerException
  1337. Loop
  1338. '获取各级错误的描述
  1339. Dim DescList As New List(Of String)
  1340. Do Until Ex Is Nothing
  1341. DescList.Add(Ex.Message.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr & vbCr, vbCr).Replace(vbCr, " "))
  1342. Ex = Ex.InnerException
  1343. Loop
  1344. DescList = DescList.Distinct.ToList
  1345. Dim Desc As String = Join(DescList, vbCrLf & "→ ")
  1346. '常见错误(记得同时修改上面的)
  1347. Dim CommonReason As String = Nothing
  1348. If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
  1349. CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。"
  1350. ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
  1351. CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。"
  1352. ElseIf TypeOf InnerEx Is OutOfMemoryException Then
  1353. CommonReason = "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。"
  1354. ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
  1355. CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
  1356. ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
  1357. "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) Desc.Contains(s)) Then
  1358. CommonReason = "你的网络环境不佳,导致难以连接到服务器。请检查网络,多重试几次,或尝试使用 VPN。"
  1359. End If
  1360. '构造输出信息
  1361. If CommonReason IsNot Nothing Then
  1362. Return DescList.First & ":" & CommonReason
  1363. Else
  1364. DescList.Reverse() '让最深层错误在最左边
  1365. Return Join(DescList, " → ")
  1366. End If
  1367. End Function
  1368. ''' <summary>
  1369. ''' 返回一个枚举对应的字符串。
  1370. ''' </summary>
  1371. ''' <param name="EnumData">一个已经实例化的枚举类型。</param>
  1372. Public Function GetStringFromEnum(EnumData As [Enum]) As String
  1373. Return [Enum].GetName(EnumData.GetType, EnumData)
  1374. End Function
  1375. ''' <summary>
  1376. ''' 将文件大小转化为适合的文本形式,如“1.28 M”。
  1377. ''' </summary>
  1378. ''' <param name="FileSize">以字节为单位的大小表示。</param>
  1379. Public Function GetString(FileSize As Long) As String
  1380. Dim IsNegative = FileSize < 0
  1381. If IsNegative Then FileSize *= -1
  1382. If FileSize < 1000 Then
  1383. 'B 级
  1384. Return If(IsNegative, "-", "") & FileSize & " B"
  1385. ElseIf FileSize < 1024 * 1000 Then
  1386. 'K 级
  1387. Dim RoundResult As String = Math.Round(FileSize / 1024)
  1388. Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024, CInt(MathClamp(3 - RoundResult.Length, 0, 2))) & " K"
  1389. ElseIf FileSize < 1024 * 1024 * 1000 Then
  1390. 'M 级
  1391. Dim RoundResult As String = Math.Round(FileSize / 1024 / 1024)
  1392. Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024 / 1024, CInt(MathClamp(3 - RoundResult.Length, 0, 2))) & " M"
  1393. Else
  1394. 'G 级
  1395. Dim RoundResult As String = Math.Round(FileSize / 1024 / 1024 / 1024)
  1396. Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024 / 1024 / 1024, CInt(MathClamp(3 - RoundResult.Length, 0, 2))) & " G"
  1397. End If
  1398. End Function
  1399. ''' <summary>
  1400. ''' 获取 Json 对象。
  1401. ''' </summary>
  1402. Public Function GetJson(Data As String)
  1403. Try
  1404. Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local})
  1405. Catch ex As Exception
  1406. Dim Length As Integer = If(Data, "").Length
  1407. Throw New Exception("格式化 json 对象失败:" & If(Length > 10000, Data.Substring(0, 100) & $"...(全长 {Length} 个字符)..." & Right(Data, 100), Data))
  1408. End Try
  1409. End Function
  1410. ''' <summary>
  1411. ''' 将第一个字符转换为大写,其余字符转换为小写。
  1412. ''' </summary>
  1413. <Extension> Public Function Capitalize(word As String) As String
  1414. If String.IsNullOrEmpty(word) Then Return word
  1415. Return word.Substring(0, 1).ToUpperInvariant() & word.Substring(1).ToLowerInvariant()
  1416. End Function
  1417. ''' <summary>
  1418. ''' 将字符串统一至某个长度,过短则以 Code 将其右侧填充,过长则截取靠左的指定长度。
  1419. ''' </summary>
  1420. Public Function StrFill(Str As String, Code As String, Length As Byte) As String
  1421. If Str.Length > Length Then Return Mid(Str, 1, Length)
  1422. Return Mid(Str.PadRight(Length, Code), Str.Length + 1) & Str
  1423. End Function
  1424. ''' <summary>
  1425. ''' 将一个小数显示为固定的小数点后位数形式,将向零取整。
  1426. ''' 如 12 保留 2 位则输出 12.00,而 95.678 保留 2 位则输出 95.67。
  1427. ''' </summary>
  1428. Public Function StrFillNum(Num As Double, Length As Integer) As String
  1429. Num = Math.Round(Num, Length, MidpointRounding.AwayFromZero)
  1430. StrFillNum = Num
  1431. If Not StrFillNum.Contains(".") Then Return (StrFillNum & ".").PadRight(StrFillNum.Length + 1 + Length, "0")
  1432. Return StrFillNum.PadRight(StrFillNum.Split(".")(0).Length + 1 + Length, "0")
  1433. End Function
  1434. ''' <summary>
  1435. ''' 移除字符串首尾的标点符号、回车,以及括号中、冒号后的补充说明内容。
  1436. ''' </summary>
  1437. Public Function StrTrim(Str As String, Optional RemoveQuote As Boolean = True)
  1438. If RemoveQuote Then Str = Str.Split("(")(0).Split(":")(0).Split("(")(0).Split(":")(0)
  1439. Return Str.Trim(".", "。", "!", " ", "!", "?", "?", vbCr, vbLf)
  1440. End Function
  1441. ''' <summary>
  1442. ''' 连接字符串。
  1443. ''' </summary>
  1444. <Extension> Public Function Join(List As IEnumerable, Split As String) As String
  1445. Dim Builder As New StringBuilder
  1446. Dim IsFirst As Boolean = True
  1447. For Each Element In List
  1448. If IsFirst Then
  1449. IsFirst = False
  1450. Else
  1451. Builder.Append(Split)
  1452. End If
  1453. If Element IsNot Nothing Then Builder.Append(Element)
  1454. Next
  1455. Return Builder.ToString
  1456. End Function
  1457. ''' <summary>
  1458. ''' 分割字符串。
  1459. ''' </summary>
  1460. <Extension> Public Function Split(FullStr As String, SplitStr As String) As String()
  1461. If SplitStr.Length = 1 Then
  1462. Return FullStr.Split(SplitStr(0))
  1463. Else
  1464. Return FullStr.Split({SplitStr}, StringSplitOptions.None)
  1465. End If
  1466. End Function
  1467. ''' <summary>
  1468. ''' 获取字符串哈希值。
  1469. ''' </summary>
  1470. Public Function GetHash(Str As String) As ULong
  1471. GetHash = 5381
  1472. For i = 0 To Str.Length - 1
  1473. GetHash = (GetHash << 5) Xor GetHash Xor CType(AscW(Str(i)), ULong)
  1474. Next
  1475. Return GetHash Xor &HA98F501BC684032FUL
  1476. End Function
  1477. ''' <summary>
  1478. ''' 获取字符串 MD5。
  1479. ''' </summary>
  1480. Public Function GetStringMD5(Str As String) As String
  1481. Dim md5Hasher As New MD5CryptoServiceProvider
  1482. Dim hashedDataBytes As Byte()
  1483. hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(Str))
  1484. Dim tmp As New StringBuilder()
  1485. For Each i As Byte In hashedDataBytes
  1486. tmp.Append(i.ToString("x2"))
  1487. Next
  1488. Return tmp.ToString()
  1489. End Function
  1490. ''' <summary>
  1491. ''' 检查字符串中的字符是否均为 ASCII 字符。
  1492. ''' </summary>
  1493. <Extension> Public Function IsASCII(Input As String) As Boolean
  1494. Return Input.All(Function(c) AscW(c) < 128)
  1495. End Function
  1496. ''' <summary>
  1497. ''' 获取在子字符串之前的部分。
  1498. ''' 会裁切尽可能多的内容,但如果未找到子字符串则不裁切。
  1499. ''' </summary>
  1500. <Extension> Public Function Before(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
  1501. Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.IndexOfF(Text, IgnoreCase))
  1502. If Pos >= 0 Then
  1503. Return Str.Substring(0, Pos)
  1504. Else
  1505. Return Str
  1506. End If
  1507. End Function
  1508. ''' <summary>
  1509. ''' 获取在子字符串之后的部分。
  1510. ''' 会裁切尽可能多的内容,但如果未找到子字符串则不裁切。
  1511. ''' </summary>
  1512. <Extension> Public Function After(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
  1513. Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.LastIndexOfF(Text, IgnoreCase))
  1514. If Pos >= 0 Then
  1515. Return Str.Substring(Pos + Text.Length)
  1516. Else
  1517. Return Str
  1518. End If
  1519. End Function
  1520. ''' <summary>
  1521. ''' 获取处于两个子字符串之间的部分。
  1522. ''' 会裁切尽可能多的内容:匹配开始使用 LastIndexOf,匹配结束使用 IndexOf,但如果未找到子字符串则不裁切。
  1523. ''' </summary>
  1524. <Extension> Public Function Between(Str As String, After As String, Before As String, Optional IgnoreCase As Boolean = False) As String
  1525. Dim StartPos As Integer = If(String.IsNullOrEmpty(After), -1, Str.LastIndexOfF(After, IgnoreCase))
  1526. If StartPos >= 0 Then
  1527. StartPos += After.Length
  1528. Else
  1529. StartPos = 0
  1530. End If
  1531. Dim EndPos As Integer = If(String.IsNullOrEmpty(Before), -1, Str.IndexOfF(Before, StartPos, IgnoreCase))
  1532. If EndPos >= 0 Then
  1533. Return Str.Substring(StartPos, EndPos - StartPos)
  1534. ElseIf StartPos > 0 Then
  1535. Return Str.Substring(StartPos)
  1536. Else
  1537. Return Str
  1538. End If
  1539. End Function
  1540. ''' <summary>
  1541. ''' 高速的 StartsWith。
  1542. ''' </summary>
  1543. <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
  1544. Public Function StartsWithF(Str As String, Prefix As String, Optional IgnoreCase As Boolean = False) As Boolean
  1545. Return Str.StartsWith(Prefix, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
  1546. End Function
  1547. ''' <summary>
  1548. ''' 高速的 EndsWith。
  1549. ''' </summary>
  1550. <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
  1551. Public Function EndsWithF(Str As String, Suffix As String, Optional IgnoreCase As Boolean = False) As Boolean
  1552. Return Str.EndsWith(Suffix, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
  1553. End Function
  1554. ''' <summary>
  1555. ''' 支持可变大小写判断的 Contains。
  1556. ''' </summary>
  1557. <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
  1558. Public Function ContainsF(Str As String, SubStr As String, Optional IgnoreCase As Boolean = False) As Boolean
  1559. Return Str.IndexOf(SubStr, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal)) >= 0
  1560. End Function
  1561. ''' <summary>
  1562. ''' 高速的 IndexOf。
  1563. ''' </summary>
  1564. <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
  1565. Public Function IndexOfF(Str As String, SubStr As String, Optional IgnoreCase As Boolean = False) As Integer
  1566. Return Str.IndexOf(SubStr, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
  1567. End Function
  1568. ''' <summary>
  1569. ''' 高速的 IndexOf。
  1570. ''' </summary>
  1571. <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
  1572. Public Function IndexOfF(Str As String, SubStr As String, StartIndex As Integer, Optional IgnoreCase As Boolean = False) As Integer
  1573. Return Str.IndexOf(SubStr, StartIndex, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
  1574. End Function
  1575. ''' <summary>
  1576. ''' 高速的 LastIndexOf。
  1577. ''' </summary>
  1578. <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
  1579. Public Function LastIndexOfF(Str As String, SubStr As String, Optional IgnoreCase As Boolean = False) As Integer
  1580. Return Str.LastIndexOf(SubStr, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
  1581. End Function
  1582. ''' <summary>
  1583. ''' 高速的 LastIndexOf。
  1584. ''' </summary>
  1585. <Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)>
  1586. Public Function LastIndexOfF(Str As String, SubStr As String, StartIndex As Integer, Optional IgnoreCase As Boolean = False) As Integer
  1587. Return Str.LastIndexOf(SubStr, StartIndex, If(IgnoreCase, StringComparison.OrdinalIgnoreCase, StringComparison.Ordinal))
  1588. End Function
  1589. ''' <summary>
  1590. ''' 输入 And 字符不会报错的 Val。
  1591. ''' </summary>
  1592. Public Function Val(Str As Object) As Double
  1593. Return If(TypeOf Str Is String AndAlso Str = "&", 0, Conversion.Val(Str))
  1594. End Function
  1595. '转义
  1596. ''' <summary>
  1597. ''' 为字符串进行 XML 转义。
  1598. ''' </summary>
  1599. Public Function EscapeXML(Str As String) As String
  1600. If Str.StartsWithF("{") Then Str = "{}" & Str '#4187
  1601. Return Str.
  1602. Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("'", "&apos;").
  1603. Replace("""", "&quot;").Replace(vbCrLf, "&#xa;")
  1604. End Function
  1605. '正则
  1606. ''' <summary>
  1607. ''' 搜索字符串中的所有正则匹配项。
  1608. ''' </summary>
  1609. Public Function RegexSearch(str As String, regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As List(Of String)
  1610. Try
  1611. RegexSearch = New List(Of String)
  1612. Dim RegexSearchRes = New Regex(regex, options).Matches(str)
  1613. If RegexSearchRes Is Nothing Then Return RegexSearch
  1614. For Each item As Match In RegexSearchRes
  1615. RegexSearch.Add(item.Value)
  1616. Next
  1617. Catch ex As Exception
  1618. Log(ex, "正则匹配全部项出错")
  1619. Return New List(Of String)
  1620. End Try
  1621. End Function
  1622. ''' <summary>
  1623. ''' 获取字符串中的第一个正则匹配项,若无匹配则返回 Nothing。
  1624. ''' </summary>
  1625. Public Function RegexSeek(str As String, regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As String
  1626. Try
  1627. Dim Result = RegularExpressions.Regex.Match(str, regex, options).Value
  1628. Return If(Result = "", Nothing, Result)
  1629. Catch ex As Exception
  1630. Log(ex, "正则匹配第一项出错")
  1631. Return Nothing
  1632. End Try
  1633. End Function
  1634. ''' <summary>
  1635. ''' 检查字符串是否匹配某正则模式。
  1636. ''' </summary>
  1637. Public Function RegexCheck(str As String, regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As Boolean
  1638. Try
  1639. Return RegularExpressions.Regex.IsMatch(str, regex, options)
  1640. Catch ex As Exception
  1641. Log(ex, "正则检查出错")
  1642. Return False
  1643. End Try
  1644. End Function
  1645. ''' <summary>
  1646. ''' 进行正则替换,会抛出错误。
  1647. ''' </summary>
  1648. Public Function RegexReplace(Input As String, Replacement As String, Regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As String
  1649. Return RegularExpressions.Regex.Replace(Input, Regex, Replacement, options)
  1650. End Function
  1651. ''' <summary>
  1652. ''' 对每个正则匹配分别进行替换,会抛出错误。
  1653. ''' </summary>
  1654. Public Function RegexReplaceEach(Input As String, Replacement As MatchEvaluator, Regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As String
  1655. Return RegularExpressions.Regex.Replace(Input, Regex, Replacement, options)
  1656. End Function
  1657. #End Region
  1658. #Region "搜索"
  1659. ''' <summary>
  1660. ''' 获取搜索文本的相似度。
  1661. ''' </summary>
  1662. ''' <param name="Source">被搜索的长内容。</param>
  1663. ''' <param name="Query">用户输入的搜索文本。</param>
  1664. Private Function SearchSimilarity(Source As String, Query As String) As Double
  1665. Dim qp As Integer = 0, lenSum As Double = 0
  1666. Source = Source.ToLower.Replace(" ", "")
  1667. Query = Query.ToLower.Replace(" ", "")
  1668. Dim sourceLength As Integer = Source.Length, queryLength As Integer = Query.Length '用于计算最后因数的长度缓存
  1669. Do While qp < queryLength
  1670. '对 qp 作为开始位置计算
  1671. Dim sp As Integer = 0, lenMax As Integer = 0, spMax As Integer = 0
  1672. '查找以 qp 为头的最大子串
  1673. Do While sp < Source.Length
  1674. '对每个 sp 作为开始位置计算最大子串
  1675. Dim len As Integer = 0
  1676. Do While (qp + len) < queryLength AndAlso (sp + len) < Source.Length AndAlso Source(sp + len) = Query(qp + len)
  1677. len += 1
  1678. Loop
  1679. '存储 len
  1680. If len > lenMax Then
  1681. lenMax = len
  1682. spMax = sp
  1683. End If
  1684. '根据结果增加 sp
  1685. sp += Math.Max(1, len)
  1686. Loop
  1687. If lenMax > 0 Then
  1688. Source = Source.Substring(0, spMax) & If(Source.Count > spMax + lenMax, Source.Substring(spMax + lenMax), String.Empty) '将源中的对应字段替换空
  1689. '存储 lenSum
  1690. Dim IncWeight = (Math.Pow(1.4, 3 + lenMax) - 3.6) '根据长度加成
  1691. IncWeight *= 1 + 0.3 * Math.Max(0, 3 - Math.Abs(qp - spMax)) '根据位置加成
  1692. lenSum += IncWeight
  1693. End If
  1694. '根据结果增加 qp
  1695. qp += Math.Max(1, lenMax)
  1696. Loop
  1697. '计算结果:重复字段量 × 源长度影响比例
  1698. Return (lenSum / queryLength) * (3 / Math.Pow(sourceLength + 15, 0.5)) * If(queryLength <= 2, 3 - queryLength, 1)
  1699. End Function
  1700. ''' <summary>
  1701. ''' 获取多段文本加权后的相似度。
  1702. ''' </summary>
  1703. Private Function SearchSimilarityWeighted(Source As List(Of KeyValuePair(Of String, Double)), Query As String) As Double
  1704. Dim TotalWeight As Double = 0
  1705. Dim Sum As Double = 0
  1706. For Each Pair In Source
  1707. Sum += SearchSimilarity(Pair.Key, Query) * Pair.Value
  1708. TotalWeight += Pair.Value
  1709. Next
  1710. Return Sum / TotalWeight
  1711. End Function
  1712. ''' <summary>
  1713. ''' 用于搜索的项目。
  1714. ''' </summary>
  1715. Public Class SearchEntry(Of T)
  1716. ''' <summary>
  1717. ''' 该项目对应的源数据。
  1718. ''' </summary>
  1719. Public Item As T
  1720. ''' <summary>
  1721. ''' 该项目用于搜索的源。
  1722. ''' </summary>
  1723. Public SearchSource As List(Of KeyValuePair(Of String, Double))
  1724. ''' <summary>
  1725. ''' 相似度。
  1726. ''' </summary>
  1727. Public Similarity As Double
  1728. ''' <summary>
  1729. ''' 是否完全匹配。
  1730. ''' </summary>
  1731. Public AbsoluteRight As Boolean
  1732. End Class
  1733. ''' <summary>
  1734. ''' 进行多段文本加权搜索,获取相似度较高的数项结果。
  1735. ''' </summary>
  1736. ''' <param name="MaxBlurCount">返回的最大模糊结果数。</param>
  1737. ''' <param name="MinBlurSimilarity">返回结果要求的最低相似度。</param>
  1738. Public Function Search(Of T)(Entries As List(Of SearchEntry(Of T)), Query As String, Optional MaxBlurCount As Integer = 5, Optional MinBlurSimilarity As Double = 0.1) As List(Of SearchEntry(Of T))
  1739. '初始化
  1740. Dim ResultList As New List(Of SearchEntry(Of T))
  1741. If Not Entries.Any() Then Return ResultList
  1742. '进行搜索,获取相似信息
  1743. For Each Entry In Entries
  1744. Entry.Similarity = SearchSimilarityWeighted(Entry.SearchSource, Query)
  1745. Entry.AbsoluteRight = False
  1746. For Each Pair In Entry.SearchSource
  1747. If Pair.Key.Replace(" ", "").ContainsF(Query.Replace(" ", ""), True) Then Entry.AbsoluteRight = True
  1748. Next
  1749. Next
  1750. '按照相似度进行排序
  1751. Entries = Sort(Entries,
  1752. Function(Left, Right) As Boolean
  1753. If Left.AbsoluteRight Xor Right.AbsoluteRight Then
  1754. Return Left.AbsoluteRight
  1755. Else
  1756. Return Left.Similarity > Right.Similarity
  1757. End If
  1758. End Function)
  1759. '返回结果
  1760. Dim BlurCount As Integer = 0
  1761. For Each Entry In Entries
  1762. If Entry.AbsoluteRight Then
  1763. ResultList.Add(Entry) '完全匹配,直接加入
  1764. Else
  1765. If Entry.Similarity < MinBlurSimilarity OrElse BlurCount = MaxBlurCount Then Exit For '模糊结果边界条件
  1766. ResultList.Add(Entry)
  1767. BlurCount += 1 '模糊结果计数
  1768. End If
  1769. Next
  1770. Return ResultList
  1771. End Function
  1772. #End Region
  1773. #Region "系统"
  1774. ''' <summary>
  1775. ''' 当前程序是否拥有管理员权限。
  1776. ''' </summary>
  1777. Public Function IsAdmin() As Boolean
  1778. Dim id As WindowsIdentity = WindowsIdentity.GetCurrent()
  1779. Dim principal As New WindowsPrincipal(id)
  1780. Return principal.IsInRole(WindowsBuiltInRole.Administrator)
  1781. End Function
  1782. ''' <summary>
  1783. ''' 以管理员权限运行当前程序,并等待程序运行结束。
  1784. ''' 返回程序的返回代码,如果运行失败将抛出异常。
  1785. ''' </summary>
  1786. Public Function RunAsAdmin(Argument As String) As Integer
  1787. Dim NewProcess = Process.Start(New ProcessStartInfo(PathWithName) With {.Verb = "runas", .Arguments = Argument})
  1788. NewProcess.WaitForExit()
  1789. Return NewProcess.ExitCode
  1790. End Function
  1791. Private Uuid As Integer = 1
  1792. Private UuidLock As Object
  1793. ''' <summary>
  1794. ''' 获取一个全程序内不会重复的数字(伪 Uuid)。
  1795. ''' </summary>
  1796. Public Function GetUuid() As Integer
  1797. If UuidLock Is Nothing Then UuidLock = New Object
  1798. SyncLock UuidLock
  1799. Uuid += 1
  1800. Return Uuid
  1801. End SyncLock
  1802. End Function
  1803. ''' <summary>
  1804. ''' 将元素与 List 的混合体拆分为元素组。
  1805. ''' </summary>
  1806. Public Function GetFullList(Of T)(data As IList) As List(Of T)
  1807. GetFullList = New List(Of T)
  1808. For i = 0 To data.Count - 1
  1809. If TypeOf data(i) Is ICollection Then
  1810. GetFullList.AddRange(data(i))
  1811. Else
  1812. GetFullList.Add(data(i))
  1813. End If
  1814. Next i
  1815. End Function
  1816. ''' <summary>
  1817. ''' 数组去重。
  1818. ''' </summary>
  1819. <Extension> Public Function Distinct(Of T)(Arr As ICollection(Of T), IsEqual As CompareThreadStart(Of T)) As List(Of T)
  1820. Dim ResultArray As New List(Of T)
  1821. For i = 0 To Arr.Count - 1
  1822. For ii = i + 1 To Arr.Count - 1
  1823. If IsEqual(Arr(i), Arr(ii)) Then GoTo NextElement
  1824. Next
  1825. ResultArray.Add(Arr(i))
  1826. NextElement:
  1827. Next i
  1828. Return ResultArray
  1829. End Function
  1830. ''' <summary>
  1831. ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。
  1832. ''' </summary>
  1833. Public Function GetTimeNow() As String
  1834. Return Date.Now.ToString("HH':'mm':'ss'.'fff")
  1835. End Function
  1836. ''' <summary>
  1837. ''' 获取系统运行时间(毫秒),保证为正 Long 且大于 1,但可能突变减小。
  1838. ''' </summary>
  1839. Public Function GetTimeTick() As Long
  1840. Return My.Computer.Clock.TickCount + 2147483651L
  1841. End Function
  1842. ''' <summary>
  1843. ''' 将时间间隔转换为类似“5 分 10 秒前”的易于阅读的形式。
  1844. ''' </summary>
  1845. Public Function GetTimeSpanString(Span As TimeSpan, IsShortForm As Boolean) As String
  1846. Dim EndFix = If(Span.TotalMilliseconds > 0, "后", "前")
  1847. If Span.TotalMilliseconds < 0 Then Span = -Span
  1848. Dim TotalMonthes = Math.Floor(Span.Days / 30)
  1849. If IsShortForm Then
  1850. If TotalMonthes >= 12 Then
  1851. '1+ 年,“3 年”
  1852. GetTimeSpanString = Math.Floor(TotalMonthes / 12) & " 年"
  1853. ElseIf TotalMonthes >= 2 Then
  1854. '2~11 月,“5 个月”
  1855. GetTimeSpanString = TotalMonthes & " 个月"
  1856. ElseIf Span.TotalDays >= 2 Then
  1857. '2 天 ~ 2 月,“23 天”
  1858. GetTimeSpanString = Span.Days & " 天"
  1859. ElseIf Span.TotalHours >= 1 Then
  1860. '1 小时 ~ 2 天,“15 小时”
  1861. GetTimeSpanString = Span.Hours & " 小时"
  1862. ElseIf Span.TotalMinutes >= 1 Then
  1863. '1 分钟 ~ 1 小时,“49 分钟”
  1864. GetTimeSpanString = Span.Minutes & " 分钟"
  1865. ElseIf Span.TotalSeconds >= 1 Then
  1866. '1 秒 ~ 1 分钟,“23 秒”
  1867. GetTimeSpanString = Span.Seconds & " 秒"
  1868. Else
  1869. '不到 1 秒
  1870. GetTimeSpanString = "1 秒"
  1871. End If
  1872. Else
  1873. If TotalMonthes >= 61 Then
  1874. '5+ 年,“5 年”
  1875. GetTimeSpanString = Math.Floor(TotalMonthes / 12) & " 年"
  1876. ElseIf TotalMonthes >= 12 Then
  1877. '12~60 月,“1 年 2 个月”
  1878. GetTimeSpanString = Math.Floor(TotalMonthes / 12) & " 年" & If((TotalMonthes Mod 12) > 0, " " & (TotalMonthes Mod 12) & " 个月", "")
  1879. ElseIf TotalMonthes >= 4 Then
  1880. '4~11 月,“5 月”
  1881. GetTimeSpanString = TotalMonthes & " 月"
  1882. ElseIf TotalMonthes >= 1 Then
  1883. '1~4 月,“2 月 13 天”
  1884. GetTimeSpanString = TotalMonthes & " 月" & If((Span.Days Mod 30) > 0, " " & (Span.Days Mod 30) & " 天", "")
  1885. ElseIf Span.TotalDays >= 4 Then
  1886. '4~30 天,“23 天”
  1887. GetTimeSpanString = Span.Days & " 天"
  1888. ElseIf Span.TotalDays >= 1 Then
  1889. '1~3 天,“2 天 20 小时”
  1890. GetTimeSpanString = Span.Days & " 天" & If(Span.Hours > 0, " " & Span.Hours & " 小时", "")
  1891. ElseIf Span.TotalHours >= 10 Then
  1892. '10 小时 ~ 1 天,“15 小时”
  1893. GetTimeSpanString = Span.Hours & " 小时"
  1894. ElseIf Span.TotalHours >= 1 Then
  1895. '1~10 小时,“1 小时 20 分钟”
  1896. GetTimeSpanString = Span.Hours & " 小时" & If(Span.Minutes > 0, " " & Span.Minutes & " 分钟", "")
  1897. ElseIf Span.TotalMinutes >= 10 Then
  1898. '10 分钟 ~ 1 小时,“49 分钟”
  1899. GetTimeSpanString = Span.Minutes & " 分钟"
  1900. ElseIf Span.TotalMinutes >= 1 Then
  1901. '1~10 分钟,“9 分 23 秒”
  1902. GetTimeSpanString = Span.Minutes & " 分" & If(Span.Seconds > 0, " " & Span.Seconds & " 秒", "")
  1903. ElseIf Span.TotalSeconds >= 1 Then
  1904. '1 秒 ~ 1 分钟,“23 秒”
  1905. GetTimeSpanString = Span.Seconds & " 秒"
  1906. Else
  1907. '不到 1 秒
  1908. GetTimeSpanString = "1 秒"
  1909. End If
  1910. End If
  1911. GetTimeSpanString += EndFix
  1912. End Function
  1913. ''' <summary>
  1914. ''' 获取十进制 Unix 时间戳。
  1915. ''' </summary>
  1916. Public Function GetUnixTimestamp() As Long
  1917. Return (Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000
  1918. End Function
  1919. ''' <summary>
  1920. ''' 用于储存 RaiseByMouse 的 EventArgs。
  1921. ''' </summary>
  1922. Public NotInheritable Class RouteEventArgs
  1923. Inherits EventArgs
  1924. Public RaiseByMouse As Boolean
  1925. Public Handled As Boolean = False
  1926. Public Sub New(Optional RaiseByMouse As Boolean = False)
  1927. Me.RaiseByMouse = RaiseByMouse
  1928. End Sub
  1929. End Class
  1930. ''' <summary>
  1931. ''' 前台运行文件。
  1932. ''' </summary>
  1933. ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
  1934. ''' <param name="Arguments">运行参数。</param>
  1935. Public Sub ShellOnly(FileName As String, Optional Arguments As String = "")
  1936. Using Program As New Process
  1937. Program.StartInfo.Arguments = Arguments
  1938. Program.StartInfo.FileName = FileName
  1939. Log("[System] 执行外部命令:" & FileName & " " & Arguments)
  1940. Program.Start()
  1941. End Using
  1942. End Sub
  1943. ''' <summary>
  1944. ''' 前台运行文件并返回返回值。
  1945. ''' </summary>
  1946. ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
  1947. ''' <param name="Arguments">运行参数。</param>
  1948. ''' <param name="Timeout">等待该程序结束的最长时间(毫秒)。超时会返回 Result.Timeout。</param>
  1949. Public Function ShellAndGetExitCode(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000) As Result
  1950. Try
  1951. Using Program As New Process
  1952. Program.StartInfo.Arguments = Arguments
  1953. Program.StartInfo.FileName = FileName
  1954. Log("[System] 执行外部命令并等待返回码:" & FileName & " " & Arguments)
  1955. Program.Start()
  1956. If Program.WaitForExit(Timeout) Then
  1957. Return Program.ExitCode
  1958. Else
  1959. Return Result.Timeout
  1960. End If
  1961. End Using
  1962. Catch ex As Exception
  1963. Log(ex, "执行命令失败:" & FileName, LogLevel.Msgbox)
  1964. Return Result.Fail
  1965. End Try
  1966. End Function
  1967. ''' <summary>
  1968. ''' 静默运行文件并返回输出流字符串。执行失败会抛出异常。
  1969. ''' </summary>
  1970. ''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
  1971. ''' <param name="Arguments">运行参数。</param>
  1972. ''' <param name="Timeout">等待该程序结束的最长时间(毫秒)。超时会抛出错误。</param>
  1973. Public Function ShellAndGetOutput(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000, Optional WorkingDirectory As String = Nothing) As String
  1974. Dim Info = New ProcessStartInfo With {
  1975. .Arguments = Arguments,
  1976. .FileName = FileName,
  1977. .UseShellExecute = False,
  1978. .CreateNoWindow = True,
  1979. .RedirectStandardError = True,
  1980. .RedirectStandardOutput = True,
  1981. .WorkingDirectory = If(WorkingDirectory, Path.TrimEnd("\"c))
  1982. }
  1983. If WorkingDirectory IsNot Nothing Then
  1984. If Info.EnvironmentVariables.ContainsKey("appdata") Then
  1985. Info.EnvironmentVariables("appdata") = WorkingDirectory
  1986. Else
  1987. Info.EnvironmentVariables.Add("appdata", WorkingDirectory)
  1988. End If
  1989. End If
  1990. Log("[System] 执行外部命令并等待返回结果:" & FileName & " " & Arguments)
  1991. Using Program As New Process() With {.StartInfo = Info}
  1992. Program.Start()
  1993. Dim Result As String = Program.StandardOutput.ReadToEnd & Program.StandardError.ReadToEnd
  1994. Program.WaitForExit(Timeout)
  1995. If Not Program.HasExited Then Program.Kill()
  1996. Return Result
  1997. End Using
  1998. End Function
  1999. ''' <summary>
  2000. ''' 在新的工作线程中执行代码。
  2001. ''' </summary>
  2002. Public Function RunInNewThread(Action As Action, Name As String, Optional Priority As ThreadPriority = ThreadPriority.Normal) As Thread
  2003. Dim th As New Thread(
  2004. Sub()
  2005. Try
  2006. Action()
  2007. Catch ex As ThreadInterruptedException
  2008. Log(Name & ":线程已中止")
  2009. Catch ex As Exception
  2010. Log(ex, Name & ":线程执行失败", LogLevel.Feedback)
  2011. End Try
  2012. End Sub) With {.Name = Name, .Priority = Priority}
  2013. th.Start()
  2014. Return th
  2015. End Function
  2016. ''' <summary>
  2017. ''' 确保在 UI 线程中执行代码。
  2018. ''' 如果当前并非 UI 线程,则会阻断当前线程,直至 UI 线程执行完毕。
  2019. ''' 为防止线程互锁,请仅在开始加载动画、从 UI 获取输入时使用!
  2020. ''' </summary>
  2021. Public Function RunInUiWait(Of Output)(Action As Func(Of Output)) As Output
  2022. If RunInUi() Then
  2023. Return Action()
  2024. Else
  2025. Return Application.Current.Dispatcher.Invoke(Action)
  2026. End If
  2027. End Function
  2028. ''' <summary>
  2029. ''' 确保在 UI 线程中执行代码。
  2030. ''' 如果当前并非 UI 线程,则会阻断当前线程,直至 UI 线程执行完毕。
  2031. ''' 为防止线程互锁,请仅在开始加载动画、从 UI 获取输入时使用!
  2032. ''' </summary>
  2033. Public Sub RunInUiWait(Action As Action)
  2034. If RunInUi() Then
  2035. Action()
  2036. Else
  2037. Application.Current.Dispatcher.Invoke(Action)
  2038. End If
  2039. End Sub
  2040. ''' <summary>
  2041. ''' 确保在 UI 线程中执行代码,代码按触发顺序执行。
  2042. ''' 如果当前并非 UI 线程,也不阻断当前线程的执行。
  2043. ''' </summary>
  2044. Public Sub RunInUi(Action As Action, Optional ForceWaitUntilLoaded As Boolean = False)
  2045. If ForceWaitUntilLoaded Then
  2046. Application.Current.Dispatcher.InvokeAsync(Action, Threading.DispatcherPriority.Loaded)
  2047. ElseIf RunInUi() Then
  2048. Action()
  2049. Else
  2050. Application.Current.Dispatcher.InvokeAsync(Action)
  2051. End If
  2052. End Sub
  2053. ''' <summary>
  2054. ''' 确保在工作线程中执行代码。
  2055. ''' </summary>
  2056. Public Sub RunInThread(Action As Action)
  2057. If RunInUi() Then
  2058. RunInNewThread(Action, "Runtime Invoke " & GetUuid() & "#")
  2059. Else
  2060. Action()
  2061. End If
  2062. End Sub
  2063. ''' <summary>
  2064. ''' 按照既定的函数进行选择排序。
  2065. ''' </summary>
  2066. ''' <param name="SortRule">传入两个对象,若第一个对象应该排在前面,则返回 True。</param>
  2067. <Extension> Public Function Sort(Of T)(List As IList(Of T), SortRule As CompareThreadStart(Of T)) As List(Of T)
  2068. Dim NewList As New List(Of T)
  2069. While List.Any
  2070. Dim Highest = List(0)
  2071. For i = 1 To List.Count - 1
  2072. If SortRule(List(i), Highest) Then Highest = List(i)
  2073. Next
  2074. List.Remove(Highest)
  2075. NewList.Add(Highest)
  2076. End While
  2077. Return NewList
  2078. End Function
  2079. Public Delegate Function CompareThreadStart(Of T)(Left As T, Right As T) As Boolean
  2080. ''' <summary>
  2081. ''' 返回列表的浅表副本。
  2082. ''' </summary>
  2083. <Extension> Public Function Clone(Of T)(list As IList(Of T)) As IList(Of T)
  2084. Return New List(Of T)(list)
  2085. End Function
  2086. ''' <summary>
  2087. ''' 获取程序启动参数。
  2088. ''' </summary>
  2089. ''' <param name="Name">参数名。</param>
  2090. ''' <param name="DefaultValue">默认值。</param>
  2091. Public Function GetProgramArgument(Name As String, Optional DefaultValue As Object = "")
  2092. Dim AllArguments() As String = Command.Split(" ")
  2093. For i = 0 To AllArguments.Length - 1
  2094. If AllArguments(i) = "-" & Name Then
  2095. If AllArguments.Length = i + 1 OrElse AllArguments(i + 1).StartsWithF("-") Then Return True
  2096. Return AllArguments(i + 1)
  2097. End If
  2098. Next
  2099. Return DefaultValue
  2100. End Function
  2101. ''' <summary>
  2102. ''' 时间戳转化为日期。
  2103. ''' </summary>
  2104. Public Function GetDate(timeStamp As Integer) As Date
  2105. Dim dtStart As Date = TimeZone.CurrentTimeZone.ToLocalTime(New Date(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc))
  2106. Dim lTime As Long = (CLng(timeStamp) * 10000000)
  2107. Return dtStart.Add(New TimeSpan(lTime))
  2108. End Function
  2109. ''' <summary>
  2110. ''' 将 UTC 时间转化为当前时区的时间。
  2111. ''' </summary>
  2112. Public Function GetLocalTime(UtcDate As Date) As Date
  2113. Return DateTime.SpecifyKind(UtcDate, DateTimeKind.Utc).ToLocalTime
  2114. End Function
  2115. ''' <summary>
  2116. ''' 打开网页。
  2117. ''' </summary>
  2118. Public Sub OpenWebsite(Url As String)
  2119. Try
  2120. If Not Url.StartsWithF("http", True) AndAlso Not Url.StartsWithF("minecraft://", True) Then
  2121. Throw New Exception(Url & " 不是一个有效的网址,它必须以 http 开头!")
  2122. End If
  2123. Log("[System] 正在打开网页:" & Url)
  2124. Process.Start(Url)
  2125. Catch ex As Exception
  2126. Log(ex, "无法打开网页(" & Url & ")")
  2127. ClipboardSet(Url, False)
  2128. MyMsgBox("可能由于浏览器未正确配置,PCL 无法为你打开网页。" & vbCrLf & "网址已经复制到剪贴板,若有需要可以手动粘贴访问。" & vbCrLf &
  2129. $"网址:{Url}", "无法打开网页")
  2130. End Try
  2131. End Sub
  2132. ''' <summary>
  2133. ''' 打开 explorer。注意参数中的路径要尽量加上双引号!
  2134. ''' </summary>
  2135. Public Sub OpenExplorer(Argument As String)
  2136. Try
  2137. ShellOnly("explorer.exe", Argument)
  2138. Catch ex As Exception
  2139. Log(ex, "打开资源管理器失败,请尝试关闭安全软件(如 360 安全卫士)", LogLevel.Msgbox)
  2140. End Try
  2141. End Sub
  2142. ''' <summary>
  2143. ''' 设置剪贴板。将在另一线程运行,且不会抛出异常。
  2144. ''' </summary>
  2145. Public Sub ClipboardSet(Text As String, Optional ShowSuccessHint As Boolean = True)
  2146. RunInThread(
  2147. Sub()
  2148. Dim RetryCount As Integer = 0
  2149. Retry:
  2150. Try
  2151. RunInUi(
  2152. Sub()
  2153. My.Computer.Clipboard.Clear()
  2154. My.Computer.Clipboard.SetText(Text)
  2155. End Sub)
  2156. Catch ex As Exception
  2157. RetryCount += 1
  2158. If RetryCount <= 5 Then
  2159. Thread.Sleep(20)
  2160. GoTo Retry
  2161. Else
  2162. Log(ex, "可能由于剪贴板被其他程序占用,文本复制失败", LogLevel.Hint)
  2163. End If
  2164. End Try
  2165. If ShowSuccessHint Then Hint("已成功复制!", HintType.Finish)
  2166. End Sub)
  2167. End Sub
  2168. ''' <summary>
  2169. ''' 以 Byte() 形式获取程序中的资源。
  2170. ''' </summary>
  2171. Public Function GetResources(ResourceName As String) As Byte()
  2172. Log("[System] 获取资源:" & ResourceName)
  2173. Dim Raw As Byte() = My.Resources.ResourceManager.GetObject(ResourceName)
  2174. Return Raw
  2175. End Function
  2176. #End Region
  2177. #Region "UI"
  2178. '边距改变
  2179. ''' <summary>
  2180. ''' 相对增减控件的左边距。
  2181. ''' </summary>
  2182. Public Sub DeltaLeft(control As FrameworkElement, newValue As Double)
  2183. '安全性检查
  2184. DebugAssert(Not Double.IsNaN(newValue))
  2185. DebugAssert(Not Double.IsInfinity(newValue))
  2186. If TypeOf control Is Window Then
  2187. '窗口改变
  2188. CType(control, Window).Left += newValue
  2189. Else
  2190. '根据 HorizontalAlignment 改变数值
  2191. Select Case control.HorizontalAlignment
  2192. Case HorizontalAlignment.Left, HorizontalAlignment.Stretch
  2193. control.Margin = New Thickness(control.Margin.Left + newValue, control.Margin.Top, control.Margin.Right, control.Margin.Bottom)
  2194. Case HorizontalAlignment.Right
  2195. control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right - newValue, control.Margin.Bottom)
  2196. 'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom)
  2197. Case Else
  2198. DebugAssert(False)
  2199. End Select
  2200. End If
  2201. End Sub
  2202. ''' <summary>
  2203. ''' 设置控件的左边距。(仅针对置左控件)
  2204. ''' </summary>
  2205. Public Sub SetLeft(control As FrameworkElement, newValue As Double)
  2206. DebugAssert(control.HorizontalAlignment = HorizontalAlignment.Left)
  2207. control.Margin = New Thickness(newValue, control.Margin.Top, control.Margin.Right, control.Margin.Bottom)
  2208. End Sub
  2209. ''' <summary>
  2210. ''' 相对增减控件的上边距。
  2211. ''' </summary>
  2212. Public Sub DeltaTop(control As FrameworkElement, newValue As Double)
  2213. '安全性检查
  2214. DebugAssert(Not Double.IsNaN(newValue))
  2215. DebugAssert(Not Double.IsInfinity(newValue))
  2216. If TypeOf control Is Window Then
  2217. '窗口改变
  2218. CType(control, Window).Top += newValue
  2219. Else
  2220. '根据 VerticalAlignment 改变数值
  2221. Select Case control.VerticalAlignment
  2222. Case VerticalAlignment.Top
  2223. control.Margin = New Thickness(control.Margin.Left, control.Margin.Top + newValue, control.Margin.Right, control.Margin.Bottom)
  2224. Case VerticalAlignment.Bottom
  2225. control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right, control.Margin.Bottom - newValue)
  2226. 'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom)
  2227. Case Else
  2228. DebugAssert(False)
  2229. End Select
  2230. End If
  2231. 'If Double.IsNaN(newValue) OrElse Double.IsInfinity(newValue) Then Exit Sub '安全性检查
  2232. 'Select Case control.VerticalAlignment
  2233. ' Case VerticalAlignment.Top, VerticalAlignment.Stretch, VerticalAlignment.Center
  2234. ' control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom)
  2235. ' Case VerticalAlignment.Bottom
  2236. ' control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right, -newValue)
  2237. ' 'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right, CType(control.Parent, Object).ActualHeight - control.ActualHeight - newValue)
  2238. 'End Select
  2239. End Sub
  2240. ''' <summary>
  2241. ''' 设置控件的顶边距。(仅针对置上控件)
  2242. ''' </summary>
  2243. Public Sub SetTop(control As FrameworkElement, newValue As Double)
  2244. DebugAssert(control.VerticalAlignment = VerticalAlignment.Top)
  2245. control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom)
  2246. End Sub
  2247. 'DPI 转换
  2248. Public ReadOnly DPI As Integer = System.Drawing.Graphics.FromHwnd(IntPtr.Zero).DpiX
  2249. ''' <summary>
  2250. ''' 将经过 DPI 缩放的 WPF 尺寸转化为实际的像素尺寸。
  2251. ''' </summary>
  2252. Public Function GetPixelSize(WPFSize As Double) As Double
  2253. Return WPFSize / 96 * DPI
  2254. End Function
  2255. ''' <summary>
  2256. ''' 将实际的像素尺寸转化为经过 DPI 缩放的 WPF 尺寸。
  2257. ''' </summary>
  2258. Public Function GetWPFSize(PixelSize As Double) As Double
  2259. Return PixelSize * 96 / DPI
  2260. End Function
  2261. 'UI 截图
  2262. ''' <summary>
  2263. ''' 将某个控件的呈现转换为图片。
  2264. ''' </summary>
  2265. Public Function ControlBrush(UI As FrameworkElement) As ImageBrush
  2266. Dim Width = UI.ActualWidth, Height = UI.ActualHeight
  2267. If Width < 1 OrElse Height < 1 Then Return New ImageBrush
  2268. Dim bmp As New RenderTargetBitmap(GetPixelSize(Width), GetPixelSize(Height), DPI, DPI, PixelFormats.Pbgra32)
  2269. bmp.Render(UI)
  2270. Return New ImageBrush(bmp)
  2271. End Function
  2272. ''' <summary>
  2273. ''' 将某个控件的模拟呈现转换为图片。
  2274. ''' </summary>
  2275. Public Function ControlBrush(UI As FrameworkElement, Width As Double, Height As Double, Optional Left As Double = 0, Optional Top As Double = 0) As ImageBrush
  2276. UI.Measure(New Size(Width, Height))
  2277. UI.Arrange(New Rect(0, 0, Width, Height))
  2278. Dim bmp As New RenderTargetBitmap(GetPixelSize(Width), GetPixelSize(Height), DPI, DPI, PixelFormats.Default)
  2279. bmp.Render(UI)
  2280. If Not (Left = 0 AndAlso Top = 0) Then UI.Arrange(New Rect(Left, Top, Width, Height))
  2281. Return New ImageBrush(bmp)
  2282. End Function
  2283. ''' <summary>
  2284. ''' 将 UI 内容固定为图片并进行 Clear。
  2285. ''' </summary>
  2286. Public Sub ControlFreeze(UI As Panel)
  2287. UI.Background = ControlBrush(UI)
  2288. UI.Children.Clear()
  2289. End Sub
  2290. ''' <summary>
  2291. ''' 将 UI 内容固定为图片并进行 Clear。
  2292. ''' </summary>
  2293. Public Sub ControlFreeze(UI As Border)
  2294. UI.Background = ControlBrush(UI)
  2295. UI.Child = Nothing
  2296. End Sub
  2297. ''' <summary>
  2298. ''' 将 XML 转换为对应 UI 对象。
  2299. ''' </summary>
  2300. Public Function GetObjectFromXML(Str As XElement)
  2301. Return GetObjectFromXML(Str.ToString)
  2302. End Function
  2303. ''' <summary>
  2304. ''' 将 XML 转换为对应 UI 对象。
  2305. ''' </summary>
  2306. Public Function GetObjectFromXML(Str As String)
  2307. Using Stream As New MemoryStream
  2308. Using Writer As New StreamWriter(Stream)
  2309. Writer.Write(Str)
  2310. Writer.Flush()
  2311. Stream.Position = 0
  2312. Return XamlReader.Load(Stream)
  2313. End Using
  2314. End Using
  2315. End Function
  2316. Private ReadOnly UiThreadId As Integer = Thread.CurrentThread.ManagedThreadId
  2317. ''' <summary>
  2318. ''' 当前线程是否为主线程。
  2319. ''' </summary>
  2320. Public Function RunInUi() As Boolean
  2321. Return Thread.CurrentThread.ManagedThreadId = UiThreadId
  2322. End Function
  2323. ''' <summary>
  2324. ''' 检查某个控件是否位于主窗口可视区域内,且控件本身可见。
  2325. ''' </summary>
  2326. <Extension> Public Function IsVisibleInForm(element As FrameworkElement) As Boolean
  2327. If Not element.IsVisible Then Return False
  2328. Dim bounds As Rect = element.TransformToAncestor(FrmMain).TransformBounds(New Rect(0, 0, element.ActualWidth, element.ActualHeight))
  2329. Dim rect As New Rect(0, 0, FrmMain.ActualWidth, FrmMain.ActualHeight)
  2330. Return rect.Contains(bounds.TopLeft) OrElse rect.Contains(bounds.BottomRight)
  2331. End Function
  2332. #End Region
  2333. #Region "Debug"
  2334. Public ModeDebug As Boolean = False
  2335. 'Log
  2336. Public Enum LogLevel
  2337. ''' <summary>
  2338. ''' 不提示,只记录日志。
  2339. ''' </summary>
  2340. Normal = 0
  2341. ''' <summary>
  2342. ''' 只提示开发者。
  2343. ''' </summary>
  2344. Developer = 1
  2345. ''' <summary>
  2346. ''' 只提示开发者与调试模式用户。
  2347. ''' </summary>
  2348. Debug = 2
  2349. ''' <summary>
  2350. ''' 弹出提示所有用户。
  2351. ''' </summary>
  2352. Hint = 3
  2353. ''' <summary>
  2354. ''' 弹窗,不要求反馈。
  2355. ''' </summary>
  2356. Msgbox = 4
  2357. ''' <summary>
  2358. ''' 弹窗,要求反馈。
  2359. ''' </summary>
  2360. Feedback = 5
  2361. ''' <summary>
  2362. ''' 弹窗,结束程序。
  2363. ''' </summary>
  2364. Assert = 6
  2365. End Enum
  2366. Private LogList As New StringBuilder
  2367. Private LogWritter As StreamWriter
  2368. Public Sub LogStart()
  2369. RunInNewThread(
  2370. Sub()
  2371. Dim IsInitSuccess As Boolean = True
  2372. Try
  2373. For i = 4 To 1 Step -1
  2374. If File.Exists(Path & "PCL\Log" & i & ".txt") Then
  2375. If File.Exists(Path & "PCL\Log" & (i + 1) & ".txt") Then File.Delete(Path & "PCL\Log" & (i + 1) & ".txt")
  2376. CopyFile(Path & "PCL\Log" & i & ".txt", Path & "PCL\Log" & (i + 1) & ".txt")
  2377. End If
  2378. Next
  2379. File.Create(Path & "PCL\Log1.txt").Dispose()
  2380. Catch ex As IOException
  2381. IsInitSuccess = False
  2382. Hint("可能同时开启了多个 PCL,程序可能会出现未知问题!", HintType.Critical)
  2383. Log(ex, "日志初始化失败(疑似文件占用问题)")
  2384. Catch ex As Exception
  2385. IsInitSuccess = False
  2386. Log(ex, "日志初始化失败", LogLevel.Hint)
  2387. End Try
  2388. Try
  2389. LogWritter = New StreamWriter(Path & "PCL\Log1.txt", True) With {.AutoFlush = True}
  2390. Catch ex As Exception
  2391. LogWritter = Nothing
  2392. Log(ex, "日志写入失败", LogLevel.Hint)
  2393. End Try
  2394. While True
  2395. If IsInitSuccess Then
  2396. LogFlush()
  2397. Else
  2398. LogList = New StringBuilder '清空 LogList 避免内存爆炸
  2399. End If
  2400. Thread.Sleep(50)
  2401. End While
  2402. End Sub, "Log Writer", ThreadPriority.Lowest)
  2403. End Sub
  2404. Private ReadOnly LogFlushLock As New Object '防止外部调用 LogFlush 时同时输出多次日志
  2405. Public Sub LogFlush()
  2406. On Error Resume Next
  2407. If LogWritter Is Nothing Then Exit Sub
  2408. Dim Log As String = Nothing
  2409. SyncLock LogFlushLock
  2410. If LogList.Length > 0 Then
  2411. Dim LogListCache As StringBuilder
  2412. LogListCache = LogList
  2413. LogList = New StringBuilder
  2414. Log = LogListCache.ToString
  2415. End If
  2416. End SyncLock
  2417. If Log IsNot Nothing Then
  2418. LogWritter.Write(Log)
  2419. End If
  2420. End Sub
  2421. Private ReadOnly LogListLock As New Object '防止日志乱码,只在调试模式下启用
  2422. ''' <summary>
  2423. ''' 输出 Log。
  2424. ''' </summary>
  2425. ''' <param name="Title">如果要求弹窗,指定弹窗的标题。</param>
  2426. Public Sub Log(Text As String, Optional Level As LogLevel = LogLevel.Normal, Optional Title As String = "出现错误")
  2427. On Error Resume Next
  2428. '放在最后会导致无法显示极端错误下的弹窗(如无法写入日志文件)
  2429. '处理错误会导致再次调用 Log() 导致无限循环
  2430. '输出日志
  2431. Dim AppendText As String = "[" & GetTimeNow() & "] " & Text & vbCrLf '减轻同步锁占用
  2432. If ModeDebug Then
  2433. SyncLock LogListLock
  2434. LogList.Append(AppendText)
  2435. End SyncLock
  2436. Else
  2437. LogList.Append(AppendText)
  2438. End If
  2439. #If DEBUG Then
  2440. Console.Write(AppendText)
  2441. #End If
  2442. If IsProgramEnded OrElse Level = LogLevel.Normal Then Exit Sub
  2443. '去除前缀
  2444. Text = RegexReplace(Text, "", "\[[^\]]+?\] ")
  2445. '输出提示
  2446. Select Case Level
  2447. #If DEBUG Then
  2448. Case LogLevel.Developer
  2449. Hint("[开发者模式] " & Text, HintType.Info, False)
  2450. Case LogLevel.Debug
  2451. Hint("[调试模式] " & Text, HintType.Info, False)
  2452. #Else
  2453. Case LogLevel.Developer
  2454. Case LogLevel.Debug
  2455. If ModeDebug Then Hint("[调试模式] " & Text, HintType.Info, False)
  2456. #End If
  2457. Case LogLevel.Hint
  2458. Hint(Text, HintType.Critical, False)
  2459. Case LogLevel.Msgbox
  2460. MyMsgBox(Text, Title, IsWarn:=True)
  2461. Case LogLevel.Feedback
  2462. If CanFeedback(False) Then
  2463. If MyMsgBox(Text & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", Title, "反馈", "取消", IsWarn:=True) = 1 Then Feedback(False, True)
  2464. Else
  2465. MyMsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True)
  2466. End If
  2467. Case LogLevel.Assert
  2468. Dim Time As Long = GetTimeTick()
  2469. If CanFeedback(False) Then
  2470. If MsgBox(Text & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True)
  2471. Else
  2472. MsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title)
  2473. End If
  2474. If GetTimeTick() - Time < 1500 Then
  2475. '弹窗无法保留
  2476. Log("[System] PCL 已崩溃:" & vbCrLf & Text)
  2477. FormMain.EndProgramForce(Result.Exception)
  2478. Else
  2479. FormMain.EndProgramForce(Result.Fail)
  2480. End If
  2481. End Select
  2482. End Sub
  2483. ''' <summary>
  2484. ''' 输出错误信息。
  2485. ''' </summary>
  2486. ''' <param name="Desc">错误描述。会在处理时在末尾加入冒号。</param>
  2487. Public Sub Log(Ex As Exception, Desc As String, Optional Level As LogLevel = LogLevel.Debug, Optional Title As String = "出现错误")
  2488. On Error Resume Next
  2489. If TypeOf Ex Is ThreadInterruptedException Then Exit Sub
  2490. '获取错误信息
  2491. Dim ExFull As String = Desc & ":" & GetExceptionDetail(Ex)
  2492. '输出日志
  2493. Dim AppendText As String = "[" & GetTimeNow() & "] " & Desc & ":" & GetExceptionDetail(Ex, True) & vbCrLf '减轻同步锁占用
  2494. If ModeDebug Then
  2495. SyncLock LogListLock
  2496. LogList.Append(AppendText)
  2497. End SyncLock
  2498. Else
  2499. LogList.Append(AppendText)
  2500. End If
  2501. #If DEBUG Then
  2502. Console.Write(AppendText)
  2503. #End If
  2504. If IsProgramEnded Then Exit Sub
  2505. '输出提示
  2506. Select Case Level
  2507. Case LogLevel.Normal
  2508. #If DEBUG Then
  2509. Case LogLevel.Developer
  2510. Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
  2511. Hint("[开发者模式] " & ExLine, HintType.Info, False)
  2512. Case LogLevel.Debug
  2513. Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
  2514. Hint("[调试模式] " & ExLine, HintType.Info, False)
  2515. #Else
  2516. Case LogLevel.Developer
  2517. Case LogLevel.Debug
  2518. Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
  2519. If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Info, False)
  2520. #End If
  2521. Case LogLevel.Hint
  2522. Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex)
  2523. Hint(ExLine, HintType.Critical, False)
  2524. Case LogLevel.Msgbox
  2525. MyMsgBox(ExFull, Title, IsWarn:=True)
  2526. Case LogLevel.Feedback
  2527. If CanFeedback(False) Then
  2528. If MyMsgBox(ExFull & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", Title, "反馈", "取消", IsWarn:=True) = 1 Then Feedback(False, True)
  2529. Else
  2530. MyMsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True)
  2531. End If
  2532. Case LogLevel.Assert
  2533. Dim Time As Long = GetTimeTick()
  2534. If CanFeedback(False) Then
  2535. If MsgBox(ExFull & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True)
  2536. Else
  2537. MsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title)
  2538. End If
  2539. If GetTimeTick() - Time < 1500 Then
  2540. '弹窗无法保留
  2541. Log("[System] PCL 已崩溃:" & vbCrLf & ExFull)
  2542. FormMain.EndProgramForce(Result.Exception)
  2543. Else
  2544. FormMain.EndProgramForce(Result.Fail)
  2545. End If
  2546. End Select
  2547. End Sub
  2548. '反馈
  2549. Public Sub Feedback(Optional ShowMsgbox As Boolean = True, Optional ForceOpenLog As Boolean = False)
  2550. On Error Resume Next
  2551. FeedbackInfo()
  2552. If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then
  2553. OpenExplorer("""" & Path & "PCL\""")
  2554. End If
  2555. OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/")
  2556. End Sub
  2557. Public Function CanFeedback(ShowHint As Boolean) As Boolean
  2558. If False.Equals(PageSetupSystem.IsLauncherNewest) Then
  2559. If ShowHint Then MyMsgBox("你的 PCL 不是最新版,因此无法提交反馈。" & vbCrLf & "请先在 设置 → 启动器 中更新启动器,确认该问题在最新版中依然存在,然后再提交反馈。", "无法提交反馈")
  2560. Return False
  2561. Else
  2562. Return True
  2563. End If
  2564. End Function
  2565. ''' <summary>
  2566. ''' 在日志中输出系统诊断信息。
  2567. ''' </summary>
  2568. Public Sub FeedbackInfo()
  2569. On Error Resume Next
  2570. Log("[System] 诊断信息:" & vbCrLf &
  2571. "操作系统:" & My.Computer.Info.OSFullName & "(32 位:" & Is32BitSystem & ")" & vbCrLf &
  2572. "剩余内存:" & Int(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024) & " M / " & Int(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024) & " M" & vbCrLf &
  2573. "DPI:" & DPI & "(" & Math.Round(DPI / 96, 2) * 100 & "%)" & vbCrLf &
  2574. "MC 文件夹:" & If(PathMcFolder, "Nothing") & vbCrLf &
  2575. "文件位置:" & Path)
  2576. End Sub
  2577. '断言
  2578. Public Sub DebugAssert(Exp As Boolean)
  2579. If Not Exp Then Throw New Exception("断言命中")
  2580. End Sub
  2581. '获取当前的堆栈信息
  2582. Public Function GetStackTrace() As String
  2583. Dim Stack As New StackTrace()
  2584. Return Join(Stack.GetFrames().Skip(1).Select(Function(f) f.GetMethod).
  2585. Select(Function(f) f.Name & "(" & Join(f.GetParameters.Select(Function(p) p.ToString).ToList, ", ") & ") - " & f.Module.ToString).ToList,
  2586. vbCrLf).Replace(vbCrLf & vbCrLf, vbCrLf)
  2587. End Function
  2588. #End Region
  2589. #Region "随机"
  2590. Private ReadOnly Random As New Random
  2591. ''' <summary>
  2592. ''' 随机选择其一。
  2593. ''' </summary>
  2594. Public Function RandomOne(Of T)(objects As ICollection(Of T)) As T
  2595. Return objects(RandomInteger(0, objects.Count - 1))
  2596. End Function
  2597. ''' <summary>
  2598. ''' 取随机整数。
  2599. ''' </summary>
  2600. Public Function RandomInteger(min As Integer, max As Integer) As Integer
  2601. Return Math.Floor((max - min + 1) * Random.NextDouble) + min
  2602. End Function
  2603. ''' <summary>
  2604. ''' 将数组随机打乱。
  2605. ''' </summary>
  2606. Public Function Shuffle(Of T)(array As IList(Of T)) As IList(Of T)
  2607. Shuffle = New List(Of T)
  2608. Do While array.Any
  2609. Dim i As Integer = RandomInteger(0, array.Count - 1)
  2610. Shuffle.Add(array(i))
  2611. array.RemoveAt(i)
  2612. Loop
  2613. End Function
  2614. #End Region
  2615. End Module