diff --git a/ClassicalSharp/2D/Widgets/Chat/TextInputWidget.cs b/ClassicalSharp/2D/Widgets/Chat/TextInputWidget.cs index eb2545e25..876d40c7e 100644 --- a/ClassicalSharp/2D/Widgets/Chat/TextInputWidget.cs +++ b/ClassicalSharp/2D/Widgets/Chat/TextInputWidget.cs @@ -46,7 +46,7 @@ namespace ClassicalSharp { game.Drawer2D.MakeBitmappedTextTexture( ref args, 0, 0 ) : game.Drawer2D.MakeTextTexture( ref args, 0, 0 ); chatInputText.WordWrap( ref parts, ref partLens, 64 ); - + maxWidth = 0; args = new DrawTextArgs( null, font, false ); for( int i = 0; i < lines; i++ ) { diff --git a/ClassicalSharp/Rendering/WeatherRenderer.cs b/ClassicalSharp/Rendering/WeatherRenderer.cs index 81826ab48..6bc2972f1 100644 --- a/ClassicalSharp/Rendering/WeatherRenderer.cs +++ b/ClassicalSharp/Rendering/WeatherRenderer.cs @@ -15,13 +15,15 @@ namespace ClassicalSharp { map = game.Map; graphics = game.Graphics; info = game.BlockInfo; - weatherVb = graphics.CreateDynamicVb( VertexFormat.Pos3fTex2fCol4b, 12 * 9 * 9 ); + weatherVb = graphics.CreateDynamicVb( VertexFormat.Pos3fTex2fCol4b, vertices.Length ); } int weatherVb; short[] heightmap; float vOffset; - VertexPos3fTex2fCol4b[] vertices = new VertexPos3fTex2fCol4b[8 * 9 * 9]; + const int extent = 4; + VertexPos3fTex2fCol4b[] vertices = new VertexPos3fTex2fCol4b[8 * (extent * 2 + 1) * (extent * 2 + 1)]; + public void Render( double deltaTime ) { Weather weather = map.Weather; if( weather == Weather.Sunny ) return; @@ -36,8 +38,8 @@ namespace ClassicalSharp { graphics.AlphaBlending = true; graphics.DepthWrite = false; FastColour col = FastColour.White; - for( int dx = -4; dx <= 4; dx++ ) { - for( int dz = -4; dz <= 4; dz++ ) { + for( int dx = -extent; dx <= extent; dx++ ) { + for( int dz = -extent; dz <= extent; dz++ ) { int rainY = Math.Max( pos.Y, GetRainHeight( pos.X + dx, pos.Z + dz ) + 1 ); int height = Math.Min( 6 - ( rainY - pos.Y ), 6 ); if( height <= 0 ) continue; @@ -46,6 +48,7 @@ namespace ClassicalSharp { MakeRainForSquare( pos.X + dx, rainY, height, pos.Z + dz, col, ref index ); } } + // fixes crashing on nVidia cards in OpenGL builds. if( index > 0 ) { graphics.BeginVbBatch( VertexFormat.Pos3fTex2fCol4b ); diff --git a/Launcher2/Gui/Screens/ClassiCubeScreen.cs b/Launcher2/Gui/Screens/ClassiCubeScreen.cs index 0b003a338..0fe5d103d 100644 --- a/Launcher2/Gui/Screens/ClassiCubeScreen.cs +++ b/Launcher2/Gui/Screens/ClassiCubeScreen.cs @@ -87,8 +87,8 @@ namespace Launcher2 { void Draw() { widgetIndex = 0; - MakeTextAt( "Username", titleFont, -180, -100 ); - MakeTextAt( "Password", titleFont, -180, -50 ); + MakeLabelAt( "Username", titleFont, Anchor.Centre, Anchor.Centre, -180, -100 ); + MakeLabelAt( "Password", titleFont, Anchor.Centre, Anchor.Centre, -180, -50 ); MakeInput( Get(), 300, Anchor.Centre, false, 30, -100, 32 ); MakeInput( Get(), 300, Anchor.Centre, true, 30, -50, 32 ); @@ -97,8 +97,8 @@ namespace Launcher2 { -75, 0, StartClient ); MakeButtonAt( "Back", 80, 35, titleFont, Anchor.Centre, 140, 0, (x, y) => game.SetScreen( new MainScreen( game ) ) ); - string text = widgets[6] == null ? "" : ((LauncherLabelWidget)widgets[6]).Text; - MakeTextAt( text, inputFont, 0, 50 ); + string text = widgets[6] == null ? "" : widgets[6].Text; + MakeLabelAt( text, inputFont, Anchor.Centre, Anchor.Centre, 0, 50 ); if( HasServers && !signingIn ) MakeButtonAt( "Servers", 90, 35, titleFont, Anchor.Centre, @@ -117,12 +117,6 @@ namespace Launcher2 { Dirty = true; } } - - void MakeTextAt( string text, Font font, int x, int y ) { - LauncherLabelWidget widget = new LauncherLabelWidget( game, text ); - widget.DrawAt( drawer, text, font, Anchor.Centre, Anchor.Centre, x, y ); - widgets[widgetIndex++] = widget; - } bool HasServers { get { diff --git a/Launcher2/Gui/Screens/ClassiCubeServersScreen.cs b/Launcher2/Gui/Screens/ClassiCubeServersScreen.cs index fa5f38be9..1d2c0bc21 100644 --- a/Launcher2/Gui/Screens/ClassiCubeServersScreen.cs +++ b/Launcher2/Gui/Screens/ClassiCubeServersScreen.cs @@ -69,10 +69,10 @@ namespace Launcher2 { void Draw() { widgetIndex = 0; - MakeTextAt( titleFont, "Search", -200, 10 ); + MakeLabelAt( "Search", titleFont, Anchor.Centre, Anchor.Centre, -200, 10 ); MakeInput( Get(), 270, Anchor.LeftOrTop, false, -25, 5, 32 ); - MakeTextAt( inputFont, "../play/", -210, 55 ); + MakeLabelAt( "../play/", inputFont, Anchor.Centre, Anchor.Centre, -210, 55 ); MakeInput( Get(), 320, Anchor.LeftOrTop, false, -20, 50, 32 ); ((LauncherInputWidget)widgets[3]).ClipboardFilter = HashFilter; @@ -82,12 +82,6 @@ namespace Launcher2 { 195, 50, (x, y) => game.SetScreen( new MainScreen( game ) ) ); MakeTableWidget(); } - - void MakeTextAt( Font font, string text, int x, int y ) { - LauncherLabelWidget widget = new LauncherLabelWidget( game, text ); - widget.DrawAt( drawer, text, font, Anchor.Centre, Anchor.LeftOrTop, x, y ); - widgets[widgetIndex++] = widget; - } void MakeTableWidget() { if( widgets[tableIndex] != null ) { diff --git a/Launcher2/Gui/Screens/DirectConnectScreen.cs b/Launcher2/Gui/Screens/DirectConnectScreen.cs index 9c6052ef8..6d787b4a9 100644 --- a/Launcher2/Gui/Screens/DirectConnectScreen.cs +++ b/Launcher2/Gui/Screens/DirectConnectScreen.cs @@ -73,17 +73,18 @@ namespace Launcher2 { void Draw() { widgetIndex = 0; - MakeTextAt( "Username", -180, -100 ); - MakeTextAt( "Address", -180, -50 ); - MakeTextAt( "Mppass", -180, 0 ); + MakeLabelAt( "Username", titleFont, Anchor.Centre, Anchor.Centre, -180, -100 ); + MakeLabelAt( "Address", titleFont, Anchor.Centre, Anchor.Centre, -180, -50 ); + MakeLabelAt( "Mppass", titleFont, Anchor.Centre, Anchor.Centre, -180, 0 ); MakeInput( Get(), 300, Anchor.Centre, false, 30, -100, 32 ); MakeInput( Get(), 300, Anchor.Centre, false, 30, -50, 64 ); MakeInput( Get(), 300, Anchor.Centre, false, 30, 0, 32 ); - MakeButtonAt( "Connect", 110, 35, -65, 50, StartClient ); - MakeButtonAt( "Back", 80, 35, 140, 50, (x, y) => game.SetScreen( new MainScreen( game ) ) ); - MakeTextAt( "", 0, 100 ); + MakeButtonAt( "Connect", 110, 35, titleFont, Anchor.Centre, -65, 50, StartClient ); + MakeButtonAt( "Back", 80, 35, titleFont, Anchor.Centre, + 140, 50, (x, y) => game.SetScreen( new MainScreen( game ) ) ); + MakeLabelAt( "", titleFont, Anchor.Centre, Anchor.Centre, 0, 100 ); } void SetStatus( string text ) { @@ -95,23 +96,6 @@ namespace Launcher2 { Dirty = true; } } - - void MakeTextAt( string text, int x, int y ) { - LauncherLabelWidget widget = new LauncherLabelWidget( game, text ); - widget.DrawAt( drawer, text, titleFont, Anchor.Centre, Anchor.Centre, x, y ); - widgets[widgetIndex++] = widget; - } - - void MakeButtonAt( string text, int width, int height, - int x, int y, Action onClick ) { - LauncherButtonWidget widget = new LauncherButtonWidget( game ); - widget.Text = text; - widget.OnClick = onClick; - - widget.Active = false; - widget.DrawAt( drawer, text, titleFont, Anchor.Centre, Anchor.Centre, width, height, x, y ); - widgets[widgetIndex++] = widget; - } void StartClient( int mouseX, int mouseY ) { SetStatus( "" ); diff --git a/Launcher2/Gui/Screens/LauncherInputScreen.cs b/Launcher2/Gui/Screens/LauncherInputScreen.cs index c67f61071..c179ce12e 100644 --- a/Launcher2/Gui/Screens/LauncherInputScreen.cs +++ b/Launcher2/Gui/Screens/LauncherInputScreen.cs @@ -14,6 +14,7 @@ namespace Launcher2 { } public override void Init() { + buttonFont = titleFont; game.Window.Mouse.Move += MouseMove; game.Window.Mouse.ButtonDown += MouseButtonDown; @@ -82,9 +83,9 @@ namespace Launcher2 { protected string Get() { return Get( widgetIndex ); } - protected string Get( int index ) { + protected string Get( int index ) { LauncherWidget widget = widgets[index]; - return widget == null ? "" : ((LauncherInputWidget)widget).Text; + return widget == null ? "" : widget.Text; } protected void Set( int index, string text ) { @@ -92,24 +93,6 @@ namespace Launcher2 { .Redraw( drawer, text, inputFont ); } - protected override void UnselectWidget( LauncherWidget widget ) { - LauncherButtonWidget button = widget as LauncherButtonWidget; - if( button != null ) { - button.Active = false; - button.Redraw( drawer, button.Text, titleFont ); - Dirty = true; - } - } - - protected override void SelectWidget( LauncherWidget widget ) { - LauncherButtonWidget button = widget as LauncherButtonWidget; - if( button != null ) { - button.Active = true; - button.Redraw( drawer, button.Text, titleFont ); - Dirty = true; - } - } - protected LauncherInputWidget lastInput; protected void InputClick( int mouseX, int mouseY ) { LauncherInputWidget input = (LauncherInputWidget)selectedWidget; diff --git a/Launcher2/Gui/Screens/LauncherScreen.cs b/Launcher2/Gui/Screens/LauncherScreen.cs index 4156f5dee..18781d74b 100644 --- a/Launcher2/Gui/Screens/LauncherScreen.cs +++ b/Launcher2/Gui/Screens/LauncherScreen.cs @@ -56,12 +56,26 @@ namespace Launcher2 { selectedWidget = null; } + protected Font buttonFont; + /// Called when the user has moved their mouse away from a previously selected widget. protected virtual void UnselectWidget( LauncherWidget widget ) { + LauncherButtonWidget button = widget as LauncherButtonWidget; + if( button != null ) { + button.Active = false; + button.Redraw( drawer, button.Text, buttonFont ); + Dirty = true; + } } /// Called when user has moved their mouse over a given widget. protected virtual void SelectWidget( LauncherWidget widget ) { + LauncherButtonWidget button = widget as LauncherButtonWidget; + if( button != null ) { + button.Active = true; + button.Redraw( drawer, button.Text, buttonFont ); + Dirty = true; + } } protected LauncherWidget lastClicked; @@ -126,5 +140,17 @@ namespace Launcher2 { widget.DrawAt( drawer, text, font, Anchor.Centre, verAnchor, width, height, x, y ); widgets[widgetIndex++] = widget; } + + protected void MakeLabelAt( string text, Font font, Anchor horAnchor, Anchor verAnchor, int x, int y) { + if( widgets[widgetIndex] != null ) { + LauncherLabelWidget input = (LauncherLabelWidget)widgets[widgetIndex]; + input.DrawAt( drawer, text, font, horAnchor, verAnchor, x, y ); + } else { + LauncherLabelWidget widget = new LauncherLabelWidget( game, text ); + widget.DrawAt( drawer, text, font, horAnchor, verAnchor, x, y ); + widgets[widgetIndex] = widget; + } + widgetIndex++; + } } } diff --git a/Launcher2/Gui/Screens/MainScreen.cs b/Launcher2/Gui/Screens/MainScreen.cs index 525354d5d..30806875e 100644 --- a/Launcher2/Gui/Screens/MainScreen.cs +++ b/Launcher2/Gui/Screens/MainScreen.cs @@ -70,6 +70,10 @@ namespace Launcher2 { MakeButtonAt( "Singleplayer", Anchor.Centre, Anchor.Centre, buttonWidth, buttonHeight, 0, 0, (x, y) => Client.Start( "default.zip" ) ); + + MakeButtonAt( "Check for updates", Anchor.Centre, Anchor.Centre, + buttonWidth, buttonHeight, 0, 100, + (x, y) => game.SetScreen( new UpdatesScreen( game ) ) ); } const int buttonWidth = 220, buttonHeight = 35, sideButtonWidth = 150; diff --git a/Launcher2/Gui/Screens/ResourcesScreen.cs b/Launcher2/Gui/Screens/ResourcesScreen.cs index 3fa41ffd5..d58eef229 100644 --- a/Launcher2/Gui/Screens/ResourcesScreen.cs +++ b/Launcher2/Gui/Screens/ResourcesScreen.cs @@ -15,6 +15,7 @@ namespace Launcher2 { textFont = new Font( "Arial", 16, FontStyle.Bold ); infoFont = new Font( "Arial", 14, FontStyle.Regular ); statusFont = new Font( "Arial", 13, FontStyle.Italic ); + buttonFont = textFont; widgets = new LauncherWidget[4]; } @@ -41,24 +42,6 @@ namespace Launcher2 { Dirty = true; } - protected override void UnselectWidget( LauncherWidget widget ) { - LauncherButtonWidget button = widget as LauncherButtonWidget; - if( button != null ) { - button.Active = false; - button.Redraw( drawer, button.Text, textFont ); - Dirty = true; - } - } - - protected override void SelectWidget( LauncherWidget widget ) { - LauncherButtonWidget button = widget as LauncherButtonWidget; - if( button != null ) { - button.Active = true; - button.Redraw( drawer, button.Text, textFont ); - Dirty = true; - } - } - ResourceFetcher fetcher; void DownloadResources( int mouseX, int mouseY ) { if( game.Downloader == null ) @@ -85,10 +68,9 @@ namespace Launcher2 { drawer.DrawRect( backCol, game.Width / 2 - 175, game.Height / 2 - 70, 175 * 2, 70 * 2 ); - string text = widgets[0] == null ? - String.Format( format, ResourceFetcher.EstimateDownloadSize().ToString( "F2" ) ) - : ((LauncherLabelWidget)widgets[0]).Text; - MakeTextAt( statusFont, text, 0, 5 ); + string text = widgets[0] != null ? widgets[0].Text + : String.Format( format, ResourceFetcher.EstimateDownloadSize() ); + MakeLabelAt( text, statusFont, Anchor.Centre, Anchor.Centre, 0, 5 ); // Clear the entire previous widgets state. for( int i = 1; i < widgets.Length; i++ ) { @@ -98,11 +80,11 @@ namespace Launcher2 { } if( fetcher == null ) { - MakeTextAt( infoFont, mainText, 0, -30 ); + MakeLabelAt( mainText, infoFont, Anchor.Centre, Anchor.Centre, 0, -30 ); MakeButtonAt( "Yes", 60, 30, textFont, Anchor.Centre, -50, 40, DownloadResources ); - MakeButtonAt( "No", 60, 30, textFont, Anchor.Centre, + MakeButtonAt( "No", 60, 30, textFont, Anchor.Centre, 50, 40, (x, y) => game.SetScreen( new MainScreen( game ) ) ); } else { MakeButtonAt( "Cancel", 120, 30, textFont, Anchor.Centre, @@ -120,12 +102,6 @@ namespace Launcher2 { } } - void MakeTextAt( Font font, string text, int x, int y ) { - LauncherLabelWidget widget = new LauncherLabelWidget( game, text ); - widget.DrawAt( drawer, text, font, Anchor.Centre, Anchor.Centre, x, y ); - widgets[widgetIndex++] = widget; - } - public override void Dispose() { game.Window.Mouse.Move -= MouseMove; game.Window.Mouse.ButtonDown -= MouseButtonDown; diff --git a/Launcher2/Gui/Screens/UpdatesScreen.cs b/Launcher2/Gui/Screens/UpdatesScreen.cs new file mode 100644 index 000000000..62cb2361b --- /dev/null +++ b/Launcher2/Gui/Screens/UpdatesScreen.cs @@ -0,0 +1,113 @@ +using System; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Net; +using ClassicalSharp; + +namespace Launcher2 { + + public sealed class UpdatesScreen : LauncherScreen { + + Font titleFont, infoFont; + public UpdatesScreen( LauncherWindow game ) : base( game ) { + game.Window.Mouse.Move += MouseMove; + game.Window.Mouse.ButtonDown += MouseButtonDown; + + titleFont = new Font( "Arial", 16, FontStyle.Bold ); + infoFont = new Font( "Arial", 14, FontStyle.Regular ); + buttonFont = titleFont; + widgets = new LauncherWidget[16]; + } + + UpdateCheckTask checkTask; + public override void Init() { + checkTask = new UpdateCheckTask(); + checkTask.CheckForUpdatesAsync(); + Resize(); + } + + public override void Tick() { + if( checkTask != null && !checkTask.Working ) { + if( checkTask.Exception != null ) { + updateCheckFailed = true; + } else { + lastStable = DateTime.Parse( checkTask.LatestStableDate, + null, DateTimeStyles.AssumeUniversal ); + lastDev = DateTime.Parse( checkTask.LatestDevDate, + null, DateTimeStyles.AssumeUniversal ); + + validStable = Int32.Parse( checkTask.LatestStableSize ) > 50000; + validDev = Int32.Parse( checkTask.LatestDevSize ) > 50000; + } + checkTask = null; + game.MakeBackground(); + Resize(); + } + } + + public override void Resize() { + using( drawer ) { + drawer.SetBitmap( game.Framebuffer ); + Draw(); + } + Dirty = true; + } + + const string dateFormat = "dd-MM-yyyy HH:mm"; + DateTime lastStable, lastDev; + bool validStable = true, validDev = true; + bool updateCheckFailed; + + void Draw() { + widgetIndex = 0; + + MakeLabelAt( "Your build:", titleFont, Anchor.Centre, Anchor.Centre, -55, -120 ); + string yourBuild = File.GetLastWriteTimeUtc( "ClassicalSharp.exe" ).ToString( dateFormat ); + MakeLabelAt( yourBuild, infoFont, Anchor.Centre, Anchor.Centre, 100, -120 ); + + MakeLabelAt( "Latest stable:", titleFont, Anchor.Centre, Anchor.Centre, -70, -80 ); + string latestStable = GetDateString( lastStable, validStable ); + MakeLabelAt( latestStable, infoFont, Anchor.Centre, Anchor.Centre, 100, -80 ); + MakeButtonAt( "Update to stable", 180, 30, titleFont, Anchor.Centre, 0, -40, + (x, y) => UpdateBuild( lastDev, validDev, "latest.Release.zip" ) ); + + MakeLabelAt( "Latest OpenGL dev:", titleFont, Anchor.Centre, Anchor.Centre, -100, 0 ); + string latestDev = GetDateString( lastDev, validDev ); + MakeLabelAt( latestDev, infoFont, Anchor.Centre, Anchor.Centre, 100, 0 ); + MakeButtonAt( "Update to OpenGL dev", 240, 30, titleFont, Anchor.Centre, 0, 40, + (x, y) => UpdateBuild( lastDev, validDev, "latest.zip" ) ); + + MakeLabelAt( "Latest D3D9 dev:", titleFont, Anchor.Centre, Anchor.Centre, -85, 80 ); + MakeLabelAt( latestDev, infoFont, Anchor.Centre, Anchor.Centre, 100, 80 ); + MakeButtonAt( "Update to D3D9 dev", 230, 30, titleFont, Anchor.Centre, 0, 120, + (x, y) => UpdateBuild( lastDev, validDev, "latest.DirectX.zip" ) ); + + MakeButtonAt( "Back", 80, 35, titleFont, Anchor.Centre, + 0, 200, (x, y) => game.SetScreen( new MainScreen( game ) ) ); + } + + string GetDateString( DateTime last, bool valid ) { + if( updateCheckFailed ) return "Update check failed"; + if( !valid ) return "Build corrupted"; + if( last == DateTime.MinValue ) return "Checking.."; + return last.ToString( dateFormat ); + } + + void UpdateBuild( DateTime last, bool valid, string dir ) { + if( last == DateTime.MinValue || !valid ) return; + + using( WebClient client = new WebClient() ) { + client.DownloadFile( UpdateCheckTask.UpdatesUri + dir, "update.zip" ); + } + } + + public override void Dispose() { + game.Window.Mouse.Move -= MouseMove; + game.Window.Mouse.ButtonDown -= MouseButtonDown; + + titleFont.Dispose(); + infoFont.Dispose(); + } + } +} diff --git a/Launcher2/Gui/Widgets/LauncherButtonWidget.cs b/Launcher2/Gui/Widgets/LauncherButtonWidget.cs index 5fb78fea7..719a14b9e 100644 --- a/Launcher2/Gui/Widgets/LauncherButtonWidget.cs +++ b/Launcher2/Gui/Widgets/LauncherButtonWidget.cs @@ -7,7 +7,6 @@ namespace Launcher2 { public sealed class LauncherButtonWidget : LauncherWidget { public int ButtonWidth, ButtonHeight; - public string Text; public bool Shadow = true; public bool Active = false; diff --git a/Launcher2/Gui/Widgets/LauncherInputWidget.cs b/Launcher2/Gui/Widgets/LauncherInputWidget.cs index 15319e033..9d3c68e33 100644 --- a/Launcher2/Gui/Widgets/LauncherInputWidget.cs +++ b/Launcher2/Gui/Widgets/LauncherInputWidget.cs @@ -9,7 +9,6 @@ namespace Launcher2 { public sealed class LauncherInputWidget : LauncherWidget { public int ButtonWidth, ButtonHeight; - public string Text; /// Whether the input widget currently is focused through a mouse click or tab. public bool Active; diff --git a/Launcher2/Gui/Widgets/LauncherLabelWidget.cs b/Launcher2/Gui/Widgets/LauncherLabelWidget.cs index c756eef20..8aff7a405 100644 --- a/Launcher2/Gui/Widgets/LauncherLabelWidget.cs +++ b/Launcher2/Gui/Widgets/LauncherLabelWidget.cs @@ -7,8 +7,6 @@ namespace Launcher2 { /// Widget that represents text that cannot be modified by the user. public sealed class LauncherLabelWidget : LauncherWidget { - public string Text; - public LauncherLabelWidget( LauncherWindow window, string text ) : base( window ) { Text = text; } diff --git a/Launcher2/Gui/Widgets/LauncherWidget.cs b/Launcher2/Gui/Widgets/LauncherWidget.cs index 08d6b403c..673e6e463 100644 --- a/Launcher2/Gui/Widgets/LauncherWidget.cs +++ b/Launcher2/Gui/Widgets/LauncherWidget.cs @@ -9,6 +9,8 @@ namespace Launcher2 { public LauncherWindow Window; public Action OnClick; + public string Text; + public LauncherWidget( LauncherWindow window ) { Window = window; } diff --git a/Launcher2/Launcher2.csproj b/Launcher2/Launcher2.csproj index 577780fce..e9a8b4474 100644 --- a/Launcher2/Launcher2.csproj +++ b/Launcher2/Launcher2.csproj @@ -4,7 +4,7 @@ {3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5} Debug AnyCPU - WinExe + Exe Launcher2 Launcher2 v2.0 @@ -58,6 +58,7 @@ + @@ -76,8 +77,9 @@ - + + diff --git a/Launcher2/Patcher/ResourceFetcher.cs b/Launcher2/Patcher/ResourceFetcher.cs index 208de776a..159ed84e0 100644 --- a/Launcher2/Patcher/ResourceFetcher.cs +++ b/Launcher2/Patcher/ResourceFetcher.cs @@ -55,9 +55,10 @@ namespace Launcher2 { return File.Exists( "default.zip" ); } - public static float EstimateDownloadSize() { - return (291 + 4621 + 7) / 1024f; - // clasic.jar + 1.6.2.jar + terrain-patch.png + public static string EstimateDownloadSize() { + float size = (291 + 4621 + 7) / 1024f; + // classic.jar + 1.6.2.jar + terrain-patch.png + return size.ToString( "F2" ); } } } diff --git a/Launcher2/WebService/ClassiCubeSession.cs b/Launcher2/WebService/ClassiCubeSession.cs index 764a99b90..0f19e4d68 100644 --- a/Launcher2/WebService/ClassiCubeSession.cs +++ b/Launcher2/WebService/ClassiCubeSession.cs @@ -1,9 +1,11 @@ -using System; -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; namespace Launcher2 { - public class ClassicubeSession : GameSession { + public sealed class ClassicubeSession : IWebTask { const string classicubeNetUri = "https://www.classicube.net/", loginUri = "https://www.classicube.net/acc/login", @@ -13,7 +15,43 @@ namespace Launcher2 { const string loggedInAs = @""; StringComparison ordinal = StringComparison.Ordinal; - public override void Login( string user, string password ) { + public List Servers = new List(); + + public void LoginAsync( string user, string password ) { + Username = user; + Working = true; + Exception = null; + Status = "&eSigning in.."; + Servers = new List(); + + Thread thread = new Thread( LoginWorker, 256 * 1024 ); + thread.Name = "Launcher.CCLoginAsync"; + thread.Start( password ); + } + + void LoginWorker( object password ) { + // Sign in to classicube.net + try { + Login( Username, (string)password ); + } catch( WebException ex ) { + Finish( false, ex, "sign in" ); return; + } catch( InvalidOperationException ex ) { + Finish( false, null, "&eFailed to sign in: " + + Environment.NewLine + ex.Message ); return; + } + + // Retrieve list of public servers + Status = "&eRetrieving public servers list.."; + try { + Servers = GetPublicServers(); + } catch( WebException ex ) { + Servers = new List(); + Finish( false, ex, "retrieving servers list" ); return; + } + Finish( true, null, "&eSigned in" ); + } + + void Login( string user, string password ) { Username = user; // Step 1: GET csrf token from login page. var swGet = System.Diagnostics.Stopwatch.StartNew(); @@ -52,7 +90,7 @@ namespace Launcher2 { } } - public override ClientStartData GetConnectInfo( string hash ) { + public ClientStartData GetConnectInfo( string hash ) { string uri = playUri + hash; var response = GetHtml( uri, classicubeNetUri ); ClientStartData data = new ClientStartData(); @@ -61,7 +99,7 @@ namespace Launcher2 { foreach( string line in response ) { int index = 0; // Look for tags - if( ( index = line.IndexOf( " 0 ) { + if( (index = line.IndexOf( " 0 ) { int nameStart = index + 13; int nameEnd = line.IndexOf( '"', nameStart ); string paramName = line.Substring( nameStart, nameEnd - nameStart ); @@ -85,7 +123,7 @@ namespace Launcher2 { return line.Substring( valueStart, valueEnd - valueStart ); } - public override List GetPublicServers() { + public List GetPublicServers() { var sw = System.Diagnostics.Stopwatch.StartNew(); var response = GetHtml( publicServersUri, classicubeNetUri ); List servers = new List(); diff --git a/Launcher2/WebService/GameSession.cs b/Launcher2/WebService/IWebTask.cs similarity index 61% rename from Launcher2/WebService/GameSession.cs rename to Launcher2/WebService/IWebTask.cs index efb0ddd9b..9d70f9992 100644 --- a/Launcher2/WebService/GameSession.cs +++ b/Launcher2/WebService/IWebTask.cs @@ -3,122 +3,87 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Text; -using System.Threading; - -namespace Launcher2 { - - public abstract class GameSession { - - public string Username; - - public virtual void ResetSession() { - Username = null; - cookies = new CookieContainer(); - } - - public bool Working; - public WebException Exception; - public string Status; - public List Servers = new List(); - - public void LoginAsync( string user, string password ) { - Username = user; - Working = true; - Exception = null; - Status = "&eSigning in.."; - Servers = new List(); - - Thread thread = new Thread( LoginWorker, 256 * 1024 ); - thread.Name = "Launcher.LoginAsync"; - thread.Start( password ); - } - - void LoginWorker( object password ) { - // Sign in to classicube.net - try { - Login( Username, (string)password ); - } catch( WebException ex ) { - Finish( false, ex, "sign in" ); return; - } catch( InvalidOperationException ex ) { - Finish( false, null, "&eFailed to sign in: " + - Environment.NewLine + ex.Message ); return; - } - - // Retrieve list of public servers - Status = "&eRetrieving public servers list.."; - try { - Servers = GetPublicServers(); - } catch( WebException ex ) { - Servers = new List(); - Finish( false, ex, "retrieving servers list" ); return; - } - Finish( true, null, "&eSigned in" ); - } - - void Finish( bool success, WebException ex, string status ) { - if( !success ) - Username = null; - Working = false; - - Exception = ex; - Status = status; - } - - public abstract void Login( string user, string password ); - - public abstract ClientStartData GetConnectInfo( string hash ); - - public abstract List GetPublicServers(); - - CookieContainer cookies = new CookieContainer(); - - protected HttpWebResponse MakeRequest( string uri, string referer, string data ) { - HttpWebRequest request = (HttpWebRequest)WebRequest.Create( uri ); - request.UserAgent = Program.AppName; - request.ReadWriteTimeout = 15 * 1000; - request.Timeout = 15 * 1000; - request.Referer = referer; - request.KeepAlive = true; - request.CookieContainer = cookies; - - // On my machine, these reduce minecraft server list download time from 40 seconds to 4. - request.Proxy = null; - request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; - if( data != null ) { - request.Method = "POST"; - request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8;"; - byte[] encodedData = Encoding.UTF8.GetBytes( data ); - request.ContentLength = encodedData.Length; - using( Stream stream = request.GetRequestStream() ) { - stream.Write( encodedData, 0, encodedData.Length ); - } - } - return (HttpWebResponse)request.GetResponse(); - } - - protected IEnumerable GetHtml( string uri, string referer ) { - HttpWebResponse response = MakeRequest( uri, referer, null ); - return GetResponseLines( response ); - } - - protected IEnumerable PostHtml( string uri, string referer, string data ) { - HttpWebResponse response = MakeRequest( uri, referer, data ); - return GetResponseLines( response ); - } - - protected IEnumerable GetResponseLines( HttpWebResponse response ) { - using( Stream stream = response.GetResponseStream() ) { - using( StreamReader reader = new StreamReader( stream ) ) { - string line; - while( (line = reader.ReadLine()) != null ) { - yield return line; - } - } - } - } - - protected static void Log( string text ) { - System.Diagnostics.Debug.WriteLine( text ); - } - } -} + +namespace Launcher2 { + + /// Represents a task that performs a series of GET or POST requests asynchronously. + public abstract class IWebTask { + + public virtual void ResetSession() { + Username = null; + cookies = new CookieContainer(); + } + + /// Whether this web task is still performing GET or POST requests asynchronously. + public bool Working; + + /// Handled exception that was generated by the last GET or POST request. + public WebException Exception; + + /// Current status of this web task (e.g. downloading page X) + public string Status; + + /// Username used when performing GET or POST requests, can be left null. + public string Username; + + protected void Finish( bool success, WebException ex, string status ) { + if( !success ) + Username = null; + Working = false; + + Exception = ex; + Status = status; + } + + protected CookieContainer cookies = new CookieContainer(); + + protected HttpWebResponse MakeRequest( string uri, string referer, string data ) { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create( uri ); + request.UserAgent = Program.AppName; + request.ReadWriteTimeout = 15 * 1000; + request.Timeout = 15 * 1000; + request.Referer = referer; + request.KeepAlive = true; + request.CookieContainer = cookies; + + // On my machine, these reduce minecraft server list download time from 40 seconds to 4. + request.Proxy = null; + request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + if( data != null ) { + request.Method = "POST"; + request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8;"; + byte[] encodedData = Encoding.UTF8.GetBytes( data ); + request.ContentLength = encodedData.Length; + using( Stream stream = request.GetRequestStream() ) { + stream.Write( encodedData, 0, encodedData.Length ); + } + } + return (HttpWebResponse)request.GetResponse(); + } + + protected IEnumerable GetHtml( string uri, string referer ) { + HttpWebResponse response = MakeRequest( uri, referer, null ); + return GetResponseLines( response ); + } + + protected IEnumerable PostHtml( string uri, string referer, string data ) { + HttpWebResponse response = MakeRequest( uri, referer, data ); + return GetResponseLines( response ); + } + + protected IEnumerable GetResponseLines( HttpWebResponse response ) { + using( Stream stream = response.GetResponseStream() ) { + using( StreamReader reader = new StreamReader( stream ) ) { + string line; + while( (line = reader.ReadLine()) != null ) { + yield return line; + } + } + } + } + + protected static void Log( string text ) { + System.Diagnostics.Debug.WriteLine( text ); + } + } +} diff --git a/Launcher2/WebService/UpdateCheckTask.cs b/Launcher2/WebService/UpdateCheckTask.cs new file mode 100644 index 000000000..8c3014fa8 --- /dev/null +++ b/Launcher2/WebService/UpdateCheckTask.cs @@ -0,0 +1,78 @@ +using System; +using System.Net; +using System.Threading; + +namespace Launcher2 { + + public sealed class UpdateCheckTask : IWebTask { + + public const string UpdatesUri = "http://cs.classicube.net/"; + StringComparison ordinal = StringComparison.Ordinal; + + public string LatestStableDate, LatestStableSize; + public string LatestDevDate, LatestDevSize; + + public void CheckForUpdatesAsync() { + Working = true; + Exception = null; + LatestStableDate = null; LatestStableSize = null; + LatestDevDate = null; LatestDevSize = null; + + Thread thread = new Thread( UpdateWorker, 256 * 1024 ); + thread.Name = "Launcher.UpdateCheck"; + thread.Start(); + } + + void UpdateWorker() { + try { + CheckUpdates(); + } catch( WebException ex ) { + Finish( false, ex, null ); return; + } + Finish( true, null, null ); + } + + void CheckUpdates() { + var response = GetHtml( UpdatesUri, UpdatesUri ); + foreach( string line in response ) { + Console.WriteLine( line ); + if( line.StartsWith( "latest.", ordinal ) ) { + int index = 0; + string buildName = ReadToken( line, ref index ); + string date = ReadToken( line, ref index ); + string time = ReadToken( line, ref index ); + string buildSize = ReadToken( line, ref index ); + + + if( line.StartsWith( "latest.zip", ordinal ) ) { + LatestDevDate = date + " " + time; + LatestDevSize = buildSize; + } else if( line.StartsWith( "latest.Release.zip", ordinal ) ) { + LatestStableDate = date + " " + time; + LatestStableSize = buildSize; + } + } + } + } + + string ReadToken( string value, ref int index ) { + int start = index; + int wordEnd = -1; + for( ; index < value.Length; index++ ) { + if( value[index] == ' ' ) { + // found end of this word + if( wordEnd == -1 ) + wordEnd = index; + } else { + // found start of next word + if( wordEnd != -1 ) + break; + } + } + + if( wordEnd == -1 ) + wordEnd = value.Length; + return value.Substring( start, wordEnd - start ); + } + } +} \ No newline at end of file