From a41579b1da3beadcae904b44405fe52bd4282acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E8=85=BE=E7=8C=AB=E8=B7=83?= <1043137532@qq.com> Date: Tue, 7 Oct 2025 04:57:14 +0800 Subject: [PATCH] 2.10.9 --- Plain Craft Launcher 2/Application.xaml.vb | 5 +- .../Controls/{ => Behaviors}/IMyRadio.vb | 0 .../Controls/{ => Behaviors}/Interfaces.vb | 0 .../Controls/MyButton.xaml.vb | 43 +- Plain Craft Launcher 2/Controls/MyCard.vb | 37 +- .../Controls/MyCheckBox.xaml.vb | 4 +- Plain Craft Launcher 2/Controls/MyComboBox.vb | 21 +- .../Controls/MyExtraButton.xaml.vb | 61 +- .../Controls/MyExtraTextButton.xaml.vb | 12 +- .../Controls/MyHint.xaml.vb | 32 +- .../Controls/MyIconButton.xaml.vb | 39 +- .../Controls/MyIconTextButton.xaml.vb | 35 +- Plain Craft Launcher 2/Controls/MyImage.vb | 19 +- .../Controls/MyListItem.xaml.vb | 45 +- Plain Craft Launcher 2/Controls/MyMenuItem.vb | 3 + .../Controls/MyRadioBox.xaml.vb | 1 + .../Controls/MyRadioButton.xaml.vb | 16 +- .../Controls/MySearchBox.xaml.vb | 3 + Plain Craft Launcher 2/Controls/MyTextBox.vb | 4 + .../Controls/MyTextButton.vb | 65 +-- Plain Craft Launcher 2/FormMain.xaml.vb | 36 +- .../Modules/Base/ModBase.vb | 54 +- Plain Craft Launcher 2/Modules/Base/ModNet.vb | 40 +- .../Modules/Minecraft/ModCrash.vb | 5 +- .../Modules/Minecraft/ModLaunch.vb | 98 ++-- .../Modules/Minecraft/ModMinecraft.vb | 62 +- .../Modules/Minecraft/ModModpack.vb | 24 +- .../Modules/Minecraft/ModWatcher.vb | 2 +- Plain Craft Launcher 2/Modules/ModEvent.vb | 502 ++++++++++------- Plain Craft Launcher 2/Modules/ModMain.vb | 121 ++-- Plain Craft Launcher 2/Modules/ModMusic.vb | 24 +- Plain Craft Launcher 2/Modules/ModSecret.vb | 6 +- .../My Project/AssemblyInfo.vb | 4 +- .../Comp/PageDownloadCompDetail.xaml.vb | 14 +- .../Pages/PageDownload/ModDownloadLib.vb | 36 +- .../Pages/PageLaunch/MyMsgLogin.xaml | 22 +- .../Pages/PageLaunch/MyMsgLogin.xaml.vb | 8 +- .../Pages/PageLaunch/MySkin.xaml.vb | 18 +- .../Pages/PageLaunch/PageLaunchRight.xaml.vb | 14 +- .../PageLaunch/PageLoginAuthSkin.xaml.vb | 2 +- .../Pages/PageLaunch/PageLoginLegacy.xaml.vb | 2 +- .../Pages/PageLaunch/PageLoginMs.xaml | 4 +- .../Pages/PageLaunch/PageLoginMs.xaml.vb | 2 +- .../Pages/PageLaunch/PageLoginMsSkin.xaml.vb | 6 +- .../Pages/PageLink/PageLinkFeedback.xaml | 19 - .../Pages/PageLink/PageLinkHiper.xaml | 3 +- .../Pages/PageLink/PageLinkIoi.xaml | 2 +- .../Pages/PageLink/PageLinkIoi.xaml.vb | 21 +- .../Pages/PageOther/PageOtherAbout.xaml | 531 ++++++++---------- .../Pages/PageOther/PageOtherAbout.xaml.vb | 4 +- .../Pages/PageOther/PageOtherHelp.xaml.vb | 2 +- .../PageOther/PageOtherHelpDetail.xaml.vb | 6 +- .../Pages/PageSelectLeft.xaml.vb | 26 +- .../Pages/PageSelectRight.xaml.vb | 21 +- .../Pages/PageSetup/ModSetup.vb | 31 +- .../Pages/PageSetup/PageSetupLaunch.xaml | 10 +- .../Pages/PageSetup/PageSetupLaunch.xaml.vb | 14 +- .../Pages/PageSetup/PageSetupLink.xaml.vb | 2 +- .../Pages/PageSetup/PageSetupSystem.xaml.vb | 4 +- .../Pages/PageSetup/PageSetupUI.xaml | 3 +- .../Pages/PageSetup/PageSetupUI.xaml.vb | 18 +- .../Pages/PageSpeedLeft.xaml.vb | 2 +- .../Pages/PageVersion/PageVersionExport.xaml | 2 +- .../PageVersion/PageVersionExport.xaml.vb | 13 +- .../Pages/PageVersion/PageVersionMod.xaml.vb | 34 +- .../PageVersion/PageVersionOverall.xaml.vb | 22 +- .../Pages/PageVersion/PageVersionSetup.xaml | 10 +- .../PageVersion/PageVersionSetup.xaml.vb | 2 +- .../Plain Craft Launcher 2.vbproj | 4 +- Plain Craft Launcher 2/Resources/Custom.xaml | 134 +---- Plain Craft Launcher 2/Resources/Help.zip | Bin 85810 -> 83977 bytes 71 files changed, 1151 insertions(+), 1340 deletions(-) rename Plain Craft Launcher 2/Controls/{ => Behaviors}/IMyRadio.vb (100%) rename Plain Craft Launcher 2/Controls/{ => Behaviors}/Interfaces.vb (100%) diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb index db86a37..cdde3cd 100644 --- a/Plain Craft Launcher 2/Application.xaml.vb +++ b/Plain Craft Launcher 2/Application.xaml.vb @@ -78,7 +78,6 @@ Public Class Application End If End Try Directory.CreateDirectory(PathTemp & "Cache") - Directory.CreateDirectory(PathTemp & "Download") Directory.CreateDirectory(PathAppdata) '检测单例 #If Not DEBUG Then @@ -124,9 +123,9 @@ WaitRetry: '添加日志 Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})") #If RELEASE Then - Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",正式版", "")}") + Log($"[Start] 识别码:{Identify}{If(ThemeCheckOne(9), ",正式版", "")}") #Else - Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}") + Log($"[Start] 识别码:{Identify}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}") #End If Log($"[Start] 程序路径:{PathWithName}") Log($"[Start] 系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})") diff --git a/Plain Craft Launcher 2/Controls/IMyRadio.vb b/Plain Craft Launcher 2/Controls/Behaviors/IMyRadio.vb similarity index 100% rename from Plain Craft Launcher 2/Controls/IMyRadio.vb rename to Plain Craft Launcher 2/Controls/Behaviors/IMyRadio.vb diff --git a/Plain Craft Launcher 2/Controls/Interfaces.vb b/Plain Craft Launcher 2/Controls/Behaviors/Interfaces.vb similarity index 100% rename from Plain Craft Launcher 2/Controls/Interfaces.vb rename to Plain Craft Launcher 2/Controls/Behaviors/Interfaces.vb diff --git a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb index 2eeaa05..992673e 100644 --- a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb @@ -140,51 +140,14 @@ Public Class MyButton End Try End Sub - '实现自定义事件 + '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行) + Private IsMouseDown As Boolean = False Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp If Not IsMouseDown Then Return Log("[Control] 按下按钮:" & Text) RaiseEvent Click(sender, e) - If Not String.IsNullOrEmpty(Tag) Then - If Tag.ToString.StartsWithF("链接-") OrElse Tag.ToString.StartsWithF("启动-") Then - Hint("主页自定义按钮语法已更新,且不再兼容老版本语法,请查看新的自定义示例!") - End If - End If - ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents) + RaiseCustomEvent() '自定义事件 End Sub - Public Property EventType As String - Get - Return GetValue(EventTypeProperty) - End Get - Set(value As String) - SetValue(EventTypeProperty, value) - End Set - End Property - Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyButton), New PropertyMetadata(Nothing)) - Public Property EventData As String - Get - Return GetValue(EventDataProperty) - End Get - Set(value As String) - SetValue(EventDataProperty, value) - End Set - End Property - Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyButton), New PropertyMetadata(Nothing)) - Public Property CustomEvents As CustomEventCollection - Get - If _CustomEvents Is Nothing Then - _CustomEvents = New CustomEventCollection - End If - Return _CustomEvents - End Get - Set(value As CustomEventCollection) - _CustomEvents = value - End Set - End Property - Private _CustomEvents As CustomEventCollection = Nothing - - '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行) - Private IsMouseDown As Boolean = False Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown IsMouseDown = True Focus() diff --git a/Plain Craft Launcher 2/Controls/MyCard.vb b/Plain Craft Launcher 2/Controls/MyCard.vb index 0274be0..b84cae2 100644 --- a/Plain Craft Launcher 2/Controls/MyCard.vb +++ b/Plain Craft Launcher 2/Controls/MyCard.vb @@ -132,7 +132,9 @@ For Each Data As Object In Stack.Tag Select Case Type Case 0 - Stack.Children.Add(PageSelectRight.McVersionListItem(Data)) + Dim VersionItem As MyListItem = CType(Data, McVersion).ToListItem + VersionItem.ContentHandler = AddressOf PageSelectRight.McVersionListContent + Stack.Children.Add(VersionItem) Case 2 Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True)) Case 3 @@ -309,9 +311,6 @@ End Set End Property Private _IsSwapped As Boolean = False - ''' - ''' 是否已被折叠。(已过时,请使用 IsSwapped) - ''' Public Property IsSwaped As Boolean Get @@ -325,37 +324,45 @@ Sub(sender, e) If sender IsNot Nothing AndAlso TypeOf sender Is MyCard Then CType(sender, MyCard).IsSwapped = CType(e.NewValue, Boolean))) Public Property SwapLogoRight As Boolean = False - Private IsMouseDown As Boolean = False + Private IsSwapMouseDown As Boolean = False '用于触发卡片展开/折叠的 MouseDown + Private IsCustomMouseDown As Boolean = False '用于触发自定义事件的 MouseDown Public Event PreviewSwap(sender As Object, e As RouteEventArgs) Public Event Swap(sender As Object, e As RouteEventArgs) Public Const SwapedHeight As Integer = 40 Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown Dim Pos As Double = Mouse.GetPosition(Me).Y + If Not IsSwapped AndAlso + (Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return + IsCustomMouseDown = True If Not IsSwapped AndAlso (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判 - IsMouseDown = True + IsSwapMouseDown = True End Sub - Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp - If Not IsMouseDown Then Return - IsMouseDown = False + Private Sub MyCard_MouseLeftButtonUp() Handles Me.MouseLeftButtonUp + If Not IsCustomMouseDown Then Return + IsCustomMouseDown = False + RaiseCustomEvent() '触发自定义事件 + + If Not IsSwapMouseDown Then Return + IsSwapMouseDown = False Dim Pos As Double = Mouse.GetPosition(Me).Y If Not IsSwapped AndAlso (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判 - Dim ee = New RouteEventArgs(True) - RaiseEvent PreviewSwap(Me, ee) - If ee.Handled Then - IsMouseDown = False + Dim e = New RouteEventArgs(True) + RaiseEvent PreviewSwap(Me, e) + If e.Handled Then + IsSwapMouseDown = False Return End If IsSwapped = Not IsSwapped Log("[Control] " & If(IsSwapped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title)) - RaiseEvent Swap(Me, ee) + RaiseEvent Swap(Me, e) End Sub Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave - IsMouseDown = False + IsSwapMouseDown = False End Sub #End Region diff --git a/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb b/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb index a0e8c9c..3703419 100644 --- a/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb @@ -12,9 +12,6 @@ Public Class MyCheckBox ''' 是否为用户手动改变的勾选状态。 Public Event Change(sender As Object, user As Boolean) Public Event PreviewChange(sender As Object, e As RouteEventArgs) - Public Sub RaiseChange() - RaiseEvent Change(Me, False) - End Sub '使外部程序引发本控件的 Change 事件 '自定义属性 Public Property Checked As Boolean @@ -59,6 +56,7 @@ Public Class MyCheckBox '更改动画 SyncUI() + RaiseCustomEvent() Catch ex As Exception Log(ex, "设置 Checked 失败") End Try diff --git a/Plain Craft Launcher 2/Controls/MyComboBox.vb b/Plain Craft Launcher 2/Controls/MyComboBox.vb index 874b48a..69f7ae2 100644 --- a/Plain Craft Launcher 2/Controls/MyComboBox.vb +++ b/Plain Craft Launcher 2/Controls/MyComboBox.vb @@ -117,15 +117,14 @@ Private IsTextChanging As Boolean = False Private Sub MyComboBox_TextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged If IsTextChanging OrElse Not IsEditable Then Return - If SelectedItem IsNot Nothing AndAlso Text <> SelectedItem.ToString Then - Dim RawText As String = Text - Dim RawSelectionStart As Integer = TextBox.SelectionStart - IsTextChanging = True - SelectedItem = Nothing - Text = RawText - TextBox.SelectionStart = RawSelectionStart - IsTextChanging = False - End If + If SelectedItem Is Nothing OrElse Text = SelectedItem.ToString Then Return + Dim RawText As String = Text + Dim RawSelectionStart As Integer = TextBox.SelectionStart + IsTextChanging = True + SelectedItem = Nothing + Text = RawText + TextBox.SelectionStart = RawSelectionStart + IsTextChanging = False End Sub Public ReadOnly Property ContentPresenter As ContentPresenter @@ -134,4 +133,8 @@ End Get End Property + Private Sub MyComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles Me.SelectionChanged + If IsLoaded AndAlso AniControlEnabled = 0 Then RaiseCustomEvent() + End Sub + End Class diff --git a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb index 0bb1b3a..6f90827 100644 --- a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb @@ -53,25 +53,26 @@ Set(value As Boolean) If _Show = value Then Return _Show = value - RunInUi(Sub() - If value Then - '有了 - Visibility = Visibility.Visible - AniStart({ - AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)), - AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)), - AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak)) - }, "MyExtraButton MainScale " & Uuid) - Else - '没了 - AniStart({ - AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)), - AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()), - AaCode(Sub() Visibility = Visibility.Collapsed,, True) - }, "MyExtraButton MainScale " & Uuid) - End If - IsHitTestVisible = value '防止缩放动画中依然可以点进去 - End Sub) + RunInUi( + Sub() + If value Then + '有了 + Visibility = Visibility.Visible + AniStart({ + AaScaleTransform(Me, 0.3 - CType(RenderTransform, ScaleTransform).ScaleX, 500, 60, New AniEaseOutFluent(AniEasePower.Weak)), + AaScaleTransform(Me, 0.7, 500, 60, New AniEaseOutBack(AniEasePower.Weak)), + AaHeight(Me, 50 - Height, 200,, New AniEaseOutFluent(AniEasePower.Weak)) + }, "MyExtraButton MainScale " & Uuid) + Else + '没了 + AniStart({ + AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak)), + AaHeight(Me, -Height, 400, 100, New AniEaseOutFluent()), + AaCode(Sub() Visibility = Visibility.Collapsed,, True) + }, "MyExtraButton MainScale " & Uuid) + End If + IsHitTestVisible = value '防止缩放动画中依然可以点进去 + End Sub) End Set End Property Public Delegate Function ShowCheckDelegate() As Boolean @@ -137,6 +138,7 @@ AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack) }, "MyExtraButton Scale " & Uuid) End If + If IsLeftMouseHeld Then RaiseCustomEvent() IsLeftMouseHeld = False RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave End Sub @@ -199,16 +201,17 @@ ''' 发出一圈波浪效果提示。 ''' Public Sub Ribble() - RunInUi(Sub() - Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()} - Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5") - PanScale.Children.Insert(0, Shape) - AniStart({ - AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)), - AaOpacity(Shape, -Shape.Opacity, 1000), - AaCode(Sub() PanScale.Children.Remove(Shape), After:=True) - }, "ExtraButton Ribble " & GetUuid()) - End Sub) + RunInUi( + Sub() + Dim Shape As New Border With {.CornerRadius = New CornerRadius(1000), .BorderThickness = New Thickness(0.001), .Opacity = 0.5, .RenderTransformOrigin = New Point(0.5, 0.5), .RenderTransform = New ScaleTransform()} + Shape.SetResourceReference(Border.BackgroundProperty, "ColorBrush5") + PanScale.Children.Insert(0, Shape) + AniStart({ + AaScaleTransform(Shape, 13, 1000, Ease:=New AniEaseInoutFluent(AniEasePower.Strong, 0.3)), + AaOpacity(Shape, -Shape.Opacity, 1000), + AaCode(Sub() PanScale.Children.Remove(Shape), After:=True) + }, "ExtraButton Ribble " & GetUuid()) + End Sub) End Sub End Class diff --git a/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb index 9784cd0..266ba47 100644 --- a/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb @@ -81,12 +81,12 @@ Public Class MyExtraTextButton '触发点击事件 Private Sub Button_LeftMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonUp - If IsLeftMouseHeld Then - Log("[Control] 按下附加图标按钮:" & Text) - RaiseEvent Click(sender, e) - e.Handled = True - Button_LeftMouseUp() - End If + If Not IsLeftMouseHeld Then Return + Log("[Control] 按下附加图标按钮:" & Text) + RaiseEvent Click(sender, e) + e.Handled = True + RaiseCustomEvent() + Button_LeftMouseUp() End Sub '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行) diff --git a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb index dba944a..9498b37 100644 --- a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb @@ -113,7 +113,7 @@ Public Class MyHint IsMouseDown = False Log("[Control] 按下提示条" & If(String.IsNullOrEmpty(Name), "", ":" & Name)) e.Handled = True - ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents) + RaiseCustomEvent() '自定义事件 End Sub Private Sub MyHint_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown IsMouseDown = True @@ -121,36 +121,6 @@ Public Class MyHint Private Sub MyHint_MouseLeave() Handles Me.MouseLeave IsMouseDown = False End Sub - Public Property EventType As String - Get - Return GetValue(EventTypeProperty) - End Get - Set(value As String) - SetValue(EventTypeProperty, value) - End Set - End Property - Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyHint), New PropertyMetadata(Nothing)) - Public Property EventData As String - Get - Return GetValue(EventDataProperty) - End Get - Set(value As String) - SetValue(EventDataProperty, value) - End Set - End Property - Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyHint), New PropertyMetadata(Nothing)) - Public Property CustomEvents As CustomEventCollection - Get - If _CustomEvents Is Nothing Then - _CustomEvents = New CustomEventCollection - End If - Return _CustomEvents - End Get - Set(value As CustomEventCollection) - _CustomEvents = value - End Set - End Property - Private _CustomEvents As CustomEventCollection = Nothing End Class Partial Public Module ModAnimation diff --git a/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb index d060e88..5da1eaf 100644 --- a/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb @@ -48,48 +48,16 @@ End Set End Property - '触发点击事件 + '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行) + Private IsMouseDown As Boolean = False Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp If Not IsMouseDown Then Return Log("[Control] 按下图标按钮" & If(String.IsNullOrEmpty(Name), "", ":" & Name)) RaiseEvent Click(sender, e) e.Handled = True Button_MouseUp() - ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents) + RaiseCustomEvent() '自定义事件 End Sub - Public Property EventType As String - Get - Return GetValue(EventTypeProperty) - End Get - Set(value As String) - SetValue(EventTypeProperty, value) - End Set - End Property - Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconButton), New PropertyMetadata(Nothing)) - Public Property EventData As String - Get - Return GetValue(EventDataProperty) - End Get - Set(value As String) - SetValue(EventDataProperty, value) - End Set - End Property - Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconButton), New PropertyMetadata(Nothing)) - Public Property CustomEvents As CustomEventCollection - Get - If _CustomEvents Is Nothing Then - _CustomEvents = New CustomEventCollection - End If - Return _CustomEvents - End Get - Set(value As CustomEventCollection) - _CustomEvents = value - End Set - End Property - Private _CustomEvents As CustomEventCollection = Nothing - - '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行) - Private IsMouseDown As Boolean = False Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown IsMouseDown = True Focus() @@ -114,7 +82,6 @@ RefreshAnim() '直接刷新颜色以判断是否已触发 MouseLeave End Sub - '自定义事件 '务必放在 IsMouseDown 更新之后 Private Const AnimationColorIn As Integer = 120 Private Const AnimationColorOut As Integer = 150 diff --git a/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb index 1fe5557..dd5ed21 100644 --- a/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb @@ -8,9 +8,6 @@ Public Class MyIconTextButton Public Uuid As Integer = GetUuid() Public Event Check(sender As Object, raiseByMouse As Boolean) Public Event Change(sender As Object, raiseByMouse As Boolean) - Public Sub RaiseChange() - RaiseEvent Change(Me, False) - End Sub '使外部程序可以引发本控件的 Change 事件 '自定义属性 @@ -76,7 +73,7 @@ Public Class MyIconTextButton Log("[Control] 按下带图标按钮:" & Text) IsMouseDown = False RaiseEvent Click(Me, New RouteEventArgs(True)) - ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents) + RaiseCustomEvent() '自定义事件 RefreshColor() End Sub Private Sub MyIconTextButton_MouseDown() Handles Me.MouseLeftButtonDown @@ -87,36 +84,6 @@ Public Class MyIconTextButton IsMouseDown = False RefreshColor() End Sub - Public Property EventType As String - Get - Return GetValue(EventTypeProperty) - End Get - Set(value As String) - SetValue(EventTypeProperty, value) - End Set - End Property - Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing)) - Public Property EventData As String - Get - Return GetValue(EventDataProperty) - End Get - Set(value As String) - SetValue(EventDataProperty, value) - End Set - End Property - Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(Nothing)) - Public Property CustomEvents As CustomEventCollection - Get - If _CustomEvents Is Nothing Then - _CustomEvents = New CustomEventCollection - End If - Return _CustomEvents - End Get - Set(value As CustomEventCollection) - _CustomEvents = value - End Set - End Property - Private _CustomEvents As CustomEventCollection = Nothing '动画 diff --git a/Plain Craft Launcher 2/Controls/MyImage.vb b/Plain Craft Launcher 2/Controls/MyImage.vb index 8f6de5b..d4c5cf1 100644 --- a/Plain Craft Launcher 2/Controls/MyImage.vb +++ b/Plain Craft Launcher 2/Controls/MyImage.vb @@ -46,7 +46,7 @@ Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString()))) ''' - ''' 若 Source 是一个网络图片,该地址将作为第二下载源。 + ''' 若 Source 是一个网络图片,该地址将作为第二图片源。 ''' Public Property FallbackSource As String Get @@ -120,11 +120,12 @@ End If RunInNewThread( Sub() + Dim IsLocalFallback As Boolean = FallbackSource IsNot Nothing AndAlso Not FallbackSource.StartsWithF("http") Try '下载 ActualSource = LoadingSource '显示加载中的占位图片 NetDownloadByLoader( - If(String.IsNullOrEmpty(FallbackSource), {Source}, {Source, FallbackSource}), + If(String.IsNullOrEmpty(FallbackSource) OrElse IsLocalFallback, {Source}, {Source, FallbackSource}), TempPath, SimulateBrowserHeaders:=True) If EnableCache Then '保存缓存并显示 @@ -139,7 +140,19 @@ If TempPath IsNot Nothing Then File.Delete(TempPath) Catch End Try - Log(ex, $"下载图片失败", LogLevel.Hint) + '加载本地备用图片 + If IsLocalFallback Then + Try + RunInUiWait(Sub() ActualSource = FallbackSource) + Log(ex, $"下载图片失败,使用本地备用图片({Source})") + Return + Catch exx As Exception + Log(ex, $"下载图片失败({Source})", LogLevel.Hint) + Log(exx, $"加载备用图片失败({FallbackSource})", LogLevel.Hint) + End Try + Else + Log(ex, $"下载图片失败({Source},备用:{FallbackSource})", LogLevel.Hint) + End If End Try End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal) End Sub diff --git a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb index 48c5d8e..cf462ec 100644 --- a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb @@ -506,8 +506,8 @@ Public Class MyListItem RaiseEvent Click(sender, e) If e.Handled Then Return '触发自定义事件 - If Not String.IsNullOrEmpty(EventType) Then - ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents) + If CustomEventService.GetEventType(sender) <> CustomEvent.EventType.None Then + RaiseCustomEvent() e.Handled = True End If If e.Handled Then Return @@ -537,38 +537,6 @@ Public Class MyListItem If ButtonStack IsNot Nothing Then ButtonStack.IsHitTestVisible = True End Sub - '实现自定义事件 - Public Property EventType As String - Get - Return GetValue(EventTypeProperty) - End Get - Set(value As String) - SetValue(EventTypeProperty, value) - End Set - End Property - Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyListItem), New PropertyMetadata(Nothing)) - Public Property EventData As String - Get - Return GetValue(EventDataProperty) - End Get - Set(value As String) - SetValue(EventDataProperty, value) - End Set - End Property - Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyListItem), New PropertyMetadata(Nothing)) - Public Property CustomEvents As CustomEventCollection - Get - If _CustomEvents Is Nothing Then - _CustomEvents = New CustomEventCollection - End If - Return _CustomEvents - End Get - Set(value As CustomEventCollection) - _CustomEvents = value - End Set - End Property - Private _CustomEvents As CustomEventCollection = Nothing - #End Region Private StateLast As String @@ -669,13 +637,14 @@ Public Class MyListItem SetResourceReference(ForegroundProperty, "ColorBrush1") End If ColumnPaddingRight.Width = New GridLength(MinPaddingRight) - If EventType = "打开帮助" AndAlso Not (Title <> "" AndAlso Info <> "") Then '#3266 + If CustomEventService.GetEventType(Me) = CustomEvent.EventType.打开帮助 AndAlso Not (Title <> "" AndAlso Info <> "") Then '#3266 Try - Dim Unused = New HelpEntry(GetEventAbsoluteUrls(EventData, EventType)(0)).SetToListItem(Me) + Dim Entry As New HelpEntry(CustomEvent.GetAbsoluteUrls(CustomEventService.GetEventData(Me), CustomEventService.GetEventType(Me))(0)) + Entry.SetToListItem(Me) Catch ex As Exception Log(ex, "设置帮助 MyListItem 失败", LogLevel.Msgbox) - EventType = Nothing - EventData = Nothing + CustomEventService.SetEventType(Me, CustomEvent.EventType.None) + CustomEventService.SetEventData(Me, "") End Try End If End Sub diff --git a/Plain Craft Launcher 2/Controls/MyMenuItem.vb b/Plain Craft Launcher 2/Controls/MyMenuItem.vb index cc70ee6..4c22da3 100644 --- a/Plain Craft Launcher 2/Controls/MyMenuItem.vb +++ b/Plain Craft Launcher 2/Controls/MyMenuItem.vb @@ -54,4 +54,7 @@ End If End Sub + Private Sub MyMenuItem_Click(sender As Object, e As RoutedEventArgs) Handles Me.Click + RaiseCustomEvent() + End Sub End Class diff --git a/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb b/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb index 5dd839f..ba77d6f 100644 --- a/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb @@ -93,6 +93,7 @@ Public Class MyRadioBox If IsChanged Then If Checked Then RaiseEvent Check(Me, New RouteEventArgs(user)) RaiseEvent Changed(Me, New RouteEventArgs(user)) + RaiseCustomEvent() End If '更改动画 diff --git a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb index 4be05b8..2753034 100644 --- a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb @@ -7,10 +7,6 @@ Public Class MyRadioButton Public Uuid As Integer = GetUuid() Public Event Check(sender As Object, raiseByMouse As Boolean) - Public Event Change(sender As Object, raiseByMouse As Boolean) - Public Sub RaiseChange() - RaiseEvent Change(Me, False) - End Sub '使外部程序可以引发本控件的 Change 事件 '自定义属性 @@ -46,23 +42,22 @@ Public Class MyRadioButton ''' 手动设置 Checked 属性。 ''' ''' 新的 Checked 属性。 - ''' 是否由用户引发。 + ''' 是否由用户引发。 ''' 是否执行动画。 - Public Sub SetChecked(value As Boolean, user As Boolean, anime As Boolean) + Public Sub SetChecked(value As Boolean, raiseByMouse As Boolean, anime As Boolean) Try '自定义属性基础 Dim IsChanged As Boolean = False - If IsLoaded AndAlso Not value = _Checked Then RaiseEvent Change(Me, user) - If Not value = _Checked Then + If _Checked <> value Then _Checked = value IsChanged = True End If '保证只有一个单选框选中 - If IsNothing(Parent) Then Return + If Parent Is Nothing Then Return Dim RadioboxList As New List(Of MyRadioButton) Dim CheckedCount As Integer = 0 '收集控件列表与选中个数 @@ -105,7 +100,8 @@ Public Class MyRadioButton RefreshColor(Nothing, anime) '触发事件 - If Checked Then RaiseEvent Check(Me, user) + If Checked Then RaiseEvent Check(Me, raiseByMouse) + RaiseCustomEvent() Catch ex As Exception Log(ex, "单选按钮勾选改变错误", LogLevel.Hint) diff --git a/Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb b/Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb index ee3bb15..a8fde05 100644 --- a/Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MySearchBox.xaml.vb @@ -39,4 +39,7 @@ TextBox.Focus() End Sub + Private Sub MySearchBox_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp + If e.Key = Key.Enter Then RaiseCustomEvent() + End Sub End Class diff --git a/Plain Craft Launcher 2/Controls/MyTextBox.vb b/Plain Craft Launcher 2/Controls/MyTextBox.vb index e847a58..55a00b3 100644 --- a/Plain Craft Launcher 2/Controls/MyTextBox.vb +++ b/Plain Craft Launcher 2/Controls/MyTextBox.vb @@ -291,4 +291,8 @@ End If End Sub + '在按下回车时触发自定义事件 + Private Sub MyTextBox_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp + If e.Key = Key.Enter Then RaiseCustomEvent() + End Sub End Class diff --git a/Plain Craft Launcher 2/Controls/MyTextButton.vb b/Plain Craft Launcher 2/Controls/MyTextButton.vb index bea7021..4bde410 100644 --- a/Plain Craft Launcher 2/Controls/MyTextButton.vb +++ b/Plain Craft Launcher 2/Controls/MyTextButton.vb @@ -22,15 +22,15 @@ End Set End Property Public Shared ReadOnly TextProperty As DependencyProperty = - DependencyProperty.Register("Text", GetType(String), GetType(MyTextButton), New PropertyMetadata("", Sub(sender As MyTextButton, e As DependencyPropertyChangedEventArgs) - If Not e.OldValue = e.NewValue Then - AniStart({ - AaOpacity(sender, -sender.Opacity, 50), - AaCode(Sub() sender.Content = e.NewValue,, True), - AaOpacity(sender, 1, 170) - }, "MyTextButton Text " & sender.Uuid) - End If - End Sub)) + DependencyProperty.Register("Text", GetType(String), GetType(MyTextButton), New PropertyMetadata("", + Sub(sender As MyTextButton, e As DependencyPropertyChangedEventArgs) + If e.OldValue = e.NewValue Then Return + AniStart({ + AaOpacity(sender, -sender.Opacity, 50), + AaCode(Sub() sender.Content = e.NewValue,, True), + AaOpacity(sender, 1, 170) + }, "MyTextButton Text " & sender.Uuid) + End Sub)) '鼠标事件 @@ -43,13 +43,12 @@ IsMouseDown = False End Sub Private Sub MyTextButton_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp - If IsMouseDown Then - IsMouseDown = False - Log("[Control] 按下文本按钮:" & Text) - RaiseEvent Click(Me, Nothing) - ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents) - e.Handled = True - End If + If Not IsMouseDown Then Return + IsMouseDown = False + Log("[Control] 按下文本按钮:" & Text) + RaiseEvent Click(Me, Nothing) + RaiseCustomEvent() + e.Handled = True End Sub '指向动画 @@ -85,38 +84,4 @@ End If End Sub - '实现自定义事件 - Public Property EventType As String - Get - Return GetValue(EventTypeProperty) - End Get - Set(value As String) - SetValue(EventTypeProperty, value) - End Set - End Property - Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register( - "EventType", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing)) - Public Property EventData As String - Get - Return GetValue(EventDataProperty) - End Get - Set(value As String) - SetValue(EventDataProperty, value) - End Set - End Property - Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register( - "EventData", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing)) - Public Property CustomEvents As CustomEventCollection - Get - If _CustomEvents Is Nothing Then - _CustomEvents = New CustomEventCollection - End If - Return _CustomEvents - End Get - Set(value As CustomEventCollection) - _CustomEvents = value - End Set - End Property - Private _CustomEvents As CustomEventCollection = Nothing - End Class diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index 7799e8f..7f1c0b1 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -104,6 +104,15 @@ Public Class FormMain '3:BUG+ IMP* FEAT- '2:BUG* IMP- '1:BUG- + If LastVersion < 371 Then 'Snapshot 2.10.9 + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:如果版本设置了自定义描述,会在标题后面以淡灰色显示其版本号")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "新增:支持为一个控件设置多个自定义事件")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "新增:修改变量、弹出提示自定义事件")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "新增:添加大量替换标记,允许在更多设置和 XAML 中使用更多替换标记")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法安装 MMC 整合包")) + FeatureCount += 33 + BugCount += 11 + End If If LastVersion < 370 Then 'Snapshot 2.10.8 If LastVersion >= 368 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化")) FeatureCount += 3 @@ -318,7 +327,7 @@ Public Class FormMain FrmLaunchRight.PageState = MyPageRight.PageStates.ContentStay '模式提醒 #If DEBUG Then - Hint("[开发者模式] PCL 正以开发者模式运行,这可能会造成严重的性能下降,请务必立即向开发者反馈此问题!", HintType.Critical) + Hint("[开发者模式] PCL 正以开发者模式运行,这可能会造成严重的性能下降,请务必立即向开发者反馈此问题!", HintType.Red) #End If If ModeDebug Then Hint("[调试模式] PCL 正以调试模式运行,这可能会导致性能下降,若无必要请不要开启!") '尽早执行的加载池 @@ -687,9 +696,9 @@ Public Class FormMain If e.Key = Key.F12 Then PageSetupUI.HiddenForceShow = Not PageSetupUI.HiddenForceShow If PageSetupUI.HiddenForceShow Then - Hint("功能隐藏设置已暂时关闭!", HintType.Finish) + Hint("功能隐藏设置已暂时关闭!", HintType.Green) Else - Hint("功能隐藏设置已重新开启!", HintType.Finish) + Hint("功能隐藏设置已重新开启!", HintType.Green) End If PageSetupUI.HiddenRefresh() Return @@ -785,12 +794,12 @@ Public Class FormMain Dim AuthlibServer As String = Net.WebUtility.UrlDecode(Str.Substring("authlib-injector:yggdrasil-server:".Length)) Log("[System] Authlib 拖拽:" & AuthlibServer) If Not String.IsNullOrEmpty(New ValidateHttp().Validate(AuthlibServer)) Then - Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Critical) + Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Red) Return End If Dim TargetVersion = If(PageCurrent = PageType.VersionSetup, PageVersionLeft.Version, McVersionCurrent) If TargetVersion Is Nothing Then - Hint("请先下载游戏,再设置第三方登录!", HintType.Critical) + Hint("请先下载游戏,再设置第三方登录!", HintType.Red) Return End If If AuthlibServer = "https://littleskin.cn/api/yggdrasil" Then @@ -837,7 +846,7 @@ Public Class FormMain '获取文件并检查 Dim FilePathRaw = e.Data.GetData(DataFormats.FileDrop) If FilePathRaw Is Nothing Then '#2690 - Hint("请将文件解压后再拖入!", HintType.Critical) + Hint("请将文件解压后再拖入!", HintType.Red) Return End If e.Handled = True @@ -855,10 +864,10 @@ Public Class FormMain Log("[System] 接受文件拖拽:" & FilePath & If(FilePathList.Any, $" 等 {FilePathList.Count} 个文件", ""), LogLevel.Developer) '基础检查 If Directory.Exists(FilePathList.First) AndAlso Not File.Exists(FilePathList.First) Then - Hint("请拖入一个文件,而非文件夹!", HintType.Critical) + Hint("请拖入一个文件,而非文件夹!", HintType.Red) Return ElseIf Not File.Exists(FilePathList.First) Then - Hint("拖入的文件不存在:" & FilePathList.First, HintType.Critical) + Hint("拖入的文件不存在:" & FilePathList.First, HintType.Red) Return End If '多文件拖拽 @@ -866,7 +875,7 @@ Public Class FormMain '必须要求全部为 jar 文件 For Each File In FilePathList If Not {"jar", "litemod", "disabled", "old"}.Contains(File.AfterLast(".").ToLower) Then - Hint("一次请只拖入一个文件!", HintType.Critical) + Hint("一次请只拖入一个文件!", HintType.Red) Return End If Next @@ -885,7 +894,7 @@ Public Class FormMain Sub() Setup.Set("UiCustomType", 1) FrmLaunchRight.ForceRefresh() - Hint("已加载主页自定义文件!", HintType.Finish) + Hint("已加载主页自定义文件!", HintType.Green) End Sub) Return End If @@ -995,9 +1004,9 @@ Public Class FormMain #Region "切换页面" '页面种类与属性 - '注意,这一枚举在 “切换页面” EventType 中调用,应视作公开 API 的一部分 ''' ''' 页面种类。 + ''' 该枚举在自定义事件中使用,是公开 API 的一部分。 ''' Public Enum PageType ''' @@ -1043,6 +1052,7 @@ Public Class FormMain End Enum ''' ''' 次要页面种类。其数值必须与 StackPanel 中的下标一致。 + ''' 该枚举在自定义事件中使用,是公开 API 的一部分。 ''' Public Enum PageSubType [Default] = 0 @@ -1485,7 +1495,7 @@ Public Class FormMain '投降 Public Sub AprilGiveup() Handles BtnExtraApril.Click If IsAprilEnabled AndAlso Not IsAprilGiveup Then - Hint("=D", HintType.Finish) + Hint("=D", HintType.Green) IsAprilGiveup = True FrmLaunchLeft.AprilScaleTrans.ScaleX = 1 FrmLaunchLeft.AprilScaleTrans.ScaleY = 1 @@ -1503,7 +1513,7 @@ Public Class FormMain For Each Watcher In McWatcherList Watcher.Kill() Next - Hint("已关闭运行中的 Minecraft!", HintType.Finish) + Hint("已关闭运行中的 Minecraft!", HintType.Green) Catch ex As Exception Log(ex, "强制关闭所有 Minecraft 失败", LogLevel.Feedback) End Try diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index 8d026e1..3ad35f4 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -13,13 +13,13 @@ Public Module ModBase #Region "声明" '下列版本信息由更新器自动修改 - Public Const VersionBaseName As String = "2.10.8" '不含分支前缀的显示用版本名 - Public Const VersionStandardCode As String = "2.10.8." & VersionBranchCode '标准格式的四段式版本号 + Public Const VersionBaseName As String = "2.10.9" '不含分支前缀的显示用版本名 + Public Const VersionStandardCode As String = "2.10.9." & VersionBranchCode '标准格式的四段式版本号 Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换 #If BETA Then - Public Const VersionCode As Integer = 367 'Release + Public Const VersionCode As Integer = 369 'Release #Else - Public Const VersionCode As Integer = 370 'Snapshot + Public Const VersionCode As Integer = 371 'Snapshot #End If '自动生成的版本信息 Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName @@ -69,7 +69,7 @@ Public Module ModBase ''' ''' 识别码。 ''' - Public UniqueAddress As String = SecretGetUniqueAddress() + Public Identify As String = SecretGetIdentify() ''' ''' 程序是否已结束。 ''' @@ -761,7 +761,6 @@ Public Module ModBase If FilePath.Contains("\") Then FilePath = FilePath.Substring(FilePath.LastIndexOfF("\") + 1) Dim length As Integer = FilePath.Length If length = 0 Then Throw New Exception("不包含文件名:" & FilePath) - If length > 250 Then Throw New PathTooLongException("文件名过长:" & FilePath) Return FilePath End Function ''' @@ -1400,12 +1399,13 @@ RetryDir: ''' ''' 若路径长度大于指定值,则将长路径转换为短路径。 + ''' 如果路径不存在,则返回原始路径。 ''' Public Function ShortenPath(LongPath As String) As String If LongPath.Length <= 200 OrElse LongPath.StartsWithF("http", True) Then Return LongPath If Not Directory.Exists(LongPath) AndAlso Not File.Exists(LongPath) Then Return LongPath Dim ShortPath As New StringBuilder(260) - GetShortPathName(LongPath, ShortPath, 260) + GetShortPathName(LongPath.Replace("/", "\"), ShortPath, 260) '第一个参数也会在调用后被修改,使用 Replace 后不会影响原始字符串 ShortenPath = ShortPath.ToString If String.IsNullOrEmpty(ShortenPath) Then Return LongPath End Function @@ -1511,6 +1511,19 @@ RetryDir: Return [Enum].GetName(EnumData.GetType, EnumData) End Function ''' + ''' 将字符串转换为枚举类型,支持数字或原文两种格式,若为空字符串则返回 0。 + ''' 若转换失败会抛出异常。 + ''' + Public Function ParseToEnum(Of T)(Str As String) As T + If String.IsNullOrEmpty(Str) Then + Return CType(0, Object) + ElseIf IsNumeric(Str) Then + Return CType(CInt(Str), Object) + Else + Return [Enum].Parse(GetType(T), Str, True) + End If + End Function + ''' ''' 将文件大小转化为适合的文本形式,如“1.28 M”。 ''' ''' 以字节为单位的大小表示。 @@ -2579,7 +2592,7 @@ Retry: Log(ex, "可能由于剪贴板被其他程序占用,文本复制失败", LogLevel.Hint) End If End Try - If ShowSuccessHint Then Hint("已成功复制!", HintType.Finish) + If ShowSuccessHint Then Hint("已成功复制!", HintType.Green) End Sub) End Sub @@ -2716,6 +2729,11 @@ Retry: ''' 将 XML 转换为对应 UI 对象。 ''' Public Function GetObjectFromXML(Str As String) As Object + Str = Str. '兼容旧版自定义事件写法 + Replace("EventType=""", "local:CustomEventService.EventType="""). + Replace("EventData=""", "local:CustomEventService.EventData="""). + Replace("Property=""EventType""", "Property=""local:CustomEventService.EventType"""). + Replace("Property=""EventData""", "Property=""local:CustomEventService.EventData""") Using Stream As New MemoryStream(Encoding.UTF8.GetBytes(Str)) '类型检查 Using Reader As New XamlXmlReader(Stream) @@ -2831,7 +2849,7 @@ Retry: File.Create(Path & "PCL\Log1.txt").Dispose() Catch ex As IOException IsInitSuccess = False - Hint("可能同时开启了多个 PCL,程序可能会出现未知问题!", HintType.Critical) + Hint("可能同时开启了多个 PCL,程序可能会出现未知问题!", HintType.Red) Log(ex, "日志初始化失败(疑似文件占用问题)") Catch ex As Exception IsInitSuccess = False @@ -2884,6 +2902,7 @@ Retry: '输出日志 Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Text}{vbCrLf}" '减轻同步锁占用 + AppendText = FilterUserName(FilterAccessToken(AppendText, "*"), "*") If ModeDebug Then SyncLock LogListLock LogList.Append(AppendText) @@ -2903,16 +2922,16 @@ Retry: Select Case Level #If DEBUG Then Case LogLevel.Developer - Hint("[开发者模式] " & Text, HintType.Info, False) + Hint("[开发者模式] " & Text, HintType.Blue, False) Case LogLevel.Debug - Hint("[调试模式] " & Text, HintType.Info, False) + Hint("[调试模式] " & Text, HintType.Blue, False) #Else Case LogLevel.Developer Case LogLevel.Debug - If ModeDebug Then Hint("[调试模式] " & Text, HintType.Info, False) + If ModeDebug Then Hint("[调试模式] " & Text, HintType.Blue, False) #End If Case LogLevel.Hint - Hint(Text, HintType.Critical, False) + Hint(Text, HintType.Red, False) Case LogLevel.Msgbox MyMsgBox(Text, Title, IsWarn:=True) Case LogLevel.Feedback @@ -2955,6 +2974,7 @@ Retry: '输出日志 Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{Ex.GetDetail(True)}{vbCrLf}" '减轻同步锁占用 + AppendText = FilterUserName(FilterAccessToken(AppendText, "*"), "*") If ModeDebug Then SyncLock LogListLock LogList.Append(AppendText) @@ -2973,19 +2993,19 @@ Retry: #If DEBUG Then Case LogLevel.Developer Dim ExLine As String = Desc & ":" & Ex.GetBrief() - Hint("[开发者模式] " & ExLine, HintType.Info, False) + Hint("[开发者模式] " & ExLine, HintType.Blue, False) Case LogLevel.Debug Dim ExLine As String = Desc & ":" & Ex.GetBrief() - Hint("[调试模式] " & ExLine, HintType.Info, False) + Hint("[调试模式] " & ExLine, HintType.Blue, False) #Else Case LogLevel.Developer Case LogLevel.Debug Dim ExLine As String = Desc & ":" & Ex.GetBrief() - If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Info, False) + If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Blue, False) #End If Case LogLevel.Hint Dim ExLine As String = Desc & ":" & Ex.GetBrief() - Hint(ExLine, HintType.Critical, False) + Hint(ExLine, HintType.Red, False) Case LogLevel.Msgbox MyMsgBox(ExFull, Title, IsWarn:=True) Case LogLevel.Feedback diff --git a/Plain Craft Launcher 2/Modules/Base/ModNet.vb b/Plain Craft Launcher 2/Modules/Base/ModNet.vb index 806da0b..e19c8b9 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModNet.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModNet.vb @@ -998,20 +998,18 @@ StartThread: If ModeDebug AndAlso Response.RequestMessage.RequestUri.ToString <> Th.Source.Url Then Log($"[Download] {LocalName}:重定向至 {Response.RequestMessage.RequestUri}") '文件大小校验 ContentLength = Response.Content.Headers.ContentLength.GetValueOrDefault(-1) - If ContentLength = -1 Then + If ContentLength < 0 Then If FileSize > 1 Then If Th.DownloadStart = 0 Then Log($"[Download] {LocalName}:文件大小未知,但已从其他下载源获取,不作处理") Else - Log($"[Download] {LocalName}:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持") + Log($"[Download] {LocalName}:ContentLength 返回了 {ContentLength},无法确定是否支持分段下载,视作不支持") GoTo NotSupportRange End If Else FileSize = -1 : IsUnknownSize = True Log($"[Download] {LocalName}:文件大小未知") End If - ElseIf ContentLength < 0 Then - Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。") ElseIf Th.IsFirstThread Then If Check IsNot Nothing Then If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then @@ -1040,15 +1038,11 @@ StartThread: ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then NotSupportRange: SyncLock LockSource - If SourcesOnce.Contains(Th.Source) Then - GoTo SourceBreak - Else - SourcesOnce.Add(Th.Source) - End If + If SourcesOnce.Contains(Th.Source) Then GoTo SourceBreak End SyncLock - Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") + Throw New RangeNotSupportedException($"该下载源不支持分段下载:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") ElseIf Not FileSize - Th.DownloadStart = ContentLength Then - Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") + Throw New RangeNotSupportedException($"获取到的分段大小不一致:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") End If 'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}") Th.State = NetState.Get @@ -1061,6 +1055,7 @@ NotSupportRange: Cache = New MemoryStream ResultStream = Cache Else + Directory.CreateDirectory(PathTemp & "Download") Th.Temp = $"{PathTemp}Download\{Uuid}_{Th.Uuid}_{RandomInteger(0, 999999)}.tmp" ResultStream = New FileStream(Th.Temp, FileMode.Create, FileAccess.Write, FileShare.Read) End If @@ -1165,9 +1160,10 @@ SourceBreak: Th.State = NetState.Error Th.Source.Ex = ex '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传) - If IsMergeFailure OrElse - ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse - ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse + Dim IsRangeNotSupported As Boolean = TypeOf ex Is RangeNotSupportedException OrElse ex.Message.Contains("(416)") + If IsMergeFailure OrElse IsRangeNotSupported OrElse + ex.Message.Contains("(502)") OrElse ex.Message.Contains("(404)") OrElse + ex.Message.Contains("未能解析") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ((ex.Message.Contains("(403)") OrElse ex.Message.Contains("(429)")) AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403/429,所以不应因此禁用下载源 (Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then '当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理 @@ -1180,9 +1176,9 @@ SourceBreak: End SyncLock '……后续处理 If IsThisFail Then - Log($"[Download] {LocalName}:下载源被禁用({Th.Source.Id}):{Th.Source.Url}") + Log($"[Download] {LocalName}:下载源被禁用({Th.Source.Id},Range 问题:{IsRangeNotSupported}):{Th.Source.Url}") Log(ex, $"{If(SourcesOnce.FirstOrDefault?.SingleThread Is Nothing, "", "单线程")}下载源 {Th.Source.Id} 已被禁用", - If(ex.Message.Contains("不支持分段下载") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(416)"), LogLevel.Developer, LogLevel.Debug)) + If(IsRangeNotSupported OrElse ex.Message.Contains("(404)"), LogLevel.Developer, LogLevel.Debug)) SyncLock LockSource SourcesOnce.Remove(Th.Source) End SyncLock @@ -1191,9 +1187,10 @@ SourceBreak: Fail(ex) ElseIf HasAvailableSource() AndAlso Not IsMergeFailure Then '当前源失败,但还有下载源:正常地继续执行 - ElseIf Not Retried AndAlso Sources.Count > 1 Then + ElseIf Not Retried Then '合并失败或首次下载失败,未重试:将所有下载源重新标记为不允许断点续传的下载源,逐个重新尝试下载 - Hint($"{LocalName}:文件下载失败,正在自动重试……", If(ModeDebug, HintType.Critical, HintType.Info)) '在调试模式下给出红色提示 + '若所有源均不支持 Range,也会走到这里重试 + If Not IsRangeNotSupported Then Hint($"{LocalName}:文件下载失败,正在自动重试……", If(ModeDebug, HintType.Red, HintType.Blue)) '在调试模式下给出红色提示 Retried = True SyncLock LockSource SourcesOnce.Clear() @@ -1388,6 +1385,12 @@ Retry: End Sub End Class + Private Class RangeNotSupportedException + Inherits WebException + Public Sub New(Message As String) + MyBase.New(Message) + End Sub + End Class ''' ''' 下载一系列文件的加载器。 ''' @@ -1890,7 +1893,6 @@ Retry: Catch ex As Exception Log(ex, "清理下载缓存失败") End Try - Directory.CreateDirectory(PathTemp & "Download") End If IsDownloadCacheCleared = True End SyncLock diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb index bc11865..8b97b0f 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb @@ -908,8 +908,7 @@ NextStack: If File.Exists(OutputFile) Then If FileEncoding Is Nothing Then FileEncoding = GetEncoding(ReadFileBytes(OutputFile)) Dim FileContent As String = ReadFile(OutputFile, FileEncoding) - FileContent = FilterAccessToken(FileContent, If(FileName = "启动脚本.bat", "F", "*")) - FileContent = FilterUserName(FileContent, "*") + FileContent = FilterUserName(FilterAccessToken(FileContent, If(FileName = "启动脚本.bat", "F", "*")), "*") WriteFile(TempFolder & "Report\" & FileName, FileContent, Encoding:=FileEncoding) Log($"[Crash] 导出文件:{FileName},编码:{FileEncoding.HeaderName}") End If @@ -917,7 +916,7 @@ NextStack: '导出报告 Compression.ZipFile.CreateFromDirectory(TempFolder & "Report\", FileAddress) DeleteDirectory(TempFolder & "Report\") - Hint("错误报告已导出!", HintType.Finish) + Hint("错误报告已导出!", HintType.Green) Catch ex As Exception Log(ex, "导出错误报告失败", LogLevel.Feedback) Return diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb index ccaddd1..c4c8fdb 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb @@ -35,7 +35,7 @@ Public Module ModLaunch '预检查 If Not RunInUi() Then Throw New Exception("McLaunchStart 必须在 UI 线程调用!") If McLaunchLoader.State = LoadState.Loading Then - Hint("已有游戏正在启动中!", HintType.Critical) + Hint("已有游戏正在启动中!", HintType.Red) Return False End If '强制切换需要启动的版本 @@ -44,7 +44,7 @@ Public Module ModLaunch '检查版本 CurrentLaunchOptions.Version.Load() If CurrentLaunchOptions.Version.State = McVersionState.Error Then - Hint("无法启动 Minecraft:" & CurrentLaunchOptions.Version.Info, HintType.Critical) + Hint("无法启动 Minecraft:" & CurrentLaunchOptions.Version.Info, HintType.Red) Return False End If '切换版本 @@ -98,7 +98,7 @@ Public Module ModLaunch McLaunchPrecheck() McLaunchLog("预检测已通过") Catch ex As Exception - If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Critical) + If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Red) Throw End Try Dim IsSavingBatch As Boolean = CurrentLaunchOptions?.SaveBatch IsNot Nothing @@ -146,7 +146,7 @@ Public Module ModLaunch '成功与失败处理 Select Case LaunchLoader.State Case LoadState.Finished - Hint(McVersionCurrent.Name & " 启动成功!", HintType.Finish) + Hint(McVersionCurrent.Name & " 启动成功!", HintType.Green) '上报 If Not IsSavingBatch Then Telemetry("Minecraft 启动成功", @@ -155,9 +155,9 @@ Public Module ModLaunch End If Case LoadState.Aborted If AbortHint Is Nothing Then - Hint(If(IsSavingBatch, "已取消导出启动脚本!", "已取消启动!"), HintType.Info) + Hint(If(IsSavingBatch, "已取消导出启动脚本!", "已取消启动!"), HintType.Blue) Else - Hint(AbortHint, HintType.Finish) + Hint(AbortHint, HintType.Green) End If Case LoadState.Failed Throw LaunchLoader.Error @@ -270,7 +270,7 @@ NextInner: Select Case MyMsgBox("你必须先登录正版账号,才能进行离线登录!", "正版验证", "购买正版", "试玩", "返回", Button1Action:=Sub() OpenWebsite("https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj")) Case 2 - Hint("游戏将以试玩模式启动!", HintType.Critical) + Hint("游戏将以试玩模式启动!", HintType.Red) CurrentLaunchOptions.ExtraArgs.Add("--demo") Case 3 Throw New Exception("$$") @@ -755,10 +755,10 @@ LoginFinish: ContentType:="application/json; charset=utf-8", RequireJson:=True)) '检查登录结果 If LoginJson("availableProfiles").Count = 0 Then - If Data.Input.ForceReselectProfile Then Hint("你还没有创建角色,无法更换!", HintType.Critical) + If Data.Input.ForceReselectProfile Then Hint("你还没有创建角色,无法更换!", HintType.Red) Throw New Exception("$你还没有创建角色,请在创建角色后再试!") ElseIf Data.Input.ForceReselectProfile AndAlso LoginJson("availableProfiles").Count = 1 Then - Hint("你的账户中只有一个角色,无法更换!", HintType.Critical) + Hint("你的账户中只有一个角色,无法更换!", HintType.Red) End If Dim SelectedName As String = Nothing Dim SelectedId As String = Nothing @@ -1304,7 +1304,7 @@ Retry: If McLaunchJavaSelected IsNot Nothing Then McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString) Else - Hint("没有可用的 Java,已取消启动!", HintType.Critical) + Hint("没有可用的 Java,已取消启动!", HintType.Red) Throw New Exception("$$") End If @@ -1395,7 +1395,7 @@ Retry: Next '自定义 Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent) - Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame) + Arguments += " " & ArgumentReplace(If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame)) '进行参数替换 Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader) If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then @@ -1412,6 +1412,9 @@ Retry: FinalArguments += Argument & " " Next FinalArguments = FinalArguments.TrimEnd + '替换 Module Path 为短路径(Forge 会在版本 JSON 硬编码这些路径) + FinalArguments = FinalArguments.RegexReplaceEach("(?<= (-p|--module-path) "")[^""]+(?="" )", + Function(ModulePath) ModulePath.Value.RegexReplaceEach("[A-Z]{1}:\\[^;]+", Function(m) ShortenPath(m.Value))) '进服 Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), CurrentLaunchOptions.ServerIp) If Server.Length > 0 Then @@ -1427,7 +1430,7 @@ Retry: '不包含端口号 FinalArguments += $" --server {Server} --port 25565" End If - If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Critical) + If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Red) End If End If '输出 @@ -1447,7 +1450,7 @@ Retry: If ArgumentJvm = "" Then ArgumentJvm = Setup.Get("LaunchAdvanceJvm") If Not ArgumentJvm.Contains("-Dlog4j2.formatMsgNoLookups=true") Then ArgumentJvm += " -Dlog4j2.formatMsgNoLookups=true" ArgumentJvm = ArgumentJvm.Replace(" -XX:MaxDirectMemorySize=256M", "") '#3511 的清理 - DataList.Insert(0, ArgumentJvm) '可变 JVM 参数 + DataList.Insert(0, ArgumentReplace(ArgumentJvm)) '可变 JVM 参数 DataList.Add("-Xmn" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) * 1024 * 0.15) & "m") DataList.Add("-Xmx" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent, Not McLaunchJavaSelected.Is64Bit) * 1024) & "m") DataList.Add("""-Djava.library.path=" & GetNativesFolder() & """") @@ -1571,7 +1574,7 @@ NextVersion: '去重 DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") '#3511 的清理 - Dim Result As String = Join(DeDuplicateDataList.Distinct.ToList, " ") + Dim Result As String = DeDuplicateDataList.Distinct.Join(" ") '添加 MainClass If Version.JsonObject("mainClass") Is Nothing Then @@ -1592,7 +1595,7 @@ NextVersion: If Not BasicString.Contains("--height") Then BasicString += " --height ${resolution_height} --width ${resolution_width}" DataList.Add(BasicString) - Dim Result As String = Join(DataList, " ") + Dim Result As String = DataList.Join(" ") '特别改变 OptiFineTweaker If (Version.Version.HasForge OrElse Version.Version.HasLiteLoader) AndAlso Version.Version.HasOptiFine Then @@ -1697,7 +1700,7 @@ NextVersion: GameArguments.Add("${launcher_version}", VersionCode) GameArguments.Add("${version_name}", Version.Name) Dim ArgumentInfo As String = Setup.Get("VersionArgumentInfo", Version:=McVersionCurrent) - GameArguments.Add("${version_type}", If(ArgumentInfo = "", Setup.Get("LaunchArgumentInfo"), ArgumentInfo)) + GameArguments.Add("${version_type}", ArgumentReplace(If(ArgumentInfo = "", Setup.Get("LaunchArgumentInfo"), ArgumentInfo))) GameArguments.Add("${game_directory}", ShortenPath(Left(McVersionCurrent.PathIndie, McVersionCurrent.PathIndie.Count - 1))) GameArguments.Add("${assets_root}", ShortenPath(PathMcFolder & "assets")) GameArguments.Add("${user_properties}", "{}") @@ -1707,7 +1710,7 @@ NextVersion: GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken) GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken) GameArguments.Add("${user_type}", "msa") '#1221 - GameArguments.Add("${primary_jar}", Version.Path & Version.Name & ".jar") '#6942 + GameArguments.Add("${primary_jar}", ShortenPath(Version.Path & Version.Name & ".jar")) '#6942 '窗口尺寸参数 Dim GameSize As Size @@ -1827,10 +1830,14 @@ NextVersion: ''' Private Function GetNativesFolder() As String Dim Result As String = McVersionCurrent.Path & McVersionCurrent.Name & "-natives" - If IsGBKEncoding OrElse Result.IsASCII() Then Return ShortenPath(Result) - Result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\bin\natives" - If Result.IsASCII() Then Return ShortenPath(Result) - Return OsDrive & "ProgramData\PCL\natives" + If Not (IsGBKEncoding OrElse Result.IsASCII()) Then + Result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\bin\natives" + If Not Result.IsASCII() Then + Result = OsDrive & "ProgramData\PCL\natives" + End If + End If + Directory.CreateDirectory(Result) '提前创建,这样 ShortenPath 才有结果(否则在长路径下首次启动 Forge 会崩溃) + Return ShortenPath(Result) End Function #End Region @@ -1981,7 +1988,7 @@ NextVersion: McLoginLoader.Input.Type = McLoginType.Legacy AndAlso '离线登录 (Setup.Get("LaunchSkinType") = 2 OrElse '强制 Alex (Setup.Get("LaunchSkinType") = 4 AndAlso Setup.Get("LaunchSkinSlim"))) Then '或选用 Alex 皮肤 - Hint("此 Minecraft 版本尚不支持 Alex 皮肤,你的皮肤可能会显示为 Steve!", HintType.Critical) + Hint("此 Minecraft 版本尚不支持 Alex 皮肤,你的皮肤可能会显示为 Steve!", HintType.Red) End If '离线皮肤资源包 @@ -2125,9 +2132,9 @@ IgnoreCustomSkin: '获取自定义命令 Dim CustomCommandGlobal As String = Setup.Get("LaunchAdvanceRun") - If CustomCommandGlobal <> "" Then CustomCommandGlobal = ArgumentReplace(CustomCommandGlobal, True) + If CustomCommandGlobal <> "" Then CustomCommandGlobal = ArgumentReplace(CustomCommandGlobal) Dim CustomCommandVersion As String = Setup.Get("VersionAdvanceRun", Version:=McVersionCurrent) - If CustomCommandVersion <> "" Then CustomCommandVersion = ArgumentReplace(CustomCommandVersion, True) + If CustomCommandVersion <> "" Then CustomCommandVersion = ArgumentReplace(CustomCommandVersion) '输出 bat Try @@ -2283,7 +2290,7 @@ IgnoreCustomSkin: '获取窗口标题 Dim WindowTitle As String = Setup.Get("VersionArgumentTitle", Version:=McVersionCurrent) If WindowTitle = "" Then WindowTitle = Setup.Get("LaunchArgumentTitle") - WindowTitle = ArgumentReplace(WindowTitle, False) + WindowTitle = ArgumentReplace(WindowTitle, ReplaceTime:=False) '初始化等待 Dim Watcher As New Watcher(Loader, McVersionCurrent, WindowTitle) @@ -2332,47 +2339,6 @@ IgnoreCustomSkin: End Sub - ''' - ''' 在启动结束时,对 PCL 约定的替换标记进行处理。 - ''' - Private Function ArgumentReplace(Raw As String, ReplaceTimeAndDate As Boolean) As String - If Raw Is Nothing Then Return Nothing - '路径替换 - Raw = Raw.Replace("{minecraft}", PathMcFolder) - Raw = Raw.Replace("{verpath}", McVersionCurrent.Path) - Raw = Raw.Replace("{verindie}", McVersionCurrent.PathIndie) - Raw = Raw.Replace("{java}", McLaunchJavaSelected.PathFolder) - '普通替换 - Raw = Raw.Replace("{user}", McLoginLoader.Output.Name) - Raw = Raw.Replace("{uuid}", McLoginLoader.Output.Uuid) - If ReplaceTimeAndDate Then '设置窗口标题时需要动态替换日期和时间 - Raw = Raw.Replace("{date}", Date.Now.ToString("yyyy/M/d")) - Raw = Raw.Replace("{time}", Date.Now.ToString("HH:mm:ss")) - End If - Select Case McLoginLoader.Input.Type - Case McLoginType.Legacy - If PageLinkHiper.HiperState = LoadState.Finished Then - Raw = Raw.Replace("{login}", "联机离线") - Else - Raw = Raw.Replace("{login}", "离线") - End If - Case McLoginType.Ms - Raw = Raw.Replace("{login}", "正版") - Case McLoginType.Nide - Raw = Raw.Replace("{login}", "统一通行证") - Case McLoginType.Auth - Raw = Raw.Replace("{login}", "Authlib-Injector") - End Select - Raw = Raw.Replace("{name}", McVersionCurrent.Name) - If {"unknown", "old", "pending"}.Contains(McVersionCurrent.Version.McName.ToLower) Then - Raw = Raw.Replace("{version}", McVersionCurrent.Name) - Else - Raw = Raw.Replace("{version}", McVersionCurrent.Version.McName) - End If - Raw = Raw.Replace("{path}", Path) - Return Raw - End Function - #End Region End Module diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb index 6a3a527..2382a0b 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb @@ -68,7 +68,7 @@ Public Module ModMinecraft For Each Folder As String In Setup.Get("LaunchFolders").Split("|") If Folder = "" Then Continue For If Not Folder.Contains(">") OrElse Not Folder.EndsWithF("\") Then - Hint("无效的 Minecraft 文件夹:" & Folder, HintType.Critical) + Hint("无效的 Minecraft 文件夹:" & Folder, HintType.Red) Continue For End If Dim Name As String = Folder.Split(">")(0) @@ -261,6 +261,12 @@ Public Module ModMinecraft ''' 显示的描述文本。 ''' Public Info As String = "该版本未被加载,请向作者反馈此问题" + Public ReadOnly Property HasCustomInfo As Boolean + Get + Return ReadIni(Path & "PCL\Setup.ini", "CustomInfo") <> "" + End Get + End Property + ''' ''' 该版本的列表检查原始结果,不受自定义影响。 ''' @@ -784,7 +790,17 @@ ExitDataLoad: End If '确定版本描述 Dim CustomInfo As String = ReadIni(Path & "PCL\Setup.ini", "CustomInfo") - Info = If(CustomInfo <> "", CustomInfo, GetDefaultDescription()) + If CustomInfo <> "" Then + Info = CustomInfo + Else + Info = GetVersionDescription() + If State = McVersionState.Fool Then + Info = GetMcFoolName(Version.McName) + ElseIf State <> McVersionState.Error Then + If Setup.Get("VersionServerLogin", Version:=Me) = 3 Then Info += ", 统一通行证验证" + If Setup.Get("VersionServerLogin", Version:=Me) = 4 Then Info += ", Authlib 验证" + End If + End If '确定版本收藏状态 IsStar = ReadIni(Path & "PCL\Setup.ini", "IsStar", False) '确定版本显示种类 @@ -817,11 +833,13 @@ ExitDataLoad: End Try Return Me End Function + Public IsLoaded As Boolean = False ''' - ''' 获取版本的默认描述。 + ''' 获取对该版本的简短描述。 + ''' 例如 “快照 16w01a”、“原版 1.12.2”、“愚人节版本 2.0”。 ''' - Public Function GetDefaultDescription() As String - Dim Info As String = "" + Public Function GetVersionDescription() As String + Dim Info As String Select Case State Case McVersionState.Snapshot If Version.McName.ContainsF("pre", True) Then @@ -838,21 +856,38 @@ ExitDataLoad: Case McVersionState.Original, McVersionState.Forge, McVersionState.NeoForge, McVersionState.Fabric, McVersionState.OptiFine, McVersionState.LiteLoader Info = Version.ToString Case McVersionState.Fool - Info = GetMcFoolName(Version.McName) + Info = "愚人节版本 " & Version.McName Case McVersionState.Error Return Me.Info '已有错误信息 Case Else Info = "发生了未知错误,请向作者反馈此问题" End Select - If Not State = McVersionState.Error Then - If Setup.Get("VersionServerLogin", Version:=Me) = 3 Then Info += ", 统一通行证验证" - If Setup.Get("VersionServerLogin", Version:=Me) = 4 Then Info += ", Authlib 验证" - End If Return Info End Function - Public IsLoaded As Boolean = False + Public Function ToListItem() As MyListItem + Dim NewItem As New MyListItem With {.Info = Info, .Height = 42, .Tag = Me, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable} + '标题 + NewItem.Inlines.Clear() + NewItem.Inlines.Add(New Run(Name)) + If HasCustomInfo Then '如果版本设置了自定义描述,在标题后面以淡灰色显示其版本号 + NewItem.Inlines.Add(New Run(" | " & GetVersionDescription()) With {.Foreground = New MyColor(215, 215, 215), .FontSize = 12}) + End If + 'Logo + Try + If Logo.EndsWith("PCL\Logo.png") Then + NewItem.Logo = Path & "PCL\Logo.png" '修复老版本中,存储的自定义 Logo 使用完整路径,导致移动后无法加载的 Bug + Else + NewItem.Logo = Logo + End If + Catch ex As Exception + Log(ex, "加载版本图标失败", LogLevel.Hint) + NewItem.Logo = "pack://application:,,,/images/Blocks/RedstoneBlock.png" + End Try + Return NewItem + End Function + '运算符支持 Public Overrides Function Equals(obj As Object) As Boolean Dim version = TryCast(obj, McVersion) Return version IsNot Nothing AndAlso Path = version.Path @@ -865,7 +900,6 @@ ExitDataLoad: Public Shared Operator <>(a As McVersion, b As McVersion) As Boolean Return Not (a = b) End Operator - End Class Public Enum McVersionState [Error] @@ -1567,12 +1601,12 @@ OnLoaded: Try Dim Image As New MyBitmap(FileName) If Image.Pic.Width <> 64 OrElse Not (Image.Pic.Height = 32 OrElse Image.Pic.Height = 64) Then - Hint("皮肤图片大小应为 64x32 像素或 64x64 像素!", HintType.Critical) + Hint("皮肤图片大小应为 64x32 像素或 64x64 像素!", HintType.Red) Return New McSkinInfo With {.IsVaild = False} End If Dim FileInfo As New FileInfo(FileName) If FileInfo.Length > 24 * 1024 Then - Hint("皮肤文件大小需小于 24 KB,而所选文件大小为 " & Math.Round(FileInfo.Length / 1024, 2) & " KB", HintType.Critical) + Hint("皮肤文件大小需小于 24 KB,而所选文件大小为 " & Math.Round(FileInfo.Length / 1024, 2) & " KB", HintType.Red) Return New McSkinInfo With {.IsVaild = False} End If Catch ex As Exception diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb index a2d7783..5769826 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb @@ -31,7 +31,7 @@ Public Module ModModpack Try '字符校验 Dim TargetFolder As String = $"{PathMcFolder}versions\{VersionName}\" - If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("游戏路径中不能含有感叹号或分号:" & TargetFolder, HintType.Critical) : Throw New CancelledException + If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("游戏路径中不能含有感叹号或分号:" & TargetFolder, HintType.Red) : Throw New CancelledException '获取整合包种类与关键 Json Dim PackType As Integer = -1 Try @@ -344,7 +344,7 @@ Retry: '重复任务检查 Dim LoaderName As String = "CurseForge 整合包安装:" & VersionName & " " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then - Hint("该整合包正在安装中!", HintType.Critical) + Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException End If @@ -485,7 +485,7 @@ Retry: '重复任务检查 Dim LoaderName As String = $"Modrinth 整合包安装:{VersionName} " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then - Hint("该整合包正在安装中!", HintType.Critical) + Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException End If @@ -540,7 +540,7 @@ Retry: '重复任务检查 Dim LoaderName As String = "HMCL 整合包安装:" & VersionName & " " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then - Hint("该整合包正在安装中!", HintType.Critical) + Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException End If '启动 @@ -639,8 +639,8 @@ Retry: '构造版本安装请求 If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!") Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"} - For Each Component In PackJson("components") - If Not Component.Contains("uid") Then Continue For + For Each Component As JObject In PackJson("components") + If Not Component.ContainsKey("uid") Then Continue For Select Case Component("uid").ToString Case "org.lwjgl" Log("[ModPack] 已跳过 LWJGL 项") @@ -667,7 +667,7 @@ Retry: '重复任务检查 Dim LoaderName As String = "MMC 整合包安装:" & VersionName & " " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then - Hint("该整合包正在安装中!", HintType.Critical) + Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException End If @@ -725,7 +725,7 @@ Retry: Next If Not Addons.ContainsKey("game") Then Throw New Exception("该 MCBBS 整合包未提供游戏版本信息,无法安装!") If Addons.ContainsKey("quilt") Then - Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical) + Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Red) Throw New CancelledException End If Dim Request As New McInstallRequest With { @@ -746,7 +746,7 @@ Retry: '重复任务检查 Dim LoaderName As String = "MCBBS 整合包安装:" & VersionName & " " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then - Hint("该整合包正在安装中!", HintType.Critical) + Hint("该整合包正在安装中!", HintType.Red) Throw New CancelledException End If @@ -766,7 +766,7 @@ Retry: MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True) Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)") If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException - If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Critical) : Throw New CancelledException + If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Red) : Throw New CancelledException '解压 Dim Loader As New LoaderCombo(Of String)("解压压缩包", { New LoaderTask(Of String, Integer)("解压压缩包", @@ -843,8 +843,8 @@ Retry: MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True) Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)") If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException - If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Throw New CancelledException - If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Critical) : Throw New CancelledException + If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Red) : Throw New CancelledException + If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Red) : Throw New CancelledException '解压 Dim Loader As New LoaderCombo(Of String)("解压压缩包", { New LoaderTask(Of String, Integer)("解压压缩包", diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb index c4f08a4..d6259a0 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb @@ -349,7 +349,7 @@ State = MinecraftState.Crashed '崩溃分析 WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析") - Hint("检测到 Minecraft 出现错误,错误分析已开始……", HintType.Critical) + Hint("检测到 Minecraft 出现错误,错误分析已开始……", HintType.Red) FeedbackInfo() RunInNewThread( Sub() diff --git a/Plain Craft Launcher 2/Modules/ModEvent.vb b/Plain Craft Launcher 2/Modules/ModEvent.vb index 615fcf7..02849de 100644 --- a/Plain Craft Launcher 2/Modules/ModEvent.vb +++ b/Plain Craft Launcher 2/Modules/ModEvent.vb @@ -1,181 +1,291 @@ -Imports System.Collections.ObjectModel + +#Region "附加属性" + +''' +''' 用于在 XAML 中初始化列表对象。 +''' 附加属性无法在 XAML 中为每个对象初始化独立的列表对象,因此需要一个包装类,然后在 XAML 中显式初始化。 +''' + +Public Class CustomEventCollection + Implements IEnumerable(Of CustomEvent) + Dim _Events As New List(Of CustomEvent) + Public ReadOnly Property Events As List(Of CustomEvent) + Get + Return _Events + End Get + End Property + Public Function GetEnumerator() As IEnumerator(Of CustomEvent) Implements IEnumerable(Of CustomEvent).GetEnumerator + Return DirectCast(Events, IEnumerable(Of CustomEvent)).GetEnumerator() + End Function + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Return DirectCast(Events, IEnumerable).GetEnumerator() + End Function +End Class + +''' +''' 提供自定义事件的附加属性。 +''' +Public Class CustomEventService + + 'Events + Public Shared ReadOnly EventsProperty As DependencyProperty = + DependencyProperty.RegisterAttached("Events", GetType(CustomEventCollection), GetType(CustomEventService), New PropertyMetadata(Nothing)) + + Public Shared Sub SetEvents(d As DependencyObject, value As CustomEventCollection) + d.SetValue(EventsProperty, value) + End Sub + + Public Shared Function GetEvents(d As DependencyObject) As CustomEventCollection + If d.GetValue(EventsProperty) Is Nothing Then d.SetValue(EventsProperty, New CustomEventCollection) + Return d.GetValue(EventsProperty) + End Function + + 'EventType + Public Shared ReadOnly EventTypeProperty As DependencyProperty = + DependencyProperty.RegisterAttached("EventType", GetType(CustomEvent.EventType), GetType(CustomEventService), New PropertyMetadata(Nothing)) + + Public Shared Sub SetEventType(d As DependencyObject, value As CustomEvent.EventType) + d.SetValue(EventTypeProperty, value) + End Sub + + Public Shared Function GetEventType(d As DependencyObject) As CustomEvent.EventType + Return d.GetValue(EventTypeProperty) + End Function + + 'EventData + Public Shared ReadOnly EventDataProperty As DependencyProperty = + DependencyProperty.RegisterAttached("EventData", GetType(String), GetType(CustomEventService), New PropertyMetadata(Nothing)) + + Public Shared Sub SetEventData(d As DependencyObject, value As String) + d.SetValue(EventDataProperty, value) + End Sub + + Public Shared Function GetEventData(d As DependencyObject) As String + Return d.GetValue(EventDataProperty) + End Function + +End Class + +Partial Public Module ModMain -Public Module ModEvent -#Region "事件执行方法" ''' - ''' 供外部调用的自定义事件执行接口,会在新建工作线程中运行逻辑。 + ''' 触发该控件上的自定义事件。 + ''' 事件会在新线程中执行。 ''' - ''' xaml中的EventType - ''' xaml中的EventData - ''' xaml中的CustomEvents - Public Sub ProcessCustomEvents(SingleEventType As String, SingleEventData As String, EventCollection As CustomEventCollection) + + Public Sub RaiseCustomEvent(Control As DependencyObject) + '收集事件列表 + Dim Events = CustomEventService.GetEvents(Control).ToList + Dim EventType = CustomEventService.GetEventType(Control) + If EventType <> CustomEvent.EventType.None Then Events.Add(New CustomEvent(EventType, CustomEventService.GetEventData(Control))) + '执行事件 + If Not Events.Any Then Return RunInNewThread( - Sub() - Try - '先执行单个事件 - StartCustomEvent(SingleEventType, SingleEventData) - '再挨个执行事件集合中的事件 - If EventCollection IsNot Nothing Then - For Each AnEvent In RunInUiWait(Function() EventCollection.DeepCloneToStructureList()) - StartCustomEvent(AnEvent.EventType, AnEvent.EventData) - Next - End If - Catch ex As Exception - Log(ex, "自定义事件执行器发生异常", LogLevel.Feedback) - End Try - End Sub, "CustomEventProcessor " & GetUuid()) - End Sub - ''' - ''' 对当前线程阻塞地调用自定义事件处理器,接受来自 xaml 原始输入作为参数。 - ''' - Private Sub StartCustomEvent(Type As String, DataRaw As String) - If String.IsNullOrEmpty(Type) Then Exit Sub - Dim Data = If(DataRaw Is Nothing, {""}, DataRaw.Split("|")) - Log("[Control] 执行自定义事件:" & Type & ", " & Join(Data, ", ")) - '检测是否存在对应事件 - If Not CustomEventProcessorsDict.ContainsKey(Type) Then - MyMsgBox($"未知的事件类型:'{Type}'{vbCrLf}请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败", ForceWait:=True) - Exit Sub - End If - '调用事件处理器 - Try - CustomEventProcessorsDict(Type)(Data) - Catch ex As Exception - Log(ex, $"自定义事件执行失败:{Type}", LogLevel.Msgbox, "自定义主页事件执行失败") - End Try + Sub() + For Each e In Events + e.Raise() + Next + End Sub, "执行自定义事件 " & GetUuid()) End Sub - ''' - ''' 存储各个自定义事件处理器的字典,以 EventType 为键,执行自定义事件时在工作线程中调用 Action 并传入分割后的 EventData 。 - ''' Action 应当在事件逻辑结束后,即可以开始下一个事件时再结束;以便于挨个调用事件不打架。 - ''' - Private ReadOnly CustomEventProcessorsDict As New Dictionary(Of String, Action(Of String())) From { - {"打开网页", - Sub(Data As String()) - Data(0) = Data(0).Replace("\", "/") - If Not Data(0).Contains("://") OrElse Data(0).StartsWithF("file", True) Then '为了支持更多协议(#2200) - MyMsgBox("打开网页事件中,EventData 必须为一个网址。" & vbCrLf & "如果想要启动程序,请将 EventType 改为 打开文件。", "事件执行失败") - Exit Sub - End If - Hint("网页正在开启中,请稍候……") - OpenWebsite(Data(0)) - End Sub}, - {"打开文件", - Sub(Data As String()) - Dim ActualPaths = GetEventAbsoluteUrls(Data(0), "打开文件") - Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1) - Log($"[Control] 打开文件:路径:{Location},工作目录:{WorkingDir}") - If Not CommandRunUserConfirm(If(Data.Length < 2, Location, $"{Location} {Data(1)}")) Then Exit Sub - Dim Info As New ProcessStartInfo With { - .Arguments = If(Data.Length < 2, "", Data(1)), - .FileName = Location, - .WorkingDirectory = ShortenPath(WorkingDir) - } - Process.Start(Info) - End Sub}, - {"打开帮助", - Sub(Data As String()) - Dim ActualPaths = GetEventAbsoluteUrls(Data(0), "打开帮助") - Dim Location = ActualPaths(0) - Log($"[Control] 打开帮助:{Location}") - PageOtherHelp.EnterHelpPage(Location) - End Sub}, - {"执行命令", - Sub(Data As String()) - Dim ActualPaths = GetEventAbsoluteUrls(Data(0), "执行命令") - Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1) - Log($"[Control] 执行命令:路径:{Location},工作目录:{WorkingDir}") - If Not CommandRunUserConfirm(If(Data.Length < 2, Location, $"{Location} {Data(1)}")) Then Exit Sub - Dim Info As New ProcessStartInfo With { - .Arguments = If(Data.Length < 2, "", Data(1)), - .FileName = Location, - .WorkingDirectory = ShortenPath(WorkingDir) - } - Process.Start(Info) - End Sub}, - {"启动游戏", - Sub(Data As String()) - If Data(0) = "\current" Then - If McVersionCurrent Is Nothing Then - Hint("请先选择一个 Minecraft 版本!", HintType.Critical) - Exit Sub - Else - Data(0) = McVersionCurrent.Name - End If - End If - RunInUiWait( - Sub() - If McLaunchStart(New McLaunchOptions With {.Version = New McVersion(Data(0)), .ServerIp = If(Data.Length >= 2, Data(1), Nothing)}) Then - Hint("正在启动 " & Data(0) & "……") - End If - End Sub) - End Sub}, - {"复制文本", Sub(Data As String()) ClipboardSet(Join(Data, "|"))}, - {"刷新主页", - Sub(Data As String()) - RunInUiWait(Sub() FrmLaunchRight.ForceRefresh()) - If Data(0) = "" Then Hint("已刷新主页!", HintType.Finish) - End Sub}, - {"刷新帮助", Sub() RunInUi(Sub() PageOtherLeft.RefreshHelp())}, - {"今日人品", Sub() PageOtherTest.Jrrp()}, - {"内存优化", - Sub(Data As String()) - If Data.Length >= 1 AndAlso Data(0) = "前台运行" Then - PageOtherTest.MemoryOptimize(Data.Length >= 2 AndAlso Data(1) = "显示提示") - Else - RunInNewThread(Sub() PageOtherTest.MemoryOptimize(True)) - End If - End Sub}, - {"弹出窗口", Sub(Data As String()) MyMsgBox(Data(1).Replace("\n", vbCrLf), Data(0).Replace("\n", vbCrLf), ForceWait:=True)}, - {"切换页面", Sub(Data As String()) RunInUiWait(Sub() FrmMain.PageChange(Val(Data(0)), Val(Data(1))))}, - {"安装整合包", Sub() RunInUiWait(Sub() ModpackInstall())}, - {"导入整合包", Sub() RunInUiWait(Sub() ModpackInstall())}, - {"下载文件", - Sub(Data As String()) - Data(0) = Data(0).Replace("\", "/") - If Not (Data(0).StartsWithF("http://", True) OrElse Data(0).StartsWithF("https://", True)) Then - MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败") - Exit Sub - End If - Try - Select Case Data.Length - Case 1 - PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0))) - Case 2 - PageOtherTest.StartCustomDownload(Data(0), Data(1)) - Case Else - PageOtherTest.StartCustomDownload(Data(0), Data(1), Data(2)) - End Select - Catch - PageOtherTest.StartCustomDownload(Data(0), "未知") - End Try - End Sub} - } +End Module #End Region -#Region "工具方法" + +''' +''' 自定义事件。 +''' +Public Class CustomEvent + +#Region "属性与触发" + + Public Property Type As EventType = EventType.None + Public Property Data As String = Nothing + Public Sub New() + End Sub + Public Sub New(Type As EventType, Data As String) + Me.Type = Type + Me.Data = Data + End Sub + ''' - ''' 弹窗询问用户是否同意继续执行打开类事件。 + ''' 在当前线程中触发该自定义事件。 ''' - Private Function CommandRunUserConfirm(TextShowToUser As String) As Boolean - If Setup.Get("HintCustomCommand") Then Return True - Select Case MyMsgBox("即将执行:" & TextShowToUser & vbCrLf & "请在确认该操作没有安全隐患后继续。", "执行确认", "继续", "继续且今后不再要求确认", "取消") - Case 1 - Return True - Case 2 - Setup.Set("HintCustomCommand", True) - Return True - Case Else '3 - Log("[Control] 用户取消了打开类事件") - Return False - End Select - End Function + Public Sub Raise() + Raise(Type, Data) + End Sub + +#End Region + + Public Enum EventType + None = 0 + 打开网页 + 打开文件 + 打开帮助 + 执行命令 + 启动游戏 + 复制文本 + 刷新主页 + 刷新页面 + 刷新帮助 + 今日人品 + 内存优化 + 清理垃圾 + 弹出窗口 + 弹出提示 + 切换页面 + 导入整合包 + 安装整合包 + 下载文件 + 修改设置 + 写入设置 + 修改变量 + 写入变量 + End Enum + + ''' + ''' 在当前线程中触发一个自定义事件。 + ''' + Public Shared Sub Raise(Type As EventType, Arg As String) + If Type = EventType.None Then Return + Log($"[Control] 执行自定义事件:{Type}, {Arg}") + Try + Dim Args As String() = If(Arg?.Split("|"), {""}) + Select Case Type + + Case EventType.打开网页 + Arg = Arg.Replace("\", "/") + If Not Arg.Contains("://") OrElse Arg.StartsWithF("file", True) Then '为了支持更多协议(#2200) + MyMsgBox("EventData 必须为一个网址。" & vbCrLf & "如果想要启动程序,请将 EventType 改为 打开文件。", "事件执行失败") + Return + End If + Hint("正在开启中,请稍候:" & Arg) + RunInThread(Sub() OpenWebsite(Arg)) + + Case EventType.打开文件, EventType.打开帮助, EventType.执行命令 + RunInThread( + Sub() + Try + '确认实际路径 + Dim ActualPaths = GetAbsoluteUrls(Args(0), Type) + Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1) + Log($"[Control] 打开类自定义事件实际路径:{Location},工作目录:{WorkingDir}") + '执行 + If Type = EventType.打开帮助 Then + PageOtherHelp.EnterHelpPage(Location) + Else + If Not EventSafetyConfirm("即将执行:" & Location & If(Args.Length >= 2, " " & Args(1), "")) Then Return + Dim Info As New ProcessStartInfo With { + .Arguments = If(Args.Length >= 2, Args(1), ""), + .FileName = Location, + .WorkingDirectory = WorkingDir + } + StartProcess(Info) + End If + Catch ex As Exception + Log(ex, "执行打开类自定义事件失败", LogLevel.Msgbox) + End Try + End Sub) + + Case EventType.启动游戏 + If Args(0) = "\current" Then + If McVersionCurrent Is Nothing Then + Hint("请先选择一个 Minecraft 版本!", HintType.Red) + Return + Else + Args(0) = McVersionCurrent.Name + End If + End If + RunInUi( + Sub() + If McLaunchStart(New McLaunchOptions With + {.ServerIp = If(Args.Length >= 2, Args(1), Nothing), .Version = New McVersion(Args(0))}) Then + Hint($"正在启动 {Args(0)}……") + End If + End Sub) + + Case EventType.复制文本 + ClipboardSet(Arg) + + Case EventType.刷新主页, EventType.刷新页面 + If TypeOf FrmMain.PageRight Is IRefreshable Then + RunInUiWait(Sub() CType(FrmMain.PageRight, IRefreshable).Refresh()) + If String.IsNullOrEmpty(Arg) Then Hint("已刷新!", HintType.Green) + Else + Hint("当前页面不支持刷新操作!", HintType.Red) + End If + + Case EventType.刷新帮助 + RunInUiWait(Sub() PageOtherLeft.RefreshHelp()) + If String.IsNullOrEmpty(Arg) Then Hint("已刷新!", HintType.Green) + + Case EventType.今日人品 + PageOtherTest.Jrrp() + + Case EventType.内存优化 + RunInThread(Sub() PageOtherTest.MemoryOptimize(True)) + + Case EventType.清理垃圾 + RunInThread(Sub() PageOtherTest.RubbishClear()) + + Case EventType.弹出窗口 + If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 弹窗标题|弹窗内容") + MyMsgBox(Args(1).Replace("\n", vbCrLf), Args(0).Replace("\n", vbCrLf), If(Args.Length > 2, Args(2), "确定")) + + Case EventType.弹出提示 + Hint(Args(0).Replace("\n", vbCrLf), If(Args.Length = 1, HintType.Blue, Args(1).ParseToEnum(Of HintType))) + + Case EventType.切换页面 + RunInUi(Sub() FrmMain.PageChange( + Args(0).ParseToEnum(Of FormMain.PageType), + If(Args.Length = 1, FormMain.PageSubType.Default, Args(1).ParseToEnum(Of FormMain.PageSubType)))) + + Case EventType.导入整合包, EventType.安装整合包 + RunInUi(Sub() ModpackInstall()) + + Case EventType.下载文件 + Args(0) = Args(0).Replace("\", "/") + If Not (Args(0).StartsWithF("http://", True) OrElse Args(0).StartsWithF("https://", True)) Then + MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败") + Return + End If + If Not EventSafetyConfirm("即将从该网址下载文件:" & vbCrLf & Args(0)) Then Return + Try + Select Case Args.Length + Case 1 + PageOtherTest.StartCustomDownload(Args(0), GetFileNameFromPath(Args(0))) + Case 2 + PageOtherTest.StartCustomDownload(Args(0), Args(1)) + Case Else + PageOtherTest.StartCustomDownload(Args(0), Args(1), Args(2)) + End Select + Catch + PageOtherTest.StartCustomDownload(Args(0), "未知") + End Try + + Case EventType.修改设置, EventType.写入设置 + If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 UiLauncherTransparent|400") + Setup.SetSafe(Args(0), Args(1), Version:=McVersionCurrent) + If Args.Length = 2 Then Hint($"已写入设置:{Args(0)} → {Args(1)}", HintType.Green) + + Case EventType.修改变量, EventType.写入变量 + If Args.Length = 1 Then Throw New Exception($"EventType {Type} 需要至少 2 个以 | 分割的参数,例如 VariableName|SomeValue") + WriteReg("CustomEvent" & Args(0), Args(1)) + If Args.Length = 2 Then Hint($"已写入变量:{Args(0)} → {Args(1)}", HintType.Green) + + Case Else + MyMsgBox("未知的事件类型:" & Type & vbCrLf & "请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败") + End Select + Catch ex As Exception + Log(ex, $"事件执行失败({Type}, {Arg})", LogLevel.Msgbox) + End Try + End Sub ''' ''' 返回自定义事件的绝对 Url。实际返回 {绝对 Url, WorkingDir}。 ''' 失败会抛出异常。 ''' - Public Function GetEventAbsoluteUrls(RelativeUrl As String, EventType As String) As String() + Public Shared Function GetAbsoluteUrls(RelativeUrl As String, Type As EventType) As String() '网页确认 If RelativeUrl.StartsWithF("http", True) Then @@ -216,25 +326,25 @@ Public Module ModEvent If RelativeUrl.Contains(":\") Then '绝对路径 Location = RelativeUrl - Log("[Control] 自定义事件中由绝对路径" & EventType & ":" & Location) + Log("[Control] 自定义事件中由绝对路径" & Type & ":" & Location) ElseIf File.Exists(Path & "PCL\" & RelativeUrl) Then '相对 PCL 文件夹的路径 Location = Path & "PCL\" & RelativeUrl - Log("[Control] 自定义事件中由相对 PCL 文件夹的路径" & EventType & ":" & Location) + Log("[Control] 自定义事件中由相对 PCL 文件夹的路径" & Type & ":" & Location) ElseIf File.Exists(Path & "PCL\Help\" & RelativeUrl) Then '相对 PCL 本地帮助文件夹的路径 Location = Path & "PCL\Help\" & RelativeUrl WorkingDir = Path & "PCL\Help\" - Log("[Control] 自定义事件中由相对 PCL 本地帮助文件夹的路径" & EventType & ":" & Location) - ElseIf EventType = "打开帮助" AndAlso File.Exists(PathTemp & "Help\" & RelativeUrl) Then + Log("[Control] 自定义事件中由相对 PCL 本地帮助文件夹的路径" & Type & ":" & Location) + ElseIf Type = EventType.打开帮助 AndAlso File.Exists(PathTemp & "Help\" & RelativeUrl) Then '相对 PCL 自带帮助文件夹的路径 Location = PathTemp & "Help\" & RelativeUrl WorkingDir = PathTemp & "Help\" - Log("[Control] 自定义事件中由相对 PCL 自带帮助文件夹的路径" & EventType & ":" & Location) - ElseIf EventType = "打开文件" OrElse EventType = "执行命令" Then + Log("[Control] 自定义事件中由相对 PCL 自带帮助文件夹的路径" & Type & ":" & Location) + ElseIf Type = EventType.打开文件 OrElse Type = EventType.执行命令 Then '直接使用原有路径启动程序 Location = RelativeUrl - Log("[Control] 自定义事件中直接" & EventType & ":" & Location) + Log("[Control] 自定义事件中直接" & Type & ":" & Location) Else '打开帮助,但是格式不对劲 Throw New FileNotFoundException("未找到 EventData 指向的本地 xaml 文件:" & RelativeUrl, RelativeUrl) @@ -243,42 +353,20 @@ Public Module ModEvent Return {Location, WorkingDir} End Function -#End Region -End Module -#Region "数据结构类声明" -Public Class CustomEventCollection - Inherits ObservableCollection(Of CustomEvent) - Public Function DeepCloneToStructureList() As IList(Of CustomEventStructure) - DeepCloneToStructureList = New List(Of CustomEventStructure) - For Each AnEvent In Me - DeepCloneToStructureList.Add(New CustomEventStructure With {.EventType = AnEvent.EventType, .EventData = AnEvent.EventData}) - Next + ''' + ''' 弹出安全确认弹窗。返回是否继续执行。 + ''' + Private Shared Function EventSafetyConfirm(Message As String) As Boolean + If Setup.Get("HintCustomCommand") Then Return True + Select Case MyMsgBox(Message & vbCrLf & "请在确认没有安全隐患后再继续。", "执行确认", "继续", "继续且今后不再要求确认", "取消") + Case 1 + Return True + Case 2 + Setup.Set("HintCustomCommand", True) + Return True + Case Else + Return False + End Select End Function -End Class -Public Class CustomEvent - Inherits DependencyObject - Public Property EventType As String - Get - Return GetValue(EventTypeProperty) - End Get - Set(value As String) - SetValue(EventTypeProperty, value) - End Set - End Property - Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(CustomEvent), New PropertyMetadata(Nothing)) - Public Property EventData As String - Get - Return GetValue(EventDataProperty) - End Get - Set(value As String) - SetValue(EventDataProperty, value) - End Set - End Property - Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(CustomEvent), New PropertyMetadata(Nothing)) -End Class -Public Structure CustomEventStructure - Public EventType As String - Public EventData As String -End Structure -#End Region \ No newline at end of file +End Class diff --git a/Plain Craft Launcher 2/Modules/ModMain.vb b/Plain Craft Launcher 2/Modules/ModMain.vb index 68aaebb..c7eb4c5 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.vb +++ b/Plain Craft Launcher 2/Modules/ModMain.vb @@ -7,23 +7,12 @@ Public Module ModMain ''' ''' 提示信息的种类。 + ''' 该枚举在自定义事件中使用,是公开 API 的一部分。 ''' Public Enum HintType - ''' - ''' 信息,通常是蓝色的“i”。 - ''' - ''' - Info - ''' - ''' 已完成,通常是绿色的“√”。 - ''' - ''' - Finish - ''' - ''' 错误,通常是红色的“×”。 - ''' - ''' - Critical + Blue + Green + Red End Enum Private Structure HintMessage Public Text As String @@ -38,7 +27,7 @@ Public Module ModMain ''' ''' 在窗口左下角弹出提示文本。 ''' - Public Sub Hint(Text As String, Optional Type As HintType = HintType.Info, Optional Log As Boolean = True) + Public Sub Hint(Text As String, Optional Type As HintType = HintType.Blue, Optional Log As Boolean = True) If HintWaiting Is Nothing Then HintWaiting = New SafeList(Of HintMessage) HintWaiting.Add(New HintMessage With {.Text = If(Text, ""), .Type = Type, .Log = Log}) End Sub @@ -68,13 +57,13 @@ Public Module ModMain Dim TargetColor0, TargetColor1 As MyColor Dim Percent As Double = 0.3 Select Case CurrentHint.Type - Case HintType.Info + Case HintType.Blue TargetColor0 = New MyColor(215, 37, 155, 252) TargetColor1 = New MyColor(215, 10, 142, 252) - Case HintType.Finish + Case HintType.Green TargetColor0 = New MyColor(215, 33, 177, 33) TargetColor1 = New MyColor(215, 29, 160, 29) - Case Else 'HintType.Critical + Case Else 'HintType.Red TargetColor0 = New MyColor(215, 255, 53, 11) TargetColor1 = New MyColor(215, 255, 43, 0) End Select @@ -490,7 +479,7 @@ EndHint: ''' 是否为 “执行事件”。 ''' Public IsEvent As Boolean - Public EventType As String + Public EventType As CustomEvent.EventType Public EventData As String ''' ''' 若非执行事件,其对应的 .xaml 本地文件内容。 @@ -504,7 +493,7 @@ EndHint: ''' Public Sub New(FilePath As String) RawPath = FilePath - Dim JsonData As JObject = GetJson(HelpArgumentReplace(ReadFile(FilePath))) + Dim JsonData As JObject = GetJson(ArgumentReplace(ReadFile(FilePath), AddressOf EscapeXML)) If JsonData Is Nothing Then Throw New FileNotFoundException("未找到帮助文件:" & FilePath, FilePath) '加载常规信息 If JsonData("Title") IsNot Nothing Then @@ -524,8 +513,7 @@ EndHint: Next '加载事件信息 If If(JsonData("IsEvent"), False) Then - EventType = JsonData("EventType") - If EventType Is Nothing Then Throw New ArgumentException("未找到 EventType 项") + EventType = [Enum].Parse(GetType(CustomEvent.EventType), JsonData("EventType").ToString) EventData = If(JsonData("EventData"), "") IsEvent = True Else @@ -550,7 +538,7 @@ EndHint: Public Function SetToListItem(Item As MyListItem) As MyListItem Dim Logo As String If IsEvent Then - If EventType = "弹出窗口" Then + If EventType = CustomEvent.EventType.弹出窗口 Then Logo = PathImage & "Blocks/GrassPath.png" Else Logo = PathImage & "Blocks/CommandBlock.png" @@ -567,9 +555,9 @@ EndHint: .Height = 42 .Type = MyListItem.CheckType.Clickable .Tag = Me - .EventType = Nothing - .EventData = Nothing End With + CustomEventService.SetEventType(Item, CustomEvent.EventType.None) '清空自定义事件属性,它们会被下面的点击事件处理 + CustomEventService.SetEventData(Item, Nothing) '项目的点击事件 AddHandler Item.Click, Sub(sender, e) PageOtherHelp.OnItemClick(sender.Tag) Return Item @@ -669,15 +657,6 @@ NextFile: Log("[Help] 已解压内置帮助文件,目前状态:" & File.Exists(PathTemp & "Help\启动器\备份设置.xaml"), LogLevel.Debug) End If End Sub - ''' - ''' 对帮助文件约定的替换标记进行处理,如果遇到需要转义的字符会进行转义。 - ''' - Public Function HelpArgumentReplace(Xaml As String) As String - Dim Result = Xaml.Replace("{path}", EscapeXML(Path)) - Result = Result.RegexReplaceEach("\{hint\}", Function() EscapeXML(PageOtherTest.GetRandomHint())) - Result = Result.RegexReplaceEach("\{cave\}", Function() EscapeXML(PageOtherTest.GetRandomCave())) - Return Result - End Function #End Region @@ -836,6 +815,78 @@ NextFile: End Using End Sub + ''' + ''' 对替换标记进行处理。会对替换内容使用 EscapeHandler 进行转义。 + ''' + Public Function ArgumentReplace(Text As String, Optional EscapeHandler As Func(Of String, String) = Nothing, Optional ReplaceTime As Boolean = True) As String + '预处理 + If Text Is Nothing Then Return Nothing + Dim Replacer = + Function(s As String) As String + If s Is Nothing Then Return "" + If EscapeHandler Is Nothing Then Return s + If s.Contains(":\") Then s = ShortenPath(s) + Return EscapeHandler(s) + End Function + '基础 + Text = Text.Replace("{pcl_version}", Replacer(VersionBaseName)) + Text = Text.Replace("{pcl_version_code}", Replacer(VersionCode)) + Text = Text.Replace("{pcl_version_branch}", Replacer(VersionBranchName)) + Text = Text.Replace("{pcl_branch}", Replacer(VersionBranchMain)) + Text = Text.Replace("{identify}", Replacer(Identify)) + Text = Text.Replace("{path}", Replacer(Path)) + Text = Text.Replace("{path_with_name}", Replacer(PathWithName)) + Text = Text.Replace("{path_temp}", Replacer(PathTemp)) + '时间 + If ReplaceTime Then '在窗口标题中,时间会被后续动态替换,所以此时不应该替换 + Text = Text.Replace("{date}", Replacer(Date.Now.ToString("yyyy/M/d"))) + Text = Text.Replace("{time}", Replacer(Date.Now.ToString("HH:mm:ss"))) + End If + 'Minecraft + Text = Text.Replace("{java}", Replacer(McLaunchJavaSelected?.PathFolder)) + Text = Text.Replace("{minecraft}", Replacer(PathMcFolder)) + If McVersionCurrent IsNot Nothing Then + Text = Text.Replace("{version_path}", Replacer(McVersionCurrent.Path)) : Text = Text.Replace("{verpath}", Replacer(McVersionCurrent.Path)) + Text = Text.Replace("{version_indie}", Replacer(McVersionCurrent.PathIndie)) : Text = Text.Replace("{verindie}", Replacer(McVersionCurrent.PathIndie)) + Text = Text.Replace("{name}", Replacer(McVersionCurrent.Name)) + If {"unknown", "old", "pending"}.Contains(McVersionCurrent.Version.McName.ToLower) Then + Text = Text.Replace("{version}", Replacer(McVersionCurrent.Name)) + Else + Text = Text.Replace("{version}", Replacer(McVersionCurrent.Version.McName)) + End If + Else + Text = Text.Replace("{version_path}", Replacer(Nothing)) : Text = Text.Replace("{verpath}", Replacer(Nothing)) + Text = Text.Replace("{version_indie}", Replacer(Nothing)) : Text = Text.Replace("{verindie}", Replacer(Nothing)) + Text = Text.Replace("{name}", Replacer(Nothing)) + Text = Text.Replace("{version}", Replacer(Nothing)) + End If + '登录信息 + If McLoginLoader.State = LoadState.Finished Then + Text = Text.Replace("{user}", Replacer(McLoginLoader.Output.Name)) + Text = Text.Replace("{uuid}", Replacer(McLoginLoader.Output.Uuid.ToLower)) + Select Case McLoginLoader.Input.Type + Case McLoginType.Legacy + Text = Text.Replace("{login}", Replacer("离线")) + Case McLoginType.Ms + Text = Text.Replace("{login}", Replacer("正版")) + Case McLoginType.Nide + Text = Text.Replace("{login}", Replacer("统一通行证")) + Case McLoginType.Auth + Text = Text.Replace("{login}", Replacer("Authlib-Injector")) + End Select + Else + Text = Text.Replace("{user}", Replacer(Nothing)) + Text = Text.Replace("{uuid}", Replacer(Nothing)) + Text = Text.Replace("{login}", Replacer(Nothing)) + End If + '高级 + Text = Text.RegexReplaceEach("\{hint\}", Function() Replacer(PageOtherTest.GetRandomHint())) + Text = Text.RegexReplaceEach("\{cave\}", Function() Replacer(PageOtherTest.GetRandomCave())) + Text = Text.RegexReplaceEach("\{setup:([a-zA-Z0-9]+)\}", Function(m) Replacer(Setup.GetSafe(m.Groups(1).Value, McVersionCurrent))) + Text = Text.RegexReplaceEach("\{varible:([^\}]+)\}", Function(m) Replacer(ReadReg("CustomEvent" & m.Groups(1).Value, Nothing))) + Return Text + End Function + #End Region #Region "任务缓存" diff --git a/Plain Craft Launcher 2/Modules/ModMusic.vb b/Plain Craft Launcher 2/Modules/ModMusic.vb index 0476e89..403ceee 100644 --- a/Plain Craft Launcher 2/Modules/ModMusic.vb +++ b/Plain Craft Launcher 2/Modules/ModMusic.vb @@ -111,7 +111,7 @@ ''' Public Sub MusicControlPause() If MusicNAudio Is Nothing Then - Hint("音乐播放尚未开始!", HintType.Critical) + Hint("音乐播放尚未开始!", HintType.Red) Else Select Case MusicState Case MusicStates.Pause @@ -131,14 +131,14 @@ Public Sub MusicControlNext() If MusicAllList.Count = 1 Then MusicStartPlay(MusicCurrent) - Hint("重新播放:" & GetFileNameFromPath(MusicCurrent), HintType.Finish) + Hint("重新播放:" & GetFileNameFromPath(MusicCurrent), HintType.Green) Else Dim Address As String = DequeueNextMusicAddress() If Address Is Nothing Then - Hint("没有可以播放的音乐!", HintType.Critical) + Hint("没有可以播放的音乐!", HintType.Red) Else MusicStartPlay(Address) - Hint("正在播放:" & GetFileNameFromPath(Address), HintType.Finish) + Hint("正在播放:" & GetFileNameFromPath(Address), HintType.Green) End If End If MusicRefreshUI() @@ -182,19 +182,19 @@ MusicListInit(True) If Not MusicAllList.Any() Then If MusicNAudio Is Nothing Then - If ShowHint Then Hint("未检测到可用的背景音乐!", HintType.Critical) + If ShowHint Then Hint("未检测到可用的背景音乐!", HintType.Red) Else MusicNAudio = Nothing - If ShowHint Then Hint("背景音乐已清除!", HintType.Finish) + If ShowHint Then Hint("背景音乐已清除!", HintType.Green) End If Else Dim Address As String = DequeueNextMusicAddress() If Address Is Nothing Then - If ShowHint Then Hint("没有可以播放的音乐!", HintType.Critical) + If ShowHint Then Hint("没有可以播放的音乐!", HintType.Red) Else Try MusicStartPlay(Address, IsFirstLoad) - If ShowHint Then Hint("背景音乐已刷新:" & GetFileNameFromPath(Address), HintType.Finish, False) + If ShowHint Then Hint("背景音乐已刷新:" & GetFileNameFromPath(Address), HintType.Green, False) Catch End Try End If @@ -303,18 +303,18 @@ Catch ex As Exception Log(ex, "播放音乐出现内部错误(" & MusicCurrent & ")", LogLevel.Developer) If TypeOf ex Is NAudio.MmException AndAlso ex.Message.Contains("AlreadyAllocated") Then - Hint("你的音频设备正被其他程序占用。请在关闭占用的程序后重启 PCL,才能恢复音乐播放功能!", HintType.Critical) + Hint("你的音频设备正被其他程序占用。请在关闭占用的程序后重启 PCL,才能恢复音乐播放功能!", HintType.Red) Thread.Sleep(1000000000) End If If TypeOf ex Is NAudio.MmException AndAlso (ex.Message.Contains("NoDriver") OrElse ex.Message.Contains("BadDeviceId")) Then - Hint("由于音频设备变更,音乐播放功能在重启 PCL 后才能恢复!", HintType.Critical) + Hint("由于音频设备变更,音乐播放功能在重启 PCL 后才能恢复!", HintType.Red) Thread.Sleep(1000000000) End If If ex.Message.Contains("Got a frame at sample rate") OrElse ex.Message.Contains("does not support changes to") Then - Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 不支持播放音频属性在中途发生变化的音乐", HintType.Critical) + Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 不支持播放音频属性在中途发生变化的音乐", HintType.Red) ElseIf Not (MusicCurrent.EndsWithF(".wav", True) OrElse MusicCurrent.EndsWithF(".mp3", True) OrElse MusicCurrent.EndsWithF(".flac", True)) OrElse ex.Message.Contains("0xC00D36C4") Then '#5096:不支持给定的 URL 的字节流类型。 (异常来自 HRESULT:0xC00D36C4) - Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 可能不支持此音乐格式,请将格式转换为 .wav、.mp3 或 .flac 后再试", HintType.Critical) + Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 可能不支持此音乐格式,请将格式转换为 .wav、.mp3 或 .flac 后再试", HintType.Red) Else Log(ex, "播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & ")", LogLevel.Hint) End If diff --git a/Plain Craft Launcher 2/Modules/ModSecret.vb b/Plain Craft Launcher 2/Modules/ModSecret.vb index 672ea2a..f4bd63c 100644 --- a/Plain Craft Launcher 2/Modules/ModSecret.vb +++ b/Plain Craft Launcher 2/Modules/ModSecret.vb @@ -6,6 +6,8 @@ Friend Module ModSecret #Region "杂项" + '标注 PCL 的不同分支,仅用于替换标记 + Public Const VersionBranchMain As String = "OpenSource" '在开源版的注册表与常规版的注册表隔离,以防数据冲突 Public Const RegFolder As String = "PCLDebug" '用于微软登录的 ClientId @@ -56,13 +58,13 @@ Friend Module ModSecret ''' ''' 获取设备标识码。 ''' - Friend Function SecretGetUniqueAddress() As String + Friend Function SecretGetIdentify() As String Return "0000-0000-0000-0000" End Function Friend Sub SecretLaunchJvmArgs(ByRef DataList As List(Of String)) Dim DataJvmCustom As String = Setup.Get("VersionAdvanceJvm", Version:=McVersionCurrent) - DataList.Insert(0, If(DataJvmCustom = "", Setup.Get("LaunchAdvanceJvm"), DataJvmCustom)) '可变 JVM 参数 + DataList.Insert(0, ArgumentReplace(If(DataJvmCustom = "", Setup.Get("LaunchAdvanceJvm"), DataJvmCustom))) '可变 JVM 参数 McLaunchLog("当前剩余内存:" & Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024 * 10) / 10 & "G") DataList.Add("-Xmn" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent) * 1024 * 0.15) & "m") DataList.Add("-Xmx" & Math.Floor(PageVersionSetup.GetRam(McVersionCurrent) * 1024) & "m") diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb index ac15996..5813168 100644 --- a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb +++ b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb @@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 ' 方法是按如下所示使用“*” - - + + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb index 9d2dae5..fda60b1 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb @@ -290,9 +290,9 @@ Sub(MyLoader) Select Case MyLoader.State Case LoadState.Failed - Hint(MyLoader.Name & "失败:" & MyLoader.Error.GetBrief(), HintType.Critical) + Hint(MyLoader.Name & "失败:" & MyLoader.Error.GetBrief(), HintType.Red) Case LoadState.Aborted - Hint(MyLoader.Name & "已取消!", HintType.Info) + Hint(MyLoader.Name & "已取消!", HintType.Blue) Case LoadState.Loading Return '不重新加载版本列表 End Select @@ -326,7 +326,7 @@ Dim DefaultFolder As String = Nothing If File.Type <> CompType.ModPack Then Dim SubFolder As String = Nothing - Select Case Project.Type + Select Case File.Type Case CompType.Mod : SubFolder = "mods\" Case CompType.ResourcePack : SubFolder = "resourcepacks\" Case CompType.Shader : SubFolder = "shaderpacks\" @@ -346,7 +346,7 @@ Function(Version) If Not Version.IsLoaded Then Version.Load() '只对 Mod 和数据包进行版本检测 - If Project.Type = CompType.Mod OrElse Project.Type = CompType.DataPack Then + If File.Type = CompType.Mod OrElse File.Type = CompType.DataPack Then If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Version.Version.McName) Then Return False End If @@ -359,8 +359,8 @@ Return False End Function '获取常规资源默认下载位置 - If CachedFolder.ContainsKey(Project.Type) Then - DefaultFolder = CachedFolder(Project.Type) + If CachedFolder.ContainsKey(File.Type) Then + DefaultFolder = CachedFolder(File.Type) Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}") ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then DefaultFolder = $"{McVersionCurrent.PathIndie}{SubFolder}" @@ -425,7 +425,7 @@ If Not Target.Contains("\") Then Return '构造步骤加载器 Dim LoaderName As String = Desc & "下载:" & IO.Path.GetFileNameWithoutExtension(Target) & " " - If Target <> DefaultFolder Then CachedFolder(Project.Type) = GetPathFromFullPath(Target) + If Target <> DefaultFolder Then CachedFolder(File.Type) = GetPathFromFullPath(Target) Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True}) '启动 diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb index a19a93c..6e1d801 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -32,7 +32,7 @@ Public Module ModDownloadLib For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For If Behaviour = NetPreDownloadBehaviour.ExitWhileExistsOrDownloading Then Return OngoingLoader - Hint("该版本正在下载中!", HintType.Critical) + Hint("该版本正在下载中!", HintType.Red) Return OngoingLoader Next @@ -223,7 +223,7 @@ Public Module ModDownloadLib '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"Minecraft {Id} 服务端下载" Then Continue For - Hint("该服务端正在下载中!", HintType.Critical) + Hint("该服务端正在下载中!", HintType.Red) Return Next @@ -241,7 +241,7 @@ Public Module ModDownloadLib File.Delete(VersionFolder & Id & ".json") If Not New DirectoryInfo(VersionFolder).GetFileSystemInfos.Any() Then Directory.Delete(VersionFolder) Task.Output = New List(Of NetFile) - Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Critical) + Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Red) Thread.Sleep(2000) '等玩家把上一个提示看完 Task.Abort() Return @@ -300,7 +300,7 @@ pause" '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For - Hint("该版本正在下载中!", HintType.Critical) + Hint("该版本正在下载中!", HintType.Red) Return Next @@ -401,7 +401,7 @@ pause" '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"OptiFine {DownloadInfo.NameDisplay} 下载" Then Continue For - Hint("该版本正在下载中!", HintType.Critical) + Hint("该版本正在下载中!", HintType.Red) Return Next @@ -832,7 +832,7 @@ Retry: '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For - Hint("该版本正在下载中!", HintType.Critical) + Hint("该版本正在下载中!", HintType.Red) Return Next @@ -866,7 +866,7 @@ Retry: '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For - Hint("该版本正在下载中!", HintType.Critical) + Hint("该版本正在下载中!", HintType.Red) Return Next @@ -1048,7 +1048,7 @@ Retry: '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"{DisplayName} 下载" Then Continue For - Hint("该版本正在下载中!", HintType.Critical) + Hint("该版本正在下载中!", HintType.Red) Return Next @@ -1803,7 +1803,7 @@ Retry: '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"Fabric {Version} 安装器下载" Then Continue For - Hint("该版本正在下载中!", HintType.Critical) + Hint("该版本正在下载中!", HintType.Red) Return Next @@ -1986,28 +1986,28 @@ Retry: ''' 在加载器状态改变后显示一条提示。 ''' 不会进行任何其他操作。 ''' - Public Sub LoaderStateChangedHintOnly(Loader) + Public Sub LoaderStateChangedHintOnly(Loader As LoaderBase) Select Case Loader.State Case LoadState.Finished - Hint(Loader.Name & "成功!", HintType.Finish) + Hint(Loader.Name & "成功!", HintType.Green) Case LoadState.Failed - Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical) + Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Red) Case LoadState.Aborted - Hint(Loader.Name & "已取消!", HintType.Info) + Hint(Loader.Name & "已取消!", HintType.Blue) End Select End Sub ''' ''' 安装加载器状态改变后进行提示和重载文件夹列表的方法。 ''' - Public Sub McInstallState(Loader) + Public Sub McInstallState(Loader As LoaderBase) Select Case Loader.State Case LoadState.Finished WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存) - Hint(Loader.Name & "成功!", HintType.Finish) + Hint(Loader.Name & "成功!", HintType.Green) Case LoadState.Failed - Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical) + Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Red) Case LoadState.Aborted - Hint(Loader.Name & "已取消!", HintType.Info) + Hint(Loader.Name & "已取消!", HintType.Blue) Case LoadState.Loading Return '不重新加载版本列表 End Select @@ -2108,7 +2108,7 @@ Retry: '重复版本检查 If File.Exists($"{VersionFolder}{Request.TargetVersionName}.json") Then - Hint("版本 " & Request.TargetVersionName & " 已经存在!", HintType.Critical) + Hint("版本 " & Request.TargetVersionName & " 已经存在!", HintType.Red) Throw New CancelledException End If diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml index 1144f09..83f5368 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml @@ -1,8 +1,8 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:local="clr-namespace:PCL" + RenderTransformOrigin="0,0.5" UseLayoutRounding="True" SnapsToDevicePixels="True" MinWidth="400" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="25"> @@ -23,17 +23,17 @@ - - + - - - + + + diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb index dd48c06..576c6dc 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb @@ -89,14 +89,14 @@ $"登录网页将自动开启,请在网页中输入 {UserCode}(已自动复制)。" & vbCrLf & vbCrLf & $"如果网络环境不佳,网页可能一直加载不出来,届时请使用加速器或 VPN 改善网络环境。" & vbCrLf & $"你也可以用其他设备打开 {Website} 并输入上述代码。" - Btn1.EventData = Website - Btn2.EventData = UserCode + CustomEventService.SetEventData(Btn1, Website) + CustomEventService.SetEventData(Btn2, UserCode) '启动工作线程 RunInNewThread(AddressOf WorkThread, "MyMsgLogin") End Sub Private Sub WorkThread() - Thread.Sleep(3000) + Thread.Sleep(2000) If MyConverter.IsExited Then Return OpenWebsite(Website) ClipboardSet(UserCode) @@ -118,7 +118,7 @@ '获取结果 Dim ResultJson As JObject = GetJson(Result) McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒") - Hint("网页登录成功!", HintType.Finish) + Hint("网页登录成功!", HintType.Green) Finished({ResultJson("access_token").ToString, ResultJson("refresh_token").ToString}) Return Catch ex As Exception diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb index 352c69b..2715ec8 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb @@ -47,7 +47,7 @@ Public Shared Sub Save(Loader As LoaderTask(Of EqualableList(Of String), String)) Dim Address = Loader.Output If Not Loader.State = LoadState.Finished Then - Hint("皮肤正在获取中,请稍候!", HintType.Critical) + Hint("皮肤正在获取中,请稍候!", HintType.Red) If Not Loader.State = LoadState.Loading Then Loader.Start() Return End If @@ -61,7 +61,7 @@ Else CopyFile(Address, FileAddress) End If - Hint("皮肤保存成功!", HintType.Finish) + Hint("皮肤保存成功!", HintType.Green) End If Catch ex As Exception Log(ex, "保存皮肤失败", LogLevel.Hint) @@ -143,7 +143,7 @@ Next If FrmLaunchLeft IsNot Nothing AndAlso HasLoaderRunning Then '由于 Abort 不是实时的,暂时不会释放文件,会导致删除报错,故只能取消执行 - Hint("有正在获取中的皮肤,请稍后再试!", HintType.Info) + Hint("有正在获取中的皮肤,请稍后再试!", HintType.Blue) Else RunInThread( Sub() @@ -161,7 +161,7 @@ For Each SkinLoader In If(sender IsNot Nothing, {sender}, {PageLaunchLeft.SkinLegacy, PageLaunchLeft.SkinMs}) SkinLoader.WaitForExit(IsForceRestart:=True) Next - Hint("已刷新头像!", HintType.Finish) + Hint("已刷新头像!", HintType.Green) Catch ex As Exception Log(ex, "刷新皮肤缓存失败", LogLevel.Msgbox) End Try @@ -184,7 +184,7 @@ SkinLoader.WaitForExit(IsForceRestart:=True) Next '完成提示 - Hint("更改皮肤成功!", HintType.Finish) + Hint("更改皮肤成功!", HintType.Green) Catch ex As Exception Log(ex, "更改正版皮肤后刷新皮肤失败", LogLevel.Feedback) End Try @@ -212,7 +212,7 @@ Return End If If McLoginMsLoader.State = LoadState.Failed Then - Hint("登录失败,无法更改披风!", HintType.Critical) + Hint("登录失败,无法更改披风!", HintType.Red) Return End If Hint("正在获取披风列表,请稍候……") @@ -225,7 +225,7 @@ Retry: '获取登录信息 If McLoginMsLoader.State <> LoadState.Finished Then McLoginMsLoader.WaitForExit(PageLoginMsSkin.GetLoginData()) If McLoginMsLoader.State <> LoadState.Finished Then - Hint("登录失败,无法更改披风!", HintType.Critical) + Hint("登录失败,无法更改披风!", HintType.Red) Return End If Dim AccessToken As String = McLoginMsLoader.Output.AccessToken @@ -265,10 +265,10 @@ Retry: ContentType:="application/json", Headers:={{"Authorization", "Bearer " & AccessToken}}) If Result.Contains("""errorMessage""") Then - Hint("更改披风失败:" & GetJson(Result)("errorMessage").ToString, HintType.Critical) + Hint("更改披风失败:" & GetJson(Result)("errorMessage").ToString, HintType.Red) Return Else - Hint("更改披风成功!", HintType.Finish) + Hint("更改披风成功!", HintType.Green) End If Catch ex As Exception Log(ex, "更改披风失败", LogLevel.Hint) diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb index 65b5086..260fed3 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb @@ -134,6 +134,8 @@ Public Class PageLaunchRight Private Sub OnlineLoaderSub(Task As LoaderTask(Of String, Integer)) Dim Address As String = Task.Input '#3721 中连续触发两次导致内容变化 Try + '替换自定义变量与设置 + Address = ArgumentReplace(Address, AddressOf WebUtility.HtmlEncode) '获取版本校验地址 Dim VersionAddress As String If Address.Contains(".xaml") Then @@ -146,29 +148,27 @@ Public Class PageLaunchRight End If '校验版本 Dim Version As String = "" - Dim NeedDownload As Boolean = True Try Version = NetRequestByClientRetry(VersionAddress) If Version.Length > 1000 Then Throw New Exception($"获取的主页版本过长({Version.Length} 字符)") Dim CurrentVersion As String = Setup.Get("CacheSavedPageVersion") If Version <> "" AndAlso CurrentVersion <> "" AndAlso Version = CurrentVersion Then Log($"[Page] 当前缓存的主页已为最新,当前版本:{Version},检查源:{VersionAddress}") - NeedDownload = False - Else - Log($"[Page] 需要下载联网主页,当前版本:{Version},检查源:{VersionAddress}") + Return End If + Log($"[Page] 需要下载联网主页,当前版本:{Version},检查源:{VersionAddress}") Catch exx As Exception Log(exx, $"联网获取主页版本失败", LogLevel.Developer) Log($"[Page] 无法检查联网主页版本,将直接下载,检查源:{VersionAddress}") End Try '实际下载 - If Not NeedDownload Then Return Dim FileContent As String = NetRequestByClientRetry(Address) Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}") Setup.Set("CacheSavedPageUrl", Address) Setup.Set("CacheSavedPageVersion", Version) WriteFile(PathTemp & "Cache\Custom.xaml", FileContent) - Refresh() '要求刷新 + '若内容变更则要求刷新 + If LoadedContentHash <> FileContent.GetHashCode() Then Refresh() Catch ex As Exception Log(ex, $"下载主页失败({Address})", If(ModeDebug, LogLevel.Msgbox, LogLevel.Hint)) End Try @@ -220,7 +220,7 @@ Public Class PageLaunchRight End If Dim LoadStartTime As Date = Date.Now '修改时应同时修改 PageOtherHelpDetail.Init - Content = HelpArgumentReplace(Content) + Content = ArgumentReplace(Content, AddressOf EscapeXML) Do While Content.Contains("xmlns") Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间 Loop diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb index 56970a4..a339893 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb @@ -50,7 +50,7 @@ If CType(McLoginLoader.Input, McLoginServer).ForceReselectProfile Then Hint("正在尝试更换,请稍候!") Else - Hint("正在登录中,请稍后再更换角色!", HintType.Critical) + Hint("正在登录中,请稍后再更换角色!", HintType.Red) End If Return End If diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb index 33cafbb..780d8d6 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb @@ -68,7 +68,7 @@ Public Class PageLoginLegacy End Sub Private Sub Skin_Click() Handles Skin.Click If (Setup.Get("UiHiddenPageSetup") OrElse Setup.Get("UiHiddenSetupLaunch")) AndAlso Not PageSetupUI.HiddenForceShow Then - Hint("启动设置已被禁用!", HintType.Critical) + Hint("启动设置已被禁用!", HintType.Red) Else FrmMain.PageChange(FormMain.PageType.Setup, FormMain.PageSubType.SetupLaunch) '切换到皮肤设置页面 End If diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml index cf470d4..8364edd 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml @@ -27,9 +27,9 @@ - - diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb index ae823a7..0d5ed36 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb @@ -90,7 +90,7 @@ Catch ex As Exception If ex.Message = "$$" Then ElseIf ex.Message.StartsWith("$") Then - Hint(ex.Message.TrimStart("$"), HintType.Critical) + Hint(ex.Message.TrimStart("$"), HintType.Red) ElseIf TypeOf ex Is Security.Authentication.AuthenticationException AndAlso ex.Message.ContainsF("SSL/TLS") Then Log(ex, "正版登录验证失败,请考虑在 [设置 → 其他] 中关闭 [在正版登录时验证 SSL 证书],然后再试。" & vbCrLf & vbCrLf & "原始错误信息:", LogLevel.Msgbox) Else diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb index 4b808f9..7199516 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb @@ -77,7 +77,7 @@ Return End If If McLoginLoader.State = LoadState.Failed Then - Hint("登录失败,无法更改皮肤!", HintType.Critical) + Hint("登录失败,无法更改皮肤!", HintType.Red) Return End If Dim SkinInfo As McSkinInfo = McSkinSelect() @@ -106,7 +106,7 @@ Retry: McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True) GoTo Retry ElseIf Result.Contains("""error""") Then - Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Critical) + Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Red) Return End If '获取新皮肤地址 @@ -122,7 +122,7 @@ Retry: Throw New Exception("未知错误(" & Result & ")") Catch ex As Exception If TypeOf ex Is OperationCanceledException Then - Hint("更改皮肤失败:连接皮肤服务器超时,请稍后再试,或使用 VPN 改善网络环境", HintType.Critical) + Hint("更改皮肤失败:连接皮肤服务器超时,请稍后再试,或使用 VPN 改善网络环境", HintType.Red) Else Log(ex, "更改皮肤失败", LogLevel.Hint) End If diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml index a64acc2..143d253 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml @@ -8,29 +8,10 @@ - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml index 9cf794e..fd4a3b0 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml @@ -83,7 +83,7 @@ - + @@ -229,7 +229,6 @@ - diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml index cc093ec..7a63bb3 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml @@ -44,7 +44,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb index f1e6312..b398ac3 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb @@ -121,26 +121,7 @@ Public Class PageLinkIoi ''' 获取当前的玩家名。 ''' Public Shared Function GetPlayerName() As String - '自动生成玩家名 - If AutogenPlayerName Is Nothing Then - If IsPlayerNameValid(McLoginName) Then - AutogenPlayerName = McLoginName() - Else - AutogenPlayerName = "玩家 " & CType(GetHash(If(UniqueAddress, "")) Mod 1048576, Integer).ToString("x5").ToUpper - End If - End If - '获取玩家自定义的名称 - Dim CustomName As String = Setup.Get("LinkName").ToString.Trim() - If CustomName <> "" Then - If IsPlayerNameValid(CustomName) Then - Return CustomName.Trim - Else - Hint("你所设置的玩家名存在异常,已被重置!", HintType.Critical) - Setup.Set("LinkName", "") - End If - End If - '使用自动生成的玩家名 - Return AutogenPlayerName + Return "" End Function Private Shared AutogenPlayerName As String = Nothing '并非由玩家自定义,而是自动生成的玩家名 ''' diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml index 0f8afa1..7e3bf33 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml @@ -29,8 +29,10 @@ - - + + @@ -51,11 +53,11 @@ - + - + - + @@ -105,288 +107,217 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -395,7 +326,7 @@ - + @@ -410,8 +341,8 @@ - - + + @@ -443,49 +374,49 @@ + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/00ll00/java_launch_wrapper" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/00ll00/java_launch_wrapper?tab=MIT-1-ov-file#readme" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.newtonsoft.com/json" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://opensource.org/licenses/MIT" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/naudio/NAudio" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/naudio/NAudio/blob/master/license.txt" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.ookii.org/Software/Dialogs/" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/caioproiete/ookii-dialogs-wpf/blob/master/LICENSE" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://iconmonstr.com/" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://iconmonstr.com/license/" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/imazen/libwebp-net" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://github.com/imazen/libwebp-net/blob/master/LICENSE.txt" /> + local:CustomEventService.EventType="打开网页" local:CustomEventService.EventData="https://www.iconfont.cn/" /> diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb index 13e7a3f..cbaec36 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb @@ -18,8 +18,8 @@ End Sub - Public Shared Sub CopyUniqueAddress() Handles BtnDonateCopy.Click - ClipboardSet(UniqueAddress) + Public Shared Sub CopyIdentify() Handles BtnDonateCopy.Click + ClipboardSet(Identify) End Sub Private Sub BtnDonateCodeInput_Click() Handles BtnDonateInput.Click DonateCodeInput() diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb index 2802cd4..6fd927f 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb @@ -71,7 +71,7 @@ Public Shared Sub OnItemClick(Entry As HelpEntry) Try If Entry.IsEvent Then - ModEvent.ProcessCustomEvents(Entry.EventType, Entry.EventData, Nothing) + CustomEvent.Raise(Entry.EventType, Entry.EventData) Else EnterHelpPage(Entry) End If diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb index dafcd2e..9d6b223 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb @@ -18,8 +18,10 @@ If Content = "" Then Throw New Exception("帮助 xaml 文件为空") Try '修改时应同时修改 PageLaunchRight.LoadContent - Content = HelpArgumentReplace(Content) - If Content.Contains("xmlns") Then Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间 + Content = ArgumentReplace(Content, AddressOf EscapeXML) + Do While Content.Contains("xmlns") + Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间 + Loop Content = "" & Content & "" Me.Entry = Entry PanCustom.Children.Clear() diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb index 7d8d927..4469cc2 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb @@ -150,14 +150,14 @@ Dim NewFolder As String = "" '检查是否有下载任务 If HasDownloadingTask() Then - Hint("在下载任务进行时,无法添加游戏文件夹!", HintType.Critical) + Hint("在下载任务进行时,无法添加游戏文件夹!", HintType.Red) Return End If Try '获取输入 NewFolder = SelectFolder() If NewFolder = "" Then Return - If NewFolder.Contains("!") OrElse NewFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Return + If NewFolder.Contains("!") OrElse NewFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Red) : Return '要求输入显示名称 Dim SplitedNames As String() = NewFolder.TrimEnd("\").Split("\") Dim DefaultName As String = If(SplitedNames.Last = ".minecraft", If(SplitedNames.Count >= 3, SplitedNames(SplitedNames.Count - 2), ""), SplitedNames.Last) @@ -182,7 +182,7 @@ '检查文件夹权限 If Not CheckPermission(FolderPath) Then If ShowHint Then - Hint("添加文件夹失败:PCL 没有访问该文件夹的权限!", HintType.Critical) + Hint("添加文件夹失败:PCL 没有访问该文件夹的权限!", HintType.Red) Return Else Throw New Exception("PCL 没有访问文件夹的权限:" & FolderPath) @@ -207,12 +207,12 @@ If Folder.Split(">")(1) = FolderPath Then IsAdded = True If Folder.Split(">")(0) = DisplayName Then - If ShowHint Then Hint("此文件夹已在列表中!", HintType.Info) + If ShowHint Then Hint("此文件夹已在列表中!", HintType.Blue) Return Else Folders(i) = DisplayName & ">" & FolderPath IsReplace = True - If ShowHint Then Hint("文件夹名称已更新为 " & DisplayName & " !", HintType.Finish) + If ShowHint Then Hint("文件夹名称已更新为 " & DisplayName & " !", HintType.Green) End If Exit For End If @@ -226,7 +226,7 @@ McFolderListLoader.Start(IsForceRestart:=True) '提示 If IsReplace Then Return - If ShowHint Then Hint("文件夹 " & DisplayName & " 已添加!", HintType.Finish) + If ShowHint Then Hint("文件夹 " & DisplayName & " 已添加!", HintType.Green) '检查是否为根目录整合包,自动关闭版本隔离 '1. 根目录中存在数个 Mod Dim ModFolder As New DirectoryInfo(FolderPath & "mods\") @@ -257,7 +257,7 @@ Public Sub Create_Click() '检查是否有下载任务 If HasDownloadingTask() Then - Hint("在下载任务进行时,无法创建游戏文件夹!", HintType.Critical) + Hint("在下载任务进行时,无法创建游戏文件夹!", HintType.Red) Return End If If Not Directory.Exists(Path & ".minecraft\") Then @@ -265,7 +265,7 @@ Directory.CreateDirectory(Path & ".minecraft\versions\") Setup.Set("LaunchFolderSelect", "$.minecraft\") McFolderLauncherProfilesJsonCreate(Path & ".minecraft\") - Hint("新建 .minecraft 文件夹成功!", HintType.Finish) + Hint("新建 .minecraft 文件夹成功!", HintType.Green) End If McFolderListLoader.Start(IsForceRestart:=True) End Sub @@ -307,7 +307,7 @@ Next '保存 Setup.Set("LaunchFolders", If(Not Folders.Any(), "", Join(Folders.ToArray, "|"))) - Hint(If(Folder.Type = McFolderType.Custom, "文件夹 " & Name & " 已从列表中移除!", "文件夹名称已复原!"), HintType.Finish) + Hint(If(Folder.Type = McFolderType.Custom, "文件夹 " & Name & " 已从列表中移除!", "文件夹名称已复原!"), HintType.Green) McFolderListLoader.Start(IsForceRestart:=True) Catch ex As Exception @@ -336,10 +336,10 @@ Sub() '删除文件夹 Try - Hint("正在" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Info) + Hint("正在" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Blue) DeleteDirectory(Folder.Path) If DeleteText = "清空" Then Directory.CreateDirectory(Folder.Path) - Hint("已" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Finish) + Hint("已" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Green) Catch ex As Exception Log(ex, DeleteText & "文件夹 " & Folder.Name & " 失败", LogLevel.Hint) Finally @@ -389,7 +389,7 @@ Next '如果没有添加过,则添加进去(因为修改了默认项的名称) If Not IsAdded Then Folders.Add(NewName & ">" & Folder.Path) - Hint("文件夹名称已更新为 " & NewName & " !", HintType.Finish) + Hint("文件夹名称已更新为 " & NewName & " !", HintType.Green) '保存 Setup.Set("LaunchFolders", Join(Folders.ToArray, "|")) McFolderListLoader.Start(IsForceRestart:=True) @@ -403,7 +403,7 @@ If Not e.RaiseByMouse OrElse Not sender.Checked Then Return '检查是否有下载任务 If HasDownloadingTask(True) Then - Hint("在下载任务进行时,无法切换游戏文件夹!", HintType.Critical) + Hint("在下载任务进行时,无法切换游戏文件夹!", HintType.Red) e.Handled = True Return End If diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb index e6e7d27..16bd2f8 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb @@ -114,22 +114,7 @@ Log(ex, "将版本列表转换显示时失败", LogLevel.Feedback) End Try End Sub - Public Shared Function McVersionListItem(Version As McVersion) As MyListItem - Dim NewItem As New MyListItem With {.Title = Version.Name, .Info = Version.Info, .Height = 42, .Tag = Version, .SnapsToDevicePixels = True, .Type = MyListItem.CheckType.Clickable} - Try - If Version.Logo.EndsWith("PCL\Logo.png") Then - NewItem.Logo = Version.Path & "PCL\Logo.png" '修复老版本中,存储的自定义 Logo 使用完整路径,导致移动后无法加载的 Bug - Else - NewItem.Logo = Version.Logo - End If - Catch ex As Exception - Log(ex, "加载版本图标失败", LogLevel.Hint) - NewItem.Logo = "pack://application:,,,/images/Blocks/RedstoneBlock.png" - End Try - NewItem.ContentHandler = AddressOf McVersionListContent - Return NewItem - End Function - Private Shared Sub McVersionListContent(sender As MyListItem, e As EventArgs) + Public Shared Sub McVersionListContent(sender As MyListItem, e As EventArgs) Dim Version As McVersion = sender.Tag '注册点击事件 AddHandler sender.Click, AddressOf Item_Click @@ -225,10 +210,10 @@ IniClearCache(Version.Path & "PCL\Setup.ini") If IsShiftPressed Then DeleteDirectory(Version.Path) - Hint("版本 " & Version.Name & " 已永久删除!", HintType.Finish) + Hint("版本 " & Version.Name & " 已永久删除!", HintType.Green) Else FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) - Hint("版本 " & Version.Name & " 已删除到回收站!", HintType.Finish) + Hint("版本 " & Version.Name & " 已删除!", HintType.Green) End If Case 2 Return diff --git a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb index 38dca35..b18c083 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb @@ -227,7 +227,7 @@ Me.Value = Value Me.Source = Source Me.Type = If(Value, New Object).GetType - Me.DefaultValueEncoded = If(Encoded, SecretEncrypt(Value, "PCL" & UniqueAddress), Value) + Me.DefaultValueEncoded = If(Encoded, SecretEncrypt(Value, "PCL" & Identify), Value) Catch ex As Exception Log(ex, "初始化 SetupEntry 失败", LogLevel.Feedback) '#5095 的 fallback End Try @@ -241,6 +241,14 @@ Public Sub [Set](Key As String, Value As Object, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing) [Set](Key, Value, SetupDict(Key), ForceReload, Version) End Sub + ''' + ''' 写入某个未经加密的设置项。 + ''' 若该设置项经过了加密,则会抛出异常。 + ''' + Public Sub SetSafe(Key As String, Value As Object, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing) + If SetupDict(Key).Encoded Then Throw New InvalidOperationException("禁止写入加密设置项:" & Key) + [Set](Key, Version, ForceReload, Version) + End Sub Private Sub [Set](Key As String, Value As Object, E As SetupEntry, ForceReload As Boolean, Version As McVersion) Try @@ -258,7 +266,7 @@ If E.Encoded Then Try If Value Is Nothing Then Value = "" - Value = SecretEncrypt(Value, "PCL" & UniqueAddress) + Value = SecretEncrypt(Value, "PCL" & Identify) Catch ex As Exception Log(ex, "加密设置失败:" & Key, LogLevel.Developer) End Try @@ -303,9 +311,17 @@ ''' 获取某个设置项的值。 ''' Public Function [Get](Key As String, Optional Version As McVersion = Nothing) - If Not SetupDict.ContainsKey(Key) Then Throw New KeyNotFoundException("未找到设置项:" & Key) With {.Source = Key} + If Not SetupDict.ContainsKey(Key) Then Throw New KeyNotFoundException("未找到设置项:" & Key) Return [Get](Key, SetupDict(Key), Version) End Function + ''' + ''' 获取某个未经加密的设置项的值。 + ''' 若该设置项经过了加密,则会抛出异常。 + ''' + Public Function GetSafe(Key As String, Optional Version As McVersion = Nothing) + If SetupDict(Key).Encoded Then Throw New InvalidOperationException("禁止读取加密设置项:" & Key) + Return [Get](Key, Version) + End Function Private Function [Get](Key As String, E As SetupEntry, Version As McVersion) '获取强制值 Dim Force As String = ForceValue(Key) @@ -383,7 +399,7 @@ SourceValue = E.DefaultValue Else Try - SourceValue = SecretDecrypt(SourceValue, "PCL" & UniqueAddress) + SourceValue = SecretDecrypt(SourceValue, "PCL" & Identify) Catch ex As Exception Log(ex, "解密设置失败:" & Key, LogLevel.Developer) SourceValue = E.DefaultValue @@ -601,8 +617,7 @@ FrmSetupUI.HintCustom.Visibility = Visibility.Visible FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible) FrmSetupUI.HintCustom.Text = $"从 PCL 文件夹下的 Custom.xaml 读取主页内容。{vbCrLf}你可以手动编辑该文件,向主页添加文本、图片、常用网站、快捷启动等功能。" - FrmSetupUI.HintCustom.EventType = "" - FrmSetupUI.HintCustom.EventData = "" + CustomEventService.SetEventType(FrmSetupUI.HintCustom, CustomEvent.EventType.None) Case 2 '联网 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Collapsed FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed @@ -610,8 +625,8 @@ FrmSetupUI.HintCustom.Visibility = Visibility.Visible FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible) FrmSetupUI.HintCustom.Text = $"从指定网址联网获取主页内容。服主也可以用于动态更新服务器公告。{vbCrLf}如果你制作了稳定运行的联网主页,可以点击这条提示投稿,若合格即可加入预设!" - FrmSetupUI.HintCustom.EventType = "打开网页" - FrmSetupUI.HintCustom.EventData = "https://github.com/Meloong-Git/PCL/discussions/2528" + CustomEventService.SetEventType(FrmSetupUI.HintCustom, CustomEvent.EventType.打开网页) + CustomEventService.SetEventData(FrmSetupUI.HintCustom, "https://github.com/Meloong-Git/PCL/discussions/2528") Case 3 '预设 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Visible FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml index f207a1d..8adb4fa 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml @@ -41,9 +41,9 @@ - + - + @@ -240,13 +240,13 @@ ToolTip="还原" ToolTipService.Placement="Right" ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1" Logo="M530 0c287 0 521 229 521 511s-233 511-521 511c-233 0-436-151-500-368a63 63 0 0 1 44-79 65 65 0 0 1 80 43c48 162 200 276 375 276 215 0 390-171 390-383s-174-383-390-383c-103 0-199 39-270 106l21-5a63 63 0 0 1 33 123l-157 42a65 65 0 0 1-90-42l-49-183a65 65 0 1 1 126-33l6 26A524 524 0 0 1 530 0z" LogoScale="0.9" /> - - + + ToolTip="在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。 你可以用替换标记来动态改变其内容或实现相对路径,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。例如: · "{verpath}test.exe" :运行版本文件夹下的 test.exe 程序 · "{java}java.exe" -jar "{verpath}test.jar" :用 Java 运行版本文件夹下的 test.jar · notepad "{verindie}options.txt" :使用记事本打开该版本的设置文件 涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。 执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" /> diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb index d194840..cf5f028 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb @@ -93,7 +93,7 @@ JavaSearchLoader.Start(IsForceRestart:=True) Log("[Setup] 已初始化启动设置") - Hint("已初始化启动设置!", HintType.Finish, False) + Hint("已初始化启动设置!", HintType.Green, False) Catch ex As Exception Log(ex, "初始化启动设置失败", LogLevel.Msgbox) End Try @@ -170,7 +170,7 @@ Try File.Delete(PathAppdata & "CustomSkin.png") RadioSkinType0.SetChecked(True, True) - Hint("离线皮肤已清空!", HintType.Finish) + Hint("离线皮肤已清空!", HintType.Green) Catch ex As Exception Log(ex, "清空离线皮肤失败", LogLevel.Msgbox) End Try @@ -468,7 +468,7 @@ PreFin: '手动选择 Private Sub BtnArgumentJavaSelect_Click(sender As Object, e As EventArgs) Handles BtnArgumentJavaSelect.Click If JavaSearchLoader.State = LoadState.Loading Then - Hint("正在搜索 Java,请稍候!", HintType.Critical) + Hint("正在搜索 Java,请稍候!", HintType.Red) Return End If '选择 Java @@ -489,7 +489,7 @@ PreFin: Setup.Set("LaunchArgumentJavaAll", JavaNewList.ToString(Newtonsoft.Json.Formatting.None)) '重新加载列表 JavaSearchLoader.Start(IsForceRestart:=True) - Hint("已将该 Java 加入 Java 列表!", HintType.Finish) + Hint("已将该 Java 加入 Java 列表!", HintType.Green) Catch ex As Exception Log(ex, "该 Java 存在异常,无法使用", LogLevel.Msgbox, "异常的 Java") Return @@ -498,7 +498,7 @@ PreFin: '自动查找 Private Sub BtnArgumentJavaSearch_Click(sender As Object, e As EventArgs) Handles BtnArgumentJavaSearch.Click If JavaSearchLoader.State = LoadState.Loading Then - Hint("正在搜索 Java,请稍候!", HintType.Critical) + Hint("正在搜索 Java,请稍候!", HintType.Red) Return End If RunInThread( @@ -506,9 +506,9 @@ PreFin: Hint("正在搜索 Java!") JavaSearchLoader.WaitForExit(IsForceRestart:=True) If Not JavaList.Any() Then - Hint("未找到可用的 Java!", HintType.Critical) + Hint("未找到可用的 Java!", HintType.Red) Else - Hint("已找到 " & JavaList.Count & " 个 Java,请检查下拉框查看列表!", HintType.Finish) + Hint("已找到 " & JavaList.Count & " 个 Java,请检查下拉框查看列表!", HintType.Green) End If End Sub) End Sub diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb index 3069a96..c4cc82e 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb @@ -28,7 +28,7 @@ Setup.Reset("LinkHiperCertWarn") Log("[Setup] 已初始化联机页设置") - Hint("已初始化联机页设置!", HintType.Finish, False) + Hint("已初始化联机页设置!", HintType.Green, False) Catch ex As Exception Log(ex, "初始化联机页设置失败", LogLevel.Msgbox) End Try diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb index 7ead8ef..766413a 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb @@ -85,7 +85,7 @@ Setup.Reset("SystemSystemTelemetry") Log("[Setup] 已初始化启动器页设置") - Hint("已初始化启动器页设置!", HintType.Finish, False) + Hint("已初始化启动器页设置!", HintType.Green, False) Catch ex As Exception Log(ex, "初始化启动器页设置失败", LogLevel.Msgbox) End Try @@ -136,7 +136,7 @@ '识别码/解锁码替代入口 Private Sub BtnSystemIdentify_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnSystemIdentify.Click - PageOtherAbout.CopyUniqueAddress() + PageOtherAbout.CopyIdentify() End Sub Private Sub BtnSystemUnlock_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnSystemUnlock.Click DonateCodeInput() diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml index b995ec8..06913f0 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml @@ -225,7 +225,8 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb index 92fe4e1..a992749 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb @@ -162,7 +162,7 @@ Setup.Reset("UiHiddenOtherTest") Log("[Setup] 已初始化个性化设置!") - Hint("已初始化个性化设置", HintType.Finish, False) + Hint("已初始化个性化设置", HintType.Green, False) Catch ex As Exception Log(ex, "初始化个性化设置失败", LogLevel.Msgbox) End Try @@ -215,7 +215,7 @@ If MyMsgBox("即将删除背景图片文件夹中的所有文件。" & vbCrLf & "此操作不可撤销,是否确定?", "警告",, "取消", IsWarn:=True) = 1 Then DeleteDirectory(Path & "PCL\Pictures") BackgroundRefresh(False, True) - Hint("背景图片已清空!", HintType.Finish) + Hint("背景图片已清空!", HintType.Green) End If End Sub ''' @@ -238,10 +238,10 @@ If Not Pic.Any() Then If Refresh Then If FrmMain.ImgBack.Visibility = Visibility.Collapsed Then - If IsHint Then Hint("未检测到可用背景图片!", HintType.Critical) + If IsHint Then Hint("未检测到可用背景图片!", HintType.Red) Else FrmMain.ImgBack.Visibility = Visibility.Collapsed - If IsHint Then Hint("背景图片已清除!", HintType.Finish) + If IsHint Then Hint("背景图片已清除!", HintType.Green) End If End If If Not IsNothing(FrmSetupUI) Then FrmSetupUI.BackgroundRefreshUI(False, 0) @@ -253,7 +253,7 @@ FrmMain.ImgBack.Background = New MyBitmap(Address) Setup.Load("UiBackgroundSuit", True) FrmMain.ImgBack.Visibility = Visibility.Visible - If IsHint Then Hint("背景图片已刷新:" & GetFileNameFromPath(Address), HintType.Finish, False) + If IsHint Then Hint("背景图片已刷新:" & GetFileNameFromPath(Address), HintType.Green, False) Catch ex As Exception If ex.Message.Contains("参数无效") Then Log("刷新背景图片失败,该图片文件可能并非标准格式。" & vbCrLf & @@ -338,7 +338,7 @@ Refresh: Try File.Delete(Path & "PCL\Logo.png") RadioLogoType1.SetChecked(True, True) - Hint("标题栏图片已清空!", HintType.Finish) + Hint("标题栏图片已清空!", HintType.Green) Catch ex As Exception Log(ex, "清空标题栏图片失败", LogLevel.Msgbox) End Try @@ -381,7 +381,7 @@ Refresh: '删除文件 Try DeleteDirectory(Path & "PCL\Musics") - Hint("背景音乐已删除!", HintType.Finish) + Hint("背景音乐已删除!", HintType.Green) Catch ex As Exception Log(ex, "删除背景音乐失败", LogLevel.Msgbox) End Try @@ -410,7 +410,7 @@ Refresh: If MyMsgBox("当前已存在布局文件,继续生成教学文件将会覆盖现有布局文件!", "覆盖确认", "继续", "取消", IsWarn:=True) = 2 Then Return End If WriteFile(Path & "PCL\Custom.xaml", GetResources("Custom")) - Hint("教学文件已生成!", HintType.Finish) + Hint("教学文件已生成!", HintType.Green) OpenExplorer(Path & "PCL\Custom.xaml") Catch ex As Exception Log(ex, "生成教学文件失败", LogLevel.Feedback) @@ -418,7 +418,7 @@ Refresh: End Sub Private Sub BtnCustomRefresh_Click() Handles BtnCustomRefresh.Click FrmLaunchRight.ForceRefresh() - Hint("已刷新主页!", HintType.Finish) + Hint("已刷新主页!", HintType.Green) End Sub Private Sub BtnCustomTutorial_Click(sender As Object, e As EventArgs) Handles BtnCustomTutorial.Click MyMsgBox("1. 点击 生成教学文件 按钮,这会在 PCL 文件夹下生成 Custom.xaml 布局文件。" & vbCrLf & diff --git a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb index 3796d42..785b928 100644 --- a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb @@ -96,7 +96,7 @@ Public Class PageSpeedLeft AddHandler Tb.MouseLeftButtonDown, Sub(sender As TextBlock, e As EventArgs) ClipboardSet(sender.Text, False) - Hint("已复制错误详情!", HintType.Finish) + Hint("已复制错误详情!", HintType.Green) End Sub Card.Children.Add(Tb) #End Region diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml index 3574f12..4eee204 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml @@ -262,7 +262,7 @@ + local:CustomEventService.EventType="打开帮助" /> diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb index cdae17b..0ad29e3 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb @@ -22,7 +22,8 @@ Public Class PageVersionExport Private Sub PageVersionExport_Loaded() Handles Me.Loaded AniControlEnabled += 1 If CurrentVersion <> PageVersionLeft.Version.Path Then RefreshAll() '切换到了另一个版本,重置页面 - BtnAdvancedHelp.EventData = If(VersionBranchName = "Release", "指南/整合包制作 - Public.json", "指南/整合包制作 - Snapshot.json") + CustomEventService.SetEventData(BtnAdvancedHelp, + If(VersionBranchName = "Release", "指南/整合包制作 - Public.json", "指南/整合包制作 - Snapshot.json")) AniControlEnabled -= 1 End Sub Public Sub RefreshAll() Implements IRefreshable.Refresh @@ -35,7 +36,7 @@ Public Class PageVersionExport TextExportVersion.HintText = "1.0.0" CheckAdvancedInclude.Checked = False CheckAdvancedModrinth.Checked = False - GetExportOption(CheckOptionsBasic).Description = PageVersionLeft.Version.GetDefaultDescription() + GetExportOption(CheckOptionsBasic).Description = PageVersionLeft.Version.GetVersionDescription() ResetConfigOverrides() ReloadAllSubOptions() RefreshAllOptionsUI() @@ -341,7 +342,7 @@ Public Class PageVersionExport ConfigLines.AddRange(GetExtraFileLines()) '结束 WriteFile(ConfigPath, ConfigLines.Join(vbCrLf)) - Hint("已保存配置文件:" & ConfigPath, HintType.Finish) + Hint("已保存配置文件:" & ConfigPath, HintType.Green) OpenExplorer(ConfigPath) Catch ex As Exception Log(ex, "保存配置失败", LogLevel.Msgbox) @@ -378,7 +379,7 @@ Public Class PageVersionExport ExtraFiles = Nothing End If '结束 - Hint("已读取配置文件:" & ConfigPath, HintType.Finish) + Hint("已读取配置文件:" & ConfigPath, HintType.Green) Catch ex As Exception Log(ex, "读取配置失败", LogLevel.Msgbox) End Try @@ -517,13 +518,13 @@ Public Class PageVersionExport If Directory.Exists(Line) Then CopyDirectory(Line, BaseFolder & GetFolderNameFromPath(Line) & "\") Else - Hint($"未找到配置文件中指定的文件夹:{Line}", HintType.Critical) + Hint($"未找到配置文件中指定的文件夹:{Line}", HintType.Red) End If Else If File.Exists(Line) Then CopyFile(Line, BaseFolder & GetFileNameFromPath(Line)) Else - Hint($"未找到配置文件中指定的单个文件:{Line}", HintType.Critical) + Hint($"未找到配置文件中指定的单个文件:{Line}", HintType.Red) End If End If Next diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb index 73c280c..a844d23 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb @@ -306,7 +306,7 @@ If Result.Any Then MyMsgBox(Join(Result, vbCrLf & vbCrLf), "Mod 检查结果") Else - Hint("Mod 检查完成,未发现任何问题!", HintType.Finish) + Hint("Mod 检查完成,未发现任何问题!", HintType.Green) End If Catch ex As Exception Log(ex, "进行 Mod 检查时出错", LogLevel.Feedback) @@ -340,7 +340,7 @@ Log("[System] 文件为 jar/litemod 格式,尝试作为 Mod 安装") '检查回收站:回收站中的文件有错误的文件名 If FilePathList.First.Contains(":\$RECYCLE.BIN\") Then - Hint("请先将文件从回收站还原,再尝试安装!", HintType.Critical) + Hint("请先将文件从回收站还原,再尝试安装!", HintType.Red) Return True End If '获取并检查目标版本 @@ -362,9 +362,9 @@ Install: CopyFile(ModFile, TargetVersion.PathIndie & "mods\" & NewFileName) Next If FilePathList.Count = 1 Then - Hint($"已安装 {GetFileNameFromPath(FilePathList.First).Replace(".disabled", "").Replace(".old", "")}!", HintType.Finish) + Hint($"已安装 {GetFileNameFromPath(FilePathList.First).Replace(".disabled", "").Replace(".old", "")}!", HintType.Green) Else - Hint($"已安装 {FilePathList.Count} 个 Mod!", HintType.Finish) + Hint($"已安装 {FilePathList.Count} 个 Mod!", HintType.Green) End If '刷新列表 If FrmMain.PageCurrent = FormMain.PageType.VersionSetup AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.VersionMod Then @@ -570,7 +570,7 @@ Install: If IsSuccessful Then RefreshBars() Else - Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical) + Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Red) ReloadModList(True) End If LoaderRun(LoaderFolderRunType.UpdateOnly) @@ -643,14 +643,14 @@ Install: Try For Each Entry As McMod In ModList If File.Exists(Entry.Path) Then - My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) + My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) Else Log($"[Mod] 未找到更新前的 Mod 文件,跳过对它的删除:{Entry.Path}", LogLevel.Debug) End If Next For Each Entry As KeyValuePair(Of String, String) In FileCopyList If File.Exists(Entry.Value) Then - My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) + My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) Log($"[Mod] 更新后的 Mod 文件已存在,将会把它放入回收站:{Entry.Value}", LogLevel.Debug) End If If Directory.Exists(GetPathFromFullPath(Entry.Value)) Then @@ -676,14 +676,14 @@ Install: Case 0 '一般是由于 Mod 文件被占用,然后玩家主动取消 Log($"[Mod] 没有 Mod 被成功更新") Case 1 - Hint($"已成功更新 {FinishedFileNames.Single}!", HintType.Finish) + Hint($"已成功更新 {FinishedFileNames.Single}!", HintType.Green) Case Else - Hint($"已成功更新 {FinishedFileNames.Count} 个 Mod!", HintType.Finish) + Hint($"已成功更新 {FinishedFileNames.Count} 个 Mod!", HintType.Green) End Select Case LoadState.Failed - Hint("Mod 更新失败:" & Loader.Error.GetBrief(), HintType.Critical) + Hint("Mod 更新失败:" & Loader.Error.GetBrief(), HintType.Red) Case LoadState.Aborted - Hint("Mod 更新已中止!", HintType.Info) + Hint("Mod 更新已中止!", HintType.Blue) Case Else Return End Select @@ -739,7 +739,7 @@ Install: If IsShiftPressed Then File.Delete(ModEntity.Path) Else - My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) + My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) End If Catch ex As OperationCanceledException Log(ex, "删除 Mod 被主动取消") @@ -760,7 +760,7 @@ Install: Next RefreshBars() If Not IsSuccessful Then - Hint("由于文件被占用,Mod 删除失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical) + Hint("由于文件被占用,Mod 删除失败,请尝试关闭正在运行的游戏后再试!", HintType.Red) ReloadModList(True) ElseIf PanList.Children.Count = 0 Then ReloadModList(True) '删除了全部文件 @@ -771,15 +771,15 @@ Install: If Not IsSuccessful Then Return If IsShiftPressed Then If ModList.Count = 1 Then - Hint($"已彻底删除 {ModList.Single.FileName}!", HintType.Finish) + Hint($"已彻底删除 {ModList.Single.FileName}!", HintType.Green) Else - Hint($"已彻底删除 {ModList.Count} 个文件!", HintType.Finish) + Hint($"已彻底删除 {ModList.Count} 个文件!", HintType.Green) End If Else If ModList.Count = 1 Then - Hint($"已将 {ModList.Single.FileName} 删除到回收站!", HintType.Finish) + Hint($"已将 {ModList.Single.FileName} 删除到回收站!", HintType.Green) Else - Hint($"已将 {ModList.Count} 个文件删除到回收站!", HintType.Finish) + Hint($"已将 {ModList.Count} 个文件删除到回收站!", HintType.Green) End If End If Catch ex As OperationCanceledException diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb index 294b486..3035e9f 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb @@ -30,7 +30,7 @@ BtnFolderMods.Visibility = If(PageVersionLeft.Version.Modable, Visibility.Visible, Visibility.Collapsed) '刷新版本显示 PanDisplayItem.Children.Clear() - ItemVersion = PageSelectRight.McVersionListItem(PageVersionLeft.Version) + ItemVersion = PageVersionLeft.Version.ToListItem ItemVersion.IsHitTestVisible = False PanDisplayItem.Children.Add(ItemVersion) FrmMain.PageNameRefresh() @@ -169,7 +169,7 @@ Log(ex, "重命名版本 json 失败") End Try '刷新与提示 - Hint("重命名成功!", HintType.Finish) + Hint("重命名成功!", HintType.Green) PageVersionLeft.Version = New McVersion(NewName).Load() If Not IsNothing(McVersionCurrent) AndAlso McVersionCurrent.Equals(PageVersionLeft.Version) Then WriteIni(PathMcFolder & "PCL.ini", "Version", NewName) Reload() @@ -263,7 +263,7 @@ If SavePath = "" Then Return '检查中断(等玩家选完弹窗指不定任务就结束了呢……) If McLaunchLoader.State = LoadState.Loading Then - Hint("请在当前启动任务结束后再试!", HintType.Critical) + Hint("请在当前启动任务结束后再试!", HintType.Red) Return End If '生成脚本 @@ -284,13 +284,13 @@ Try '忽略文件检查提示 If ShouldIgnoreFileCheck(PageVersionLeft.Version) Then - Hint("请先关闭 [版本设置 → 设置 → 高级启动选项 → 关闭文件校验],然后再尝试补全文件!", HintType.Info) + Hint("请先关闭 [版本设置 → 设置 → 高级启动选项 → 关闭文件校验],然后再尝试补全文件!", HintType.Blue) Return End If '重复任务检查 For Each OngoingLoader In LoaderTaskbar If OngoingLoader.Name <> PageVersionLeft.Version.Name & " 文件补全" Then Continue For - Hint("正在处理中,请稍候!", HintType.Critical) + Hint("正在处理中,请稍候!", HintType.Red) Return Next '启动 @@ -299,11 +299,11 @@ Sub() Select Case Loader.State Case LoadState.Finished - Hint(Loader.Name & "成功!", HintType.Finish) + Hint(Loader.Name & "成功!", HintType.Green) Case LoadState.Failed - Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical) + Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Red) Case LoadState.Aborted - Hint(Loader.Name & "已取消!", HintType.Info) + Hint(Loader.Name & "已取消!", HintType.Blue) End Select End Sub Loader.Start(PageVersionLeft.Version.Name) @@ -329,10 +329,10 @@ IniClearCache(PageVersionLeft.Version.Path & "PCL\Setup.ini") If IsShiftPressed Then DeleteDirectory(PageVersionLeft.Version.Path) - Hint("版本 " & PageVersionLeft.Version.Name & " 已永久删除!", HintType.Finish) + Hint("版本 " & PageVersionLeft.Version.Name & " 已永久删除!", HintType.Green) Else - FileIO.FileSystem.DeleteDirectory(PageVersionLeft.Version.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) - Hint("版本 " & PageVersionLeft.Version.Name & " 已删除到回收站!", HintType.Finish) + FileIO.FileSystem.DeleteDirectory(PageVersionLeft.Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) + Hint("版本 " & PageVersionLeft.Version.Name & " 已删除到回收站!", HintType.Green) End If Case 2 Return diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml index b3a872c..7c1e11d 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml @@ -34,9 +34,9 @@ ToolTip="与其余关闭隔离的版本共享存档、Mod 等文件。 若存在多个安装了 Mod 的版本,可能会由于 Mod 冲突而导致崩溃。" ToolTipService.HorizontalOffset="100"/> - + - + @@ -209,13 +209,13 @@ - - + + ToolTip="该项不会覆盖全局设置:启动时会先执行全局设置的命令,再执行版本设置的命令。 在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。 你可以用替换标记来动态改变其内容或实现相对路径,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。例如: · "{verpath}test.exe" :运行版本文件夹下的 test.exe 程序 · "{java}java.exe" -jar "{verpath}test.jar" :用 Java 运行版本文件夹下的 test.jar · notepad "{verindie}options.txt" :使用记事本打开该版本的设置文件 涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。 执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" /> diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb index 414d5f0..b22e277 100644 --- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb @@ -98,7 +98,7 @@ JavaSearchLoader.Start(IsForceRestart:=True) Log("[Setup] 已初始化版本独立设置") - Hint("已初始化版本独立设置!", HintType.Finish, False) + Hint("已初始化版本独立设置!", HintType.Green, False) Catch ex As Exception Log(ex, "初始化版本独立设置失败", LogLevel.Msgbox) End Try diff --git a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj index b5eafff..e40bfb6 100644 --- a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj +++ b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj @@ -206,8 +206,8 @@ Designer - - + + diff --git a/Plain Craft Launcher 2/Resources/Custom.xaml b/Plain Craft Launcher 2/Resources/Custom.xaml index 07ed9a2..65c3675 100644 --- a/Plain Craft Launcher 2/Resources/Custom.xaml +++ b/Plain Craft Launcher 2/Resources/Custom.xaml @@ -53,114 +53,37 @@ - - - + + - - - - - - + + + + - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -198,7 +121,7 @@ Logo="pack://application:,,,/images/Blocks/Grass.png" EventType="打开帮助" EventData="帮助/自定义帮助.json" Type="Clickable" /> + Text="EventType、EventData 与按钮几乎一致。唯一不同的是,列表项不支持联网加载帮助文件。" /> @@ -261,17 +184,6 @@ - - - - - - - - wr$(CZQHi-2Gd|^`3&j>(3y*%;!go$OqbO0?S9ElX) zoF6o{jI?!(OT`ZTyEVM|k!u^Vrp=iC&hAfE1#vRv#bW9X<%WDScEJWZXC{!XlNf7y z2PktuuV5aHaw#HXIEc#op*?>3;)JQh(|@T9awYVS1*!WC+qhSz zqy#RZi5@Qz;MF^76DX9;egH!nOY&&NNOM!0URJyO4y^oB{6)a9qZmvh`h_S|@1>bf zb4X^?(kQeVZ4B%i!F9d&4;DCIs+aM|=eCWg{<|ke>1n{GL;tzu6SL<}>Cij2U3AI6 zD?e|vqpz3&xm`&{5{f6C`aO!rq$bD{XRR_j&RkqSXk3IzfBz{ycR&#h`i7{*tDAgR z*oY%#bIHl0rJ*vOtg7$SO=?JUsFt}kSTRK&=yU1qV8NGJ6L%#lOc8;_yV+4s@P}kl z8-cKZ=^KN4jT}@12;2|bWhz}#w*?cM&n^_RkF^liOvi{O;tG6im2tl3P~8!D7)Zkw zL5FdxQxn}D?0q+TEdaCW^+U*2G=Y<0c7vxoTbE**qYEK)aCMT4M@RZ}Saww;bXLx7 z{zg_Vbo9F8%)xp6Be)0DP`8C(Xz$^+dL?8_&mq7Y^Q3~e-+svq%f(W_=bwT~m4t!I z@I|`~S?-Ejp`?c#j$WZT&bEP*lBHn+wS)wA=w_+oTy|p~1>db3)FS$wH zMJ8`3s^@d)%9OIvKS%UM3OdPGYP+plPjZDW-n_{)9J2e-8>g`LwV*UGzyC|SX9l5U zQp8IeqZ88%129?FGQF~695|(Z^r#J zgN}_i;%m}ZdpdRRp`N{^;QE?$0E3|ARZAW3;bVu;;ji&^jtDxHn|TDhssuw~-sXoa zv=?h`t)A&)j*s{^WjBw)BFRTJJ^T-fDg|;L;@9~MHvoIO-+E$4$;PlmF>9r9(#*gIusqE4?~TMvjL zHL|)oQavR>t~}9!4Qt*hGqlPOv#ManJZ+S*OXWd?j9?z6HhF56A*<%P}=ldq$q23Osns2s70B(swcc0j7%MUFq8vm(#zm!8yE?wiA3I{2sZ#` z{vgPjsi7}zyptwc9kAQVC?tJ86MMf^aPrXxOtVE6$LavcEp^;i#lZ1a&J5_6%BB`TEN+TXo#mb#`;w0;>kv*MTqsETO zuUC^a5T|0`4 z=^hH4RbWCvW`3>0dHVzB@Eh_1AzlzTEI{Q(1#}^>LHMPy1=s16)giO^9gsBU4lN4g z6W9=3NGnL~6Vd59%c9|d1xU)Fkt{4j0saD(tR2-L(Ac{I|1y)eLvQ_ly6=~L65c9$ z-D5&_1VzE9VSr;vRTvew3`sA9B1tb)UeK4#V`!7BBEo8p^a7wSdbyP;%)&;-4DE?& zZ}&Mmzd#zOMBEh5?y)~OBMNWjJGdr)pCUk&OV6yL116mnB zDBu6&Q3S|!VHB7{f;;9NyDI_Mk_&vJjh8;HhJeF!GklI#$CM&y1;#P#^-v=(*11`6 zI{wmZ8sE2)A}mmoI1!h1#kHNJ{J`q@NHu~7r=J^M9GwCdOk`pG`X z=4bD(I-#XMQ@?C8xz+=2w>!;n{OY4zevs;10Y-?S>f`u^CRn5}{TuC_QEqb+P`p-Q z_l*yG;mdRQXVV53wEV9^RVR?wz)a$PzuUJ6rmGpaVxjbblM6Gk@fc0Z0Ajx#Vumao zToIB-b6a4sxprE~l4+I%pPzmnZgoQ+L`0&6tX8GRk3dJKuXl4`f(3Ol=8(pX5hb3# z{;gD`Z@t+ZC2%u|ll+^M1qe>rf-=&UwR$w~q*Jn*;);Zp@9qP8mO=Hd(#KQJ)#Y?9 zj@fU4s#9?~+oLF;cKMAGfNzheGXf`8aq*#;Fds@UjX8`NjW!T4f}Mv?56s*StP_)( zTr&ub@i%811sYy!Dhs+V@9E$p9A=y%P4$E|3f=mrz*j@S?d>UNZTtnjPMdxI%>K36 zm@O^4@hy6w0F>*%ZuAGGRfT&r?PM(+<`qJc$HtBy)97sFl5mr$lmbOx1`-H$$ix0KzZ1!CltU~~z8kZPA15ciR375Q z-gOX}9*K-jL2!rNfBFk7GekN{V7j`X_O9o=LMb?+y!P(MNL&08pNEAsQ1!vi|4%B|$Rg*vfGJ z=bz7)zo@(wn39#X7}KzoG_Qw#XEjVlQyU|0WmdaS@-prRf#_@d(zcicmcYI+Drb1R;y+9F;FZ#*)yiw&*}WQ4_Jjdj z9aisTbEe8G9+e1n(eHVjxIe;5P8Qyf01HFV9Dffiu@}AowjA_%Eraz@{SGk99q^DK zyoQ~Vg3n&VG(*yq0kC&nzeEk1S*#<>RT&WNvWO24G0eeKjY=9hKqi$D zg{)j&zPc9-`MkrmO}zFYF$UBnaTc0jcU>f~_E!StS5n-3sxF(gCrL%M=qBhGiMJV4 z8q!-*&)-7X(?jr-J=mBk((qIK1PFYg`iVd`$NWkjhgwl?Lic?d z){$wlY7Uq$)M40-G12dxbSzS4z?en;hoWEJNvWrCxJke%UcrY46em?U1`uh3Qw=T8 zife&P>+1%>j;}?G_5$dqKsXVt$un=VgHEmHF)KY3^)PGCM;9+V@JAEo3lhWQgJIaS z50|ZKrBN#nT71Mrdi-a_2hr=>AonlTxgH&Ti`i>;j&NuFNcDzvf;9vUARdZ?iO4|) z=bS}KKo7xv-S==U=Rnx|feLX0Bx>(aV>~3bZ>Y}t6L#|LChpYpRgN586*i}x7(3e< z>2)EX=M&5>k(_hz!7~`|J$RSACD72i84cW#>rX<`!nYc0Tjxa96 zr!nItif_q@ro?Nhv@W_Az~qL7GYjR@g2O%g!t{lqsE4UH#qdOxlgPr6wIX`8dF* z#E6#48wyduEw4sj;y$~~@>Wen<%99sLRgVr)LIpAU0wNjZ(3UdlUhq5Jx1`ZSapsZ z{E{h&B__l2y6&l}_Z;^|-0Ft#rem7bb!kh)+O@<-F6xc1hyIHqrtgLL1%2Y@qVCcb&kZXesC3P^-RU1kR60KprF)Xokeh%rK zRbd1~ODBoK8`5bsn$2W!o_gdtEf-aK z_5Y*r3?Q6-;^0#)vIG2JiRA3`gT)be!#kICFYXWF`=9T>u{Wn&Abk-zgjzrQIf@QK zgbge6AQxrNALD^#bd+A$169Pw3&)v$KLzK%jMk$TO$X~(InHG03DrLSlI9*v0MoBr zJ0qY~n36?Ol*n7Qw)6RHr1gJ(awLi+n`nE610GbY4(is`hudA6MA2gkcSo;FE!8-hT{*=0+ORx$D#N=o#N1yy8v~vXNei>3Es(OaLMV7G7AjeI|b8PW%`rJP2?nwxD z19n_Zk%0fW98WyNYt$STJ3s0pn*9pbVki);2D2&?%s~?PtHliI;8tW?d#& zMhUz?NzkAO`6&Y?5p|ZPu=u#29Oc4PIkwa1<}w|NyNI+@IE-OJC)oGBn8EoAK3GvB zOfXDk*gY6o3#Zb)-F_1|J{dvp6{}UNW{dt^X<1_qk^cd z=3u|I{nYXbz<_4|8&H9QeHahnz&VL|l;Q<)sP4UE;yJfQ@lt7?4E`I*mb9}YW*LkpCCH+8e>Cs7C<}$G>UY%=xq!uEAbv}$Efh3 zB*NY+lv%6fW+B>W9RFgmC8!bb2i`>XJIb4Ydyq)LFv)P^gcSYm1)$u=XB~k#X+zwP z`IghA_L}$m+kIx(2F5+g0F5D|*N2+&%{6y=*U1fmIS>Sb(}jLPftituGT;*a%&=s@ zZ@}$9#z;_1aDVJ+fui_mL-#_nt~0?9Zu9M5piEl+*}u_OP=Uch7H!~9+^)fD$s+Q( ziNn;AY{|Aqr!}5Qr#4WqE07-tT%1`??@9mnHp&RS)a`H_mw`_I)QZx3%b_9@`oaw) zPCRn{2pX&~-0w4zuxL{fjwcNG1YCUY#H24d(r~(LsDG=1&-><$j+-TaJFdw7M!l9! zjd%C=Om^E$mI8qPFWyyPApbAk-PVglyu$zijnF1eU|MLmO(0yY7!@^q06oIUN zb#Svb(&%b+f|YifE5F2}i?B56&C?Mf)~JkQ4TLNf%iP zDSm@_czFIg)JuT)D{IJCsLJWd=00ZMX5Hk}F#O}9e4dAJIs8yTye>Yu&yIbT*F zVHg2coI>~}J!Y7z=8qz4E)NjPVHx-7U{Y;KgMWU^xgB?cr8+~8wUy;q>2;9{<1@=2 z={HUn{$;y0j~8<5CRS=Kp_yYz!YD>Ko*@)^P!x9K3b#WyYbczY2(%Jldz2}J?5@G* z!)!F4Tv8^wIJcg>vGEk8P&0;USJT`=(7^$;n3J4P%lL?YGVs4-rPGEZ1sSp670v{5 zK31>IX+_E_Ik~;KVyOomznitQ=Rz{e($q}Zn@{-?(-l0hERQTTYii2cs}w^_D+&D~ z9SO1_T%f5~Kcs79sAE@>zVZ^J4pr7Hl~wifME%A2gGyeiAkkFC?ILc9IIk8ReG~vS zF$T4kx3-qW)v$->MTl2mQmP9s?e9qV{Xj*3&n@34Hb|1zlP(uKs}wvV(M|JXw{U@Z zkRh4HN3*unBNDPpXy@+`eRRaCZRBZNPE{o^1Z&K~kknr;sJRtV$#HLz#fr@)E&}L( z8&x@@_d9t@t7WK$8$Mn*pFp0}uMGfTn7Z$~+}@QqF75MmSg!D>k^KJC4T>A_mke`* z0_qtC9nzpGmMxxe!I;_M4}Hy3o_^Hl2E^;uYQ!Chm5E*%1A{r{lC0)(b^?U@yx?V- zlyz~onSrcAuu>&Kp4CkCCI$4ZGXMH2Z7I_6#BD#@-a4D-hm?&mkCPTb_6s1eWiE z^=5?64|gBzE@M@);Q50&03%%_+as7PV^q`cX6_|Ir=oBYoj{<1(K2>rSrbCt$e2B- zo(gFTC80hlLc~s(;SC5|y%HwX*v}0yD?u5RZ^~x+YDrLQ5K+z=nryl&x0(u<8DrK$ z>Zdc&R2aVr#I|T+mEE)I9Hs)LE2{6_?{xS(?Z`{j=K&Uj=D|b>Zccuc2sY0UPCO|N zob&prA?J6@Wd19=2z(-2dbeBF#NZp7ARu$|BVCXy@uVDImJLw$KA9JKEGuvNE?-hS z*UOgSq`yLQS3t!~Dar3+CW|5tW4!&DVxr79VxFo^-Ee@Q_mHJ%0N9cSwlK~g$UM9LzK`RBkax^rMTB6 zKRX7mUCNPn-2waw{06{+ATuUcq@y|^jc;*x(Ir7iOL@3<=~2zJ##kI1)-ZJ zm%AM5H%a6j;fmUV)LX|N^2)!c0X_xZu-FAXUF_SjdnLcFm$3trIf51;`(Pham8) zp2vR?#RH!2LQUs+0su);%L1<`hti-lzz`SjHU{026d zTBJI>i^moa8&Lqxz_!0KF+Boi7Z8)) zB;w?z{wvXvci8;?8eE*S|5i~PZlE?~LebK=&JnN)3W8>~LQXd%yJCqI<-fp2oy!p6 zS%5O*>iDt*cOi-?;zMUma~K}cc;D0=^tty7ss>PoI0P1MJSVu`%qBY z(FKDQf+Ha~41~9IMj-V)iw(I?IqZpCKL*Gx70-x_A*FIg}C%iY=wLP$E!)&zjXILn-g*DfiEcq0C8V+_9dVb)i!WXA01*GRqiP9wLBR4< z0vmHMgPhmcaXDKG514a|Kp?NR{}%9P0!e!}`->&VvJeSYUkz1M{aStQofO=MNuy%! zD*LXwTFbSaq2Ee!G*LtPpn~te$iGg5Sga2Sq76|bs}y-;&>uq)>Mngf(ICRkUSowl zMHG#RF6PPEE7IU-H-#5<^t|Kp_ki*s`uLnFY!E{|KwGsmJy*XehY)@@Zow5D@DcGz zh<>aQ{y4Vv+chuDVB9ij+1Sm@flNPCV2?Azrl5d-<32LK{vf7rBG zC1l(RVUad`5UE`AwZR<%%Y2!gMRG(JPpN6jlbgv%^78P?M1WHpVzkDo#SpX&n1Ta* zZk9S^OUZ#R%G6WZ`IVa{3VQ6&11(+w8T;8YQx!uWG%E!Y?;2!-f1G_=;3mpUM>L&U zp{Sx(LcrC|0!@2pS-fjcE}%``YI84F5f+?Vn(zqG>znlu#d`;6){`5Uf$lC}pa1FM zXa#O!-V|Xi%+V-|TYO7{|(4y4;lTwr!w9NDg6I(=KD!|4Ws`&0ztUM9~$&TJaPuWvZ{{UrYdI7Owl*w>?*126$fdU zGxoVAbuv6WDgAu7Pga}D!5F*AwaSEk-2Ji*wbuLG2Jm)83L-R`p!i;I6K9Lla&O(L zs{|Y!BfHFX*4V?l=hdsn&MRl>XQO=@u~ew5M?#ehJHmyI@(gRFD8`^M8q42=@G#9B zEbJ?QS>e6?2hE8nzR7)E)%oD}d8RglYGhQo(G=zx0(+VL+eB%ub7vgrd89zCMTbF% zm;O7?l(v$x^_#j!5h^A|&N-gy!}`90j2amM&YX&ju%+7WHtPbJr96{LRzY?<4|DHa z8SNUPnRx}`xkhnZ!8*b#2Aa9Lc>gkwKJh)km5*A#nJpBB%g`Ef6tcua3G%+x6tU}q z{g@a2yi8pgW*zmKH``Yz%9a66`(^-jyNr+gQ7byorQxfzQG>`T@q8|BLbKY}tMIFXb;Yxsr$vq&5j*+(Wa9Sapp8w*_NU3Bx+<~obb7&VW+z4ourGbX zAr_0hO!jNEvxIID0qyuF@7hYI9~>n;0%HEvvcpj>utZYn`jkPLOlxeg3e^W?(lqWr z&7$6$^Jj4h2AJ9i54R3lUVZ#TpN#>K1u6ARBJn^P{!lYv@6`(jOt+z}$UDs+rBu7P zlB+LA1<;Qi=$)4y^|nCgQgP)tmWqyUz*lV6woH1^yKF3OqdwY`jZ zP~5}ADvR_{NXec#ke(*fP(8XX-V6UeE8 zC*8>%<8xxH_7|n8s!=A&dUV<$r4jeBFlSdg%}C@^PlopL zb|aKg`Wr=51Ukc3gq>zu^9BBYKuHTeVM`(tC=rC_=6{MmxQSej*N{L!aajLBlO#YK z-51|Cc%>G{JD#Br)!6cY7N;Ts&o!Sqw!CGjh3&qYlqucVssb*&etLO4$QZ~B1S^ZN zJ?$!i2*Ub}AzUjZ_W|RiNizk^f9gn4qhdu_p<&5q>Ga?3`wZvJuaw)XcpB|(bA(4j ziro$S&Qj_@MTSDple=L8HbG=turpx6$$^oM#_|;l>(FYAg_5l9Kw&?UBD^TcjKe$U zHLH`9%qTU91G&D+*)8|x;zF(nJn8LWV3{#N#8z1m#l3HN2EF-QPohQ~`E*%`$J5bR2yHEDyuH#u3riGmTJeYmRU9C;1;mVayK%G z+ucD~C@m^E459sWQTOdPxpDe7=L1@^;+TwT*8HyMWY_LA_4)BEYIP3bMnvbR**@lz zs8B3G70l^q6@rsM7K~%y+dCs0x24+nGNoIvy)$Pz!U@C}vbzLp>|5BbWnq^3^v$gm zMBG4%St3y*Td|4T`B=icXbq6&N;Uu0DQ0+W_o)5%riOs9M2+6`u=w|xnAR14(6hY$ z-o^DR*GN&2P4*Kpi#-N}!o?v+E|%c5p?|!Vwe@sOp~(<^##SFH77R;7rN-$4i^mH| zBxcxeijV#KuHAWU}|KRGf7DbUZIYaMP$Jb~8A4=++qBt)Nx7vqH*WegztNsi->VAhW~1g88Q zg>r^^`13;{gA#Ho*6z;uZ}*>;=P#j_xP@qECpa{aNf_w4jqR@4#r9Tu(*tPOjQ64? z&GkJ2yhC$hlvgrcv4rOFF_^Oz$%yI(}}q(+@d^_w~x@p8eB)Jz4sSQ!$A zXn8yhCCeUm@+d&RKTfdMq|SWD-7fh8_YfEC8fY1=gh~4OJc7L*q2~f|l#16B9E4VV zx&;A_za>S_+$lu)m>{v-<>XHs_)##L7tGePjODnyaHO9_EEQ+^c96I;h`OGkYeqBP znt2QCh^QiUQzJ^uwH_iO7Ta`9JQ6}#eKw2*y7VVgmk^+{v%rQP_ZWp6>>Dh1x@8Ee zzUN+XR(BP{jveQ-dh;?gk|#+QJ3dkz0uE9^upcu0*G&T%5A!qafnef?FB7UKuRgPr zGTah(;PQL$3IfoSJBc1uf*7-Q{yBH3O!=MNU)W>@97JY)sb!LxbTYN3u22uttFoq1 zo_``BI0h)R*yOI{qJ6lkiI=#YYdK_fq73Knv6*Qcy2m%7f%~n$IuKnjYVfo9ljwcZ zRz|AciW5$aPgX+qB$?QOa)vAjyj}%Ff z-8)bO53syjf%b?eBw?Z0RA6;}Oz7_Mt?g+Nc+elh4=nN~)x!Ab`Oz2P=jw#li!YhjjQOUsoadx=qWe5Xyk1W1a%I?HV0* zq@~4-b*M(Kk#mB)?V6I2H~_kHif(v)#ZslV1{iz|+m3o!Xd)N+HP=Ehu3?Rp55W22IFH|ji+y)c%16VX;P zW2=2yU*E&xC&C~a{as+ELQM;u-5>^OA!6N^hB>zj6a%ebA;Q~QloB{dUl}!X& za|CGC!#b3Gu;(}98lycIQL2|z zp`QgR!@LFf^4-3MbBoT){j0G`Qs=V3m94A%)>Ds zK=@2Qc5b{M7_QoVP{hIX*07VegVoobeEz*=opPTRJCdU!jO8z;+UY=HGo3Wxo+Dys z)4!m;+ZRNhFZ)KhbX*OfC{GpM>sd`QSRn!c^=_w@?QP#r?7ABx>0RAihNYoag;?C5 zaI@C`W#e7S0?AjGk{eZ1`A@VtZ|m4n>BG?6{~}VA07JKVrEYhHSsFS6B$1T$7Ir-B ztr!X|`YE}4p)(qHc5Xi|$1RcVnAeFUgiCIYlojomuj?%=02@2Axq%oq_6SwcX~$%M z5rkoV|CvQ=Cm$vbrXm-}{^d>2j%`ZfMrTbMgKp|)vy~@@V|C{xGC%+pzQgE`%zuFd z=c+Fi++SO(ohUx*#z%v~Usd_lsGl+%_H!;ku!SAprl?5y_Ux)}x=j9cZZky!9=Jp^R%FN+T#zlJTgLY_nVjZbhV3*FB z7N$F=5(;w2p4$|7x+XJAUG8H8xqFa$_Uz*+QrBN}(%&C=X?N|Z;=FvFxqxo~C=lGt z?S|IklaRuo3!^EMEuD426!S#&e)Q-aPBg>%a>cX$P=*9(QkeZv5#XZV8&YW$8CQe$ zpx&ztxwdizm{Gu#E&LSbiL9qLn0iy6?UYy8$p_AegKe|pX$LGL0>udsouUugn6#xA)R?#;Io|GYIAON+Ne*1rmRT=2=Yr{4 z_3ToxvOFb_)`oDuN56j7PKD^&;YlKdeNXq0q~OmYaR|E6 z)mMzl^rFG2G{!*uo}2**dFV`V!1svDG*CUgg=ZCbV+=NSiep=eMD!BUmDbC}!q_@} z2?aMQw4V*m6v`17#F9B6%rUk=Y&*pDF~f7xJ`$$%E`sz?+YiMD7~Iz?LJ#UJ;^+}V zT?W;M*Z&#Ta5QiJ_cbOngL&;j??x4c>nj+8{r<_7&bKsWEbpDI0wT0a6K>a7I;Z1^ zA`rygS#NG{Z81tm2NH_a`b@p|#$c&={zWx19kF{eC%pG@#M(OY>5Y9G?<}M2A@Vwz zRV5y7QI*So(I|0#?}S`g&NVt&RWZ?}mc$mk{MEHfa)wOPturA+ zv^#|y7vhI@y)6cCKaju!J9{U=)#Fj6Cv|I3@Z#{5@ zAr>KF7T^%3>Fu}28%1irAn{3^(aD#qr7`kil~A#lX1d(}jyTx&h`j>mf;-dAd5ox_ zcK41rLovQR9kvdv(%7!NtQdpv_6q6UK4~Pa(~mWpar?SCW6N?aoM)%l&ARV0^mhe=8 z5n{d(P511aU}9h%#5vWUB9H@xz3O&VNz5mg{(s#)+)CUp<^FpA z7t82^QilTO6x4tNov_9@{JIowusqL5o!Xm4)!n&ZHEpt2py0;AG=K2OPA`=_bkC5+ z&2GoOZSofUjQHGf!o9H%H?>Y~gqoP#lvkaJc}Rt6NX!lAIkCt5QwS;Rbul1|s-kT& zilNiar--oB9Rl;rOq$tq!OqJj?(9Lx^fHE{G<*O85Bd~S10l`r+Z*N^v@)EPnb1#4 z4}qk2FxD5T>al$dy0JHp7a3xIzIE;TYVQU<3x`LKzhC<_uk`Ibr>ab$w;84cPNeFf zEh4acuuS&I9@(6dzvLLLqiHcEB;g$hSUKr&`{4}G^!?e zfRF%8I73mP4oOpc&n8}O{?=g2bVnTEtUV$5&XOhyC*bnX)kWs*&UvO@EPd=zU)S=q z=v61+luj*G^!v zGQh%B>r?|!O&sk#Q<{_aa_xo{UBxWxgP2#B?|J zYdTpp@lR%G6~k;+?tyciB*^)mVke#vM{s|jdKpBzuvmL~B)x|?`**UNQWu*n>Q4bK z3UrPnm~#bqtA)T9yh+osTGzs3V1io)#tm|$w`~-Q-t!?7*_oQ}CRWTA)!QP)SAYx} ze9#Q@_FV<20*`&O$q*L;<5c9!R25ZZZ{FlrFHTwXZrWN8@SQ@uW_HIf2x&Njy|!fj zCQ#*A*VI6NaSrSiqp4K2Uc;*ajvT-?;rPT2qs4m^`Tdis4H0xX+VF)h{))2LJacFt zz!RsG++W;}Zw&YJ!+D>HF2K7X7A?O@URm4?j9}C@2fqtAf{9xj{EQqp&g+3Td^vWmOYv%TA#>nOT6@!7#nWpxl(Y)FHafB@;(O2f%4*<{htbEc$NWpBTD{P6DY&740sV#RnIT)V2)gc}zNERZ=6b0Z|bg$^! zk|hq35WIOC8CAH+$M_HsM0+N`#W7UG_U{QTsZ>1ZMFwCk>i zC<&{}Ne<7-=jp8@G*dYGQR!xEeFbOFQj zqT;i@aDE$8d2$MI$Pzc(pUrdCK+Rdt;*pq=I~Iwd+d z==}kI<$c&`zz;jKBlL`Vme>9>fd3{eJK>Xqeh_-4L)#{%Z;q+Va&U10@R_8i?OL`O z9U18wT!|5?hR=@FYqGtu3Jjke9>;N)y6bg^rJgz%PTd1q{Q%Sgx!7JNhjQr6A*wxH zBsod={q3fpuVn+YiJ4bVkEiJ=RywD>n_P&gvGo?=$KjRN_Jd@}{GkR@vzRt_kbdQ1 zc;C<{#eii~n&dZj&kuM#0l>n1ND*VVZ{=6AT&APYiLm@Fj6b6Lk9x@|duE<+G6*G! zJ;lKMGw%pXi2&r$e_+L8+z;UkXQ+BR(g~bnS?fbqQ|&#{=6DEFa`(lBsni^NWYsG) zFL_0fyv1xc7az=q9sxJYs43@L$98}0*Jj)?t{xUbi?}G@A9uA9lLQ*TZW!1MQBWi( zn8-haxBKO)1z3tdKnhw5LxXXu7z=62@1ssLL==#I1OtG$?-52J!jiH#oTiU2o78IS z%+o_`f<+3PR%3su0VF8H5^!KPG+)#5-vMM{5u=f;dxO-@ceu5tbZY91CuVxfEasoQ z#xS)P8UYW%FdSgKW%=jLvfcn*M7QfdBmHc{LwGi%r%MA3GS zZq4CiX28r*zmvp3VrD24OpVgrSs5rYgEwH5TT>u0vpQ5`Z4^2wkL2XLR_t&bG5RCG z!#&lvMLf{AXi8#L5W8emw;&n@kE>K2{VRj-Di&;=kpe8)o{`dOKko$5-dY%mGyOXJ zm>#v4oO);eo0N>Sp{j(cpI?6O%a9*BNQic_9}vxvjA&ujyXyhmy=Ou>g-J21k6ZO; zN$o$wlZrKc*vi7FQq&!bdH9}KQcfUa2fv$8MfAPoSV`D6TO3JUIdu{{Fu`e-( zIY&9V7$T#(AQZ9sqQHpa@ZHA=@S7FDTL4}ZhOSDc0!srd68CPuMgVh9q=HJSkgx;x z1TZ_eIRV2Ql#!at#wz?7Y7myO+X@s9W5GRQdom!CH<` zNDG*aitR^)Iq{wMlvRhdv*R}uhRX$5j{51mny*IbpO4E#;E0P&6|zPXH>H5ZLm5_q z1v8gAajm_4^Lf(%pic9m!HM;ciDb&p0PObIk3P<6`N_s!<1pY99h>~YxL}^-`7!FI zIQI$TgkU-U!2He49gD#H#cNuWnB1<2SiX}8oju_f*h#bmijQ5#WurCelyt&lNcrR{ zghr7W5@g(p7In&*mSw~&uY`?v>nFB8kHxuNAKp=qO9eZ~(#?tf`#MQEmmbya{Jia*$zP$UM2-{`mEm%V zqK=MkzAw6+w43EjeAoL>yZkH~7p1<5c~`x{b3tTJ+!oFocS%*W=6Dvobr*SE&n=h0 zxqCalHT=UlPD=L(p&*}Mg1s}(0OTrq7RXR;cGE(Nwe#d98qGP1n{}Pd!AsZ}C>TIW zLMrD%l;q>Y6}hrcZs|`Adi7C8G2|nR976@F`ZD9hj_)I~ceaYfj^4{v3e!u{+1w{A zFvPL!=PWenj$=3bDEnAjn?bvzQojVpkY`@9Nd`P=vKBWLgZMV#yAt@U0OZ$~#luE4 zIpA?GiQB3{BDu5=!%6L+b36@U$?D!%3m3Bb=jqRRYj`iuDCV&6K$BRn*-Gn;Aluy4 z$}R_)xsi6*Ny5sF^n`mN-wDrP5FejfA{r?e3G0Z+;b&!0ZJg0|_M)+8jW|lsNmxb)=34;kWx9 zy(%ojAZa`5EU`C-r_3r!aXNy#9eww#0l8)Yy$D%5~^gJNGlxd zlv4SBMSnaj!=UJL+%4h;`L4w}G$fRV1k6mDLtp0~+y}Ps7846iql^^{Ts$%`Jj+IXG z5t%6PMlKLz;CS3Ch^@4@Q@8x6VLO8O1a7~V3 ztKyG9#g!x30G$kc%wn6S`dju?&EF0l1S#Cl?h>*;SdIrqmGBVdp!<+o3xTJ72{lF8 z{f(@VEZ|mkTw*)(T>?2j^!wyNf&l{%0RdoCR_ZY3*7B%=j%l%Vl2C+i8}?q&ck8yO zce0iob3`6a+P)X8?R{$FnRrl9ky=23uCFZxcrzt_fWH~#Z2vWP_7P~sAQ=#_W2{QxICNeHOV2t-sct*7F&(61Yny#q z!ibDlemtZqs9?PZVDL+y{y)CnF}RoL*&2>*+jdTD+sTP-Cnt7(!HI3#wr$(CjT7U^ z{onh3xKBOxPR-P=+O_9HSIzG3>0WDXYwpjM-11Lxxks5xsoqz_|CM)ZQ#wiVRs#V6 zO#vm;sSzdgsvi6&4VPtYJpBX*0vbjBnU(|Ss;p~Zb?>BoBCcId>QVa(5;>a+IIlFZ zTvfAh9~3!BBBWA-pzg0f!Pp{G`OdmpYZnqOTiI5vR%;I--u5(RlIi?oyu?Ky(yF?0 zE^(PNl#fq%KJ>{x@!3Y@?zXfyAJtitq#QdiF)eWp#-4AqxP%)4N4K>Yv`-)z*c1c+ zYPR<5Zr-wZcp?MbnUItqy*j4v_&CvySkaB`IDcdooc%5BzxOU`xGQsgbP17Kc=CrE zpX5XTU^{I|Lyes8#iATnqPJ!XnR0Tv6Ys)?C(Gy;X6KYfQk+E1OWr@Y>v%`wrbDB@0OBro>yQuC= z;}l&CkL&X3Qj}p%!EIm5r(28Fvraoll`_L=fZt`jtiFa;(iqx81#f+3+3y1mre=p< zFr==3ho_x)Nr+#U^QTpBQ=L9zjBQJ^ef4hYaLCL|9k#c*wbb)TqkU_3xT-}Njt>XH!5olG3ieGDrownwQpMp3Jxv+T z%S%xZF>ECWItG~J<8OAm;uN{w{)Sx9Y8^@WMg7Bw0bBdfo+%L;HQ@#rU-B=R_erov zQ&|_o2EB_bEsi`@kfxeW4_6jYC0|hTcVB8dTJh>3k^u(i|Lt#Ug311)h5mIH9!WkW ztVyUJJW?WXOq67ksh15IgvR|KRkQYI|nQh&;?xp&&q zMV(c|M4rf)m^BQRPYOPu`K>j@c;DX)i6{X%02Q_X>gakn5$+sd6+uCOti+&FYOlU7BL z6kifw6_|%<4IO|OIRZr7_YlKmlBaef`o&M=m^nruGgpDBsU6$KR&g+O`71Hqbi3(n z#>hoKfBs>D?dW%S#rt{wsb4!*yDe~><~F**MOul8Q<<`_(e&ZiBE2=k7K^5nlvTgG z<&}G`jD~vpswU>%@%hKHOQxFOVS114?9y+Lu&SK1GHa z`K$|njiG;RDXYN(h_>FW3R^bIbNp{M#ovo_xtJGc?renia?NqaSf?SIGrd6U~lb%8bCKI(2r=C+cS!&L{-CA6J~VvAUVzt#CPrbACHc2b_?m{V7`j8Z(%J0 z^3(!H){JE1!my$+?T~xXOJ$e8gzr=lvlVGCW6_KwGSV2s0urRHH{7D!RHI)yHOFR4{+U62rd6%HKN#L+fQ zwrU`vv5mt#P28B`UH=kv`};|8Jj20#V`eT#v-gK)+hLY(BU_!Cp0AacVeC>Jw0#RB?{v&2G%Y}>>7qGcmUM6@E|6-x;x>>lKs;|YUGB18gH^%?W%mOcz|DifJ>3(qrmvP21V zj~peoVQ?f9Cg{vbk^|Gb30$81p)kZln-WbrWUH?o@Z`5npxjw2Z42jeDP8%+x!rr@ zqY_akuTd?=(q>@gfVZ=70qYF0%Mp{Z%Bo|T8n!h}CRoGV1DBDl01g0f*;E#}SpNg} z=#FTR%8U+dCvtaU5&ueCMAVN<(&gkE3~k8ZYy&F^W+=d7LABRh855T5Dm5`b;+w=@clkPh5~ak5~RS zpvj(29icTNo{FqxaqIdhTM<-Y+&R<2d`~SZ8ok6VCPb1#-2ivIb|p-DxFNJxuP_A#z!{yLfEuFG-V2W$nn8r$ z3#%N}_5z#Ua1MJFXUlWSo2fV)6uH>&BdEid?e! zANiOg>d5LUP|{QWy@n~;DRCaBGfgnIaq#ro^+nLJWsj*l%Gf?vs*BXg&@yp$+j_~j z)gu97K5%)Y-4+0+-*&o|;mJL!`jo`>XW}nml|r z4NS71T(m`%XtFMu`nobb>Evy%j#Trwq49=TNjdQ2h`YeY$L zT<;GQ%vqO3pPeSeLD%cf5f)!xsvKq3?j;Y{Y4a{QlPRQ2biaAK z0zNdbW6G9`{R-u1k${cqB)q8$B+1GO1^iiwV?k4Rl6a~3%E=Y2*4^7(aAS$6RItw_ zlVS}C9al~3p^>gq>Khfhuep4`tybXrK2HvuKXZ3171-C$($kM-J!$g!@c4|=atXp{ zwhqB{ww$0%I(rM~&?rXj$g8va2mAa!LI)5naZ5k`{+;%(dE#*l-_BrhEEZ9D8i3@H zgxizdFVCBX@0--IptIQ3piqKfeU$cg-*Vm0$DUCmKvj1H@A=`w_Z-#Ia-j;iEq3iX z7-y(Ybh=342j=1QY)!!B64Z z3}0_h2f=d_W7be+f-8Toikuse1d#N$z@m^D_o#5>KpF<#4md=G(${6S18omNUlB#| zyDMLUdR_ta&j+?F?9$ME$ErC3iTV5+Nq__hX)Y3CPn#{IJsZ{iT%|`frtj8bRg01~ zWZ7iNftNOPBC*Es-*eKrvc&?hH~222g!6v-#h?rN*x|t?ANGQgnUyF2!QEI{tHrv} z3$Lq)CcIPneTg0-UkiuisUE-BR_0q~by|UnE5HEE(E6Owl|g+Q-=EJvA(frVgaD|5 zje%&dG;J6{Cq_!bMnR7>?I(4(0z`^h@9OTvz^3#c3`e@x)uy?AB_E=0hhtHVwqE`Q zI(OY3!DYwL7*TD>n&TY6s~cVCFB(T zH{&&WYt6{bt&8%YG4XksV%_7`;}|ZUp(jV_k^T*80~ldO-hNp;`LG z?=Qo$ecY;GAmNH|YW-)e01o6=I~A+Mye8Y*?=u=U~YV$3dEoZ10hRhEDn^9rK&)JL7?4WlVw*}K~ zV%&T;Rd%oah1`fMHq;7k7_0LO39Xlx6-3X=w6r#+;ZyQUgt8qOA-iW^N@6Tsj0AlP zTCV_p$rUrkJ=pv<{GKT)zJiOHK=GSZb=fIRo`k6)3&R$}gKYqjkrsQloq~mBvK(gM z$q?5Y*W@MyIfS^IYd{KBUrJ<24tX+I0Jz)CV!!qL2_YQ{LjxHA{K0f-*!SFD zjJX+q<-1f={s)xG!wIhFoiuQo>P{@+n^x|2EJ{W_3&8;`IN$9f)u(G~iM#oQ*XDJd zK4_bf7p)ARzTtIB5iCd;;y4?R0Ak={VBn9MBF{`L7hghxXNTmIO_;Yj5ug}ktq9Nu z`xacg=Vvx!&sfbdoxt^c#!Fu*bOCbhKA-;^qks6jFoYUy>mR87`^K9$b>u{JKe$xx zbMykvr3hiodVT&zu*rEjL&cyV2IEG6X=rCrDrVizLI~Rp*enn5BM-8mc~&>;qwE1; znv5Po{Y-yVA8u!E_jptTU}hl;WP^mR-^Itan%^ZSiE`76(GI~m0q*;M@caKG+?f#a zhx0#`unv|d-I1RH*$q&_jRRi7*`JsHOm}W*y|3T_0fm_VOp5_5Rd6-qbKFRd1v-n2 zPX_3OpM?}UjBU(po`ciQYWc3#LO)4hfzvTih@tg)_=qF9z@YYyyVngshao{)>X1=J z0C%UVQaK-(><^}@WztZ@OtxO6!^3{%+i{Hg+l=~e6#$Mj4PX1X688JHq>pWX-tOUa zqd1~>1=qN{Kuo~U3+dThc;f1}`G>DBAhc#1(8HsHrA0kG0$Z^)|AL?(CzJ3w|1q(D z@G87-?mhu&RygP3al!0`nNw17vkx0dsXsu+1jmxr{q?WL_p_oVz%Ym7#q?o|!oDy_ z7jZD)%gKc|j;?a(LZg9W9s???qG9nszoG%5o$2LasR@w!*(V+Bf^>bNfOlRYzZ zS7cecu6s~BfD*j*+0zT6b>TvcRzRK&ge$tDbrB>$o_E7I-1)IAU|n8B>YQBUZnp^g zije`@WSi(#TQ@R)Bo505s!luA0DDmw0T zGyJh9zdD~?42^X?`GDOMNA!;xNIPp%O56^!?7Dd)MrTZ6&z$*Ley?s6a6Pn9-Wzc! zY%vacy}ViZnj^n0GdB>ZoXhK+h7a`<v<{| zG!Q|0!wdr%$z0(qrio2yO)D|#I!&9 zdaEQwMhRJ3nlfs1?C*z-%zCq~>IGI`-*YiH(jU@Jls~8mvspPI$Rw>k&6EAnh6}vSIwBm-2`X%D|Pny zFJ%im`MjJe`_UM(s58M~HnwYAvjUEVWZ^4XT_OU4p(%OYaotSah92WA^ zbW~Z*Ar^v*n%)j{AIX4N>>*7^5CkYvuzZ2J`<14LXM*GX-3Yt}gz$qXDATQcJu_ zFdO~J4Q2zjc6j~}t`V57EY=sIgI--M+IUOZ>bKssG^`9^$lY%Z21sFM zaJOIy(LEeR30Up9bt)@cqN4Rr{8~UetdL*zxv%Y$r4y9!h%d|zm2;pmknh(9AzZLM zi={!l?!Oi)&(zTR)r`WemnlrQ>F;P~q#eB=pTEQr&pEeZg`%%V<-^&%Ve^4#IXj6I zZiRzLggOcFG5&ap|0=greA6ih{DR|GPy_ySyk3KSfDj%HMkf*!GS@{!I~@Vie6n>_ zS$DT(6ulQK*@s>BqSYl*N!B4A2OoVoh+{*@iil#KyTrR-*SWSpZwiOL_75!K#nR}a zL^7-6Q|8f2@wllYJU@hSGLOr|G$`WJr7;(9M+vtLe*5%GIE49WS^A_2jE~DW>@p9^ z=_@nCu;#0lAb&ux^cl={q-FpZ3R9Y1rvqBTQ6YJ|BpUXfrmhC!1)B6!s*pOS9RH5F zqSGgw-)qI%J6K9&%V2svJ<>!6Lu%WIGCTY>16g9fPekklk3`caBLAJLU4)MllWu>B z_AD-B5>H%q{ZyehYgO9?vOF)6?OAme+}{^jj4wpmJFN_wJGS1GT0sLKSd{xN9@r>G z&R;cE9vK`GOdJHe#7XN&*E*JARHQfeQ_Gp~LVOj*8-z`B47!5v{sw~P+yadk$ibzy zsk}Xc42rYOPA`;tnbx-SB>an63CT)1WAbPuG6Zli`<;tV3^W<&%&C<+FZ_FB(UNPp zwB%o+?9@h!J;&xFgjPEM$9oUKDYsy16X$UUdK2!=-*~ri@UB)_-PG(0-C!7NCe>H!SLKxv3a}}_7pY9z(h;?jCf`L1 z+Ad+7RI>0)lSH{Y?Fbh;mgu#M%m((pcUpU0;;E{o2s{&8^)KOM9(-qYSTaGq zf8ep8C{9=Xcp_kE2d6?M59t*2k}GB|jVi2|<533Cv!VM`XI9Zp!IOT8a$hkgw;0zx z>&Z&LMo*;8I8Y7<^ptAn7)BlQrhw$m$VaxgpXK3tPmRPMLSAe((wbX6Glv@f&~k7? zZtQmcE_$ShqEIl4{jj$@r;nM5-Bq?>BrZ^>(=8XdqTArM-8wmMEK;T1>=Ob>ahn0H ztTi0^GUUGn;e6BeM(=j^W#ki#ns1<-LA>*qvzS()4T=H;HLOdx?B#RN6F^D6c|hYw zeBxV{AvTS5ymTJZF9df-6m0Z*?HoTJ3nk{EFboiT;(uyO6lif2%oR5i6u_24GXD*D z_t2LgIT{3-_d_^k>BE9AT3y*{sHOG>0J#XeN*b8(Ad+=a3yb-&yTj063|J0jv+>VL z1H#j^24X1tc2ILU@x;6zF0dwwezzu|WLUAccMP2RCp)h3N@p1>wzBxVfPk6$fbRb5oFT}dC-#LNQJ>!vy3aXsD-MQQUFG6Y)RnP zp~6f)DtTLU^AZ2aFG?^isRo#%HhN1ie22uOG#AHi>j#)Y0xfx(?$=YM;A%8@>d3kI zr+h#rgKhTy*OOgfEu;kDRDPdCuC+XIqky0pEgtQvUrf|Po%!RZi4F-~fFP6K_g@7? z(15EEQjMM@3;S4VwtKYC;*tv9f3cmw@qKOKJD(*9|K4&Y;%_QF+$@Eb8|y}6ulbP9;Z zg+>O_gWi3}oAJdG6q}@ID&Y% zJM%E^!tj{arzlPzx-c`@u-wuC^+5&_x_YlIa3oMU``7263hc4 z3D4oD@PRf!b};8NdJ@x{7MI#TF*bc;f;kUzL@W-|SP8w&1dY#T#d>Og;`~4!qQug= z6aQPRx#vs8i@9x_#dR)9-|Q3>FH~e|fnoO}YAwxO>vqy_?;IV*wA&X~qu`nv9G*Jnls@j9b0e)NOxbnh>N$g}x+uHUYM*GbzTcZjGug4N$9!grurQ&(-_zB=m$3gfV z))B^}owJ^=@fm%(NMtXyXJJEmSXq3`6|BTGWF~t7A;UEX_~s`P;zf9OXAY*SpOX{n zA0s~Bx0#YEg|D1zwj+F-edG7k&>ov_Q@wYkco<(kD`A|$)LE!YKvvdJ??1b}kzNvB z60$!b;y3N{)6t!kCYcQtECXKIxbu@GdR=`cQd+`gU`*VJvOV>%w2lIul)d*A{~kJ# zm_On#mppVVYa4JHw|m(1X@6(7o$-t7}k%Ob%crRTj1SBlb94 zD#s>H3oI8ce1vcRZyV;NKM&$Xt%Up#%5xqM{Z5KgM?`vj?3ng-PP)zuPP5}ak+K6OZ?lZ3y(2*HbT_JOqTe!y3g2WT7iOke-bdQ&D#+97usGv-s%BoXew(zW~ z2@f9B`%&USFQsv6G%wPRrH4xM=0oalt?)SwB>KDHZ#=H6eQkY6)s$=}gI4blJ#4Fab0sXI5_tOTqNf z`n=hzi=l_v84~@x^=<>KAcHCMcchawt}Ks$d}l)#S1Ebk1LLjrzD|BHN#SQC%0;8! z<$hX8peQBsErbAelDSxNudv=#aqf5;U#ULXR%+{czugIP z6Skv65x_@h`oJ}QDm}yDaSCmWD545c6o3$pn)e~edV2$qYTS9azwyf9#oN3-7y4G$ zwSxA4$pp3K#XznSZmgG&&dbKIa)F+VY~HIt$!-f92_-tY@_W2Fuj zNYs|2BeI2>AzT2x-%?`AhUaLq88Lg!cRcNikhkF=k%h-9?Ujo8UFvk)V?2o)zzNK) zTar2G5-+v+Gx)^+c&E(42N7FVMdp7)QqNe{ofAJL1RmdtvAv(ak!>=~obj8eq{9-1 zYXHq+d%hCW9W#&eDR#0MyJ)c$N0F?uga@Bc&+VcQQro)EW`7?X7ATd!IiVuxYTj2WXC2>s;`Ccsgulc46eC9xK-vz1zE->+ zh|?RG_6-G!xn)>vm!ycqiyjfyBl}-c+heb*;$nd`?f&l4 z*WmABY%?Z($TUJ|_dv5u8i3YO;_qFgwQUetN#IX3C@mVlS%f z68>Q|72k71H)?hao7E#z_VZWdZas4fy+ys2?#m3*UvNDL+OA%HHI2{-E9SMR1+B$7 z4Nb~S$=qqq&Jj!Hp7CNuiV=tFESGqHH49KIU5VIR*6xZPs!FpiVOS5h`r7ZksAX88*ia^1-MA2smzV+Bh0~nyDOxQ|aZ+Kisj(|4 z*+9#nZ20cjHVD^c_chC=T5Wj90_w|@?@vMB71(mayI4H9XB|&)e-<0S4npWoN6|86;=&1xamWrd5oFw2 zH8}^>+kZQ12atz#Zee?~QNxu*bZV1x^&1t=)%M%dEf$9r;R4>dS{(ud0*2J|an*oy z;*=!{z>x!LM-R4QZNr;{C)6x}U-$HTX5iV9*P#E?G-iC?Du;lDJZyT4jRDrPzPb7$ z`w}PXneEB&uTvaRP4!QB65V(?6r|m{;!s3sDixL!4`7~eHP2i@Zkj4-4LJ>b1{^Z1 zLe>a`T+@Ox?^mWWw4lDZD1cVdAsr}7S|D<1fttOc{tC>t* zopI%+uTawf_Ot4!6~h$xvh$0%i5f%P4)}H+4Hd#JaPh??3}HlzOCxK>>}J6cKlX(mqNZEi3jTYH`wrJz`o$WWmvGHMnfnA=?U{-MG^FI3jgXwVoIOK z>wP04=Phl_>?Ka7LKytR3ji)d6bEteC(V;qSw>^qd!rj|jDoUG zK<9$9g$o%FClzxrWkRW1gOqWC?ur1V?z(2|I?yQROzU*#e4#4~WD(s%X84w2#oB^7h#fbIZ>bZ)(2;}KeIyK2^^$QS{(t{ZTy zExHc^H4wOi8*U}n!snP;lc5?YlFTX<-Xp(;44?*9h60N?#P)7ijL?pP|uIk^Q+?U@B0OUcWmhy)#jdFwNol#NJMMwak5xwE^IOD$(?f zcnS_BUhI^@FdoN8881n+cPOV3c);1cm3>}?9pyuYye38-p1+H^3t}rd(mxlm0<1IkjZF5LuuuZy zy%*RUUD~Hg;ALh?5p`u`h+lEbKo6-*e>Vk_vP5(FbFlN?)bqvWBVj8`$cAFmURFcYI##GJN)%|5-ngCA4z96hXYbLIRC1M|Nvn*P%%Lig+dk27 z-E2Ac7e90C7Ui>nBkjDB*7?<-Ksi3ii6M!eb*)|OO&1U$wMdvT{`K$Xu>#K9Y!-xK zR~rq_yZ7;?8*4aty>#RUypvMUgnJ=yHm)l2w^GEM^nNH+Ylcb(q*Y2zttgZyqfMF^ zN*1!q74D^4)!_%kltOK%xQb%gYI)#>&&>Ll#Rt2H0*@3(bCjF-HEf3ZIYXEY3qIRr z#@JDfWam2vSrjb`(8qOA0m{2N3{7B^Hh$sR7$#zupkaW|JwGG@=EeAq7?r$9sHbd7 zDQ`h}*QUHy?x>7*>8r?dUNtLvM!H+w=Zu0FB((Rcv==E22?KNY ztmWuIVM|SI6NcQS(+Tnhnj$cVd>@mu2Sg&oJWBbccI~k5(U1knkMuVfq#pI>eHfgm(M0h6yt}#kI95o^%nT)AOLvZUkijR>n)G3QnWSkeGX$Fx!UD!UPRgV+3 ziHy8|%Hy?|;LYr{jbEZV-k6y0PXyy_9#(M1$)WI%XpzNqEbwK3`6Xu~7CuK{yll<;3&5uqx(pJrE4I9LBhI2vz&nTK=5w< zDM)tc-yUvo{P|zm`o?y{DLKH|ckQzJ^{Y8xrZxTEApC4GL3H8DOxCjHHhUCP<^TGX#{#k*bzj_8Z=PL*LO4H|(fE`0O1Es+V_99KFicL~k zV_uL)(Uh#b-ec%xW$5?H@uwE=1PRl~74cF{2~PccK=tjMJN~nRd_lW#(#Q`Yg>F!i z%UH=Cd9GK5t9#(?Y3%!;h`@N@l zh!(263t{J84_#a+`HH)b`21pl0@cRZRAh7}FTsRHE`e!NLWL!azilBR(TfQ!F;K<4 zcfXDBSL-I#79(OJ@>@5!jb%Tk9D32ENU1w=J1EY(FO>T5>Qg6$9^S8k;#n589m2Q`Nz2-r`5F#R!;VIE`NxCfZ2ZGeEq-IB;-kAr})7E z;{Y0-1uk?SZG_$^a(>dXaKOvLL_t2KQDVO*M3;euoh`lYcT-*Z@WwF z`JAOefq>|LU|9b@cDoze+Wg1vxV6}k|F_)?|Fvf5x2?j0BHGub@6Z&0?QS7A8k22Q z(xjV$lvp}{XVSDnS7e33S_qoBY_c!dDYsOFJuo*ltUU_M!I<#_^VGIO;R{?r9WT(p z*|$zE-R4IHCi8@ArY+i6=pH%G-eO*)-;m!}VZvh}d++Al#QV)Vd zVi=9N)mpK#=Cr-Gbma_aBRZEQwfXB)2nVvR!E)ZY;OWJ5e^26-aMb3vKU^Kr3hcFC zSHG2NR{u{jdDv`9XR7oVVR7VmlVYmMqNr7kZe^u=4ij2ya_&_@>+T{(I)LG@={{I( zi$%{Ys;XYCEZ50gS9dD~oqtrG)c7bYdwU_L>f+{jQm0RPRO}Lf$DOmZH1brY3zz$E z#YxKpGPi1QNz9hR*3Bt*+F%f9&i7a{5?DfDfo9upz@a^r(6wp(8ws_1K86M3OcrAe zZ|>_7RuuE0x4U}y!n$af(6g>|1S4_^P1hY!dTAvE*}`Bs*}@t0ssfu-69~$OYlr5# z`)xV6dCvx}J+%-JM9-q!w6CogHK~F_J9~~2C9t5v^Ka2rWqxPgF5GvWf%} z==<)CO1h{JAG&+V05k%)mVtKcerTcuCHPqZd*x{)2*l(Ft;INrvs zYPEbPa0PlJcyfl-<8n*0I~4CpW`7Cjk%JBOgtv8LKIXHA*aw_VT}*O{;yDoop1PDvQCb|*(*5*Eyh zF_fetyXDXK6ZVlWN!SbgYiwG)N1Apg*++!@8Ks{^95-NL@(b+KgUCEG5{Aw-(^6x9 z9V~K!f8ju;F&}5?gbCS(P=_4G;lMBduL;%IowR|HcjE#jcp9-D}4~0kGIn2N~SnN*GZ?l%v4^s4@M2 zfREZl&i`mIT_no>w+7QMss$p)pRN0jneddzn&6Qv09ewn{?R_g`RaT{*8)&zsLNan znk&cKr4cuz5oa|Ln(7Kh8MBBtS_)vJ;^Hz+V1;#c10oK&&j^AH!z;n6{Rn)F`;z+ep+v!Kd zBPDxc&^_JTLykPL^0p?fJk-4`d_f*|1g<5YnVb>5eb6i~p7W}}zViG~$$6WsPh`}5 zw$mmLgz?ojq-e$|$6+O`z1)Q2G?k)xi@BYTze{Wce1fi_!G@QHWRP8%Ym)jMrL_C?{YXReX zRb7&?@6E}Z&Tw!3p-JFGrRo+SS9{6j4xWhq`*y65H z$bqhB*?*|U2D>48J9yN>)TZinP8^Ea4A35G;pBxY|Mjlk;5z!ky>vlns;G7mwKm*2Cy68 zKR;1>=^vThLD6`F0QlW1gM+=?=Ngf4;oJw6$eF(paGOzUxr&qTR7q zY%~7ZP1+o?<#51MtsdqablLxI0x0@E=FfWNOVke7Y(yN_8}Hgs0M+mMI9>hj_?!eF zfqW0PVm#2!h1x|ro5y~W1+5z`{Zq*4QMsH(6)4$sUeO=ui0F>~6JJwd59sg%%N8kO zpa5nRidMSAOw=MTbZ#n0qS`oGDXk4;-!ILU7*n<`H&MNqzO;s-Ump`P2Q+p?(rS`; zz-kk@T2Pu^)#RPE^Yw`Me7)|G+h| z2~q|6i#!dLn11n_|rx$3fXo(n1gJ_tu{`--T?~LCZ&ytZeU8RCZ3j z)Rd{K;M{%n0h!omlW{~=1lrnHet}GU4*{Jn}ujqZ(PRp{Mo(Fs?#}~+{@?a`32%IIqOI40AhWkKAlmr z;bGs4@}WYPVI7vb22$FzOTKW8NBeF*dIix_NBPAfcmY_DdK5-jap8ri<~$t9=(pPb z?_lVxUJi`5!&`@5QUH^|J6pc3L5m(k!KPE9H1`zcAV#}jE;v$uqtmLI(5;pzo$dv= zFjzrpUk2F3<#k;UsmhTIXV*#eRIHkMppr+lMpMu$>f)6VcG3z1<7#F<3`t%g$#{D& zHC&CPqvU=e5N&-QkXKOeu~%8Meme`(;?T^`8K!cvWPV8cuYhv&nccM74qG`>-=-*Snd#A)#3;ngtx=mBCt2*;&y^7JcAz^GKaU}!%YJtd-=(9oWehD%6t zAt2>s(sYdH+yKg8(IiA2VkFVovf&@~L#om|8%x`d(DR7hxiMyCD&&$sCY5}mK)9Sv zx08`4fIg^#PrP1nUT);Zg(vr`;lErs?81u%hqQ9c1&TW*Dcq5VN2*NQv@bJ6K4megvJ;A#u)wteoCJ8K}j`i|w_!w*Z~ue?G7A!X%}v1&)<8zd-2A zgajoQJ)+IX^bS0}CKXiP(e#0$49S;0OslZBzdSmMe~^bk zarWrl&Bhm4A@x4Du?KuF;j|>he$}S`L$-fDT13eCUkP@@Zg%B=hiW2qD;b|3=9w#I z!bLVqiXta4Si)Q`4?t~40aX*tR}b|&WX-N#p?A`m+jJPbVSIQ<6ctt0owX%v<5!)m z6c5C|GQZViLi+k#MQJel`K6%!cIiB{FHE=VtTf86Aif35Jd#R>24*bFgyO7huj{sx z_iMNA=o@YAvHt2b`FP>^(%;W+jy~=DNIkvvhN?Fms1~r|?|`*!+Bpp!`!2kJ7k;yv zQe-@HmzTaLQdMJnXWfVTt(L{F+bUl+tcc+PV%>E&UidCldJN5?M-Wtk^$L%>NHBnHdv`RSa}?Gt?A z9+VUQP=ERI8Q?CQp1*pCw(-`8v=YM;b!Uljz_kUDqz&Wb>VawHH@urM+h&QIr~8^y zYw6h$-OXbvOHI@joJs`}3~oB}4+ZwVRkbR8$e})qT>B>SKC+3!lwRTTMm9BxjyfgJ zw>>qt$g)D~7tPJ+BbZay9MW-$xJ0FLSRu?GyN5BVK|sHiwSD$<(&@U>sG%8q<;uj* z4jPLv^rDhd#&xy<@1Ja9#m6Q`+!5}q`^zIr~BA>`GAH@Pv^| zrR;7X&-a&C;Oo!xyn=HpRG3uvsZBg1&Jv$f@y~HnX$2K#+(&o|T%uQ#tl#mnd{#7> zW+jnVAb{WSL9v&GpCsUQCWc|v=vJRxlAvswq^cJE4kfOx^a}BLnM!3?OPZ2a0-5Yi zh?^op)){dj^?Yy}z1e(Vgaj_NQ@^>>bh^rmMIjI1W;~*ifM5EZiL1xPN-cu>k$0enl*AYlbjPUB{>L@NG!#Kbv=h zykwL+$BhY>WTWS;NmB`9ngyNW3q5_P);=qh)u4i|ByuAF^KHs|=RL+Z_W>&NQP1>9 zj2%i0>#M8i;l+0)If*zKWVXtG#_+%N2Z%pwA0QWH@>*F2}J z7Uuz=Gh^FOf`dT5Pxr}A3l!#3^}_Y`1^@@2C|EY9d1dan=8_c!`cXXu5AsO&oKwtL zG?Z89nGe%TQ;X#j6f zEltoDI(ZB%+GK54OgF97X^c6smrg0ugdDqyf921M zMQ)4vE{|HC_E{+ZC9>FAl1mJSUyFn~_xb;yzP<#Ws^|Ux9=pgcvhVx8?_2hr>Yv%ZaSXzGYALEy_-04cW3r$e#akZ~Ex-{rvvt^_u&>u6f?`%ri63%sF#r zrf=CQ+v3Z%J*8XWQWEDFXe^e}K?vh3H89QG39BNFNxng#U@M1i{mz{6x|YZ`d{`7q zMEHD)Yp|~1P&iqvebIRLW@5@FUM$ny)!0vk7;p5R*>P?e+wwyDtqNc0Y;1*&jpZ^; zNOg|7$376cHhZ_f_+v1yDgF{%qAoU*^i9TfVrs@HHCgpLd_{=uan4yFe9=iD(3}9% zS14Mti2vIT^9ms*IUTNV%!0il0UVR8kAFYt!BYe+U1Ed(ar5zy$v4NcU))BjINk2I zKg}LxNe>&`XK^aANcriIWeg7_=_GAjp7ErQ^;0JMeGUEcqLc&H z(cedUT8=ne{L%tF3L1eS7_w;*3RxHkhO8gRdoS|rAWY;973<08PQ(B2c7m|d3j8g>>*#h(x9*zg;vD}8wbNtGKY2% zVS!85rs9?iEKU(Dvl>K!g2UGn(0HftV@Pa;iWB49ZLjkkv-TC{XB`F?@?6(WOUfjq zW%7|(9+aFMIjWOgxHSpN$_AO1Q5}>zW`+ECFf-@NyffS}+qX|$_H*c}xq@`t`dj#V zV-wazkHQe_wMOD(_l^<5p~RiH7!(v#&xLquTywqN?tCm2tDQ)XZmWV_;G6ApIu9=v z>>C)E99#$uvdp?IhQ1@DdlaOeFmJSfL8a?8Vlf75OYMCb6-lIqChh(;?@#*I4}tZ{6Zqj5ADX-$ zGaC{68gO;!aW%tp{cVX4KPG43$iaTwF`Cb#2JNdjCw z;cjeLtnygS=El*?Y;C~wrfj~6{_P9T;rB};)>LVe^Y@lkK2M~VM$~V+)X?*2UKjtI z-Px2S`SoX6{3oNd8|w6+%78j?fM8}rj!UIkdr?(KQPL9es8!<{B|W)y`W39vmjQwn zMWTAGG|3*^7k3>16Q}H&&~x0~ix=47?P|k`YR=TRZ(e-)6l3sJ#JedxZq55Y4m^CV z&ujk*Ze;7R_6;PP%rwe5Ciy_Sw3}W`_9DS>TRCw{Eop<24trMJR{MD;3xUGGY`N1_ zj`u-0iIM`BL%GS{8vS^1yq+xEriT9=(@nXiG8|}3ey-Ai{k`CZP7_w-v;Fp7omBY5 z(5E0d&Sgp`Gm}m+a5WLYx{oi8Y93Ifj%js7NvS zQ#K2iG{FqgFb(#el;Knk%uLj|?f3VDhWITd4@;2ZJnQh*Tczew!WR&tCY6+I+Ab;1 zCd!C+WntRsk0m3vopK`yz$4E7#ayTx*?CPr;V{mOXU{3smX2?=DKeJ-@nv2(_Wj&a zy5>X1+cd6%kAC#r$u=Qr)*!a9d8Yn?)U~l`P!r})ZU-HZsIW`Xx-4H=v;rKubREoT zq#GV*X`%bMWJ#&fm<-IvaOzprO8dIey9r7O58`i=%2e14hG=0IP*|grwtPt4w871} zHrSF!6|a9EZLMI22hZJIw|jh0)v&kd(#*|E$Fotg*-P_-72{G?-5SN$WcNI4_G-^P zkyg-~$qR$?@9mLm;AgZcoI4l?EG%w^9FhJtu7p-A9V0h1C=jBs?n8sYSfHJ_8DU&t zgA722$gSar8(oE-#8TERn2OX|RXi+WrVi`oV2OQ!-+cdba%4!M<-4z+2NNF^r@f?l zBxQyc`1m8`(;^q`pVp~a^rnZHdg9Nn3B325w$cQ){E?85;W~1<=41f1J0imEYcFI1?yZce8 zXqeO#WpDFCT*J2ofy7T4MK8F$ zgsbh>?ob!R!jGmiuI{3J-kCy_Y>M6Ruwr;&ZYgd}PZ^2nR(!LJTVS$8Q(86MMnq0B zKCpB&NUQDftO%mDxbHQayk_pZ)GL=~-zLDgMvuiFFG|cGEY?k`&}-q`?aiW?UW~GK z4eq0Ml(?YL?Z{MeQ}grX#cL^gY_*r6X)*6syC#ar3_o~aX2&ZuUw^b`>aqTK0yQad zj#8@knA$c4qtIw@ZF$;+%2HU-zC5FzMGRQT4asLqj%`I4eFJVFT#qsYm8=>5~drG|TbqoYcD z%6E_I$4tFGdzDXgsKaO8k>Q$sQFtmqRnXXlt`o~doRMS8fsTfuq4AnU^}4V257LLp zGn}^@HWkhr(W%-)_a1@Ly+u9`jb@fhlHz5ZO>a9YsE-@Z`6y^T8tA?7i;46YSQaT=;RCOp7N&aGUXZ1 zO}6cw`FE`?hq(zQd%=&QY`B{Dg=3i{VwMkc;BNKKchuf@a=WiCkyo-mF0;^0;iM$| z{^?H9Ofj)%LxeH2aJZ>zx#1jsc#e~j z$XI?=sTM=RC(4OaNPUiCA^4}VW>~qKplZ~)b53{U-|PjyrCB|w9;P6k<>=4KEMKy9 zTVRIou#jS@FcrSFDJsMREh(O4*nkfWeu=TsSluZ)d{dk{On?7llY%l)l9SA|EsNOT zcTLw=aje|Rh!!e#AHUx191^|UB$@PceovZ`bQ?UgLT?X0_M$+{_8A*I$H3W`b= zlxDflgx5ZPSD;Wa|3=*6i0?nQ=%}#5ZJ;4YvSfU@hEh!#*FXBH7Pa$Oh2eT(&z+ll zWpxIa9@9?U`M0L;QRz*vKIF>U{#sU59feo4@u|WfSvb#Qqe;sDyp3v_v7Wq$bGpSt z8UZ-|<*i$p5BWbkN#LhDzv^tmCc}6!2#VPNc6XNq#V`EIBMiYD7uHf_I_Y0iHksaS+Ijl*&M6tRznwd-~lPTsnYW7CQqb}U> zp0KlnPxr70a_uz7B}GLjDc>)<<+dK`J^t1h-jL3^9%7y|JTKi#f2ZLv?-8y-P;wQa zt|*rWQaeAyXU;3PQK$3<@C;8UrQaf2^F1m~FtI(|I2DwD9JJbrk3?CSg zkj6KhAG%7}caDF(y{YKld`r_q0nF&T%%WFQt_lpb$@3iMlSNBO27>|a#vs$jC!-6S zKI0GT<-gg+=C*4DMo<{3J|i=2U--F)#vP2G+3PfmcbQ~1sEjbm$~iO%4wg!O*+iOPk-hnbXQ1NgK~DG`jfI10V-2t1 z9hQQym{^&wYDfL+Z^~Kq8m)t+3STW2t2XS~+hv0_0|`i$dK{iim!peBc9eSNJ#m9J zQNNY2cHKoI)r0Iu{`Fsu4EG!FFfJ<8ZWzUq{=CHRohWGa^xJfo>NAJ86Lfk(k-N=_ zJ#9k7As*Oh#A2QAnrj-cn*!yD@OS-a0gvJMnDm0Mb%VgEU;hYlHv&A3k&6kgtKPLh6s{C;w?C?6EnOq=S{d=tzC^*raW!H6S2IVjdd>X zmsWwft?@RWhR3#qBtr@sp5A@oPo`+hXi2^xPo{4F0!!(&O{x#GknvmGhKY%$L1~}4 z;(=K22l8_6yY5D^2cp~`t;hO(=I}=rx(W0XO)THP+!W|D+$H^lM}I7@H^p3wyM?O= z?-e&yFRm%;Yclb)M~hx9^G&_AJ+U*H^y^5ca_nftd|%u*&Ow(sW^46HlHGUwFmi;d ze)g@gY6=HKCmE*fD{l8i<4LH#PS*Ea97#wj;3U51o9})eYrM`io4G~p@Jer&Pfega zC2mMyqI74l+4Ku{SzY<#wtoJV>z5Sy>EZbAFzGvjFFh2abU?q-KoJ$xbdAxy-~DUp zH{}lS$q0|p`)5+>6&^n7PUyzHmkoQAq|22w&~VU6DuM-w*jw!z+0bit+w*HAwBOUK zT&*WH@WWZ&d2RNIR?kY!Vo{DJTBoGxa#6rfe;VR~luY)UYx;spp%};82gZK)o|8<& zAKDKrh2AFAC;r0a*s4)oJN`n@Z7e3>tpvJIwk1|9lVSYK1cnup&w~*wDRXwr#BB3P zM0mH9Z5UU%3W=bjJaNvDrnF~mt&WZb#*GEYPin3@s6OLbI>l#ccjnd;P%5esjt;Ekb-kh~Jl>Zfom zOHL}_y9ta%XXI#6r?-YML6#o>h99TS>dm1Thw@bU_>%DCORUVa&XEkA`B}pIyYT35 zF~y`sX*Z;v;$vt^Ua7Xc5F_St@Z9gSJ$d>5yjx<=%XI#`YiTC!`d*`9(?7`c_1rMq ztu}AQS$W)SCGLN1-XpRWDznvSEVkeEu2`H+)#zX^;)sd_L(%l(JsUdhlED)1Hiv6B zz^4^hCKLn1yNyIG2(s<5%c;D*V&@0t+g>4h&A3Z>s5Y>S1OGm+ z)vJzpzy)bFu)~2 zF`=`ctZIfk>P0zb!CM@(ZIIjI+Tr8b(sjGB-miJ2?;rEvZ*4p(;bu^9Sy)^5_EfY9 zd`iIAaPJDor^W{uN>*%B=P{HRQ>5W5ctlSQ8zmXugb~)6N&K|1&f|$+oUZ-R+;O-3 zyWT4n+z)a^Jv&<7*;ZEtLi1FAvb*hw)%?=2`#SL?zajnCH-IIlPV9Wo76~mKfmLwx zazMl_Pipn^nGE6UITO?}(Amw_f8EMhK7+BB6+sVMN}<3GE7hAKL5<7y;aW`zgfLhh z58}?WE!@21;ab?+<1|%LLA~1wLS?J*KgY*DCx2yv$;++!IEyVwzliGJ^no^2o2 zs3Q>r5yQ2hmRGR?W|lYi_+{_w8q4nSk9Q(i&4+JOR|;O2b~P|wFk0KQpWpLzztt|c z501%CywStg@<>`UXuFSxmoJ?0o=fQE*bMbem#Y=z#-`}oYn)I{xkW|eW z`x!=@wfwGC%r&R}d+h2D)@6kQ(T%06ZP9DT!K?MlH6tFl$3HD!Tk3){ZlP|obqDWS zy<30y8tl+tAuMBYQzo$Hb8;@7P|g|jx_~dA7rLbs?}ct*F*KKSV_P)h(*c_lmOsIm zWgnx)5S$J-j2Pq1=qrm4n`Va6BPP_Ga&Fqnbqb7|bktfURtijeP64eC%{f7_{GK{8#Jo}PBrfrL%y}O56v>Gcd8%ihKGO7Z`)xbA8|{bA zSVz+!); zdW@&SU%JQB>s5nzmf76^6^gP9UOKZOS*v;8ncM4OQfm#Snlf}#$vnR8FCKP=t2wda zx2%^m!RHO%Fy4Bo%D7dZLA2jEg)x%Ub2Ac?`RJJ)+7^Sfl2A6MkhGqO{D-I7W*yG2 z$$Qx>mP()gYT(_Ckmo53#LlJ1KeaC|#tI@X z!8P~x?&|IjB1Cvw=|fzB&YObtdV}V&`*}Amp&iM=RSJqqdWE77Utyx}63I@dq>R_G z24ov$V00es>B&F*VOslK-+7tA{SCEF*b;BO#ipQS*JW}ee+E4lmO;(Pg?``k3^5A^ z2D1qjxbF*0%{PR4&-;>Fe!i`+Xmb6^TU?M3_tSqcaQtp%Tql^D>B=g+@>4FlnqfWK zr#fA1+~2Y&d?tjwePO3vsEj!nlb~BT%F<h$ik?<4gT#EOZhVLdxhZ!dD}Ra|No&^2eq^chbG9?D$;U(`C-d7!~hVdnO8o68ft@@Uay zBQVsqpIjh!1_(k|_~KNnOO?K&G(XAfLnJ02B`n!eBYBxnsd9EghSGs}?tJcU(%o(h z@vqL_4@?NV5^p4y)|$8zNiw|N6k$M5sq<^F1@KqCJlPtIv?M{8?g_D(R3rzMVG&0? zfxlQ=Hcv@D!W1A6LLWs>PsL6TX&*iCul`2c$=>DVO5H@q@k8BUw!}dRkM{B5bHX3( zbzjDPsbzj($+OxHF7X9s_6mh4`Uh-hTeUAs5~97zxv&`$MlEeFeXF{L1c~j-6U9g%M zSVa<{vNgmtMM*^cA?BWx89!C-W7QjMlMdAnx*U9-`Z`a(T-4*c-^b5`C7j-qCpt@D zqqeLu|L`Slpl}1Wvv*insips_jsc)go)TG-#KW!Hs7b7TDTc_?ut51qqz^C3vxjQUmf|hSLM28ag>M7ouzyk&xx8bs}Ybv{1&a_PfyS*Gvmu?53qO>8NOi~)& z${NA8WkM{YTEJnum!JEL|2-X@oC0VvZ?^q9g&p1Z2*ZY+Omc|MS^lHm;q6yt4lp|} z;&bA)_%F#TLqoQa~|GO zN$vxjT>?|@NqOocB+vu-Ho5z07anWvH?eN$_wK3;k(_gG0eofhiUOQ#Ylx>PY+^+( zaHcK&@)NkDT^v9;IOr>gAF#!Qe=bkYUoZ0QMV8yq=e3F)o~sn!z~*8~Hg`?IJcPHf zw0M-)J@Ce!A|?`+s2&L$Nerw}xS(Lqe9MO~(kt*-Xo155e zn1&sJDMnD4DwrdAFpgrk6D@GoM2Q=|n?uuBXe0y36R+NPxu>|UGsS3^-Yg=(tS<8O z;w+ymEmH!_GBCZD)+%RR!N@NA5`xs;>T*-VgB#qR*mXngeuy-F!3zV({i&_s?GJnD07 zD3cd*ln*szZCI`DeL8{Ix(z1Z$) zu+wl+oaOuQCr4wB0p$evJr^bv6^q2pT?hg)V}bN$)!Jdi5y>mHPr?kng+ud^?%OLm zqm|3^=E1nc&5s{+Jym+ylp|f~a~nQMZ1cIa)v0>CI+BJw*YQFC`r`cY#M51aggd;s z&!0>alJ5BCG+d%oGT$Ss{+_WA;K!jk!F2QTGVzROlh%3lzJ=`gIO9adtd@%y`WMz0 zyAm&KL?nBKcz<{{#@J4E!1(F0d^iHLs*$U@hOAeXn^sp&VshwwT<9lLMgLB8_<@4( zi;J#majKYwb#H-@&%;)t6$tNjjp~Ta8ry)a&RNlNs!unRWUeI)`J`Q3;-(qtsubWZ z5*Iv*4HbzRwjDOxUczkcbclh8EqHAN1q)_vEGCW^QQ-A$rMFGt*5=pX9S2kaKSDJ8wOcM`9@nSWSCN&__lC;7106= zdTci@fhc*nD}Ge^L=C@XEtAv_<)AQgjEBK8wz~^b*9)VJ`#T@86Pgu=?W7OLSUvl7 zSfqA+t^U_e?{IS{0eB$AoB1-<@i~LX?2GRaPxAtaTf*-PkJ&nMVw$@Xz&Kvh<%++P z)f*eV9_f<&QxG26>fj}JedYTbxDzh@2d@e@+t<#A0y-{vJ1RTnAQ>#4q}ijjc`oad z^fFsQe|^PebQ#R{m@IVu6z|Hr* zRdtiYD_Do_+Gek%uSc_mz&yTqRJXAo_m3@?{T!!HrlDN{n!nT5h>tAaJ6vB~*DL(; zYdV12?-4qPjlE)uHGQw)cBvJ-bc}CjqFQS9?YOvrfa~_sT%9wBEzoGaJ>HXZGO{1=XK{Rct;W_Dp}C# z3tLxFD^c2<+;JyqNsQNB*qtUd*=ocz7zRGHyj_!LTT_pAP!UTS=r0)^ZJ1lVE~LIW zVBTEszfm31kT`F=sz}AJyr=nI0`}%@fDet%3?sFI^&I=Hb6Q^6hJdkRIqO7?B%8ln z3YX$wBS_}@@S6UCZ&jZh{QdfL=y#L3zFlsY_MeD7mx8}WlUlRBVRYcWv}Q4}tJ@VQ**hFCrZ^wF$-dY?GyK?dbIq2h&I+ zcoSaamE&AkBC|~wR3Gt$GwZ0N^L;l^u}${VFG+}%zrVXc-bO0t@ot>o%~>cUEhlfX ze^J5UrdHQeP*0Un^y-bY2BIJXY^ft>nMsN2)*5#2#bS#Irh9uKyB7|fS(68|c={<2 zlf&%(*?Oz#h^QSBEa}VoPLzSW_w}BXJ2-CTa7HLNhdTBbOq&l_!hfx$+p#z-HOPu9 z-6dPza(!;6{`Hj|;{+x3gNDuTNm2BpZOv9kg4uc0oFCYVgK1<9$yHoh7N;&_Kk5hC z2CR*jojo5j#0Zf{XVn91gTEA}Dz4kM2p;Cst;}oh?>pm`fO4YP+jVO$wjZpM6)myS zL_d#NKjJ8&@SOrJoD@HbC_nwY8-`Z2S&Jcx8>9 zL#Ju3$4`DoXRE$H@fZDHH~OX98`!8hnByPf$XOYL1unq(D$pH>9RTIFdlq3Ui zQ4v+hfhA;8l|nRt3hFm+T3`|N&4C^WK~5Zj-x+?V!NPc&hCLJDgNio83`nA)(X#@g z?84F1RL5fD06mKX%?V}{ks<%q*8&}PZR)GX&< z3{c)-FaSmgJ534m8%pvN5oW|JAU8XJ&vdfqGB<96cN6qRiYAmMHB22T4MVc@5kv2( zq_G44&B}N60#bifF%%-Mk+6U@5Gzuv4oD1R{Q^WFzyVxj`Y*MN@=$`YK_>{ZasUF1 zCklNpRteub2ZO!mIoXk4^^8K8lrTgOB^4OM0pL@f=FWH@MRSUXMM@^}e_7trQ;2A{ z6f9x`$T3WrAyYB3QiJrI0RA8A-d3kT<_3kJA}1h3f1=DrhpYK@$T~{+|F+Hz5a0x; zzX7XuV7O2ZI(vK(L%&zaKFrM-xxK_OVr1IQt1{yBmC-!vvN zCp0Y3q2w6|hOmL&@_8t9rrt$Xp(IWb0as@St@#0b zq$d9y33{@Vxzgrlgo&qi9eFUK0R>7*5)ivLhKK9I{3{6#j1HWR{?UAV7gou*Lwp z&rJ;E0?0ysBM4BV#asis1Od)J zkxdj(kx>ZZ3!(Z0P*Di-Y-ra{{Oyf@8UNl5ln4sJRH5I-|9e~evz39}4#|Vu&q<_l;1m(7uFRm72!M~Y=|4LK!(db}HK>qIE(R!kzXgVi zoZ;E0MJ0ZU2*P{J;Hn6K1OMIYS^m9}_G|~k7!IA8`+u*i{jW}=Mj4gdDgNJUZ{hze z(EXQxviy6E>sgHyBmPf~PFA!2mqzndD2+}_;dE6j_y2EM?5Re-*YP3CQ{Y<^3^o^2 zG#8#d7oW|4Hm&F6tjNOpa)c`3t2j(2C9Ed;yM&!ZAuq)o4MQ9}69#KU&$K^xlarX^ z01Ck`qJRdn8UM3R5C#*1S)Y_HbTL5TUd#y%Kj!y|fO;v&SFGfN<5Ga=A6M9i&Vn=$iUeer2IP>v6Y05U z=U1W3kOTi##k~;nYw&VLkW=-Hp#`{6JSYS|N&^x|o_}77{x{D@Xc`dWq2>6$TAzU- zit-c@ofTaFQzz!FO}bAynv*%jzfERW2G+{}{C~V6ekzLY*b{|_{Aw;xK=!v+{QK3) zv&HF9e!@c!`pBMXH22$3Bq#*aWC7%2U2t3$>OoT9Lf!6$98@3W8}LASxie-8SwqR8 z5LA={q>+scE(ghh?@)4V|2d2~brdKB=j8xdWPfp09iZm5gsS4)+rqiYe z5-Yp}(Ed?6rWqxHLU8&LAcsu-pQ-YHkA$N}{?MEU?Uc{t#Fm&IX#f<0;mW_qz#Ym^ z>Pd?yV&veFGC=&Nxp+vRBu)`Q_k;0&N$Q3w{P0Q|^em{x(bm|h2esQ}`CB3}hjkx>Y0sR9bf$Wf|LqY&IZ6_!?mgy;89gegH+wKMsBjE`z6rwBTs11w;z+V5)Kc_MglfE46J zZh!G#uK2XQ{XsYeM53$yduVskRGt0M0oeSL(78zPucpc%!Hz7U({VscIBHyQ-xVJQ z@UMuT`z`bDKI84LZ=?N?!GA!31!e$!o?Hx2C_x8%H34c+PXj=Cp+9B2GmPRsMNl3e z-~}x-e&_Y329(!Lj6c%YV7A7Yy40q_hSU^=V7JC^ABGPlhL($WBF2JzH1nU{Wcnpa z`inaXLFoAn|Nm4h|B)jCZ!8#Wk_-l8gF&}3$cq8`T*W_0K;%y~QEW!>pb(_eg4`l> z7Xn&nLE4Oxf&|(C4cMZ6#tT$6K<3c3c)EIfCFhBJYA^6 zIZp*CcUKpn`p=T$F+_=;A{fI2FoO}gziUat2}1-m!~p31AJ36aH9kdWkVwbDQ)gZ)1+MIEyM delta 49371 zcmY(KQ*@wBuxMl3wl%TsWMbQz*iOFKwrx)AWMbP+CYspR{m(s5=b=~CTK(ExUAxxq z$OOp7JV->PpWqM}ARsUxAmbp2iHMZoKDh`D91@^N|3zJCHys!`WDt-y!9-z_G$K%l z11oGd1T@dL?fTN zJ6iCi5eeZA={}|KKZ>;yvEY8rk8~n^nXe(kH5a!}vKTQE6t2vT5>Z=iyGCUV9&semkZ5{l}kIN>%S4c>J$%7W*eb7$wFl2+#eiyR-JZMw3 zZ?gOT2kUixAIFdFeecw2qZ4Ed|HmE##lzt}OniTHKZ?s>OK-cS=WavTesDde&A`AK z0JxFXv~3PvAhs%B@{1jfEeCcVBVRWJ`|f)Qg3=D-94laLtrN46Pq%e!0yatYOZgZlSc_x-&4 z)YO|5Otf@OAj;Cp-el=H3VH4!AE<@EX1}blr3cAhGqX@j$eGh~d&_P10~39w^uBDD zcixPr=*zrlCLrLdpy#reGk?`({^ZQ@AwmS-Ef+P!{?EU;m;Hi+-t*p{PrMkFqrY^n zUAwl3%B#0ei*oG@T0hAy;eRtsyB-vNVIr`|w{|`R;4|?doA|yyyZdba*u5zNoHHab zy8|*d?zQbq41BmWKxe?D5xL3RN{`t_Wc3&?Pm8NDE(>YeJsm1mFC&im zx|eqco+S+N#lU+6zMeSC2F2d_vH5U3phq2>Lhann*hO)oh~HbH8^3SE1D3#a!NvWZ zTF{^3Sjv9lm&HpS_MG7IN}t(>hDvASg)ydF#?9@zCKJACD66=8?{@cku07)ofy*rk zE<(q5FrxJ6$9yMi%S}DNR(I29edkX;GD`2vRWL0PYkyn!nBU-SKT6#sc6M?miuQDQ z%PlA)CYv*UxHLBmMU6iDIya2NvaiVWXv`sg3wJDfp0;kl10027lbS`G5dec%=LICHR}~U3jMeQTWj=<;LYL z(RJl&omUUL9p6wsvcTgyiG=Yn_-!_KMunQCewqL4uz9&U-XP;>GR??=d4Y!5;}R9; zPFfSe465BaKW&xG*Lbld;3x%!3p| zkmfC({Px#ox)e32-ZY7bLHOa?@Gf-MDd#5W-uH=4{#Xd$^;EAnG-aw*P9c4#&VvWr z@x6oL7X+>BY9(D9Gn`-L0!Cmk@I%VJ<2@t51+#E!rPsY^7Ya&jqzc7{NUXBtu1ts_;se;e#e<7c&m!>Oq z({|>HDr(HnI_3Div!#}QNVP5fOf8psNOyWTAQ?*QZ3=IsP5FaSCyjeg#32M>E&cHG znGEBy=nqme`Ka=VASW_?z2~D$W^Z=yxc<#p zBZl+ryo>oIx0eV;hVc;t)71M^#%n))0(uoEw2~e!%$!sUQ>vY}h*sa*jP?)w*88K% zdHIJwppa`j@O-uH@-U!v%zqtzk-N)NjH=)+$t?jIO~&l;#WeV|jyvxV&kU+?%GZFe z1Kz_Sp9}d9bZl8PpN%3E_NMQByZ=n5<>23vxvb^JC+%5KuL{Uq_EKomWU-YM()82; z0&lWJWz1t_NvP9r0Z_S`7HSd+opX2Z8BZ;1U`i;TVP6cFepJ#?T^F!89J#5Ap!oJM z9c}LSbH=xVme?}3>$e(r`kcn+$PX${rZ1sEY4hbZbu1pv;Y)K~S=k#ZiS|Gu=!MvZokluDDunpV0P7>v2Fu>QKfDbL6m+EAUJ^3&8Z|>ufm28y z(4T{k&(hJ67uvl;JqjBLt)!Am3R=L_MHc@N_nb!*XgRIoaXJtR|2rmR*?#Dk0_We0 z5K1x%zWdyckB~7TW|ksZ)s6kZeI0oHQeW9^B^L86uARI3I}lhRvI zYWZDbPKH;)ntu@6Byea49t(Ax^`odNz<^TkxNt7>K5@oXvGrQ{oq?(|3NdG3XTP~R zG6()4g+JV#ERF0NN-n7*B=yd5aL!Lx*&zs@cKaX!Bf(8t7qC(B_AwUU7oVLXZ)Ih*_zx93KBI`S&X10u9X3@x~SQF?RuoP&%DvCu$ z{4i7Q#T`NC8I-7R{cqhmKDq*k;wN?>t4W^#A554fc|8?U#KXsbA6_B_qHpp@8IEho zm)=Y_{xMx$UCF0HLpjCeOo7h*&Z54ea#ZDxc4O-nikig3+6oYilCRu!(ecLh*PQO7 zi&t1~T*{2%{R>qc7P4OKYcL(36#b@z#3;zF!qcorrjNEoEIXTS>e8MKqjJ6L07&iU zCVs^{S@iQZWVr|-M(k7ptRq!+uGyVW`q;|>)H|zzm@_Y(uZ0G_Q7cpcys94us>8z8 zLS5$wB=8)(?2X*-Nl;j+dtf-cj2*Kp=@dQ73 z_M8=u^cYW_>H5moW}TeXr24rjCU>B&pvN!ZPCMo6^d+*_s|*@YedsW6n$(`N@TdUL z0TdBhRZ`lbjA$h3tD+JmIf?;j3EAT>tRpfwHHeYx^bYAmms&WpPq9pN#9Zq+7`n76PiyF7_q###W`-n)Qk1uXv*d$!^Xq3`>Xl#Lo<{yXK9iv;e zc&B1Q`xY&yHL~e*7dUw(qWO6+;XbK|+-cCPB!P676F`&beVAuDs;AI!7*3`!rb3*s zOzBP9;7LMg!(fuG6@}{hau@--V1RCSr5$6oqz(F8cIrxirL|@fKD}jYRoQ;>Pk3#& z>r90^5@ngb$9{B0(wk|>Jm$W97p zCYv*lr=SKuR*iz;6^7n=h_&;fnKtip!qV+O#T4SYByj!OaUb=4&~bR;LKa1)(@^8N zaw{V#Vx9V2zNR2?C>};N!{~%1Ftv0t7rB>iF||!(Qa(y>J`gl*o^$}D1-s$#{ZETqntO%~nn%m5`KC$2Xr`Zgsyx7x$7 zYHGi!+8yE{m?|h0f%*wX&3A*9fuT+S07p3Agm<0p@0Q8%^oX;1posCSp*~JKYkpDb zGN$J`RPgX^pC*k@8*BK&KYu(&)4IS{Rv|5B>oDvLociMvnFgEI_$$Dr4N80-vJDvh>04gLK(#w^{s0^rer z<_%MqS*q@N92iFu@M?Ov&kfLgb%ws`Y#Ri6GL0U$97cHUT$+0sWyGlFF%*QJtWZMh z;5iuUmM>VbZt`kjPuc$QPGX0vz+b&78Go*jCft!LDs4NgRZTJnRDm94yA0=Ep ztRBa{6SR0K3ehRX7EtWiLvf-#1FQVXxbAPDf_9uN$XKlukqt@tri829AAPnR^8?QF zvycCs-8L}I+x5vY%j5&O)O310aC}C!gJZ03`l16t$^PZmj`fQJhuf`sqC)luTh0(;y&kF%$G|&OlH21!9C6209WBN`r-Mr z(H_ece;A=`7REnL5H@A`q}ELI+|*}SwH;>ECY zYyCA6Fh0ph3z@Gw!x=~{xWB7K!_Vr2m(EXr! z%v5*1H{njNDSpIJZ}7(yo#wrY}}N0}(wkMDP7X9u4{c1gKRfX><)zNqb#X($MLjgjAYY!iH6- zW1)so=ddCgun+7fL3iU$L)$Bj0Wv?{>a2W1|CdI&I{nJ8rqt|A@1*hbb6 zVI|DCc><85v|Hg@5-BYr>-=NFaIM9R(cl(l82;+O?OZ#z5TNSP94%f>5_KZtl zC=8zPI;e#0pkRfEC+ZG_)&y!21t5(Bzew;bwN3VTK(vPE{@$?DE9j+*17heQ&;R`L zT(QV7m@W!GnwVtzWOR5FIp%z9z*ARro2vW0EG^P&7y@s#Ik%djf|1@;Y8x<9X2X0T z{4~lPd*wO{ZBd14!)?e^^{MwLIXMiMD@Te(i2U-F>3V zJ(aj}Uhg*=X@jT%wLh8>E-ZwK2{=o{(3%kpEZ7QXS^~s3Wo90(!0V5T)6>lPh@Tfj z&T`hV`gO)-Kg#>QW_Sz@x1J3Yk<7UCoh`0= zbSrbwdM=@oy3)l#a$>09;D(2m^LYav9t;W&pEn`+JmMs%Wl+XUy!Is^P@sGyJoq%F z3MxEl0%m#f{*-#pC_2jCK^@d0XvITdJgKa@y`q6+jew zI=!wzOZ&C`Nbg<*0@e)uj zWW-ZG#^<ftE_)W-t%oH2ORb|eT z>Z`}wpB7VkHNxcHE%y8X;Uo9Oy3&j)$alfpPm2dOj#I(B)S}7^xk3lHC87G!zrQB7 zrVe)4Ak}bJ&xKBxf7x_L%0im@uX^8*pW>#j?#CKT|64|6%V(QVvPDOLQR=@UwbGR? zjC}XD8)@${$B>Nmg^DjiEN*y3;fNXnGhuf#Q{iABtx1myx@PBj3nyK9{pQ^ep}>U^;-m{DYEdHz8~mqOPQ_ z8uaV~=F3h?JqP;-_7}~|>AMsiRG4yaemWH{IZB#-EbXby9IreyFW%iZ$}vk11{7|Q z&|crKxaB$V+&3OoD%|IjDKvA8yIKdj3^ygDUM+NdE9aiio|cd1mWP7k#*@+;Lb@ah z&|3`jWyzI1fc!^-=M_Q045eyA_PDrC6o3|wNENmOvF0hVyzr#wZuEC1n~}vgAc?k6 z(W;vGOI;Q8%=yk+HZ0Tq-Yn*{rz~bjrYy#qT!c0?Zrq8ZB9xp|W<^v_%Z!5*Z-G0} zJhfutS60nUGff28Y$ycp0$&ae0?6>eEsflL;!`*j;N!5sb&9c@t$>7%2Bn>K6y(Z1 z7A2CXNhz5kT4%Vx7!sXayGKfrbAwtr)RK+0f*QRT*zzsO|Uq}ULB zWre5$%x|!y&4vVY?^9QT9Y_HYspBc+ipKK?jyH@;hm2_T6hB)Gs;N|gg|5u{!U-_f z6Vu_>#A+3|5zxK33j4ib(Xmlf>vy5o928sF!55OL)i-U;P6wPpxBIESmBvg zVLDGE@6&pHXbx5@yQmkL&2NQFj{pNArCwP80yhr%7iwObrvm0u1(vZEWr>Wnma2N( zGFcD`l8b8yI&a##2=sS-5BFYCDqDE!kszyEIo*V0QQpRY)7`{x=(G zbL!e=&dmZ(Kjh_0^Pb3?5L;)V-cK7#D1lYl@9!*laiuo_zy| z@M4(#Gtx>Lw2i?2bt8+)^*FKss=d`cV|FvMDR;w(o|7b}^fivvaQR7ea-00~WY;3t zoztow!q!tk3yzzQzC7{Vo*P?R;dszQEs8FvCbHl{A`j_T(3qrz2wICm;jWKZEK(oY z!60g^y1BvUG~Rb(Dk<_oJH{-@w8Twzj4)71Gpw zbKTbgt@SN?J{yJNc`S`}>f{}GRe6KaDm^fZp)5xe!RWIwog-so?>CujG! zKAj;fT>Gl*DF4z;8(IEIv8Rtr^MvrB=K9i@{trj7S{nV2MO~) zBfzq@S9oXob?sIGjHL6vJUnH>BjiG2gR(!fbv=@o2Z+bh^aan~egc_a5g=lDe!LZ} z=ggn$wf(v=7>-=#6OgkC z!T^mM3c#LwLz&ca9(j?n@ExFX#=6Yu1m%fY_c?w`IS`(|Ob^M!5>=#(%dCyzE zl+BfDH?&yfrVP0z_4RP&1TMSj3stm{PqGzK|8e2WRP!d+E-@ZqeG>edA5(W24{)%a$Nczi}cM6IHwaq;SHwr3!WT=GPnX-n-3x|t2W za#b5o zb4MjruP7!%W;(`T@{eSSE2=xKG>r@s;t*io$l=`DO11c z$GALhc6DtmCg3uQ5C6qj#MSIAS<2X*dz1^)4*dC~%39}HG`w!6BiA{Ahy$G;kQhdp z3?=Ixy1~N_8fL>zEVa)=5XVeJ3|*gD4k0c^=aSjIx$D<1;j|VnMvrvy%pV@x6^V`c z{(CQ5Yr1-r!b&3AgvwJBVH}1iL&`$t$WDlgE0a)+s*aIi&nk@d!Yn0KiUftS$3OL@ z1fX?$Zo`n?NwZU6ZPISD;Ee^C<`5#~wxki__z&j1n=^j(V{aIGQ$KKX(0bywXt%O* zxBKJa6Z}i>{loJi>dZ`q{6}w)?Nt%w=H|!g$gb&v5^JH}MYy3ZvZW4svLpxm!m0DO z)7=H|e|SLGlNIHq2?V6cmK^ebxqzBAld}i{1cV$h8K95=rZx2yH#Km2Ef&6lPi!Lk z#Db*D6XTRCZLLV0BI7O(_EsXvH&arpQ~F5`syEeI?+RPMJJlJ9(dZzg_xhT7;#`*d zey_UAfMMXAm;Dt_o4w_U{}h~k7d&bIY)Zq5Vj$AELpO6P@>Q}fh9ac4X=9O-^?(XL zj4&z7{OTnF(xO`_`Yh~AmEn-B$~fkdyQe*j$tSt7Mq*;GjI*tOb=;m(UkaKz+&RxF zEH>t*A!P^#Wh!j{agRuq^bWh-&#VI#$i>6qWE4Ye0NrNBG;4O-RV4da`sO#Wr5`*Bf2TA1$cq%~Pv^Mpu)HciZDm2z&Zbk-9 zT>lhbseGyTg*IvnB+JfyEuT_HxcZq4?kmd89W9`s{B7g3(Hx5;iMKg zm`?vP;}GUssW%;Snvw%gHDN=x1;bcT4QAE_5}1NYy%VaSk{pBTS#x%Nhy`Q0F;ym5 zkc`l$TAfY$fNXgP!B-w01Ej+QO3Hq3XWOyaVES9cRCfnGKe6EM^=}X9#{r>RiTN?95@52hw+9oTc-E` zEhpP!H8Z}%xrzzSiAEBDVT-K&hpDWw-T{0del+9Pip#*;UGce>`iNWO5!8ob5J)q0PC5 z4nC`((utEPQmY?~x|+YfvWP;0QrRkX&GS%!Q$jU&jM)t348fll6W3Tu>t_tN1*aT* zn59?XzGv7$M#U0dEVz{#iV029bcsUw2A{bN9T&`K_{68~HqYtAz&&bJx6nCI4O1P- z*K|H}o%olaLOKa>E$98X_* zfmA7c>zhvH$Nk9CsLsZ_&%p-(MF2!c-5I>}V9hWu`&9iC8Rzhs4)F|05MAeB@8(nSLZ)+)7GJm(I zhYcCIxUrJr{#h4>LZiY?)@L2kL-?k=O*|_92>o?Bmeg*}37%SNEstsb^Q2h;_v}S_^cP-j+DK>5ReA{Q*HW4&0sh&fvsRV087iUTi9S$L zMzZUK6T8Z)Ex0;7W5)IR>dR2p*?+0LKb`+5;P zqqjSBKITGiB0wLAVz-F)Ze67bXLSj>KIr+T$a`Tx*X(n{lN%%+Jf>a_tA*_2cIXNV zadw+c>?_~Wa*Q1A5!s`ih9(p3FY$e!WVQGzD>dTVcRzcG6Rxvm%^kFZuPhcGInV&u z2jlVp%ayZD`suCi4lYToNEY=(+ePlaVESx~jyq$^M!*37@dwLSlbJvcqO+XR3mYV~ zH3q!o>rr?v8N(CtpkGDLt)KmiL$It$o8mj-#F|uKG8E0*!)-Ab(SNt{ar&1tMsY@P z(wry z4y)4}KV59Mx3a4p7!$$+X)dUvk&_+NAxqtqH38PW&7+tq1oJ&hR$;H*K3Td*S2ztf z$hv~|tKcclm8h9e5&hW`Kkm>OClE|6QCSlZ&ijGU8kL?RhE30=ZcO194~h}N0)ZNu zAc_L8D2#%yH-WXwzEsFOoMGZy1tdo+rq8pjcA7I2%>v3~E{7ci*|frT^Rcu^wdjN9 zFhE=#?FZd~3V6jAYMb>bTw6_CJA%?MJ5Pu|{cUM9zRHj!B;1 zM|IqlaQm|JA3WC@(d23oXIP7VT*J1S1%M%82&6#PBYm{l$pCe=+Br@-snvUh1zW6< zwL%RJdG%iPtLxgpXr*ufWaau*An4f00KVx)T2;e|r*En!gc+z8>F^HN)l;YWc&|Ao z=qQo0Gk*(Gq7|g0W!_q)W}8v*BGVeaQ{~)L>`N=KylJ}1UN0%iT`PTbI-)!?t}VD;Oh2kG2L8S5Mdx-v3(Q1G{AWxuY$SsW zpfx@!t&f!cqUN+$+ZwpWa#yit0PaahU)Dn`^VA5@yquS3O5&|HowC-G(L{O$W|D`D z;~JI`zZ3Ba-dKaHLikZt*Y*NxC}rC4qivci&Z1Jpn*&V97EzHWr6b&iAmgwlhNLKY zFd`*}<9^eGgrX3mX2k7ztc!z4r^=0xpd+s(#7UVpU>>(?^v(|D$mm0}QKvFj};HO-VfTkQEvnL8mO~%q1^u=Bkz8n=nLz*%|uo zhaV|t7k9Aa<&_%AuEGRoC(1VP#XsMew$3sTVW5Nd6);H_^*T*TO(mhHzp@44z;NO$b3r;?BIcvU9mL zYmRHQRZEEfu_U!DOia%0$PZ=-QvRMo>dCul{HdZWI*m|Pxbt=Jx3)P$rKFF1T99}- zSH@xq{;YuW&*wdP>Owa8KcOsLdu-@j?zyS+epnINMTI^^Jb<@LJ&a8HYQuqaXMoA_ zr&Cpf8Vmj8AF9SbFTW7e^O9DGEWlL6^`5kUjD$Tq`d9IEL6hs>@8kcpG{n5M|BA6> zkl{)q*0AhuHt*KEbH>@TV$!a(iBH#P`?PI-J*?BE)k%j%pcu|wWxu-kuir*a-R#R5 zRzqNu)5GG{jssjGU~eO$f9@v?iJKBi$Auo0Z?H|W#92ZZyoJEsd#O4Di=W|(3+Jk& z)YZrZeJw{j_Fp;+mov8Q%qNt_zc##P6MwuMvt%pmP+0Km{1WUi6{PocGin7vZg!7X zczsf*iWRL~-ulR=pzuOJoEaEBrm#9-5zg%)h%MIAi_;7{0GsVs!aobBL1+M*b>XLX zk&el?j*HS`$M~ygStpP{QM=yqc-(D!e8(KEKTd&h+WCj=+GTJ$zdYOYfO(zc%J#vH zcz9zMEGu=DI5ca<&gf^Llk2X1-Ujm@`%o{?r zA>7ih6^+8Is`wOlEAJvTms=}IoL5wqhLMJe2_T_iE@Ea&Fp7ln3M6LpbMO#+#eUkl zf={b@uS6nl`f3tx-x(db2|&exI_xe)_Z_-W(kTuQvw#U z^)#*^H)e_w4JUs(Oeg{kPk_b5z0>)TUw3=(qPC6s93CMJa_Q{@RNe;rXtHNRL{z57 zuyQeXuw?n|+U&98#2V4yqL?S`qtpke_bZ4{HkWL)rz0pOXKn4t$PC!TVax#h$I=e` z*OSFA=5<%ad4mEioZP1>+^`zJ zuILU;)+UfkXva>@$=T*zx0}|v%*2RxX>DU~Twhse5KpA8J{$W)AIpf!?Igy?@vs(k*8Gg_U%a8Inqmvx#h6mby8_W=bM%St|xTblJHj+<&6d#RO^JB4f{$~+eE6!>&(|ne~`x}+J?~#bg6MGpAp?Y=6$O9Q97`W|vT3zxbSYo!HSc}Vc z$)@QEIo7;=b9s>@kJhL(6U1dNmjsm!;VD``-_CGT#boGD+uZbgL8Tj^FKPjmOVF16 zo0)TuBD5nqOgKmUI1Rj$y&==E87nZQP+QjO&7g7wY);7Y6k`~FwT;r;6O1@oclw_e zv#_I5pW#h_1EA#j{8}Mw2glAkf=-WP>HN@ipcS6h4We^y817Ro9Aqo055^hoaliL0 zXt#ELJaTsM7WvUNP~BB)s?#AeaeW-I8Qs=fDRt;kzu<)?cFoYBAcu8j>mAmm5lcEY zui>ga5UmB2#V;8N_=7M@76wSB-I$E55%mvt(X<3Q1AH_>ztX6t2D*6~Ll2%s+Ku`> zog!ca?>qT3Ifw&P;5_}-z2ZhjjmEsR=2M!8KKW(!kf@lTdrv=wn(#k z{yd-q;px9seK!Ua(np0?T?u zM2Y3>3&ht9^BcGH{p`Shf?*|ZYNTn93qbV+#qCvJpM_-Ce((Zsrh^eAmU`#u-^RcC z>gqB~6@lI35o4`*nPY0}+J64$NZeyizLk676=2DsPK+lsE@I%Qe_p;cTjcBg2LZF|IB5N=ZQ8CwfZ1T@r6xZQT zWJAwxE_5@BtIfT7Y6XaA>1_PSDGWi_ZLV>Ne3biXy&n-qHD^*%ov(PyZiCfbn4-fL z6dR=s2yb4^B?~Ck z_b3exakUR1sQ$buGLNBBFI1gF_Sar_N$YPX!2}{bDs|5l>ZyMUw(hyxHyG0(as)9 z79Y$z;zG7WNU~q-%;${iRdHf=TZ-nkC}_aQfdp+OXgfs;{&Cnl#jyrV3hkeY!|g*@ ztwYlbOoGe}$o}mw0?I{CJ79bxn>=JAOPAzesxVOc1Z-s>sB26T7pN zRsUWa6j#ZO&m@`koG@e3h09fnU6lMtH)}L7hUqfK#o$*RrD+a*| z=fuZp7`iHeEl?9e51-{TvjzVw!)gS*RM*OJH_wjq_7_PmJ5-?!D_xYzzz{nsB7AlEV6S%N{coV$= z3+`^>;c#;!#-C4}xg!gjV1rbX+-}??ZW1x=LW$oT4ih4jiuepYxVuZiX>x6J8LRuV zSV7Zz_sqolL4mno1lVdb1s(OxVRl-f)BY<*;!boVheR+gH6yc3)J*66USi=sPN0}o zjjSwqZ@KuFZI9|~=9ven$bDb)=LZLINk7?TI)=`nuVCn-53VFyH+!`=PnxD8!>y$i zOwLnaytdK?7K~9~fb+_kWVyChWHwh&pcMtyyzAL!_O7yqJpiFtpX_Vx5a0?Op5*CV z&7U4}F)Q*ZQl+Kp%iBWAQZi1Mg9n`skry%55P!|UUz}R3csCEGrqT$8i+f^frT!Fc zExaH;PW{{9?GR+B{aFGr=}UHf)l!4$ME=;j*%1UIf6PiSuT(<$Ks@n&Qcu`aLISmW zVZ0+-)%HieJ0QW3)0RHFIeBt<#I|FA^-CLnKQTKxAx7@WXN+Xu!bN;^fd2*k ze_Au$xq58Uoc~n9_Wu?U2sMa&a;L-@#{Wo$GtI@f2mg66eANHDle{N^m%Jd!2xzO~ zYhw8meMb?=s}rfDE!AqYQH-`z^=tE+YU-KJdhV#y8VE=JM;oGlsd1;sgwqI`GKN`V z`p4o>WBMiGgT9mg?b!dP#7uSg=~}~rCYnx&6-1%=wYki`&UmD)3=8q)%C#iaT`_*V zAJ}N_UdR`gZq;V5YAbwjctL-&1L3W#o&wcBwm1y7hf5i<@I+oyZp|QOr_MWM)W(V< zEj`^4>iH|Ns2=n>b+oI94sTr(ZBy6T!{}Q|OKu;K44D49?))cz%OS3gEO@#{E3j!e zkMv_r$n6~oG^YouFFWI<{X25&=?=V8wR*a$A156-?RLE)L&ajeIs`r|_WJNT1SxY`-~A0_!w@pq<1CD8Scy)8 z3$MFLSft~!H@~9Ez&4i``(vwsoRv3`c9voCoae?9fo_4x9GQ|Yh?%#Mly42asA18>JSON@S5Kh6xS{See5XrC8%nib z{m+wRZ-55&!!Q`$dKuL!{qtk~iIX*PtOLgoG#_p@woU?XcF~zS!N-1xsa*Pu(}D`_Z@0NW&L;Vh{|jcijsIEOjU|3XWvjx&aM3cy@Yd zd-B(j@RKck$S}1WJh=Biogxem^~mpQi-nPU16inh_bK`O%eY=@TXCVu1DrRCz^DE+*@j0 zo;^|Qo<|8A_>2j6_VmOjlNS(NPb+vp;QSr5LUZEKlomY=T*$yxAfuCUS_L=q8{{@jlaXfiAu7zrj1?2~Byw)^fSTD5!P z#DDHYo5vZB-W>Giw_iCq*5W$pr9%`yG&ng?$(L<&HniplvGek%K8=t5U6#7;9YT5B zM7!^>;DOc71c0WBg-`Dq>fDff>P_N-9(t|xH)z{HB5!Yp8cZCM2t8_ts- zoI=>S`~e|GIVTU~GK`lbd=Qx{HdunaQ3J>b^21}nb{+D)xfEuNtjokte>||M9Nie+v~~V`ltS3n{e<6IL}?DHy0S*zj15qLn@dkPBRLsi^87N zTJhHqJVkK=ogbZ->qqrxAnRV@H)4)&QqLMVI`Lg zEwWC1pG1xG00ov8KzCC_5>*~n4}>oDE8IS&HB@pUxVNxzyE>@s`dHw#=+*KElz`Sw zqdGIjYQPI8&t{XVFv=xUuIJ;cvnu^3*3zA=E5A&&<9^WCTpUiFgrwe%&>bc~h-IE= z#B4Ukx1xd;;A$76giK9y;}k0=rEQa-f#zpU&X3J;6B^z18}_CP2D!|gF`)i_$*{ItZezlK_jE5uB=sp=y>QrA%|^@{P((emri#kbN0ot2eFS(y=BiOD0Z zu71vs&7Y`-^UU*|CBaf9L7>YL`p79^1n&{z6>N^u3dU1`W5QQaL4OFQCU=(~vwX({ zT;`-RcCvW*guexDGzT+D!j}X2O8o7e#ruK1W9I7c5~3wp*Fr-^;iK7++uOHWaplg$ zoQkDt&Pg#3Xx|Y?L_ly_zvTGmtuWaJ)iR%=fXc@~iO+e)MhSwaud7~*j%;WN%NH1O ze8IftA7u9Iax~5SRuS%x!`x9UU0WGDM)lOrWHwnMrE|}TIqe{YCc2U3}2B1 zHuZZ5{hTInm>M^giVZ-#OlrMeh)$D4%AIEwrd`u~z`#^~lIBlA4%&OZztJL5d(~Vmz>DdD|1^k9Fxhym zEUR=b%4^KAQBdWj*!h1{ol}rz!M3f-w$Wv~x@_CFZCiiYwr#7+wr$(C`}Wyq$Gve^ ztf%#u5o_k0ImY;M+Q|B|z1yQ@V{VAXBF94~E$lp%m>r@7ppkZH<|9D1pZFIjj4 z_Iy-0h1+a$*S|Q9{u4-QXXXeS-S`)3wnka0LJdK}zL(L^wPbn`%CH$@bCi_cyfl(% zx?w6l;a6xDAf7v&W!cmEYE5qlyNct&?&`jx@yZxo7CJJM+EI`IH@IV$1Y9%#HZoV) z&A92!J+Zj?v9YBoHXVA|%5BV50BuOvBSg-HdE8KYJIT%TDg|#&kcSooq#MyHVFjy<~xnf%@K2ufG1rpZKXU0}F z^^NI1cL9=6k}9*TW?%yTQUrfJc&dHyqxj*XhIq|!yh-*qVS+D;uh`Rc`0&_MRK>S) zWD_O5NGs*?6<=N-x8jBNwCzXMGg2kL8Y`wH~Ms4{DB{zco#g=kkkg&CSN0! z2zmSqVBVUCQ#x_6bhYSTkl_RxcxHQ*(tGyBEDjVX}&F^~c2j*^&Rf^Yl~c|T&r z8Grrq*{LjtS2tFgSB|QXBgKPdB{YLM3Z21V`07r>RLF8;O@}#@=3{~Mj&Q?QYg?Z^ zg0u;`0| z#q%zCq{jdth9*9K2B8H4G+&%c%r}3uC(7zyKn8hi;dE;)#fW8zY%JPXP{D;ja6&zT z07Z|TGXs#Ec=qgn3PBBMX(skq^aDo7n&BN!u%|yLUcUZbfj;5@uDn8lzuT%s zF8oTU5te!5ht;@^|Hd<);2$cDpz%!HcuMbHtz0tflQ-JFxtkpI)aaw|hW4nx+#=o{ zWrpfhc! zo%`KDB%SDl-BlV-A+C~PvAp7WeBfZMb!)boI{O6sqzp3fnVR_2N3qSxapNYf4dsF= zXmTzqeYckw2AB?ZZ&>{>mG-|hh=@jPJ>M(aRiQ<@758=tyf9&ZLH|2AApbXnMByyK zQ~PgfmRGmmdrP1XPOp@3SmG+j&t8&F@y`Z_vfeQ-sFCMxtqE za%wZnl)$76)1a{#T@4<5PfRsSJO!kKO_BgyvbNC;X|Og(3F*x63;?)}l>Z*ntN0Hd z1*rfm=+d}{+QQ8`@9w`rA$IFqRvlB^+{*2@{O$3j^>X)Cyk#k~wH3p zIAkmUs&H%h6Boc3U~Iye$pjJigfHYvntLJ}gv6Jyu%N*^;_CW+_pH8MJO8uuTJKG0 zYBE*(ZD5#u2l>P?&N6MtJKo8C=Nsfj%+@x{V}m4L!O`=27olfkvE^?!A{jT!*(y^; zubuW&S^|vPeY^))+_n#_A>oibxaO4DKt+4W7Z_!+zX0O2 zy9wKlb-Xz%4_LJ1#lF06FS_Oi^UZ?g=DQid*YEa-5_3bD5@IvDmlV3|hTRnlTEQ$vN^0i-7~KHab#uyK<0Xek$e!8w3UY)Heml zx5#c%s9X5e=w(>w^cu_OwU9@}z{0LPYj)h1Cv>0F^1D~Y<$O7tA8Pr!hT6V+`EHEf z=is~hKXw)*6-UVOg7dwU!1=O(r#TX|a{f@&jwV8O-417Ov+a?4G7|2eZzE?7{*#7(?rG2Wb};Iv1Q%lU*SqE zKB_<0(AA{SBsVj7k3HwovJslg2`u@!-8y>zIx|$w>X>n2wr+blp=A7>>PllTdxDDz zv$CA&YrmX}+myT2KrlOBVJZ(mPOa(GygaFPbxlY;*{arWvrQX0f3KRkAVJc?82`B7 zfczMrPbm@mp{1kiP_M6vno+dzM}&0X5pIE~AH3Fw*WH}ie_a+*d>D%7uCzWuGNA3s zDk{t?k{@j?7|OrjswdDZP?PxLo93#sas|s7mR}pzVDLd59Q~I=4~0*$DfOrS z2y2$fPN|h(8Kf8=%XcFlpKJ^la-zf`(?AzRH!OytCJH1D9#%l3N-U-XCi)fsb#8Jc z{ue3&NY5X@0}||GoF+2GPaO>-HfLYDKx^nhnh(Sf*+qEYhz18ZBdn!N!K<;c*b)i@q)c3J89o18k+U^U!5m3qAi>tgs}`oeX$>8Diu z=hq>v|#U zy+8fzvh{0D1rx9Lc+TVpk2^hG^ycgqJv?>RXK_rTYYOI zT(AkG=B5+fK8+XY8*Tc_gb>&fciqyQWcCbteNqG)2fZrYHn}Nhre*6FXyF1bDx-fZRDK?c}ve7!8~!`q#f_V53%OE$G= zS^)o#9+^a>z5Jie>k{*JDfVBC_7y&fRvR~oK!*aLZY__hiS<(*@z<~=uf(}vQ45ru zCL$`rPKI$(oOv-fRa1IXChUqn52C&~M6EJPCrYaw4P8qO36}0oe~Z)H?0>Y$oLmf; zNKI7XY;xICot?=?zFTLxPQQz@zNj!W@(^_;vFkl$UqgY#gNRF7T(#x^qIJA*_{Q&6 zBP)Q=Wm^R?OZ1wy6d0KXyxy9iWJKeu*A0}-EvPBumjNYyG7Ae9EIAjKold*Yv6DI} zMm+KA?ay+R2v_o4)D+7;`0Nkc?TAI0T+4i5PGi9RxCS z6ur@`iQ9Ubs8%A@vN~QwWeixLzu7O-gaG_vUOB<>V+y$RKFL~_f38;4{v#giz$9L& znwUGlT{r%NB{{n{&8ehA-j3;Ed7m>=AIgho6ITq8@fnFBxLxa3 zRy)h=9|Omu>zj$@;UOPcIChUC*mbdA3zK6zi6fnpZ68<_7IXEfN`aGO8UO|Q*X`tBSpg_x5eQ8>gc?aatbYMK!X~k;IW6gVh?~fmlOQZIIvWUs zoT;39O-foM1u2-dT~73UpWfJYSydZG5TdORC@$N&%+E<&P^MVtWo9+JP$=AEHlmwA z_gZ65&e?P-v@3F6i2JHJyMg3pQvre9STJP zqqyUFhr%DNId~B@2a`2%8kFqMmhQ#4Q^3J2dN46~Tp;0t)X0;Z>qAQ=OD#8*WOD9a z-NqZ`^r{aY(v{tDKCh_xGQ0a6wC&t;7&ko@!*|LVH0pLTOK@^EM$hTG-T>J$x0&{* zS=-#NGv%exl?XmV)4=4Z1}gl45%M zDZezj7=d88X821*t<>v7O$3+BRbU5LqfD#1gv|7{QebR`4>>O_=BdppkXlx2qj=|W zur7=?RWE(;nx5e@d}QB2NdcAuZ{WPfUd0x>1WH{c(XFU6+P}cFK=<|*TEn*)Dp9%| zaVr1Da5E_FeWv8I0nu~Z{F;;ZKi!7&Uk}W9FUNG~0TKBuXZY=Q-`)KLb0$#W(BWs( z8Q}zq5GDsdTo~h*A!10HN^Em;nqv;ICXBALmm(1nwP+y7S=hGm)pDfP9)$IdxIgP_Z^J*StjdMe3F9&>|Y&^x>*IPB%Ks( z_vA8+E&cy){iYm4`~Ot(CPZW6|8`CJiJc#zL(`m$kRoh^JzLbBc9;+dFNJ z%X{{2JG;Zq3;`6P+aqgoMJj1R=|6k3(K?+X0`g`q+1PqILX}z8u?D2mL|rm4Gm8Qf z*w5Hs%I<3Gu>M|@hA?M{RF=`4=;Qri7{PfY=Ktxc)hOxOJ7TByrkUBwY}Io~HLq+E zHMgMAvH9~gd%+O5)!=LH($w&^D~-A%Ht*>Zc7D^^_ylNbUKZ9rfv78uNRe3YW^i`L z5vy`{%TDN)a_nOJtL*x3y!fr=U4JuE#{t>9Bniv^^AQNeJENaqJZRKDlb}F;WAk zFpTGYmh1#1YJ1;02niTryNP_>a3lYOoQJPJ?Cp-NGJ>qkfdHUrGi6`3A$WUM}(Lr32x@-=$9Y}2F@(jels{Z?;D>C-vyCV zGB`c<3^RTh1ahZVO&|BE8G{9D8Q0Z(UYQhmo=ucU=UhJ|HYqxVs#SJ!FAq2%trgw) z^aH$GFm&Q_#4|stQV7L8UOUE|m4JAinFX)(Jb=B(>e@JzCu7V2G9$;fbWu zh4~T^QGZDmQH}Wq$BPkSxy*X&DEx4@#Zs|a7c=51Y2!Qsjy+NjH8hn>;G-u3^A9}8 zmz_y-h4NZHI1bqPc$|K$^-d3EOx+h=iLP%F8u7B#mZ4@aPEc6M$cXr_PLy zORxTNXR90Atme&BHQD3$R_CVzLN}@th&lU2=B7A2kPom6fRZ=Ti*rUxma3$6^P8i{ zTkke~f|DBh09i^YxmVv3ABAr_I)gsU^{lnP6p(n$jgCRJxV(#<55E$FL8bb`Rdt9i z(sYOgktB*G(s)u9)wyuO2|!{0J&3y<{i|ih3-*ftY z)1d5yrnA_nm*ZGV%$F%GG`z6N`dKNF)X2i>B2!tFR1qQx5!FtG(SQWf^UBKZ5()dp zM#X1$lM36Q$;MHpt1P=0j~n0P7iE{d+at9|Q7F_{^#&k@okB?d}thavHgFT#M#A)ozeFyO9z-D2SW?C7Y zK}~pQJN~pgLb$q)fZfRj<`o4is}vIoqC+RTY8DQm)kmv5M`>axu2K=jz9K091T~RHE?1XI` z6wLY5M1BKqC_^%9fItVQ)*gFjALeZiS6?9W4kPwzzY7}4zuKXRgSh5=JKAV2=|WvW z;x~x6w(6%dd;nN&^7$055}&oa>Z{q##i{}&gk}0E-m62%!7n~P4jLpX3X6T%Bx!~g zoOk{OVuF;hunLh47Rf*>4%(vad#YZG9<8u|3cd`t2Wf?P;jdp9J0%(QyzUwqr?AV% zy7vF9s!v|&5?W{=CN)v3k#$K?nH@HfiB?vDwG)+;$^dv=2^LP_!vv!9QxW=DV@~)n z@Dj7p)ujpYg^HD;nNZ_{!HZSDJGfQ2{_eI6x$64K%=*NhG#Z0BdDs&6d1dm1)3QT17j-3CaWc z+_qc{lksM&%ZO50jy9{lQF#1$yn7tFtw4fR_yJOrAPwsgmCFH4X#)npjZ zjepI`0NA_UisAf9_;gg`+j$4>PMtwM?=!QT!tebh12#OpTQxjuMHS8g z#kp@49ndXUL3Q2zz84$YM84C#6Ccz?&dd7~9JD`HA)(&9?blhm_UE2I_&@}YP(t@o z9kA$_i!*5pU#VT8(-JpZTmMLBu1ViSt@yV!>2#*?Ye&YqjkD(tdZ{qjlB9AioAr?8rs^ zS3NSAe@=e6F`xs&qT8itu`Ue@5^8rG6YwmzG)Gn0L%&Y51pVS`LE{ag@DkvX~+umeF2l)ZN4omHVCzD%gp3hIT+pT2%b z$CHUSq(N|2OxXh+AQ=#eI%fb2fB;1cSZ)9A`Jry*onHNXy8t6t_qS@70i>>9!}X{z z1BKP>=py#om^D0Sm14w=DteOgY$xqzshB0byFfTlICkt5MRyCjL?wK6@!AGvb(pHa z9BKNwGxz?6G^TZ%5y0(>e>gX1+%!^2%14wj<|Oa6WLc?+j;@T-G_~6wWXy$_%n+a? zcQp4BL8}FIt*=KfnJ!!b1}If-?*bQbql2AM&#Zdo?Sv7!R8zc*Ex@+`zrE`N7_oj* zsUjTR`gf_H8Ies3*hQP}^?Td>9-J2heT=bBkTYqwJO@q_P?pmsxx7IrsNKcit~!`{ zDo7#U`yvy);Ewz&&EI1m98%#xt)z5iVFs>GHjVFqdn(6e$#?~90Y>Pcrb0;2amz z62$`Knzh&ick?MM0B}~AE#!~qqTRh(cWQ<ygRE7qCl2QF&}jO+CI zrb?Q6un%;!HkHihLbI`o!-|gvQSj2J;>P3(wuADX2Y3d>cOX;+GP^j}3f?L{woUcS zFvLl4lC~}S&w*+|=jinq1P=+T9d={4vgaGfEFe>~=s=m(0KF(ywV@!kbA4BjO&1}w zCYg)$HmvZ(bbnn^XLa>SpMCJkW4O5sXV+8wyr>`#B4zsCh7tE$V&F1wfrrzU&_`=& zpL;t$kD$0{WeT6i9Hw^I0oW1H%p`Lrv4(>EUPe~xxmYBi6G5(Dv@({2nYI@#ct%T$ z9+9Odt-F4k0Eb~*i)*X95bo-LShmaM{J&m%QgevE7BO1fKB|!c%`{lnXK;U(O&}z+ z0(;1!BI3j4Qq5-LMHA$2E4eKpKuA2mJ;qoN1u~v88D5I^a&^ndgNqNiUxA;OXq%W( zUoiELcWLkE4YRH1ruQu1s?lJ%WkRfp?T_xgE`Tmk@^W8X5z1E!104Apll8NzmU*l|G5=WJ2?$Ie!k%ed z*-^v#mdpOz;4XHUWY6LpU2r{aJGW+N!2XCk?oxmZLTXfso*G5}vy@sDN1nmE{{ba@Nc>4WRlq z1Cmu>v1RF)xfxRuyJlMt^WkA&?L+Q+#y^`bobR@CeRQzxTJbJWplyg_&S7OcYyLax^6#ddW@p?wllquz$O zFS2ninla64+fM@Ul(pPegq#+Z5=njpo=m}wgujL2GtCC3v_dlohoyx(RGFBN&@Q4J zSv%F!J(Sb^fJk3oktpf69aPMBCZN-V>lo1K?>w0WWjItej`k-HZy(1B6q7p?!y^%uss+RC?gy&W^$g7gUn<8fT zyrgwW40cj9#FRoM07a5NO^eipVo#>=y)O`*Mg4{+-is`_lrl8hpo4`X zRbx!R zOP+_g9_bq^VbXJl_|Tg-(bmphJ?k2_hYlDRw{sD*!!|)R#=XX;T3B5HAg6L)Eves) zt;o2eS!eoGTHn|Z@aif`e24X_+aIAhwSW~4{t%~+b|6Wq6IuYLq&bC)()ZK;9WNuu zDD4lPJ(^7iPXRfb)*yUQsb;i6MSE11ul(Mfv$b;2TbRx^fQSOr3L$Zxz(^LLAm))) zdk0-$uuv*gru^F?#Y|8afPJ){K!UPy@0J4{>Ene*E&e>&`Sd^PnE*|phid{utngoAfRo^r8j1}Vg;&LU zfcnIqG+$#sS9gQklUhm!Z@qBBQ#}TKWyE-VaANe~OBkCT;n;2cjxDjU<BU5PY1S}1}pE$d| zyHV&_p1mR}5u=6@fEFeGS-m+}d)8G&fuD(CP-Nd9Oa(F#r;aSc6?F-lOkiNy@$rIq zAe3Pe_vD7>LtHTNbo{NJ&4iT#7WErBbvpR3RqmkuaQ?u9y;Rl<#1G$wk0#>2qv}?g zBWwG%1G*d`BhU?8bv=3uas$}HkdK+`>HFpJL5h}Q0$bKyfCchtGdhlys4PQykuRfi z^H#L&zwmH?d9VZAf|o0#5B);Np8CN?4Y{;c399Z{hkt83s~qBzy7t%p-soa9eZx2L z0X4mJCMP;Vi&`>`nUVHm*-R?Ae85!4Y96in2D;TK&R`8L_3_$6&=Wf)N{ z+S^u6HY*DIK}JdS)EYfhvRVqAFTRq9f%Ah5A9i6CWSV;hNs?WN+%v;p#vk(9?qs)e8ZFo7^)w!7P<&SUPw_-k2$1l9f)T983lAf=89xP zOeZuek^W6`r%#I(p?ZR}tP@}0bor8wpYtNPE>=wAVO0o^qf9#K#v|Dx@A_iR*UU*H z-ce{?q*=k#-E-?Hi!yOAO-?!WdBUH4x>LWG_oC2^vr7c_48;%O4+P%(=a9MsAe}{n ztVJg%I%!%}r4cLLU9X|(>yrNkcdf@bt*k9};%-9Q2r`@7LXqbO6ZO}!IYhTY-2g+O z-0b!6FH5tiQt^FH>?>Rp^@0XH0z`lrNOd;|V9hCPaaDK4ym>QcB9v-x@YGbz=RPQc z1iaKp=NMf?RTSM?=s|;lzSWrklplo=zv~6@?cGOr*7kTjS_Kl@B@2bX)izekB9kU2 zcZSJAoF;e3XiYJkARV;a%3rcu_~G_Z_qt(!R)iB6zV8y!zWNI-c@xZ9ghYdnx+JrYL6bfPVi9rIuy3EqW9w}nMk6KX!(}i(qppRo%y*86 z8f4@TurwhdvdCx>)St>bS1fjNV?{C_Q2+WSD`6ho@R6R#ckZE=pTFHdK-ls@Y-IGy7oC=0?kgJH1QTF7k?la#TqiKJKsZauIlkdyh4Gv)5(jb z5Qu}RTBbO2MT>j5yt(T^D{=w+!!q%sRC!f9DP~)#|qB)%jBAf z#oPUc8T%{MlxreAQRdj&&Ei;tUM`j_AF{Y|Ld2CN^8!pP2pfz%xHbgir@E&He<-|l zn&3~QWu#Vbv_}y@k%+IdIE93s^P3o~2=rOGYqRZ#BqRAX2n?%K!tv-B(#3^nSp2?? z8n1h?EMBeR_@u#5Rv`l^>%qG_B!rG@pVAm7qLwjN5h=-y)Ga)Yei5UGsb0l{Q8lTl zrehC9Fu=FF&ErY(*FY5uw~@7eU*_Zs(Mc0C1PM6!#TFPKUtX5)@U`=K(QG7N6{Y+0 ztpI0H33Q3R=w}-OzIQiS#5uK^9AwgFGL=)aG>5kt-P>t3DWj5ryejfmYXA>#N1^JC zd~H?>qh+1%dHxa+dKrCho>D{hCn$jWY4KJ5?@h1g4#}-9yE@Kc%W%7 zeyT6D++Xfi=f{Ipr&lB3m=W?dwHF3@C2wnsZem#~j-3bMlJlY$0p=sqM8{8fP<{%N z#KlpGU}hsu*h~-XJok>j}W7 zHJr{{LD*v0S$>pKwHMx;Sf3NG&v`v}aF*VvFc+4HIBAjWRl$-x3&xT`VhaCPBbfST zyG#u1t^_te+6;9z0@mZYX!dJw;u%XZ{)I)V;^QZMwXejK4Zv$|VL(%a2Fh+1(x0nM znWO=;SFk-jQO_eQzrV*`I0)7S7?er5iUuY&KTVa9mu!HWp*hNvPopSFbQl8z*E1rn zFW0Dm3Oig(PtXL*h5@D+%Lo_ij#IgKx7$sR>Ac*#o#v~*z1>t79|etGisn*2(@9)+ z`9QdEoc@-~5 zY|VTZezAmx7HOs7gn+z0@(NEZWlHqU$$A2n7j|S8BYJtX<$^#CnycL#FrfFnxlnmH z(8)Ec`>bdrv5+0agQV-eKYdX6R+_t&cnlnLZdQA!5TnqpPKD$KKt-D#e{*f?ASk|AWq;nH|A>|nAtfP&o zom4|2N8vJ5&h8>Ilpylv4Hn1hTG1RV>A!IbQpv>d2iLqL&GlgKxv>z*h2j{kwcN0S zJOY$%Vkv{hx7f&c{KUDz=K(<9^x346pQ42yJW_PzQm+uqkI5-6p?E+&SEaM;E=i^alP`vms;tUc( zh>A=-MssL~_h@*)6R*7|m#+YY4WX#d?Vkz8c6de$!FMV$&%AFre#0^4$20F4ljG1# z(q$tERUTWPMjDI}Tg7yg!b_%0#Fff;dI3RrSxF_z{?vlf)Hh)ZFc~w-Cuoq=bV94a zwr}5xcZ8{Kb;AOY5l}2HNWMxMR04-76&V65kVzK&Pol$jb%ZJ=_$9!7E@+Kub+RCF zcO6Ozc&|La@=iOh{`&xtvmXPJ^~ECEEUobmye+ea?^D|xztA9c{VEq9Bbg@GbEwiVzAIVegWFz#R;j>Po0P>P7{yo8;-D0@55M2ls3?bp#wlBC@&uhlcx`lZYTV6S8 zaoXmt_wCTKV`b^=X+3>10+C1d$r(cWa}PVUJ{%K6esNV{-45^?Ug_4k2yS1ooK-!z zFqFx&nos@}T6jG@U7K~9$y2?4{JCT8mT!J~j? z9=F-T(n%xxg|Mq3xyw)%;UsutX2h)g2tg}zOXl~j5@9?c7_$7Cmzf-M%E)Kqlg=5U@gkzz@PW@NNTdMbCo#*!0)n4!q(#<`QKQGk zm5}xDIR$4IZV=Y$e`mBKko222L zEwC z)Ea;U`UKtbP)GW8=wZY2&0ziQ!b&sgV(3o->Z$?jz@}OLuQP4Dc35-{e@ey|jvCtX zMJ>|$-La5-Ts-O>Y@JvXHmVX%rDm<43|PS_YD^Ne^f?@oO0#srBO?Ouo9#LvX`Zeu zkY$IfFhmGvocjfd)pO1S#Ma=+SVNQ6^JCgiVk~z>+Hg@?61M#!-SzDdhZs$y>xsX* z_v`^RQ|@7AVbqOzhaOsLlZ}W#s%Z&vD7afp{o)lGeSi(~q5SB1n!i{8Abv_>W-3|+ zA9EIeHozX1=Gz%1kCi)XkeJJdjzA$ksha_g0TN`TOz0PcRv+{PXj>_EdwhUW(5%3X z#P;+whL1@rhr)7);=F*aLHLOU>K#!t1OXtR=@*|k$sPAntAaI#4SX?BSFqC@4sW1a zu!%o;KC31sC$L{WD=eGTbt_AU|FfVJOTmH@XxLie8q(kkSTUak^bmMUFtuzfl~w=! zdc0*8@(KpG6V?V6eOi!K+Rn=CbpBT3fz`oAOKE@q3c?`-4RL6sXIL7`K0tLFh z?{5!hB19sA`gYXU%HeKs>wo4LFir%>24SX;@_-PFEVn~;17$mV*y1^bFg$hSs@41@ zOOF+W8X81)jL7EyJj85qm`UlX!4CM7rchd)2B8=-ApMW=rwmez1ckBvdkz%1kKmhmyDLj0<9PTL6rG{<-ILCwubGZ4~l9 zBR+XQr4dX|WG?g&{d-YE9jIb-6MGpM`YUVlG$l15H-}}6pd22J{_(KJ5CPy(@Sj== zUK>h+=X%AW@i5vM9(lCZ3qJPmWuOq)aYqjhY^@{Bej4u^)G9YH~N+lW7A!MURw$cEK)bgh;^Rcm$>t_UoxqQ@LawJz_w zvEW6VInw>&zuQR>nuRgI6HbR)wGtG@SimvBM-7d~J##~BP{^Bv11#cE7MAN6El-Zo zz=nRjpYfU=;8m&)f`b>r$bzlpi`ET0UU$?6=lX`;#~gp?nC0ijb%pi@efar|vv6J9 z*}*la_TLE&;=z>TENRI^;!jT1BGmK1Fx-q46shss&+f6ZlFyg z56N;aVvc#z4|t*) zl&`faWH2&CtQJLaS}Jtg1n!X0`#%q&p^9XC5ByimEe2J@0Ce~c5vzi$?v^J0-8!-U zE)ZrImc|D@F0yvsky}TF$!?H(30K4D9v_{PW~E54Ss_6+(IAq?$4-PFe(CY5%`uO} z!xYN@T0@Nb#Jvi;oz6&YBmTrHc7;s*4F4!5t?l(_vpCr|VGk?^%7iuokkN)*rpD$|FHq7H2= zq>i6$OF0_7p7kr50o&?<;xD>tw8vQ_GZ}U0{ot#uRUtdr$-{|BaIRdbRxo5Q_*V{e zdmJVRoAA`x&~D25=g^d83S#YG4){)I`R&Y-fjP4Tz?y)(ptVyuvV z!C{wr?7Zk8pVhZB)?XusTA*urqUa>s`;QIqABZ$k(!H?wVWI;8Ap9YMh9j>$5j;~n znQGMXix=NF>c=k+EPWpcj}c}wUn=9sCX)M#cd?FOH)Y)f`FSbo!GCnaLct;FBB;}3 z`ii0ifJEJ>3w91z6&M;(bupRh0!)8hs}SdMjSCZv>a39YA8lf{VRNIh~U?euD+i$&vax1SC zhMxY%(k5q)aqpxjP0C^;^#{QQNNT&Qyt~5~KrXC19%B!RuiPO?JPWWJ+{XYeh2pmL ztC(6Hj0lR-HH@(5eo^vGTcTRCuu1g}23|FtH>hbu8wodZe8A@gFqk)OoU0kW1-QpgA6A+J`cW01rw?e#=bF!d?z|Ef47|O;MofwT&vg01+Zxm zqaok?<0kf{c-U z;GEK@XX$~zP0w{5H=rfBv@|97?|+#d|8>43J;!qV$LqovC-c8v*mzQtdfb1gR|?!D z?l_jDo_K!1w8sC#c<>rmyxNrzDkHqeln=1h4p7bVyijvtppaz_-6sJ_OV|wj<|b}2 z3S^B%=HL*64gZ_?bp1a7kE1O+V3a6knRIMP)k?eVYubOgJkHk7S$2CA7D;BJM1*-# zL%fu$kVxJ%*OlD-=Z>TtQV3aRkE0|7z}aQpVNriMn)fe)lprY|N|P-Z@gK<%%Xui8 zq3Nu=oio0sjBb5lWrm-e9Eddl=@`eSYG4Yqd3SJQ(~CSfci!-%^rpq*c32R0@@9h- ze;>aZr)5f6H>$mh_+cd4_t6b|w~`Rq4X&BWB{WA#v1*oBJC%2x1^>9_^0`<60Ojoo z0yysmSXl{wC&@@Fxs8U5Sge{tz*(1eU6ffDZ8iINx~q3W9elMA@5 zj8>R;+79=d+&ZhKC7NsrR=L%F9BrsyCwawilkMTh236W?nxEoteO} za|>ta(5cu|*AmZZF{I;m#9%3A+)BTpEXA?;yb5p)Mz`~a7`_Db+D)C3i-Pf`S{U$s3^ewv0kIX1#dOq_`M8y78RPBYK0= z02yk9@B&7Op?51;X*EQA_s;_Z8+Wnlf*Q&~!USaxhe^1B!25YGYigcX5M&b73p$ij zXVCe%3uirAG_$`bwtsrkXFHzk1u{C1|GW~@*O8B6&AguSTs-_2P?~$Q#Wz6*%$75l zh}ez@i5;#^KtT5a;28S>MhzAdZx)e=7!C>5CV)9YyF|nA7d}xVNX2pE9FmniVH~^9i=n7uDpaneQ)(So za(`pUM7cRLV%stRHo2S9A5L2RujcGFxHN>5RL$+u-VUOAHo>MSjEd$d16p$=3Z7;y zPWy6vAe*5kSc9H8c=&%sgV#=h2t>z|D0r2oOmDesQ??>jhfo<>PnC7ThI6%o1_y|~ z1as*fN1VI^T^HwI8mE6)Iommn5I9-YWpA{6+pKU+87J@qMkoQ&SQno2vqQ6%n0Rj$%YV@ztJZ-We@T&Ix82`+lSTx*SqRO`2~Xw+23<%j)sdL^l9H0i2uD~Ei7kpq8G0zDfQKll%B6zI z8F54|ND3YRuwqX&#P-GYJ6*nLfsW3cJ$$II%=e;K*p!jVe^^!iSUH{RhyJkRu`uF*Vl2FR!cfatSZf?y zGgaMv6R8qWu@cI6H0a#V+DI@i5g4LKI7JVL?RO*t!s-Hio^pfAkc-=AripHm^JHy6 zXqC!rh6TO{=OI3;B(@a))V3ZOR2NiLc+VylVv{!oI@yj@fra`1sp~7is(8Nl@1?uD zyIZAbCdN=4<=C`RUPDHv=@oSqk%$OR(#ULJrGHJfw+Vse})cXp)xJBj*FQW=15M=`-3wZkiDPtyuSjCbt2(P>-rr0vLls`yNV0`@?+r(hEGMA>rWZ5 zxw_4B3+-=Mv35W=EjPz{Y7<=Ydmq56A*p75SXobn8v3ijRS;K)RqO^_$9u`$JiR6j z4#y!#4DAy)Z$3Om4j_>12&#Xr@@@fEBW2tm;((`$o3s)ckk5HnTH$k2PJ-+Uk1Htr zp`1`6q^q1SUc|A`J}F|5pJ??UDGgn942fgV2HX|4bDNI^)4G< z^&HstK8usWJKCTP&8R+AMtLrdB5}+8v6NewXhuVh>HYW`yj_{5nSN`$yvEut?6Uyg zy+~)v4`wjizK7dh=q+sRJL`&VEwHVSQB|Sv22A|Q-UlNs(+5r1gFnB0SEZ8sj#7+X}PiT$bPgqizhIY zCSCu6mp#_b)%-}sli$myvM=4=(m7gZ7=y8FTCKr?EDkbVsdhwALc)==Jck=)8en zZ(px=%cI7$)UZXdWX$9A&XIEsBYdlnd{2;rB5wWI>73I~;Fyj2arq zuBy3=@H_`M2NExyOew4MYI>Pj^}R#svus3(r51f4eoXR;FBh#=$pe{!wt8Zc?gKnR z=z(H&rk#n{n!~9QH#lqisvjE?{2VNRn7f4SMmG|HC>#0;(-q;q=02` zWT$i}#ilp;^qp_nU9$EL^<*59X_n=JMW?8?@9Bm^^3SjdWoS0v>+JZWhCY4NwVkV5 zxV?CT*GfT|#{vjVs#`Phfjq^9&?ki6WGPAwzx*_n(}~T*oIS zKT$2}X2$Iiq|v%U)WccD!jb$`<=M?U;U2WjDPF`)p{tDX>w)vqdWY_>VIB{$@Wdz$ zS=P0BHgf`2zEIT3k5X)Z!RhO~A(yEMCFR1V-pZm)W6CG$Y<%42=O{g*-OgLB`ssQ2 zbK-9DXob|Lgxq07F$>z-b0li|;yUv_QTSyUY5WoI%bAFYs$1FtV@l)id2Wef+9>$9 zY`u4p8DiZGVvF}gg^|elOJTP1wu~WWxh=Iqrwts?H#TWfOwMwuUxU1=s-I?6c4)2`fU*p+5#>} zVyRw@JIE^->!XYNNW5*+XdZ}cFI|$nWt2aWU9nRj^y9Oq%oZM0AI7Qi1Z!HUY_Bya zs8uROuZ0`C-(QATc6dUv3D?r-)-9bg$vyp@JTw{#lZnA@vCdWNYgd#u=QRUR^oqU{ zQ6|@-(R;&VL`0z(u2fnAP7qa>l3J0zp{C@UL;cG!yePsR}3Pg}Qf@h2ZKpY-$Ik$t(8 z9205hmZig)b%NcHtmIdkoZqvwHPAj^v`xmlkUC6tW@Y`N)Fm@_20Ogw#?*DH&7936 zOv2hC<*#Han(XtPfoOY_*6O&|KCtBAoeGOM`?ru{2Jx~FX6M!$A*XSgvXjQ)7R-N?={mTo`eSTrnMX)f%{_?NbiJq zfO8-R5;2J|^@yCY61fiZ{Ls&vIW;nrD2wU2;XuKqf3>@}Fe6e_ z7}KiEaW!qzGpAJH1Y0wXLh#jf%x?d5SuWzRZnYjQS{*gr)~Z`-k4UKFy+iOiEz!_e zTcO65Mv^=pjc>!hV6g8Z^`RJo)Z4KEf>6lo}g(avzZpt=j89mswTqVo+8O2A#jj(qhR z&r(3M7zuYSMg_-Pf)7}%;bh7LcfHZLaAHY}3N_!bl>ypvDS5c)S z>#5ZxE{DmUVlT@(qn&QqDlf|8rajAa4<^p?#4hzJ($oG5o-j+-DQOjs?vtM7PE?HC zvnzZpQF~eDeoa?ewD(#iLuic0Rj5^BicNVBLtY=ZD%2_Y3%zKog8MyGD(}W&$FAz& zP>dyZQ~9uHh!RN3$gjc3V5a1`#L0FI^YtV;YB+Z2Tj_5`8$S>8`e-ucMv_}(>~F!= zevViS7^JT)(}fPd6tQ{J*&Q(AgbJs7T^XWs9IrPpn@nt9q@+lr*~NiJvCIrE7g zcFSQz;LUK(>Y@?C|uE2Hsy9tW9Sf#z>?SW(OKuDYX)7<=rrq$>j zsE~eI`AueJ^}&0oUu(^02SyJb;u=h&d1M490S!G3-gkKHdh09iczF+;R&BuUT|aW6 zJ0hmvJ)vqg#Z8Ynn83f5*dV+uYwES;HOY>fD-qI7J-rZdGsAUl`_U(5fzej}W`fwX zPXd~r%e(;`F5?w$ANnir;w3GdX7y#{URP60`3dhJM6wvq)8*mYHIp^7`}5kV9vgtrXI!qv>wUAfxri}J$aUWOBd z^L#?lnXP*aiQ5fM%E|v$VQGUA*)qFavCAlc!XxXvjD4Nk2Mwu1P~Ha89_Fu@0JS1* zRUlAIV+*g+ji&1KYYwlayZU`CPbDJU_Y3`Zw5KL&rFcl)ZTh<@Xl=Zc7N}rO6QD^1ls=5|`8NEAMCz#tz zvP;Xl1uOZ&&zxmFY0@@KwF`?13;3ZnHg-G4>aD2wtk1wvs9?I}Gpq5<1QG_%*i&EZ zl<5zmCHjqRDsv+5ea~FDOG>~A{LHL1DXyVAuAV^{efcgl=EH70Zml@zSESXE$O?)I zY=qTr5;HJ{6OqJdd#Y*bS_CJ0hpz2Ut z?j>?e34t&|xPSpEC=-0Emm6lZev{aU?B|xbo0!QW5OjZ8?Zw;%4&J+=0!QAh*Crx* zx8$7T%u?{+_mxxA3NpWT5YTZkVB~RN%wKE$6!iG<0yu;Afn>%3xW`Dd8bergiMC0D zox+dJWAT`o!!(cjzC0Z@Z7tK4b!}lH ztFaJ~>6l=PPTy`ytWxSLW<^L3WQt^jhv({mvbR+p%?4 z5+ce8PoAFia*_;uPe0&{#^&~AVU*0V6KmHOtuRbtNFluts=jlEDi1$UtEzn&&SA@wh^?ePcBuq^i7nJExS9S#L9K|GZ|%u>db?hppmcz(Vn_d zi(8HXJ;s`nz3K8h92F;9GR9%P*7S zdReg2KrQNw9SDx~!`V?tJ=ZXe!kM^GT60s?5)4EO6yG#SR65(hmm--GBT24|^h$EF zbRN(;*aiovQ-}o(%sqcFZ?^9;IdV9rk%U4@LI^9xU3@e5By_og= zYk7>%K7@Wt-mHDSDN4QQek;NwqkGiYkF@xW>rcHq#nS|gf7R`D`h3Qz zE|PAkq$5$)2e_Y;RNLDG zvM{@0BIt3?d4@iCTafwYRBAGBX|#Xpv5E-Q19dkehG} zurOwS=E_k0#)ZZsv)7J^?TgGPH=nI?bRmyVWR~~TRAVcDC3fufh`xJgrBI_FbtTj?BQnr3OaHkoHIcvNp%ot7Lh%7Xb!o zO(xd{Je*ySOsH~DD@2j8or2_)(sp)!UP+>N(6q2}4X&$EUf((-$9;b_LA7(|?mJ{v zf4P%`cb#%(%RXn3gBvb8!q{(gNIDYK>q&wGz{;IIN)K$FaATKtEBel38b8H=o%4kdCyH$WDW7WEyaQcwWPnnZqC*mw~1@512!9jpC*GgEpwyeSryUwy)uGZVJ)P2*Q z2hYJv=PeY;`c7cdlZS5MI^_Kn{nwDA7}cU2UaRZaY-Pc!zin95ND#R--FUx{ zWnaw9_+sB~gG=b^4~CzkobgXvkc7~>REy+ODXa;5Ug^ZW(lM$t5N=PPl-Yjn9QDA? z3#t(JJ+)j$e>21Ux^WWjENX=eriXR|+K`%U&a?|#8Ieq7u|v+hNL*j|8ZHj6(I!=J zgl=*+-J>(A0e9gRm|BASt)E{c;%3;0v-`Cat0V0)Vm2IVC6_Z}e_jcPo|LLuKOK^i z*M5$5?6>-2tt0tsX#D#PBc;2oqkgk2G}({O`1zvscvdxsVlzJB4glBbw>PJpl=YvePyVg;3kDouc>7qHLq}6_~ zM=BiBP81~1iJn4if;=AT+*s87v9j=i$0ldz-QuiemT#>cIiLMU9OB-JYik9dhnYDF z_DnBRPCTl@t}ac2&EOlZWKUr1H4NXgRmNN$UN8N@Ws-31HH_ChgMaqYwEKhjHS8))F*+I<8`QcK(RO|A&a*Y$yE+cVb)!prXo(}fU$Il5 z6nA6xZ87fFSZ@bO6Y(FRl$wwjqr_~)u_};+WNLive(;>&2=@MK>$_cr-o-_`rnYZp zojvHMS=LlEHXAx=dU?!Fqk~>7IjD|JEfH8t zL7#$?wnD?uyn?t5U((Ix-D|Em;C6DlsnVtTaV9Mw`LUuv;S(C_Vfh<4T%oN?E;4(p z{6yBl`rH}oiTN;foG*6kkk5IA3e;au!~^K-Wx$n&QJWHp+CI%T3#xeG*3`**v{d&z zKe`UOx&V{8Zn8L}>@#x@Iw`Qvv?CXYSvd*p2u=KvhdNRtUjP-q@zx26tBr z^zR{~*Jnn=ps%q)L!X8K0wLuCr#C)-iNMB4AEw`0l)E2aAngOe zEYlRaAyj^aF88_vQGwNII{{63GC)d1Q!1pxy=q=2H8js^wJ_;+(Qa9=q& zaFRo+pK*o2=MUD`?~yydZ2pmc>c5E}T}@>wSS>`jm|sK0K12+i^LwZl1jVo<01) zSY7kFSSK3U%ZUf0fYuR9jsWVOy15J`Rot2VaUbzSo@1372P}Od9P*8ZbI~W~*4&tF znQrk%$Lb`FOjtvuCQ><)C(BitFC#S{?*Di#3FxR>7K^?S1%`G+;W2?0IRfY}9j>C^ z^N6A!i7l?f(ZIN)>^`piy=BX-Mx@t(_aU-( zbUz364ev6QaQWdh%WsG9l$D}ITsN}{@kxl{A}yPObk)>AzRJk>)td) zKjmB`p?LzXrJYaq3#OI3%9OzNbDeGwe)UKh?bLp%9bf2E-_~PG&Srwg*cLaUd=Gs? z^qAxt3|RWN3k~ACn6P`l?Pxi)#+bigKUx1a-9Qwk&PfmTXgS{ek?~WZu>JWbAq?6L zRo<&r^;(=hNvR9#sJ8+cwDQ^F6~k@g{BuffturtfPAS6t!wzFC=$jut-Djz3uWn<; zVNYkS(bt7Lml?-OZRrDtw+9GHY#vYT8@Q39oEiB!4n4u2&90n#y@j{4>QsT_+4AwE zft<3vCm|ARVb+?Y?a=pGsLjsxr2vKZ>y;vOH-buxa`Lu#0vsly#~!+zE3ak`s(ahAWVG+?RHPoXur;>F8u^jRGir z>JlGC@ss5GzKT>_Uy6F?9>8-y`REDV_V`s(uiG{4wiu`B?3zGQ)dL1K>K8S_FOnH`pQ&}|2~-`lI_Vlat8~+T4MsnG zPjCB-*40=^#LrKiGi-#1qn(^fWr;h;t*qn9?P>}(&&*e3BbJP-k&VZis^5N+OGPey zzV)Q-$|eKM!<*?CIJ+C*@VsiCrckrl6qP>CQD7=TW;QGFKI&5kU&t3oY^`p7><5m9 z*u&Z`y2m~ER@d=_v_AAd>gV+l!+&h}`UFq!f19zAjcJ8OXFx03e@DzY)*&v{Fgj2~fKg{fZ8aI`N?IKvbmeT+PgoSjLX z{iI`DDzIUN_9J_>y({%wI*zZdW(yU@w{f>3PM+iL>_qFE1kg)=#gk*TdOk-M{AyPE zi9%q|^RR?&2TC!d7v+-Q9*5C<2{7{2)VNz^yy+D0|AnR;H(ESH9L+iLq2Ic`0WfyY zm%na)u#X$2p!W7QpDx?0wcwOuzm#WC4}s6OSqkDswOVh7tGqJ~AaunEi?X>UL0d$Q ztEZgp0e30i+cV@Tg}ZEdi=BqoksXD!S=`<89V(ba*|3ud|Co}*Adc)`mK>TW-hyg4 zO-c&U+wH`dxL)axQbZCYjDi`&{#?<}+(@L1HLtN577_01p}Vj~5lQn7dkWlaG9_U~T1-*2LT9W0y6n=og4M=^3>EbdFDi{X`a+x>fzl7bueaDvyiYJrN#2jt z69_~Z!^YTF&7QOj0gTePKk&0e7pM}cQ0BN*)nC2P!n5m>HfV@78MpQ*8hb3Us-6ZYhab;8Ky$f^eWO6G`M~~6Lx>N42%Smq@EhGK# zF`sF=Rogqh^qp^uN`V2|3yBjSO*!Ot8-hMLJd9nu!!fxdbWh(<@}mq90lTMK@dBYh z@SLWiBENc+!30a5VD-UM9brp!OND$MVJVCuSq6)HfrFn?6EuR3t@L>AV|N*JPmd<7 zG(vT}oL4hN(Dqd~&f z7&`4I+R*oppr`CcQGk&M+U)$DJD9iE_f!MT1J(4?avm2PK4@(eY&*)n1wOhbK>5@y zz)>U%mDRIWja~7?yJlP2#q_xnnUt`PrwshA8O(}ku0hvGvJQC%BaAY2YUMp>KQjjG z%nHJqCW67n5jLIvOmQVfBt`V433;jpqphkkgQqM=C;g7>bA5}3q%pKiczC||%?bSM z-JwdZ1qDwAvbEiVWlMDvM+)@r&8xmE9=FU=5+J-+1=)#>QKkyvNY`JsJ$R23yx@Dg z2X>f8^QPn$51t(1X7xRHwH?h_Mw{$TVFBiA!m*SKe7uw_$qLFaCtCKU7re)?;Ih|D$|jBm@>k}k%v zQFo+uN%m~&bor@j>Gfl9&cv2OKHaN}5Q_3MV(JTK-7x6(3{d!R=ETyz)==*kC99yo zKWb=?6?9huRW&rqx19Z$^P0I%U!Z)0Zt@7c?VFhWOlgWO8JjW=^SFpA7A{u%*7q9N z_i18_(WVdTCXZ{-WM`(UBQx7-{&Jn8cb_Y&{|E{5lLh7#aI!WIP#WlSmV*K~ z)Tfy2Z>|&0drvCiUK?2EpiFq4!I1l&5=DFIheP#Ks{QbEp9tU1_zA|2H7SLmF#cdT zYF!tW$w^0P9eHx9zk7KxH4lD%X)q)ScI*{+SK!KQG*1-PZKnsQ6{XGPc`htft>0A1 zm-X6^R+Bn4)CDs&QG6x^ienV73>k=5q?B0eo0V7|vEt`%RgT3ruMD`~VNOv%GE7@K zR3dwu`l0=b>P=G~<-r)5K=!K2dENv3kGQdNLuemdm3SW2es5crZcunsq-uP<=>u#A z)x^N-kxDjIl^mV$Ba_4dRzqQwHi0&cqhu|Ey=vrCIn8bB{K*AAs$eQYcEO6U*UvB( z9vorWUdc56F}e8i{>v7p&w>YxoWt(TPaQl@4t6jzf4a~a9; zj(@Vdki|PRH#o{X^2M^jdglpdhVj&gdpJ%lHTbXT6klz4!?Dv6x576bl}9+J=*n1H zzr)G9_i`*(Fy--UQw_&wDA;XJNyj7AU}h0i+zbPAE6_w#9`y``?-u@;Cc*^0Kri8O z%nfw!tz{+cY%FZyscIS5wD_)YoxZg85d{-$A(kG`M$5{Sok3BnxGjl{r+dg-{wwad zF=x$9Q_jwQ;sTOGqi9cNrhiyeO<5jqr3BH_Y?>lZjY=NXOC8r2j~>mIPdEtvfEn_p z(c+iCgMt?J=~>rcQjz0t)jF zG(vMKo3aa=vU5)z406Hkv($y zww*_4#^2~U++heiCw}FW%L+Ar10fm`D)r zBj31~#rLz_8niY!_0eLLWFEpCFEu!O5~A#4EcGNUG{Y&x#|u+%uGny6d>bQK;1)YsA}!RICYmp;lY( zr&f4u%)j|8F@N;13!^Qk7H)knjxjtkF6928gvF!njB%$K5x%*!07FZ>UN{d?N@u@; zdR@^i>-%j*;rpvxtDQHWcM+M5?v$?Y_P}ie{Y3C~evGT4IU)0N2kG~|gqXS`)i?2?gKUIlFlEw7m<( zyg2xF^lQos#t97l{0$ZmfegKPg;m>v43)hQuR(=YU5MA#VngLGPSqaaK-UoGmWiP% zh;#g;&{f0{H97Rj#rrVeCB-kcF^XSowNzA4Kg4B;)KCe;WkWPj5qACHyBOKnlh76I zB&>6EZ?nAkTU!H)L;@j!AR&qc2+%^+$O7kzEFOXv?Xg*1T!cU%h88Mv@v{4xr!mHL zd5C~j=oDz8br${oP^+h=m2#nK11(?oL?%h^q83H%E4Hg6^v_e$R zlLZ3<7%M0)ct|A{lGj^dQ(HG#nC}!M$b+XNhU?qpQvY`AYv(Fm@b8j&M z;h8KfKrzUCVWmbeLnvEZVDT)u$l-yU|IJaif{?xBf?II10J2QKayJU>D$0Q?i_oe6 z&D|3B1u-?#e-(Ke-{!&$2n6z(epzms39LB5!eEZBPy^)5P=epKo)bk#AP|sYhKgKN z87#kCEO+oT|FtCNyA*4jTMY~hU^Epur=kB(#jKkm*e($sc!eD}W&WiY92QV9LNcIs zVJuMg|0Fnhj3Br~xS5my-#;zP!~pP(HwRxxqjR1=o4j?NF)%QI(M1+P&J6ru`J<3j zg9ugx0<^49Swwqc1v8`swg+$zW;!5+^^Z2ddxQ`{Akf4L6+u+!zjkl_Q!k2Y7d%t| z9~+c_@v;_Ui`==e$R&EZGDDz`yS|KrNm2nm;H4KidCCT*Mhepd1hD-wOg%fOhd@(McZ?j+ z|E^h>EeL|oz&QeB9KULo1}K2MZU%u1nowQ1gx^V?Eg~c?5w2mv1axxza!LQ&5$IpX z@Xq|4j10ix{6mcdxCrNaiJq=>5U2&)tye%92b5Is+=u*IOE#_S?vO$u5Cbs&?eq%n zK;XreG(ZOD9|q?WMx=m1;0-5K=%OKv>zsTXlx;HdTaU%Q~fSAs|kYU z65+De?7%~=U#0%v<{AHzDcGHpQ3D^i{;0Q?T@C_)4KC2`U?=$ZC%7SEV9w41__QAU zcs`)O4W%G+L36yQb^}O=&g#}{hjX*Ja6=g{YEHwIebaXE`fV^$L9T+o=Yau?)viDp zHO zzZ^vOxj07Nxj4oDx5FlkMo?TL{9zUgpegi=;@}*>g~~1wE|AB?`)~U%`l6&s423{k!T5JCQCbOs zV-|A&lYeqzP$4)k5iVFP00;>G%DtR0m>02X2>e+I695Cx{%#*C3J5s_0$IXP@e9B4 zzj@c^v!-sGTMY~hV2r9cr=bHVME+2MZ483O!tNXaS&?5#@DTwMc4~mYgQ}Q-I*~sT zj;cXOAS8haP~sxt|IMTPH({5?a~e7zQxuBwpTmb2QwT}~0^Xuf@rz10m+C$o2CDHC zj4TkH^MZhZ0gM=}K))zd=(je?P!J6jfdGXVRO*6(o*hkF2b69GBOB!Axi}aY!1(jc zIRiBiEe56hO>^1a5ePJiL1ivR|EFS5YR7l)Adtn^C5sXH{C|#tSRKwC)FkxM-=A14q1nQ!%%lQ;Gd9;E@r60f9h(1XLd3AtXQ#Vc&gz zF$It*3B_Y`@NjYdvzV6y3-urb0|Eg~NvPZfgQFzKkn!<11Ksadqmze_xI}nBn;_4> z+8D$Wsy+9j1fZe*uD0)ofPN_`J&-8{{qGU2XCH$965%O5T>ppv@5zLJHVg8RbAJBc z+4b5-&|M-p<&X>9isJ8^Ep2JAv{z;?N()c_O!%L^ElSz`!d#DO5eP5+EC8%Y|MIYZ zzv=#SG->b!p+)euH|=lBl{q3v5C~YxKxHp%|5RA!aV4m@elSu%RKVZ!zyQXT-E$gJ z`1UF)a9#e7YSa*iph6&UECc1gFd?rjSg5aGFQfo-St#Leow)TNBrXv?wZ;hK%l;|} zPiw*LFfc&?jDJ@@oR zibe@RhCsks{@374nqg6z6}(H)Bl?$273G5Lf8&@-_kcj)CrI(zr66@+13C&&QZnQ% zmy4QmInMCCd+yEve+4Mrg=Nwdpw!S);HMSRgVgs`Z;K=T>Nfvh?oZlsSx^hCG zylx}M03j-r0oXx?qWqrAWQc5C;stSd^6j_h#4P{E zTqoB~70;{T4jBJ-w%>`tgoJ-rr!^S_?5;>?(mS8niDUs0<3Q(f~JS5O|{kcChCU zl+L>p*c<~CvYgbsocrvrF2(FQq6@ivi?L!sqz5E45nfS z#=pn$hD^Xu)j!7ZhRz5+1OhTwp@J9oa=QvDYlInyz6$01t*py}BM|7h3gyGQ3f?B* NfKN3DSi!U){|~(mS-}7R