2021-03-18 16:40:20 -04:00
using Gtk ;
using Ryujinx.Common ;
using Ryujinx.Common.Configuration ;
2021-11-28 15:24:17 -05:00
using Ryujinx.Common.Utilities ;
2022-05-15 07:30:15 -04:00
using Ryujinx.Ui.Common.Configuration ;
2021-03-18 16:40:20 -04: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 ;
2023-03-21 19:14:46 -04:00
using System.Text.Json.Serialization ;
2021-03-18 16:40:20 -04:00
using System.Threading.Tasks ;
namespace Ryujinx.Ui.Windows
{
public partial class AmiiboWindow : Window
{
2023-03-21 19:14:46 -04:00
private struct AmiiboJson
{
[JsonPropertyName("amiibo")]
public List < AmiiboApi > Amiibo { get ; set ; }
[JsonPropertyName("lastUpdated")]
public DateTime LastUpdated { get ; set ; }
}
private struct AmiiboApi
{
[JsonPropertyName("name")]
public string Name { get ; set ; }
[JsonPropertyName("head")]
public string Head { get ; set ; }
[JsonPropertyName("tail")]
public string Tail { get ; set ; }
[JsonPropertyName("image")]
public string Image { get ; set ; }
[JsonPropertyName("amiiboSeries")]
public string AmiiboSeries { get ; set ; }
[JsonPropertyName("character")]
public string Character { get ; set ; }
[JsonPropertyName("gameSeries")]
public string GameSeries { get ; set ; }
[JsonPropertyName("type")]
public string Type { get ; set ; }
[JsonPropertyName("release")]
public Dictionary < string , string > Release { get ; set ; }
[JsonPropertyName("gamesSwitch")]
public List < AmiiboApiGamesSwitch > GamesSwitch { get ; set ; }
}
private class AmiiboApiGamesSwitch
{
[JsonPropertyName("amiiboUsage")]
public List < AmiiboApiUsage > AmiiboUsage { get ; set ; }
[JsonPropertyName("gameID")]
public List < string > GameId { get ; set ; }
[JsonPropertyName("gameName")]
public string GameName { get ; set ; }
}
private class AmiiboApiUsage
{
[JsonPropertyName("Usage")]
public string Usage { get ; set ; }
[JsonPropertyName("write")]
public bool Write { get ; set ; }
}
2021-03-18 16:40:20 -04:00
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 ;
public AmiiboWindow ( ) : base ( $"Ryujinx {Program.Version} - Amiibo" )
{
2022-05-15 07:30:15 -04:00
Icon = new Gdk . Pixbuf ( Assembly . GetAssembly ( typeof ( ConfigurationState ) ) , "Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png" ) ;
2021-03-18 16:40:20 -04:00
InitializeComponent ( ) ;
_httpClient = new HttpClient ( )
{
Timeout = TimeSpan . FromMilliseconds ( 5000 )
} ;
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 14:47:00 -04:00
_amiiboLogoBytes = EmbeddedResources . Read ( "Ryujinx.Ui.Common/Resources/Logo_Amiibo.png" ) ;
2021-03-18 16:40:20 -04: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-03-21 19:14:46 -04:00
amiiboJsonString = File . ReadAllText ( _amiiboJsonPath ) ;
2021-03-18 16:40:20 -04:00
2023-03-21 19:14:46 -04:00
if ( await NeedsUpdate ( JsonHelper . Deserialize < AmiiboJson > ( amiiboJsonString ) . LastUpdated ) )
2021-03-18 16:40:20 -04:00
{
amiiboJsonString = await DownloadAmiiboJson ( ) ;
}
}
else
{
try
{
amiiboJsonString = await DownloadAmiiboJson ( ) ;
}
catch
{
ShowInfoDialog ( ) ;
Close ( ) ;
}
}
2023-03-21 19:14:46 -04:00
_amiiboList = JsonHelper . Deserialize < AmiiboJson > ( amiiboJsonString ) . Amiibo ;
2021-03-18 16:40:20 -04: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 ;
}
catch
{
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
{
2021-08-20 17:48:00 -04:00
GtkDialog . CreateInfoDialog ( $"Amiibo API" , "An error occured while fetching information from the API." ) ;
2021-03-18 16:40:20 -04: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 ) ;
}
}
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 17:25:16 -05:00
var usageStringBuilder = new StringBuilder ( ) ;
2021-03-18 16:40:20 -04: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 17:25:16 -05:00
usageStringBuilder . Append ( Environment . NewLine ) ;
usageStringBuilder . Append ( $"- {usageItem.Usage.Replace(" / ", Environment.NewLine + " - ")}" ) ;
2021-03-18 16:40:20 -04:00
writable = usageItem . Write ;
}
}
}
2023-01-18 17:25:16 -05:00
if ( usageStringBuilder . Length = = 0 )
2021-03-18 16:40:20 -04:00
{
2023-01-18 17:25:16 -05:00
usageStringBuilder . Append ( "Unknown." ) ;
2021-03-18 16:40:20 -04:00
}
2023-01-18 17:25:16 -05:00
_gameUsageLabel . Text = $"Usage{(writable ? " ( Writable ) " : " ")} : {usageStringBuilder}" ;
2021-03-18 16:40:20 -04: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 ) ;
}
}
2021-08-20 17:48:00 -04:00
}