2021-03-18 20:40:20 +00:00
using Gtk ;
using Ryujinx.Common ;
using Ryujinx.Common.Configuration ;
2023-04-24 02:08:31 +00:00
using Ryujinx.Common.Logging ;
2021-11-28 20:24:17 +00:00
using Ryujinx.Common.Utilities ;
2022-05-15 11:30:15 +00:00
using Ryujinx.Ui.Common.Configuration ;
2023-04-03 10:14:19 +00:00
using Ryujinx.Ui.Common.Models.Amiibo ;
2021-03-18 20:40:20 +00:00
using Ryujinx.Ui.Widgets ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Net.Http ;
using System.Reflection ;
using System.Text ;
using System.Threading.Tasks ;
2023-04-03 10:14:19 +00:00
using AmiiboApi = Ryujinx . Ui . Common . Models . Amiibo . AmiiboApi ;
using AmiiboJsonSerializerContext = Ryujinx . Ui . Common . Models . Amiibo . AmiiboJsonSerializerContext ;
2021-03-18 20:40:20 +00:00
namespace Ryujinx.Ui.Windows
{
public partial class AmiiboWindow : Window
{
private const string DEFAULT_JSON = "{ \"amiibo\": [] }" ;
public string AmiiboId { get ; private set ; }
public int DeviceId { get ; set ; }
public string TitleId { get ; set ; }
public string LastScannedAmiiboId { get ; set ; }
public bool LastScannedAmiiboShowAll { get ; set ; }
public ResponseType Response { get ; private set ; }
public bool UseRandomUuid
{
get
{
return _randomUuidCheckBox . Active ;
}
}
private readonly HttpClient _httpClient ;
private readonly string _amiiboJsonPath ;
private readonly byte [ ] _amiiboLogoBytes ;
private List < AmiiboApi > _amiiboList ;
2023-04-03 10:14:19 +00:00
private static readonly AmiiboJsonSerializerContext SerializerContext = new ( JsonHelper . GetDefaultSerializerOptions ( ) ) ;
2021-03-18 20:40:20 +00:00
public AmiiboWindow ( ) : base ( $"Ryujinx {Program.Version} - Amiibo" )
{
2022-05-15 11:30:15 +00:00
Icon = new Gdk . Pixbuf ( Assembly . GetAssembly ( typeof ( ConfigurationState ) ) , "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png" ) ;
2021-03-18 20:40:20 +00:00
InitializeComponent ( ) ;
_httpClient = new HttpClient ( )
{
2023-04-24 02:08:31 +00:00
Timeout = TimeSpan . FromSeconds ( 30 )
2021-03-18 20:40:20 +00:00
} ;
Directory . CreateDirectory ( System . IO . Path . Join ( AppDataManager . BaseDirPath , "system" , "amiibo" ) ) ;
_amiiboJsonPath = System . IO . Path . Join ( AppDataManager . BaseDirPath , "system" , "amiibo" , "Amiibo.json" ) ;
_amiiboList = new List < AmiiboApi > ( ) ;
2022-05-15 18:47:00 +00:00
_amiiboLogoBytes = EmbeddedResources . Read ( "Ryujinx.Ui.Common/Resources/Logo_Amiibo.png" ) ;
2021-03-18 20:40:20 +00:00
_amiiboImage . Pixbuf = new Gdk . Pixbuf ( _amiiboLogoBytes ) ;
_scanButton . Sensitive = false ;
_randomUuidCheckBox . Sensitive = false ;
_ = LoadContentAsync ( ) ;
}
private async Task LoadContentAsync ( )
{
string amiiboJsonString = DEFAULT_JSON ;
if ( File . Exists ( _amiiboJsonPath ) )
{
2023-04-03 10:14:19 +00:00
amiiboJsonString = await File . ReadAllTextAsync ( _amiiboJsonPath ) ;
2021-03-18 20:40:20 +00:00
2023-04-03 10:14:19 +00:00
if ( await NeedsUpdate ( JsonHelper . Deserialize ( amiiboJsonString , SerializerContext . AmiiboJson ) . LastUpdated ) )
2021-03-18 20:40:20 +00:00
{
amiiboJsonString = await DownloadAmiiboJson ( ) ;
}
}
else
{
try
{
amiiboJsonString = await DownloadAmiiboJson ( ) ;
}
2023-04-24 02:08:31 +00:00
catch ( Exception ex )
2021-03-18 20:40:20 +00:00
{
2023-04-24 02:08:31 +00:00
Logger . Error ? . Print ( LogClass . Application , $"Failed to download amiibo data: {ex}" ) ;
2021-03-18 20:40:20 +00:00
ShowInfoDialog ( ) ;
Close ( ) ;
}
}
2023-04-03 10:14:19 +00:00
_amiiboList = JsonHelper . Deserialize ( amiiboJsonString , SerializerContext . AmiiboJson ) . Amiibo ;
2021-03-18 20:40:20 +00:00
_amiiboList = _amiiboList . OrderBy ( amiibo = > amiibo . AmiiboSeries ) . ToList ( ) ;
if ( LastScannedAmiiboShowAll )
{
_showAllCheckBox . Click ( ) ;
}
ParseAmiiboData ( ) ;
_showAllCheckBox . Clicked + = ShowAllCheckBox_Clicked ;
}
private void ParseAmiiboData ( )
{
List < string > comboxItemList = new List < string > ( ) ;
for ( int i = 0 ; i < _amiiboList . Count ; i + + )
{
if ( ! comboxItemList . Contains ( _amiiboList [ i ] . AmiiboSeries ) )
{
if ( ! _showAllCheckBox . Active )
{
foreach ( var game in _amiiboList [ i ] . GamesSwitch )
{
if ( game ! = null )
{
if ( game . GameId . Contains ( TitleId ) )
{
comboxItemList . Add ( _amiiboList [ i ] . AmiiboSeries ) ;
_amiiboSeriesComboBox . Append ( _amiiboList [ i ] . AmiiboSeries , _amiiboList [ i ] . AmiiboSeries ) ;
break ;
}
}
}
}
else
{
comboxItemList . Add ( _amiiboList [ i ] . AmiiboSeries ) ;
_amiiboSeriesComboBox . Append ( _amiiboList [ i ] . AmiiboSeries , _amiiboList [ i ] . AmiiboSeries ) ;
}
}
}
_amiiboSeriesComboBox . Changed + = SeriesComboBox_Changed ;
_amiiboCharsComboBox . Changed + = CharacterComboBox_Changed ;
if ( LastScannedAmiiboId ! = "" )
{
SelectLastScannedAmiibo ( ) ;
}
else
{
_amiiboSeriesComboBox . Active = 0 ;
}
}
private void SelectLastScannedAmiibo ( )
{
bool isSet = _amiiboSeriesComboBox . SetActiveId ( _amiiboList . FirstOrDefault ( amiibo = > amiibo . Head + amiibo . Tail = = LastScannedAmiiboId ) . AmiiboSeries ) ;
isSet = _amiiboCharsComboBox . SetActiveId ( LastScannedAmiiboId ) ;
if ( isSet = = false )
{
_amiiboSeriesComboBox . Active = 0 ;
}
}
private async Task < bool > NeedsUpdate ( DateTime oldLastModified )
{
try
{
HttpResponseMessage response = await _httpClient . SendAsync ( new HttpRequestMessage ( HttpMethod . Head , "https://amiibo.ryujinx.org/" ) ) ;
if ( response . IsSuccessStatusCode )
{
return response . Content . Headers . LastModified ! = oldLastModified ;
}
return false ;
}
2023-04-24 02:08:31 +00:00
catch ( Exception ex )
2021-03-18 20:40:20 +00:00
{
2023-04-24 02:08:31 +00:00
Logger . Error ? . Print ( LogClass . Application , $"Failed to check for amiibo updates: {ex}" ) ;
2021-03-18 20:40:20 +00:00
ShowInfoDialog ( ) ;
return false ;
}
}
private async Task < string > DownloadAmiiboJson ( )
{
HttpResponseMessage response = await _httpClient . GetAsync ( "https://amiibo.ryujinx.org/" ) ;
if ( response . IsSuccessStatusCode )
{
string amiiboJsonString = await response . Content . ReadAsStringAsync ( ) ;
using ( FileStream dlcJsonStream = File . Create ( _amiiboJsonPath , 4096 , FileOptions . WriteThrough ) )
{
dlcJsonStream . Write ( Encoding . UTF8 . GetBytes ( amiiboJsonString ) ) ;
}
return amiiboJsonString ;
}
else
{
2023-04-24 02:08:31 +00:00
Logger . Error ? . Print ( LogClass . Application , $"Failed to download amiibo data. Response status code: {response.StatusCode}" ) ;
2021-08-20 21:48:00 +00:00
GtkDialog . CreateInfoDialog ( $"Amiibo API" , "An error occured while fetching information from the API." ) ;
2021-03-18 20:40:20 +00:00
Close ( ) ;
}
return DEFAULT_JSON ;
}
private async Task UpdateAmiiboPreview ( string imageUrl )
{
HttpResponseMessage response = await _httpClient . GetAsync ( imageUrl ) ;
if ( response . IsSuccessStatusCode )
{
byte [ ] amiiboPreviewBytes = await response . Content . ReadAsByteArrayAsync ( ) ;
Gdk . Pixbuf amiiboPreview = new Gdk . Pixbuf ( amiiboPreviewBytes ) ;
float ratio = Math . Min ( ( float ) _amiiboImage . AllocatedWidth / amiiboPreview . Width ,
( float ) _amiiboImage . AllocatedHeight / amiiboPreview . Height ) ;
int resizeHeight = ( int ) ( amiiboPreview . Height * ratio ) ;
int resizeWidth = ( int ) ( amiiboPreview . Width * ratio ) ;
_amiiboImage . Pixbuf = amiiboPreview . ScaleSimple ( resizeWidth , resizeHeight , Gdk . InterpType . Bilinear ) ;
}
2023-04-24 02:08:31 +00:00
else
{
Logger . Error ? . Print ( LogClass . Application , $"Failed to get amiibo preview. Response status code: {response.StatusCode}" ) ;
}
2021-03-18 20:40:20 +00:00
}
private void ShowInfoDialog ( )
{
GtkDialog . CreateInfoDialog ( $"Amiibo API" , "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online." ) ;
}
//
// Events
//
private void SeriesComboBox_Changed ( object sender , EventArgs args )
{
_amiiboCharsComboBox . Changed - = CharacterComboBox_Changed ;
_amiiboCharsComboBox . RemoveAll ( ) ;
List < AmiiboApi > amiiboSortedList = _amiiboList . Where ( amiibo = > amiibo . AmiiboSeries = = _amiiboSeriesComboBox . ActiveId ) . OrderBy ( amiibo = > amiibo . Name ) . ToList ( ) ;
List < string > comboxItemList = new List < string > ( ) ;
for ( int i = 0 ; i < amiiboSortedList . Count ; i + + )
{
if ( ! comboxItemList . Contains ( amiiboSortedList [ i ] . Head + amiiboSortedList [ i ] . Tail ) )
{
if ( ! _showAllCheckBox . Active )
{
foreach ( var game in amiiboSortedList [ i ] . GamesSwitch )
{
if ( game ! = null )
{
if ( game . GameId . Contains ( TitleId ) )
{
comboxItemList . Add ( amiiboSortedList [ i ] . Head + amiiboSortedList [ i ] . Tail ) ;
_amiiboCharsComboBox . Append ( amiiboSortedList [ i ] . Head + amiiboSortedList [ i ] . Tail , amiiboSortedList [ i ] . Name ) ;
break ;
}
}
}
}
else
{
comboxItemList . Add ( amiiboSortedList [ i ] . Head + amiiboSortedList [ i ] . Tail ) ;
_amiiboCharsComboBox . Append ( amiiboSortedList [ i ] . Head + amiiboSortedList [ i ] . Tail , amiiboSortedList [ i ] . Name ) ;
}
}
}
_amiiboCharsComboBox . Changed + = CharacterComboBox_Changed ;
_amiiboCharsComboBox . Active = 0 ;
_scanButton . Sensitive = true ;
_randomUuidCheckBox . Sensitive = true ;
}
private void CharacterComboBox_Changed ( object sender , EventArgs args )
{
AmiiboId = _amiiboCharsComboBox . ActiveId ;
_amiiboImage . Pixbuf = new Gdk . Pixbuf ( _amiiboLogoBytes ) ;
string imageUrl = _amiiboList . FirstOrDefault ( amiibo = > amiibo . Head + amiibo . Tail = = _amiiboCharsComboBox . ActiveId ) . Image ;
2023-01-18 22:25:16 +00:00
var usageStringBuilder = new StringBuilder ( ) ;
2021-03-18 20:40:20 +00:00
for ( int i = 0 ; i < _amiiboList . Count ; i + + )
{
if ( _amiiboList [ i ] . Head + _amiiboList [ i ] . Tail = = _amiiboCharsComboBox . ActiveId )
{
bool writable = false ;
foreach ( var item in _amiiboList [ i ] . GamesSwitch )
{
if ( item . GameId . Contains ( TitleId ) )
{
foreach ( AmiiboApiUsage usageItem in item . AmiiboUsage )
{
2023-01-18 22:25:16 +00:00
usageStringBuilder . Append ( Environment . NewLine ) ;
usageStringBuilder . Append ( $"- {usageItem.Usage.Replace(" / ", Environment.NewLine + " - ")}" ) ;
2021-03-18 20:40:20 +00:00
writable = usageItem . Write ;
}
}
}
2023-01-18 22:25:16 +00:00
if ( usageStringBuilder . Length = = 0 )
2021-03-18 20:40:20 +00:00
{
2023-01-18 22:25:16 +00:00
usageStringBuilder . Append ( "Unknown." ) ;
2021-03-18 20:40:20 +00:00
}
2023-01-18 22:25:16 +00:00
_gameUsageLabel . Text = $"Usage{(writable ? " ( Writable ) " : " ")} : {usageStringBuilder}" ;
2021-03-18 20:40:20 +00:00
}
}
_ = UpdateAmiiboPreview ( imageUrl ) ;
}
private void ShowAllCheckBox_Clicked ( object sender , EventArgs e )
{
_amiiboImage . Pixbuf = new Gdk . Pixbuf ( _amiiboLogoBytes ) ;
_amiiboSeriesComboBox . Changed - = SeriesComboBox_Changed ;
_amiiboCharsComboBox . Changed - = CharacterComboBox_Changed ;
_amiiboSeriesComboBox . RemoveAll ( ) ;
_amiiboCharsComboBox . RemoveAll ( ) ;
_scanButton . Sensitive = false ;
_randomUuidCheckBox . Sensitive = false ;
new Task ( ( ) = > ParseAmiiboData ( ) ) . Start ( ) ;
}
private void ScanButton_Pressed ( object sender , EventArgs args )
{
LastScannedAmiiboShowAll = _showAllCheckBox . Active ;
Response = ResponseType . Ok ;
Close ( ) ;
}
private void CancelButton_Pressed ( object sender , EventArgs args )
{
AmiiboId = "" ;
LastScannedAmiiboId = "" ;
LastScannedAmiiboShowAll = false ;
Response = ResponseType . Cancel ;
Close ( ) ;
}
protected override void Dispose ( bool disposing )
{
_httpClient . Dispose ( ) ;
base . Dispose ( disposing ) ;
}
}
2023-04-24 02:08:31 +00:00
}