2024-01-13 10:45:38 +00:00
using Gdk ;
2023-12-04 13:17:13 +00:00
using Gtk ;
2021-03-18 20:40:20 +00:00
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 ;
2024-02-11 02:09:18 +00:00
using Ryujinx.UI.Common.Configuration ;
using Ryujinx.UI.Common.Models.Amiibo ;
using Ryujinx.UI.Widgets ;
2021-03-18 20:40:20 +00:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Net.Http ;
using System.Reflection ;
using System.Text ;
2024-01-13 10:45:38 +00:00
using System.Text.Json ;
2021-03-18 20:40:20 +00:00
using System.Threading.Tasks ;
2024-01-13 10:45:38 +00:00
using Window = Gtk . Window ;
2021-03-18 20:40:20 +00:00
2024-02-11 02:09:18 +00:00
namespace Ryujinx.UI.Windows
2021-03-18 20:40:20 +00:00
{
public partial class AmiiboWindow : Window
{
2023-07-01 22:25:07 +00:00
private const string DefaultJson = "{ \"amiibo\": [] }" ;
2021-03-18 20:40:20 +00:00
public string AmiiboId { get ; private set ; }
2023-07-01 22:25:07 +00:00
public int DeviceId { get ; set ; }
public string TitleId { get ; set ; }
public string LastScannedAmiiboId { get ; set ; }
public bool LastScannedAmiiboShowAll { get ; set ; }
2021-03-18 20:40:20 +00:00
public ResponseType Response { get ; private set ; }
public bool UseRandomUuid
{
get
{
return _randomUuidCheckBox . Active ;
}
}
private readonly HttpClient _httpClient ;
2023-07-01 22:25:07 +00:00
private readonly string _amiiboJsonPath ;
2021-03-18 20:40:20 +00:00
private readonly byte [ ] _amiiboLogoBytes ;
private List < AmiiboApi > _amiiboList ;
2023-07-01 22:25:07 +00:00
private static readonly AmiiboJsonSerializerContext _serializerContext = new ( JsonHelper . GetDefaultSerializerOptions ( ) ) ;
2023-04-03 10:14:19 +00:00
2021-03-18 20:40:20 +00:00
public AmiiboWindow ( ) : base ( $"Ryujinx {Program.Version} - Amiibo" )
{
2024-02-11 02:09:18 +00:00
Icon = new Pixbuf ( Assembly . GetAssembly ( typeof ( ConfigurationState ) ) , "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png" ) ;
2021-03-18 20:40:20 +00:00
InitializeComponent ( ) ;
2024-01-13 10:45:38 +00:00
_httpClient = new HttpClient
2021-03-18 20:40:20 +00:00
{
2023-07-01 22:25:07 +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" ) ;
2023-07-01 22:25:07 +00:00
_amiiboList = new List < AmiiboApi > ( ) ;
2021-03-18 20:40:20 +00:00
2024-02-11 02:09:18 +00:00
_amiiboLogoBytes = EmbeddedResources . Read ( "Ryujinx.UI.Common/Resources/Logo_Amiibo.png" ) ;
2024-01-13 10:45:38 +00:00
_amiiboImage . Pixbuf = new Pixbuf ( _amiiboLogoBytes ) ;
2021-03-18 20:40:20 +00:00
2023-07-01 22:25:07 +00:00
_scanButton . Sensitive = false ;
2021-03-18 20:40:20 +00:00
_randomUuidCheckBox . Sensitive = false ;
_ = LoadContentAsync ( ) ;
}
2024-01-13 10:45:38 +00:00
private static bool TryGetAmiiboJson ( string json , out AmiiboJson amiiboJson )
2023-12-28 01:43:17 +00:00
{
2024-01-13 10:45:38 +00:00
if ( string . IsNullOrEmpty ( json ) )
{
amiiboJson = JsonHelper . Deserialize ( DefaultJson , _serializerContext . AmiiboJson ) ;
return false ;
}
2023-12-28 01:43:17 +00:00
try
{
2024-01-13 10:45:38 +00:00
amiiboJson = JsonHelper . Deserialize ( json , _serializerContext . AmiiboJson ) ;
2023-12-28 01:43:17 +00:00
return true ;
}
2024-01-13 10:45:38 +00:00
catch ( JsonException exception )
2023-12-28 01:43:17 +00:00
{
2024-01-13 10:45:38 +00:00
Logger . Error ? . Print ( LogClass . Application , $"Unable to deserialize amiibo data: {exception}" ) ;
amiiboJson = JsonHelper . Deserialize ( DefaultJson , _serializerContext . AmiiboJson ) ;
2023-12-28 01:43:17 +00:00
return false ;
}
}
private async Task < AmiiboJson > GetMostRecentAmiiboListOrDefaultJson ( )
2021-03-18 20:40:20 +00:00
{
2023-12-28 01:43:17 +00:00
bool localIsValid = false ;
bool remoteIsValid = false ;
2024-01-13 10:45:38 +00:00
AmiiboJson amiiboJson = new ( ) ;
2021-03-18 20:40:20 +00:00
2023-12-28 01:43:17 +00:00
try
2021-03-18 20:40:20 +00:00
{
2024-01-13 10:45:38 +00:00
try
{
if ( File . Exists ( _amiiboJsonPath ) )
{
localIsValid = TryGetAmiiboJson ( await File . ReadAllTextAsync ( _amiiboJsonPath ) , out amiiboJson ) ;
}
}
catch ( Exception exception )
{
Logger . Warning ? . Print ( LogClass . Application , $"Unable to read data from '{_amiiboJsonPath}': {exception}" ) ;
}
2021-03-18 20:40:20 +00:00
2023-12-28 01:43:17 +00:00
if ( ! localIsValid | | await NeedsUpdate ( amiiboJson . LastUpdated ) )
2021-03-18 20:40:20 +00:00
{
2023-12-28 01:43:17 +00:00
remoteIsValid = TryGetAmiiboJson ( await DownloadAmiiboJson ( ) , out amiiboJson ) ;
2021-03-18 20:40:20 +00:00
}
}
2024-01-13 10:45:38 +00:00
catch ( Exception exception )
2021-03-18 20:40:20 +00:00
{
2023-12-28 01:43:17 +00:00
if ( ! ( localIsValid | | remoteIsValid ) )
2021-03-18 20:40:20 +00:00
{
2024-01-13 10:45:38 +00:00
Logger . Error ? . Print ( LogClass . Application , $"Couldn't get valid amiibo data: {exception}" ) ;
2023-12-28 01:43:17 +00:00
// Neither local or remote files are valid JSON, close window.
ShowInfoDialog ( ) ;
Close ( ) ;
2021-03-18 20:40:20 +00:00
}
2023-12-28 01:43:17 +00:00
else if ( ! remoteIsValid )
2021-03-18 20:40:20 +00:00
{
2024-01-13 10:45:38 +00:00
Logger . Warning ? . Print ( LogClass . Application , $"Couldn't update amiibo data: {exception}" ) ;
2023-12-28 01:43:17 +00:00
// Only the local file is valid, the local one should be used
// but the user should be warned.
2021-03-18 20:40:20 +00:00
ShowInfoDialog ( ) ;
}
}
2023-12-28 01:43:17 +00:00
return amiiboJson ;
}
private async Task LoadContentAsync ( )
{
AmiiboJson amiiboJson = await GetMostRecentAmiiboListOrDefaultJson ( ) ;
_amiiboList = amiiboJson . Amiibo . OrderBy ( amiibo = > amiibo . AmiiboSeries ) . ToList ( ) ;
2021-03-18 20:40:20 +00:00
if ( LastScannedAmiiboShowAll )
{
_showAllCheckBox . Click ( ) ;
}
ParseAmiiboData ( ) ;
_showAllCheckBox . Clicked + = ShowAllCheckBox_Clicked ;
}
private void ParseAmiiboData ( )
{
2023-07-01 22:25:07 +00:00
List < string > comboxItemList = new ( ) ;
2021-03-18 20:40:20 +00:00
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 ;
2023-07-01 22:25:07 +00:00
_amiiboCharsComboBox . Changed + = CharacterComboBox_Changed ;
2021-03-18 20:40:20 +00:00
if ( LastScannedAmiiboId ! = "" )
{
SelectLastScannedAmiibo ( ) ;
}
else
{
_amiiboSeriesComboBox . Active = 0 ;
}
}
private void SelectLastScannedAmiibo ( )
{
2023-06-22 23:42:23 +00:00
bool isSet = _amiiboSeriesComboBox . SetActiveId ( _amiiboList . Find ( amiibo = > amiibo . Head + amiibo . Tail = = LastScannedAmiiboId ) . AmiiboSeries ) ;
2021-03-18 20:40:20 +00:00
isSet = _amiiboCharsComboBox . SetActiveId ( LastScannedAmiiboId ) ;
if ( isSet = = false )
{
_amiiboSeriesComboBox . Active = 0 ;
}
}
private async Task < bool > NeedsUpdate ( DateTime oldLastModified )
{
2024-01-13 10:45:38 +00:00
try
{
HttpResponseMessage response = await _httpClient . SendAsync ( new HttpRequestMessage ( HttpMethod . Head , "https://amiibo.ryujinx.org/" ) ) ;
2021-03-18 20:40:20 +00:00
2024-01-13 10:45:38 +00:00
if ( response . IsSuccessStatusCode )
{
return response . Content . Headers . LastModified ! = oldLastModified ;
}
}
catch ( HttpRequestException exception )
2021-03-18 20:40:20 +00:00
{
2024-01-13 10:45:38 +00:00
Logger . Error ? . Print ( LogClass . Application , $"Unable to check for amiibo data updates: {exception}" ) ;
2021-03-18 20:40:20 +00:00
}
2023-12-28 01:43:17 +00:00
return false ;
2021-03-18 20:40:20 +00:00
}
private async Task < string > DownloadAmiiboJson ( )
{
2024-01-13 10:45:38 +00:00
try
2021-03-18 20:40:20 +00:00
{
2024-01-13 10:45:38 +00:00
HttpResponseMessage response = await _httpClient . GetAsync ( "https://amiibo.ryujinx.org/" ) ;
2021-03-18 20:40:20 +00:00
2024-01-13 10:45:38 +00:00
if ( response . IsSuccessStatusCode )
2021-03-18 20:40:20 +00:00
{
2024-01-13 10:45:38 +00:00
string amiiboJsonString = await response . Content . ReadAsStringAsync ( ) ;
try
{
using FileStream dlcJsonStream = File . Create ( _amiiboJsonPath , 4096 , FileOptions . WriteThrough ) ;
dlcJsonStream . Write ( Encoding . UTF8 . GetBytes ( amiiboJsonString ) ) ;
}
catch ( Exception exception )
{
Logger . Warning ? . Print ( LogClass . Application , $"Couldn't write amiibo data to file '{_amiiboJsonPath}: {exception}'" ) ;
}
return amiiboJsonString ;
2021-03-18 20:40:20 +00:00
}
2024-01-13 10:45:38 +00:00
Logger . Error ? . Print ( LogClass . Application , $"Failed to download amiibo data. Response status code: {response.StatusCode}" ) ;
2021-03-18 20:40:20 +00:00
}
2024-01-13 10:45:38 +00:00
catch ( HttpRequestException exception )
2021-03-18 20:40:20 +00:00
{
2024-01-13 10:45:38 +00:00
Logger . Error ? . Print ( LogClass . Application , $"Failed to request amiibo data: {exception}" ) ;
2021-03-18 20:40:20 +00:00
}
2024-01-13 10:45:38 +00:00
GtkDialog . CreateInfoDialog ( "Amiibo API" , "An error occured while fetching information from the API." ) ;
return null ;
2021-03-18 20:40:20 +00:00
}
private async Task UpdateAmiiboPreview ( string imageUrl )
{
HttpResponseMessage response = await _httpClient . GetAsync ( imageUrl ) ;
if ( response . IsSuccessStatusCode )
{
2023-07-01 22:25:07 +00:00
byte [ ] amiiboPreviewBytes = await response . Content . ReadAsByteArrayAsync ( ) ;
2024-01-13 10:45:38 +00:00
Pixbuf amiiboPreview = new ( amiiboPreviewBytes ) ;
2021-03-18 20:40:20 +00:00
2023-07-01 22:25:07 +00:00
float ratio = Math . Min ( ( float ) _amiiboImage . AllocatedWidth / amiiboPreview . Width ,
2021-03-18 20:40:20 +00:00
( float ) _amiiboImage . AllocatedHeight / amiiboPreview . Height ) ;
int resizeHeight = ( int ) ( amiiboPreview . Height * ratio ) ;
2023-07-01 22:25:07 +00:00
int resizeWidth = ( int ) ( amiiboPreview . Width * ratio ) ;
2021-03-18 20:40:20 +00:00
2024-01-13 10:45:38 +00:00
_amiiboImage . Pixbuf = amiiboPreview . ScaleSimple ( resizeWidth , resizeHeight , InterpType . Bilinear ) ;
2021-03-18 20:40:20 +00:00
}
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
}
2023-07-01 22:25:07 +00:00
private static void ShowInfoDialog ( )
2021-03-18 20:40:20 +00:00
{
2024-01-13 10:45:38 +00:00
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." ) ;
2021-03-18 20:40:20 +00:00
}
//
// 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 ( ) ;
2023-07-01 22:25:07 +00:00
List < string > comboxItemList = new ( ) ;
2021-03-18 20:40:20 +00:00
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 ;
2023-07-01 22:25:07 +00:00
_scanButton . Sensitive = true ;
2021-03-18 20:40:20 +00:00
_randomUuidCheckBox . Sensitive = true ;
}
private void CharacterComboBox_Changed ( object sender , EventArgs args )
{
AmiiboId = _amiiboCharsComboBox . ActiveId ;
2024-01-13 10:45:38 +00:00
_amiiboImage . Pixbuf = new Pixbuf ( _amiiboLogoBytes ) ;
2021-03-18 20:40:20 +00:00
2023-06-22 23:42:23 +00:00
string imageUrl = _amiiboList . Find ( amiibo = > amiibo . Head + amiibo . Tail = = _amiiboCharsComboBox . ActiveId ) . Image ;
2021-03-18 20:40:20 +00:00
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 )
{
2024-01-13 10:45:38 +00:00
_amiiboImage . Pixbuf = new Pixbuf ( _amiiboLogoBytes ) ;
2021-03-18 20:40:20 +00:00
_amiiboSeriesComboBox . Changed - = SeriesComboBox_Changed ;
2023-07-01 22:25:07 +00:00
_amiiboCharsComboBox . Changed - = CharacterComboBox_Changed ;
2021-03-18 20:40:20 +00:00
_amiiboSeriesComboBox . RemoveAll ( ) ;
_amiiboCharsComboBox . RemoveAll ( ) ;
2023-07-01 22:25:07 +00:00
_scanButton . Sensitive = false ;
2021-03-18 20:40:20 +00:00
_randomUuidCheckBox . Sensitive = false ;
2024-01-13 10:45:38 +00:00
new Task ( ParseAmiiboData ) . Start ( ) ;
2021-03-18 20:40:20 +00:00
}
private void ScanButton_Pressed ( object sender , EventArgs args )
{
LastScannedAmiiboShowAll = _showAllCheckBox . Active ;
Response = ResponseType . Ok ;
Close ( ) ;
}
private void CancelButton_Pressed ( object sender , EventArgs args )
{
2023-07-01 22:25:07 +00:00
AmiiboId = "" ;
LastScannedAmiiboId = "" ;
2021-03-18 20:40:20 +00:00
LastScannedAmiiboShowAll = false ;
Response = ResponseType . Cancel ;
Close ( ) ;
}
protected override void Dispose ( bool disposing )
{
_httpClient . Dispose ( ) ;
base . Dispose ( disposing ) ;
}
}
2023-07-01 22:25:07 +00:00
}