11using System . Diagnostics . CodeAnalysis ;
22using System . IO ;
33using System . Windows . Input ;
4+ using ImageMagick ;
45using Nito . AsyncEx ;
56using RayCarrot . RCP . Metro . Games . Components ;
67using RayCarrot . RCP . Metro . Games . Data ;
@@ -109,9 +110,9 @@ public InstalledGameViewModel(GameInstallation gameInstallation, GameCategoryVie
109110 public LocalizedString PlatformDisplayName { get ; }
110111 public GamePlatformIconAsset PlatformIcon { get ; }
111112
112- public string GameIcon { get ; set ; }
113+ public object GameIcon { get ; set ; }
113114 public bool HasCustomGameIcon { get ; set ; }
114- public string GameBanner { get ; set ; }
115+ public object GameBanner { get ; set ; }
115116 public bool HasCustomGameBanner { get ; set ; }
116117
117118 public ObservableCollection < GamePanelViewModel > GamePanels { get ; }
@@ -360,6 +361,29 @@ private void RefreshGameBanner()
360361 HasCustomGameBanner = GameInstallation . GetValue < string ? > ( GameDataKey . RCP_BannerImage ) != null ;
361362 }
362363
364+ private void CropToAspectRatio ( MagickImage img , uint width , uint height )
365+ {
366+ double aspectRatio = width / ( double ) height ;
367+
368+ uint newWidth ;
369+ uint newHeight ;
370+ if ( img . Width >= aspectRatio * img . Height )
371+ {
372+ newHeight = img . Height ;
373+ newWidth = ( uint ) ( aspectRatio * newHeight ) ;
374+ }
375+ else
376+ {
377+ newWidth = img . Width ;
378+ newHeight = Math . Max ( 1 , newWidth / 2 ) ;
379+ }
380+
381+ int cropX = ( int ) ( ( img . Width - newWidth ) / 2 ) ;
382+ int cropY = ( int ) ( ( img . Height - newHeight ) / 2 ) ;
383+
384+ img . Crop ( new MagickGeometry ( cropX , cropY , newWidth , newHeight ) ) ;
385+ }
386+
363387 #endregion
364388
365389 #region Public Methods
@@ -588,13 +612,55 @@ public async Task ReplaceIconImageAsync()
588612 if ( result . CanceledByUser )
589613 return ;
590614
591- GameInstallation . SetValue < string > ( GameDataKey . RCP_IconImage , result . SelectedFile ) ;
592- Services . Messenger . Send ( new ModifiedGameIconMessage ( GameInstallation ) ) ;
593- RefreshGameIcon ( ) ;
615+ try
616+ {
617+ FileSystemPath dstFilePath = AppFilePaths . GameAssetsPath + GameInstallation . InstallationId + "GameIcon.png" ;
618+
619+ using MagickImage img = new ( result . SelectedFile ) ;
620+ CropToAspectRatio ( img , 128 , 128 ) ;
621+ img . Resize ( 128 , 128 ) ;
622+
623+ // Save the modified file to the game assets directory
624+ Directory . CreateDirectory ( dstFilePath . Parent ) ;
625+ await img . WriteAsync ( dstFilePath ) ;
626+
627+ // Store as a modified file so it automatically gets removed with the game
628+ GameInstallation . ModifyObject < AddedGameFiles > ( GameDataKey . RCP_AddedFiles , x => x . Files . Add ( dstFilePath ) ) ;
629+
630+ GameInstallation . SetValue < string > ( GameDataKey . RCP_IconImage , dstFilePath ) ;
631+ Services . Messenger . Send ( new ModifiedGameIconMessage ( GameInstallation ) ) ;
632+ RefreshGameIcon ( ) ;
633+ }
634+ catch ( Exception ex )
635+ {
636+ Logger . Error ( ex , "Replacing icon image for {0}" , GameInstallation . FullId ) ;
637+
638+ // TODO-LOC
639+ await Services . MessageUI . DisplayExceptionMessageAsync ( ex , "An error occurred when replacing the icon image. Make sure the image file is valid and try again." ) ;
640+ }
594641 }
595642
596643 public void RemoveCustomIconImage ( )
597644 {
645+ // Delete the icon if it was added
646+ FileSystemPath iconPath = GameInstallation . GetValue < string ? > ( GameDataKey . RCP_IconImage ) ;
647+ if ( iconPath . FileExists )
648+ {
649+ AddedGameFiles ? addedFiles = GameInstallation . GetObject < AddedGameFiles > ( GameDataKey . RCP_AddedFiles ) ;
650+ if ( addedFiles != null && addedFiles . Files . Contains ( iconPath ) )
651+ {
652+ try
653+ {
654+ Services . File . DeleteFile ( iconPath ) ;
655+ addedFiles . Files . Remove ( iconPath ) ;
656+ }
657+ catch ( Exception ex )
658+ {
659+ Logger . Error ( ex , "Deleting custom game icon file" ) ;
660+ }
661+ }
662+ }
663+
598664 GameInstallation . SetValue < string ? > ( GameDataKey . RCP_IconImage , null ) ;
599665 Services . Messenger . Send ( new ModifiedGameIconMessage ( GameInstallation ) ) ;
600666 RefreshGameIcon ( ) ;
@@ -612,12 +678,54 @@ public async Task ReplaceBannerImageAsync()
612678 if ( result . CanceledByUser )
613679 return ;
614680
615- GameInstallation . SetValue < string > ( GameDataKey . RCP_BannerImage , result . SelectedFile ) ;
616- RefreshGameBanner ( ) ;
681+ try
682+ {
683+ FileSystemPath dstFilePath = AppFilePaths . GameAssetsPath + GameInstallation . InstallationId + "GameBanner.png" ;
684+
685+ using MagickImage img = new ( result . SelectedFile ) ;
686+ CropToAspectRatio ( img , 1024 , 512 ) ;
687+ img . Resize ( 1024 , 512 ) ;
688+
689+ // Save the modified file to the game assets directory
690+ Directory . CreateDirectory ( dstFilePath . Parent ) ;
691+ await img . WriteAsync ( dstFilePath ) ;
692+
693+ // Store as a modified file so it automatically gets removed with the game
694+ GameInstallation . ModifyObject < AddedGameFiles > ( GameDataKey . RCP_AddedFiles , x => x . Files . Add ( dstFilePath ) ) ;
695+
696+ GameInstallation . SetValue < string > ( GameDataKey . RCP_BannerImage , dstFilePath ) ;
697+ RefreshGameBanner ( ) ;
698+ }
699+ catch ( Exception ex )
700+ {
701+ Logger . Error ( ex , "Replacing banner image for {0}" , GameInstallation . FullId ) ;
702+
703+ // TODO-LOC
704+ await Services . MessageUI . DisplayExceptionMessageAsync ( ex , "An error occurred when replacing the banner image. Make sure the image file is valid and try again." ) ;
705+ }
617706 }
618707
619708 public void RemoveCustomBannerImage ( )
620709 {
710+ // Delete the banner if it was added
711+ FileSystemPath bannerPath = GameInstallation . GetValue < string ? > ( GameDataKey . RCP_BannerImage ) ;
712+ if ( bannerPath . FileExists )
713+ {
714+ AddedGameFiles ? addedFiles = GameInstallation . GetObject < AddedGameFiles > ( GameDataKey . RCP_AddedFiles ) ;
715+ if ( addedFiles != null && addedFiles . Files . Contains ( bannerPath ) )
716+ {
717+ try
718+ {
719+ Services . File . DeleteFile ( bannerPath ) ;
720+ addedFiles . Files . Remove ( bannerPath ) ;
721+ }
722+ catch ( Exception ex )
723+ {
724+ Logger . Error ( ex , "Deleting custom game banner file" ) ;
725+ }
726+ }
727+ }
728+
621729 GameInstallation . SetValue < string ? > ( GameDataKey . RCP_BannerImage , null ) ;
622730 RefreshGameBanner ( ) ;
623731 }
0 commit comments