Skip to content

Commit 7460624

Browse files
authored
Stable 1.82.17 Release (#685)
# Note We are going to stop providing new features and improvements starting from this 1.82.17 release, unless some hotfixes that need to be pushed or backported. We are moving forward to add new features and improvements into later release (1.83.x). # What's new ? - 1.82.17 - **[Fix]** Regex calculation timed out, by @neon-nyan - **[Imp]** Optimize CombineUrlFromString function, by @neon-nyan - Up to 58% faster while using 75% less memory. - [Read more](CollapseLauncher/Hi3Helper.EncTool@2834b5d#commitcomment-152132480) - **[New]** Confirm before doing forced launcher update, by @neon-nyan - **[Fix]** Retry game property loading if it fails, by @neon-nyan - **[Imp]** Update dependencies, by @bagusnl - Win2D 1.3.1 -> 1.3.2 - Sentry 5.0.1 -> 5.1.0 - **[New]** Prevent user to close launcher when critical operation is in progress, by @bagusnl - **[Fix]** Taskbar progress does not disappear after game installation, by @shatyuka - **[Fix]** Keyboard shortcut that depends on game property (e.g. open Screenshot button) not working, by @gablm - **[Fix]** Local notification data not saved, by @gablm - **[Imp]** Bring window to the top before closing launcher, by @neon-nyan - **[New]** User feedback form on exception dialog, by @neon-nyan & @bagusnl - User now can fill a feedback form telling us what happened when an Exception dialog is raised - This feature (currently) **requires** error collection to be enabled and working (able to connect to Sentry's server) - [Read more](#683) # What's new? - 1.82.16 - **[Fix]** Errors when updating game that uses HDiff, by @neon-nyan - **[Imp]** Update dependencies, by @neon-nyan - **[Imp]** Reduce CPU overhead by swapping ``SoftwareBitmap`` to ``CanvasDevice`` and ``CanvasBitmap`` to draw video frames while "Acrylic Effect" mode enabled, by @neon-nyan - This reduces CPU overhead by removing routines to copy the video frames from software-based ``SoftwareBitmap``, and instead use Direct3D-based ``CanvasBitmap`` as the frame source. - However, this improvement still runs single-threaded due to the copy routine still being done on the same thread as the UI thread. - **[Imp]** Execute metadata config download and loading in parallel, by @neon-nyan - Instead of running the metadata download/update/load routine sequentially, the process will now be running in parallel at the same time. Making the metadata loading runs faster. - **[Imp]** Reduce UI hangs while switching between regions, by @neon-nyan - **[Fix]** Crash when clicking any Community Tools buttons, by @neon-nyan - **[Fix]** Regression where some Game Settings might not refresh the page when Registry changes occurred outside of the launcher, by @neon-nyan # What's new? - 1.82.15 - **[Fix]** Taskbar progress did not disappear after game install/update, by @shatyuka - **[Imp]** Use Persistent folder for Audio VO when possible, by @neon-nyan - **[Fix]** Lock .NET SDK version to 9.0.1, by @bagusnl - **[New]** Check for UAC availability status, by @shatyuka - UAC is needed for the game, when it's disabled it might give "Anti-cheat error" message and refusing to run the game. In this case, we might not recommend you to use debloated Windows Scripts/ISOs to avoid any compatibility issues. - **[Imp]** CodeQA based on Qodana and ReSharper suggestions, by @neon-nyan - **[New]** Introducing ``Hash`` extension for hashing ``FileStream``, ``string``, ``Span<T>`` and ``FileInfo`` with both synchronous and asynchronous version of the methods. The methods uses generic type of any member of ``HashAlgorithm`` (Such as: ``MD5``, ``SHA#``, ``HMACSHA#``, etc.) and ``NonCryptographicHashAlgorithm`` (Such as: ``Crc32``, ``XxHash#``, etc.) to specify which kind of hash to be used, by @neon-nyan - **[Imp]** Separating ``InternalAppJSONContext`` members into its own ``JsonContext``, by @neon-nyan - **[Imp]** Update H.NotifyIcon to work with CsWin32 v0.3.162, by @neon-nyan - **[Imp]** Update NuGet packages, by @neon-nyan & @bagusnl - **[Imp]** Recompile and Update StaticLibs for NativeAOT libraries link, by @neon-nyan - Update Rust compiler for ``csharp_bindings`` to rustc 1.84.0 (9fc6b4312 2025-01-07) (Stable) - Remove dependency to ``MSVCRT`` library (Manual VC++ 2019/2022 Redist installation should no longer be needed) and fully utilize ``UCRT`` as its runtime library. - Update libwebp libraries to: https://github.com/webmproject/libwebp/tree/f8f241071001e1a3807f34a26a03a742ea843458 - Favor speed + inline optimizaton build (/O2 + /Ob2 + /Oi + /Ot) - Recompile all static libraries using Microsoft Visual C++ 2022 (19.43.34618) compiler. - Optimize Rust library, ``csharp_bindings`` to use full LTO. - **[Imp]** Update dependency submodules, by @neon-nyan - **[Imp]** Switch from managed ``Process`` to directly use Win32 APIs on ``ProcessChecker``, by @neon-nyan - **[Imp] [ZZZ GSP]** Change ``High-Precision Character Animation`` option from Checkbox to ComboBox and add two other options: ``Dynamic`` and ``Global``, by @neon-nyan - **[Loc]** Sync locales, by localizers <3 ### Templates <details> <summary>Changelog Prefixes</summary> ``` **[New]** **[Imp]** **[Fix]** **[Loc]** **[Doc]** ``` </details>
2 parents 8478c98 + 82532f1 commit 7460624

File tree

408 files changed

+20980
-16786
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

408 files changed

+20980
-16786
lines changed

.github/workflows/qodana-scan-pr.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ on:
33
pull_request:
44
branches:
55
- main
6-
- preview
7-
- stable
86
paths-ignore:
97
- '**.md'
108
- 'Hi3Helper.Core/Lang/**.json'
@@ -21,13 +19,11 @@ jobs:
2119
pull-requests: write
2220
checks: write
2321
security-events: write
24-
2522
strategy:
2623
matrix:
2724
configuration: [Release] # No need to distribute Debug builds
2825
platform: [x64]
2926
framework: [net9.0-windows10.0.22621.0]
30-
3127
env:
3228
Configuration: ${{ matrix.configuration }}
3329
Platform: ${{ matrix.platform }}
@@ -45,7 +41,6 @@ jobs:
4541
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
4642
fetch-depth: 0 # a full history is required for pull request analysis
4743
submodules: recursive # many many submodules
48-
4944
- name: Install .NET
5045
uses: actions/setup-dotnet@v4
5146
with:
@@ -62,7 +57,6 @@ jobs:
6257
pr-mode: true
6358
env:
6459
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} # read the steps about it below
65-
6660
- uses: github/codeql-action/upload-sarif@v3
6761
if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
6862
with:

.github/workflows/qodana-scan.yml

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ on:
33
workflow_dispatch:
44
schedule:
55
- cron: '0 0 * * 0,3,5' # At 00:00 on Sunday, Wednesday, and Friday.
6-
# pull_request:
7-
# branches:
8-
# - main
6+
pull_request:
7+
branches:
8+
- preview
9+
- stable
910

1011
jobs:
1112
qodana:
@@ -15,31 +16,26 @@ jobs:
1516
configuration: [Release] # No need to distribute Debug builds
1617
platform: [x64]
1718
framework: [net9.0-windows10.0.22621.0]
18-
1919
env:
2020
Configuration: ${{ matrix.configuration }}
2121
Platform: ${{ matrix.platform }}
2222
DOTNET_INSTALL_DIR: '.\.dotnet'
2323
DOTNET_VERSION: '9.x'
2424
DOTNET_QUALITY: 'ga'
2525
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
26-
2726
permissions:
2827
actions: read
2928
contents: write
3029
pull-requests: write
3130
checks: write
3231
security-events: write
33-
3432
steps:
3533
- uses: actions/checkout@v4
3634
with:
3735
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
3836
submodules: recursive # many many submodules
39-
4037
# - name: Install winget
4138
# uses: Cyberboss/install-winget@v1
42-
4339
# - name: Install Qodana CLI
4440
# uses: crazy-max/ghaction-chocolatey@v3
4541
# with:
@@ -50,21 +46,18 @@ jobs:
5046
# Import-Module "$env:ChocolateyInstall/helpers/chocolateyInstaller.psm1"
5147
# refreshenv
5248
# qodana --version
53-
5449
- name: Install .NET
5550
uses: actions/setup-dotnet@v4
5651
with:
5752
dotnet-version: ${{ env.DOTNET_VERSION }}
5853
dotnet-quality: ${{ env.DOTNET_QUALITY }}
5954
cache: true
6055
cache-dependency-path: CollapseLauncher/packages.lock.json
61-
6256
# - name: Qodana Scan
6357
# run: |
6458
# Import-Module "$env:ChocolateyInstall/helpers/chocolateyInstaller.psm1"
6559
# refreshenv
6660
# qodana scan --ide QDNET-EAP -o ${{ runner.temp }}\qodana\results --cache-dir ${{ runner.temp }}\qodana\cache
67-
6861
- name: Qodana Scan
6962
uses: JetBrains/qodana-action@v2024.3.3
7063
continue-on-error: true
@@ -73,9 +66,8 @@ jobs:
7366
pr-mode: false
7467
env:
7568
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
76-
7769
- uses: github/codeql-action/upload-sarif@v3
7870
if: always()
7971
continue-on-error: true
8072
with:
81-
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
73+
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json

.github/workflows/vt-scan-releases.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@ jobs:
88
virustotal:
99
runs-on: ubuntu-latest
1010
steps:
11-
-
12-
name: VirusTotal Scan
11+
- name: VirusTotal Scan Executables
1312
uses: crazy-max/ghaction-virustotal@v4
1413
with:
1514
vt_api_key: ${{ secrets.VT_API_KEY }}
1615
update_release_body: true
1716
github_token: ${{ secrets.GITHUB_TOKEN }}
1817
files: |
19-
*.exe
20-
*.7z
18+
^.*\.[eE][xX][eE]$
19+
20+
- name: VirusTotal Scan Archive
21+
uses: crazy-max/ghaction-virustotal@v4
22+
with:
23+
vt_api_key: ${{ secrets.VT_API_KEY }}
24+
update_release_body: true
25+
github_token: ${{ secrets.GITHUB_TOKEN }}
26+
files: |
27+
^.*\.[7zZ]$

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
packages/*
77

88
CollapseLauncher/Deps/*
9+
CollapseLauncher/StaticLib/*.lib
10+
CollapseLauncher/StaticLib/**/*.lib
911
CollapseLauncher/Invoker/*
1012
**/Generated Files/**
1113
*.psd

CollapseLauncher/App.xaml

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
44
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
5+
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
56
xmlns:conv="using:CollapseLauncher.Pages"
6-
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
77
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
8-
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
98
xmlns:primitives="using:Microsoft.UI.Xaml.Controls.Primitives"
109
xmlns:ui="using:Windows.UI">
1110
<!-- ReSharper disable Xaml.InvalidResourceType -->
@@ -29,6 +28,7 @@
2928
<ResourceDictionary Source="ms-appx:///XAMLs/Theme/CustomControls/ImageEx/ImageEx.xaml" />
3029
<ResourceDictionary Source="ms-appx:///XAMLs/Theme/CustomControls/CommunityToolkit.Labs/DataTable/DataColumn.xaml" />
3130
<ResourceDictionary Source="ms-appx:///XAMLs/Theme/CustomControls/CommunityToolkit.Labs/MarkdownTextBlock/MarkdownTextBlock.xaml" />
31+
<ResourceDictionary Source="ms-appx:///XAMLs/Theme/CustomControls/UserFeedbackDialog/UserFeedbackDialog.xaml" />
3232
<ResourceDictionary>
3333
<ResourceDictionary.ThemeDictionaries>
3434
<ResourceDictionary x:Key="Default">
@@ -2190,13 +2190,13 @@
21902190
<!-- Force ShouldConstrainToRootBounds to True -->
21912191
<!-- Fix https://github.com/microsoft/microsoft-ui-xaml/issues/8657 -->
21922192
<interactivity:Interaction.Behaviors>
2193-
<interactions:DataTriggerBehavior Binding="{Binding ShouldConstrainToRootBounds, ElementName=Popup}"
2194-
ComparisonCondition="Equal"
2195-
Value="False">
2196-
<interactions:ChangePropertyAction PropertyName="ShouldConstrainToRootBounds"
2197-
TargetObject="{Binding ElementName=Popup}"
2198-
Value="True" />
2199-
</interactions:DataTriggerBehavior>
2193+
<interactivity:DataTriggerBehavior Binding="{Binding ShouldConstrainToRootBounds, ElementName=Popup}"
2194+
ComparisonCondition="Equal"
2195+
Value="False">
2196+
<interactivity:ChangePropertyAction PropertyName="ShouldConstrainToRootBounds"
2197+
TargetObject="{Binding ElementName=Popup}"
2198+
Value="True" />
2199+
</interactivity:DataTriggerBehavior>
22002200
</interactivity:Interaction.Behaviors>
22012201
<Border x:Name="PopupBorder"
22022202
Margin="0,-0.5,0,-1"
@@ -2627,13 +2627,13 @@
26272627
<!-- Force ShouldConstrainToRootBounds to True -->
26282628
<!-- Fix https://github.com/microsoft/microsoft-ui-xaml/issues/8657 -->
26292629
<interactivity:Interaction.Behaviors>
2630-
<interactions:DataTriggerBehavior Binding="{Binding ShouldConstrainToRootBounds, ElementName=Popup}"
2631-
ComparisonCondition="Equal"
2632-
Value="False">
2633-
<interactions:ChangePropertyAction PropertyName="ShouldConstrainToRootBounds"
2634-
TargetObject="{Binding ElementName=Popup}"
2635-
Value="True" />
2636-
</interactions:DataTriggerBehavior>
2630+
<interactivity:DataTriggerBehavior Binding="{Binding ShouldConstrainToRootBounds, ElementName=Popup}"
2631+
ComparisonCondition="Equal"
2632+
Value="False">
2633+
<interactivity:ChangePropertyAction PropertyName="ShouldConstrainToRootBounds"
2634+
TargetObject="{Binding ElementName=Popup}"
2635+
Value="True" />
2636+
</interactivity:DataTriggerBehavior>
26372637
</interactivity:Interaction.Behaviors>
26382638
<Border x:Name="PopupBorder"
26392639
Margin="0,-0.5,0,-1"
@@ -5019,7 +5019,8 @@
50195019
</Style>
50205020

50215021
<!-- PipsPager styles -->
5022-
<Style x:Key="DefaultPipsPagerStyle" TargetType="controls:PipsPager">
5022+
<Style x:Key="DefaultPipsPagerStyle"
5023+
TargetType="controls:PipsPager">
50235024
<Setter Property="Background" Value="Transparent" />
50245025
<Setter Property="HorizontalAlignment" Value="Left" />
50255026
<Setter Property="VerticalAlignment" Value="Top" />
@@ -5031,7 +5032,9 @@
50315032
<Setter Property="Template">
50325033
<Setter.Value>
50335034
<ControlTemplate TargetType="controls:PipsPager">
5034-
<StackPanel x:Name="RootPanel" Orientation="{TemplateBinding Orientation}" Background="{TemplateBinding Background}">
5035+
<StackPanel x:Name="RootPanel"
5036+
Background="{TemplateBinding Background}"
5037+
Orientation="{TemplateBinding Orientation}">
50355038

50365039
<VisualStateManager.VisualStateGroups>
50375040
<VisualStateGroup x:Name="PreviousPageButtonVisibilityStates">
@@ -5112,9 +5115,23 @@
51125115
</VisualStateGroup>
51135116

51145117
</VisualStateManager.VisualStateGroups>
5115-
<Button x:Name="PreviousPageButton" ToolTipService.ToolTip="{Binding ElementName=PreviousPageButton, Path=(AutomationProperties.Name)}" ToolTipService.Placement="Top" Style="{TemplateBinding PreviousButtonStyle}" HorizontalAlignment="Center" VerticalAlignment="Center" />
5116-
<ScrollViewer x:Name="PipsPagerScrollViewer" VerticalScrollBarVisibility="Hidden" VerticalScrollMode="Disabled" HorizontalScrollBarVisibility="Hidden" HorizontalScrollMode="Disabled" IsHorizontalScrollChainingEnabled="False" IsVerticalScrollChainingEnabled="False" HorizontalAlignment="Center" VerticalAlignment="Center">
5117-
<controls:ItemsRepeater x:Name="PipsPagerItemsRepeater" ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PipsPagerItems}">
5118+
<Button x:Name="PreviousPageButton"
5119+
HorizontalAlignment="Center"
5120+
VerticalAlignment="Center"
5121+
Style="{TemplateBinding PreviousButtonStyle}"
5122+
ToolTipService.Placement="Top"
5123+
ToolTipService.ToolTip="{Binding ElementName=PreviousPageButton, Path=(AutomationProperties.Name)}" />
5124+
<ScrollViewer x:Name="PipsPagerScrollViewer"
5125+
HorizontalAlignment="Center"
5126+
VerticalAlignment="Center"
5127+
HorizontalScrollBarVisibility="Hidden"
5128+
HorizontalScrollMode="Disabled"
5129+
IsHorizontalScrollChainingEnabled="False"
5130+
IsVerticalScrollChainingEnabled="False"
5131+
VerticalScrollBarVisibility="Hidden"
5132+
VerticalScrollMode="Disabled">
5133+
<controls:ItemsRepeater x:Name="PipsPagerItemsRepeater"
5134+
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PipsPagerItems}">
51185135
<controls:ItemsRepeater.Layout>
51195136
<controls:StackLayout Orientation="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Orientation}" />
51205137
</controls:ItemsRepeater.Layout>
@@ -5125,20 +5142,29 @@
51255142
</controls:ItemsRepeater.ItemTemplate>
51265143
</controls:ItemsRepeater>
51275144
</ScrollViewer>
5128-
<Button x:Name="NextPageButton" ToolTipService.ToolTip="{Binding ElementName=NextPageButton, Path=(AutomationProperties.Name)}" ToolTipService.Placement="Bottom" Style="{TemplateBinding NextButtonStyle}" HorizontalAlignment="Center" VerticalAlignment="Center" />
5145+
<Button x:Name="NextPageButton"
5146+
HorizontalAlignment="Center"
5147+
VerticalAlignment="Center"
5148+
Style="{TemplateBinding NextButtonStyle}"
5149+
ToolTipService.Placement="Bottom"
5150+
ToolTipService.ToolTip="{Binding ElementName=NextPageButton, Path=(AutomationProperties.Name)}" />
51295151
</StackPanel>
51305152

51315153
</ControlTemplate>
51325154
</Setter.Value>
51335155
</Setter>
51345156
</Style>
5135-
<Style BasedOn="{StaticResource DefaultPipsPagerStyle}" TargetType="controls:PipsPager" />
5157+
<Style BasedOn="{StaticResource DefaultPipsPagerStyle}"
5158+
TargetType="controls:PipsPager" />
51365159

51375160
<!-- Pivot styles -->
5138-
<Style x:Key="ThinPivotHeaderItemStyle" TargetType="PivotHeaderItem" BasedOn="{StaticResource DefaultPivotHeaderItemStyle}">
5161+
<Style x:Key="ThinPivotHeaderItemStyle"
5162+
BasedOn="{StaticResource DefaultPivotHeaderItemStyle}"
5163+
TargetType="PivotHeaderItem">
51395164
<Setter Property="Height" Value="30" />
51405165
</Style>
5141-
<Style BasedOn="{StaticResource ThinPivotHeaderItemStyle}" TargetType="PivotHeaderItem" />
5166+
<Style BasedOn="{StaticResource ThinPivotHeaderItemStyle}"
5167+
TargetType="PivotHeaderItem" />
51425168
<Thickness x:Key="PivotNavButtonMargin">0,-6,0,0</Thickness>
51435169
</ResourceDictionary>
51445170
</ResourceDictionary.MergedDictionaries>

CollapseLauncher/App.xaml.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,26 @@
1414
using Windows.UI;
1515
using static CollapseLauncher.InnerLauncherConfig;
1616
using static Hi3Helper.Logger;
17+
// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault
18+
// ReSharper disable CommentTypo
19+
// ReSharper disable StringLiteralTypo
1720

1821
namespace CollapseLauncher
1922
{
2023
public partial class App
2124
{
22-
public static bool IsAppKilled = false;
25+
// TODO: #671 This App.IsAppKilled will be replaced with cancellable-awaitable event
26+
// to ensure no hot-exit being called before all background tasks
27+
// hasn't being cancelled.
28+
// public static bool IsAppKilled { get; set; } = false;
2329

2430
public App()
2531
{
2632
if (DebugSettings != null)
2733
{
28-
#if ENABLEFRAMECOUNTER
34+
#if ENABLEFRAMECOUNTER
2935
DebugSettings.EnableFrameRateCounter = true;
30-
#endif
36+
#endif
3137
#if DEBUG
3238
DebugSettings.LayoutCycleDebugBreakLevel = LayoutCycleDebugBreakLevel.High;
3339
DebugSettings.LayoutCycleTracingLevel = LayoutCycleTracingLevel.High;
@@ -68,7 +74,7 @@ public App()
6874
RequestedTheme = IsAppThemeLight ? ApplicationTheme.Light : ApplicationTheme.Dark;
6975
PInvoke.SetPreferredAppMode(PInvoke.ShouldAppsUseDarkMode() ? PreferredAppMode.AllowDark : PreferredAppMode.Default);
7076

71-
this.InitializeComponent();
77+
InitializeComponent();
7278
}
7379

7480
protected override void OnLaunched(LaunchActivatedEventArgs args)
@@ -85,7 +91,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
8591
WindowUtility.CurrentAppWindow!.TitleBar!.ButtonForegroundColor = color;
8692
WindowUtility.CurrentAppWindow!.TitleBar!.ButtonInactiveBackgroundColor = color;
8793

88-
if (WindowUtility.CurrentWindow!.Content is not null and FrameworkElement frameworkElement)
94+
if (WindowUtility.CurrentWindow!.Content is FrameworkElement frameworkElement)
8995
frameworkElement.RequestedTheme = isThemeLight ? ElementTheme.Light : ElementTheme.Dark;
9096
};
9197

23.5 KB
Loading
20.9 KB
Loading
21.2 KB
Loading
24.4 KB
Loading

0 commit comments

Comments
 (0)