More work on launcher, copy stuff across from old launcher.

This commit is contained in:
UnknownShadow200 2015-10-16 20:31:37 +11:00
parent 6e3a926fab
commit bc4405ff64
14 changed files with 878 additions and 64 deletions

View File

@ -1,6 +1,6 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
# SharpDevelop 4.4
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassicalSharp", "ClassicalSharp\ClassicalSharp.csproj", "{BEB1C785-5CAD-48FF-A886-876BF0A318D4}"
EndProject
@ -27,7 +27,7 @@ Global
{23B9BDA8-4330-46AB-9012-08D87430391A}.Debug_DX|Any CPU.Build.0 = Debug|Any CPU
{23B9BDA8-4330-46AB-9012-08D87430391A}.Debug_DX|Any CPU.ActiveCfg = Debug|Any CPU
{23B9BDA8-4330-46AB-9012-08D87430391A}.Release_DX|Any CPU.Build.0 = Release|Any CPU
{23B9BDA8-4330-46AB-9012-08D87430391A}.Release_DX|Any CPU.ActiveCfg = Release|Any CPU
{23B9BDA8-4330-46AB-9012-08D87430391A}.Release_DX|Any CPU.ActiveCfg = Release|Any CPU
{BEB1C785-5CAD-48FF-A886-876BF0A318D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEB1C785-5CAD-48FF-A886-876BF0A318D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEB1C785-5CAD-48FF-A886-876BF0A318D4}.Release|Any CPU.Build.0 = Release|Any CPU
@ -35,7 +35,7 @@ Global
{BEB1C785-5CAD-48FF-A886-876BF0A318D4}.Debug_DX|Any CPU.Build.0 = Debug_D3D|Any CPU
{BEB1C785-5CAD-48FF-A886-876BF0A318D4}.Debug_DX|Any CPU.ActiveCfg = Debug_D3D|Any CPU
{BEB1C785-5CAD-48FF-A886-876BF0A318D4}.Release_DX|Any CPU.Build.0 = Release|Any CPU
{BEB1C785-5CAD-48FF-A886-876BF0A318D4}.Release_DX|Any CPU.ActiveCfg = Release_D3D|Any CPU
{BEB1C785-5CAD-48FF-A886-876BF0A318D4}.Release_DX|Any CPU.ActiveCfg = Release_D3D|Any CPU
{35FEE071-2DE6-48A1-9343-B5C1F202A12B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35FEE071-2DE6-48A1-9343-B5C1F202A12B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35FEE071-2DE6-48A1-9343-B5C1F202A12B}.Release|Any CPU.Build.0 = Release|Any CPU
@ -51,7 +51,7 @@ Global
{4A4110EE-21CA-4715-AF67-0C8B7CE0642F}.Debug_DX|Any CPU.Build.0 = Debug|Any CPU
{4A4110EE-21CA-4715-AF67-0C8B7CE0642F}.Debug_DX|Any CPU.ActiveCfg = Debug|Any CPU
{4A4110EE-21CA-4715-AF67-0C8B7CE0642F}.Release_DX|Any CPU.Build.0 = Release|Any CPU
{4A4110EE-21CA-4715-AF67-0C8B7CE0642F}.Release_DX|Any CPU.ActiveCfg = Release|Any CPU
{4A4110EE-21CA-4715-AF67-0C8B7CE0642F}.Release_DX|Any CPU.ActiveCfg = Release|Any CPU
{3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5}.Release|Any CPU.Build.0 = Release|Any CPU

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<ProjectGuid>{4A4110EE-21CA-4715-AF67-0C8B7CE0642F}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

View File

@ -0,0 +1,50 @@
using System;
using System.Drawing;
using ClassicalSharp;
using OpenTK;
namespace Launcher2 {
public sealed class FastButtonWidget {
public int ButtonWidth, ButtonHeight;
public int X, Y, Width, Height;
public NativeWindow Window;
public Action OnClick;
public string Text;
static FastColour boxCol = new FastColour( 169, 143, 192 ), shadowCol = new FastColour( 97, 81, 110 );
public void DrawAt( IDrawer2D drawer, string text, Font font, Anchor horAnchor,
Anchor verAnchor, int width, int height, int x, int y ) {
ButtonWidth = width; ButtonHeight = height;
Width = width + 2; Height = height + 2; // adjust for border size of 2
CalculateOffset( x, y, horAnchor, verAnchor );
Redraw( drawer, text, font );
}
void CalculateOffset( int x, int y, Anchor horAnchor, Anchor verAnchor ) {
if( horAnchor == Anchor.LeftOrTop ) X = x;
else if( horAnchor == Anchor.Centre ) X = x + Window.Width / 2 - Width / 2;
else if( horAnchor == Anchor.BottomOrRight ) X = x + Window.Width - Width;
if( verAnchor == Anchor.LeftOrTop ) Y = y;
else if( verAnchor == Anchor.Centre ) Y = y + Window.Height / 2 - Height / 2;
else if( verAnchor == Anchor.BottomOrRight ) Y = y + Window.Height - Height;
}
public void Redraw( IDrawer2D drawer, string text, Font font ) {
Size size = drawer.MeasureSize( text, font, true );
int width = ButtonWidth, height = ButtonHeight;
int xOffset = width - size.Width, yOffset = height - size.Height;
drawer.DrawRoundedRect( shadowCol, 3, X + IDrawer2D.Offset, Y + IDrawer2D.Offset,
width, height );
drawer.DrawRoundedRect( boxCol, 3, X, Y, width, height );
DrawTextArgs args = new DrawTextArgs( text, true );
args.SkipPartsCheck = true;
drawer.DrawText( font, ref args,
X + 1 + xOffset / 2, Y + 1 + yOffset / 2 );
}
}
}

View File

@ -0,0 +1,25 @@
using System;
namespace Launcher2 {
public class GameStartData {
public string Username;
public string Mppass;
public string Ip;
public string Port;
public GameStartData() {
}
public GameStartData( string user, string mppass, string ip, string port ) {
Username = user;
Mppass = mppass;
Ip = ip;
Port = port;
}
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<ProjectGuid>{3E84ACC1-27B4-401B-A359-6AAE4DF6C9B5}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -7,8 +7,9 @@
<OutputType>WinExe</OutputType>
<RootNamespace>Launcher2</RootNamespace>
<AssemblyName>Launcher2</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<AppDesignerFolder>Properties</AppDesignerFolder>
<NoWin32Manifest>False</NoWin32Manifest>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
@ -43,15 +44,21 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Drawing" />
</ItemGroup>
<ItemGroup>
<Compile Include="FastButtonWidget.cs" />
<Compile Include="GameStartData.cs" />
<Compile Include="MainScreen.cs" />
<Compile Include="Patcher\Animations.cs" />
<Compile Include="Patcher\ResourceFetcher.cs" />
<Compile Include="Patcher\ZipWriter.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WebService\ClassiCubeSession.cs" />
<Compile Include="WebService\GameSession.cs" />
<Compile Include="WebService\ServerListEntry.cs" />
<Compile Include="WebService\WebUtility.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ClassicalSharp\ClassicalSharp.csproj">
@ -63,5 +70,9 @@
<Name>OpenTK</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Patcher" />
<Folder Include="WebService" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -15,11 +15,41 @@ namespace Launcher2 {
public void Init() {
Window.Resize += HandleResize;
Window.Mouse.Move += new EventHandler<MouseMoveEventArgs>(Window_Mouse_Move);
Window.Mouse.Move += MouseMove;
Window.Mouse.ButtonDown += MouseButtonDown;
logoFont = new Font( "Times New Roman", 28, FontStyle.Bold );
logoItalicFont = new Font( "Times New Roman", 28, FontStyle.Italic );
textFont = new Font( "Arial", 16, FontStyle.Bold );
}
void Window_Mouse_Move(object sender, MouseMoveEventArgs e) {
FastButtonWidget selectedWidget;
void MouseMove( object sender, MouseMoveEventArgs e ) {
//System.Diagnostics.Debug.Print( "moved" );
for( int i = 0; i < widgets.Length; i++ ) {
FastButtonWidget widget = widgets[i];
if( e.X >= widget.X && e.Y >= widget.Y &&
e.X < widget.X + widget.Width && e.Y < widget.Y + widget.Height ) {
if( selectedWidget != widget && selectedWidget != null ) {
using( IDrawer2D drawer = Drawer ) {
drawer.SetBitmap( background );
selectedWidget.Redraw( Drawer, selectedWidget.Text, textFont );
FilterButton( selectedWidget.X, selectedWidget.Y,
selectedWidget.Width, selectedWidget.Height, 180 );
widget.Redraw( Drawer, widget.Text, textFont );
}
}
selectedWidget = widget;
break;
}
}
}
void MouseButtonDown( object sender, MouseButtonEventArgs e ) {
if( e.Button != MouseButton.Left ) return;
if( selectedWidget != null && selectedWidget.OnClick != null )
selectedWidget.OnClick();
}
public void Display() {
@ -33,18 +63,18 @@ namespace Launcher2 {
}
Bitmap background;
Font textFont, logoFont, logoItalicFont;
static FastColour clearColour = new FastColour( 30, 30, 30 );
static uint clearColourBGRA = (uint)(new FastColour( 30, 30, 30 ).ToArgb());
public void RecreateBackground() {
System.Diagnostics.Debug.Print( "DISPLAY" );
if( background != null )
background.Dispose();
background = new Bitmap( Window.Width, Window.Height );
Font logoFont = new Font( "Times New Roman", 28, FontStyle.Bold );
Font logoItalicFont = new Font( "Times New Roman", 28, FontStyle.Italic );
using( IDrawer2D drawer = Drawer ) {
drawer.SetBitmap( background );
drawer.Clear( Color.FromArgb( 30, 30, 30 ) );
drawer.Clear( clearColour );
Size size1 = drawer.MeasureSize( "&eClassical", logoItalicFont, true );
Size size2 = drawer.MeasureSize( "&eSharp", logoFont, true );
@ -64,67 +94,51 @@ namespace Launcher2 {
static FastColour boxCol = new FastColour( 169, 143, 192 ), shadowCol = new FastColour( 97, 81, 110 );
void DrawButtons( IDrawer2D drawer ) {
widgetIndex = 0;
using( Font font = new Font( "Arial", 16, FontStyle.Bold ) ) {
DrawText( drawer, "Direct connect", font, Anchor.Centre, Anchor.Centre,
buttonWidth, buttonHeight, 0, -100 );
DrawText( drawer, "ClassiCube.net", font, Anchor.Centre, Anchor.Centre,
buttonWidth, buttonHeight, 0, -50 );
DrawText( drawer, "Default texture pack", font, Anchor.Centre, Anchor.Centre,
buttonWidth, buttonHeight, 0, 50 );
DrawText( drawer, "Singleplayer", font, Anchor.LeftOrTop, Anchor.BottomOrRight,
sideButtonWidth, buttonHeight, 10, -10 );
DrawText( drawer, "Resume", font, Anchor.BottomOrRight, Anchor.BottomOrRight,
sideButtonWidth, buttonHeight, -10, -10 );
}
MakeButtonAt( drawer, "Direct connect", Anchor.Centre, Anchor.Centre,
buttonWidth, buttonHeight, 0, -100 );
MakeButtonAt( drawer, "ClassiCube.net", Anchor.Centre, Anchor.Centre,
buttonWidth, buttonHeight, 0, -50 );
MakeButtonAt( drawer, "Default texture pack", Anchor.Centre, Anchor.Centre,
buttonWidth, buttonHeight, 0, 50 );
MakeButtonAt( drawer, "Singleplayer", Anchor.LeftOrTop, Anchor.BottomOrRight,
sideButtonWidth, buttonHeight, 10, -10 );
widgets[widgetIndex - 1].OnClick
= () => Program.StartClient( "default.zip" );
MakeButtonAt( drawer, "Resume", Anchor.BottomOrRight, Anchor.BottomOrRight,
sideButtonWidth, buttonHeight, -10, -10 );
}
Widget[] widgets = new Widget[5];
FastButtonWidget[] widgets = new FastButtonWidget[5];
int widgetIndex = 0;
const int buttonWidth = 220, buttonHeight = 35, sideButtonWidth = 150;
void DrawText( IDrawer2D drawer, string text, Font font, Anchor horAnchor,
Anchor verAnchor, int width, int height, int x, int y ) {
Size textSize = drawer.MeasureSize( text, font, true );
int xOffset = width - textSize.Width;
int yOffset = height - textSize.Height;
if( horAnchor == Anchor.Centre ) x = x + Window.Width / 2 - width / 2;
else if( horAnchor == Anchor.BottomOrRight ) x = x + Window.Width - width;
if( verAnchor == Anchor.Centre ) y = y + Window.Height / 2 - height / 2;
else if( verAnchor == Anchor.BottomOrRight ) y = y + Window.Height - height;
drawer.DrawRoundedRect( shadowCol, 3, x + IDrawer2D.Offset, y + IDrawer2D.Offset,
width, height );
drawer.DrawRoundedRect( boxCol, 3, x, y, width, height );
DrawTextArgs args = new DrawTextArgs( text, true );
args.SkipPartsCheck = true;
drawer.DrawText( font, ref args,
x + 1 + xOffset / 2, y + 1 + yOffset / 2 );
Widget widget = new Widget();
// adjust for border size of 2
widget.X = x; widget.Y = y;
widget.Width = width + 2; widget.Height = height + 2;
//FilterButton( widget.X, widget.Y, widget.Width, widget.Height, 150 );
void MakeButtonAt( IDrawer2D drawer, string text, Anchor horAnchor,
Anchor verAnchor, int width, int height, int x, int y ) {
FastButtonWidget widget = new FastButtonWidget();
widget.Window = Window;
widget.Text = text;
widget.DrawAt( drawer, text, textFont, horAnchor, verAnchor, width, height, x, y );
FilterButton( widget.X, widget.Y, widget.Width, widget.Height, 180 );
widgets[widgetIndex++] = widget;
}
class Widget {
public int X, Y;
public int Width, Height;
public bool Active;
}
void HandleResize( object sender, EventArgs e ) {
RecreateBackground();
}
public void Dispose() {
logoFont.Dispose();
logoItalicFont.Dispose();
textFont.Dispose();
}
unsafe void FilterButton( int x, int y, int width, int height, byte scale ) {
using( FastBitmap bmp = new FastBitmap( background, true ) ) {
for( int yy = y; yy < y + height; yy++ ) {
int* row = bmp.GetRowPtr( yy ) + x;
for( int xx = 0; xx < width; xx++ ) {
uint pixel = (uint)row[xx];
if( pixel == clearColourBGRA ) continue;
uint a = pixel & 0xFF000000;
uint r = (pixel >> 16) & 0xFF;
uint g = (pixel >> 8) & 0xFF;
@ -138,6 +152,5 @@ namespace Launcher2 {
}
}
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Drawing;
using System.IO;
namespace Launcher2 {
public partial class ResourceFetcher {
const string animationsTxt = @"# This file defines the animations used in a texture pack for ClassicalSharp and other supporting applications.
# Each line is in the format: <TileX> <TileY> <FrameX> <FrameY> <Frame size> <Frames count> <Tick delay>
# - TileX and TileY indicate the coordinates of the tile in terrain.png that
# will be replaced by the animation frames. These range from 0 to 15. (inclusive of 15)
# - FrameX and FrameY indicates the pixel coordinates of the first animation frame in animations.png.
# - Frame Size indicates the size in pixels of an animation frame.
# - Frames count indicates the number of used frames. The first frame is located at
# (FrameX, FrameY), the second one at (FrameX + FrameSize, FrameY) and so on.
# - Tick delay is the number of ticks a frame doesn't change. For instance, a value of 0
# means that the frame would be changed every tick, while a value of 2 would mean
# 'replace with frame 1, don't change frame, don't change frame, replace with frame 2'.
# still water
14 0 0 0 16 32 2
# still lava
14 1 0 16 16 39 2
# fire
6 2 0 32 16 32 0";
unsafe void PatchDefault( byte[] data, int y ) {
// Sadly files in modern are 24 rgb, so we can't use fastbitmap here
using( Bitmap bmp = new Bitmap( new MemoryStream( data ) ) ) {
for( int tile = 0; tile < bmp.Height; tile += 16 ) {
CopyTile( tile, tile, y, bmp );
}
}
}
unsafe void PatchCycle( byte[] data, int y ) {
using( Bitmap bmp = new Bitmap( new MemoryStream( data ) ) ) {
int dst = 0;
for( int tile = 0; tile < bmp.Height; tile += 16, dst += 16 ) {
CopyTile( tile, dst, y, bmp );
}
// Cycle back to first frame.
for( int tile = bmp.Height - 32; tile >= 0; tile -= 16, dst += 16 ) {
CopyTile( tile, dst, y, bmp );
}
}
}
}
}

View File

@ -0,0 +1,159 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using ClassicalSharp;
using ClassicalSharp.TexturePack;
using OpenTK;
namespace Launcher2 {
public partial class ResourceFetcher {
const string classicJarUri = "http://s3.amazonaws.com/Minecraft.Download/versions/c0.30_01c/c0.30_01c.jar";
const string modernJarUri = "http://s3.amazonaws.com/Minecraft.Download/versions/1.6.2/1.6.2.jar";
const string terrainPatchUri = "http://static.classicube.net/terrain-patch.png";
static int resourcesCount = 3;
public void Run( NativeWindow window ) {
using( WebClient client = new GZipWebClient() ) {
WebRequest.DefaultWebProxy = null;
int i = 0;
DownloadData( classicJarUri, client, "classic.jar", window, ref i );
DownloadData( modernJarUri, client, "1.6.2.jar", window, ref i );
DownloadData( terrainPatchUri, client, "terrain-patch.png", window, ref i );
}
reader = new ZipReader();
reader.ShouldProcessZipEntry = ShouldProcessZipEntry_Classic;
reader.ProcessZipEntry = ProcessZipEntry_Classic;
using( FileStream srcClassic = File.OpenRead( "classic.jar" ),
srcModern = File.OpenRead( "1.6.2.jar" ),
dst = new FileStream( "default.zip", FileMode.Create, FileAccess.Write ) ) {
writer = new ZipWriter( dst );
reader.Extract( srcClassic );
// Grab animations and snow
animBitmap = new Bitmap( 1024, 64, PixelFormat.Format32bppArgb );
reader.ShouldProcessZipEntry = ShouldProcessZipEntry_Modern;
reader.ProcessZipEntry = ProcessZipEntry_Modern;
reader.Extract( srcModern );
writer.WriteNewImage( animBitmap, "animations.png" );
writer.WriteNewString( animationsTxt, "animations.txt" );
animBitmap.Dispose();
writer.WriteCentralDirectoryRecords();
}
}
ZipReader reader;
ZipWriter writer;
Bitmap animBitmap;
bool ShouldProcessZipEntry_Classic( string filename ) {
return filename.StartsWith( "mob" ) || ( filename.IndexOf( '/' ) < 0 );
}
void ProcessZipEntry_Classic( string filename, byte[] data, ZipEntry entry ) {
if( writer.entries == null )
writer.entries = new ZipEntry[reader.entries.Length];
if( filename != "terrain.png" ) {
writer.WriteZipEntry( entry, data );
return;
}
using( Bitmap dstBitmap = new Bitmap( new MemoryStream( data ) ),
maskBitmap = new Bitmap( "terrain-patch.png" ) ) {
PatchImage( dstBitmap, maskBitmap );
writer.WriteNewImage( dstBitmap, "terrain.png" );
}
}
bool ShouldProcessZipEntry_Modern( string filename ) {
return filename.StartsWith( "assets/minecraft/textures" ) &&
( filename == "assets/minecraft/textures/environment/snow.png" ||
filename == "assets/minecraft/textures/blocks/water_still.png" ||
filename == "assets/minecraft/textures/blocks/lava_still.png" ||
filename == "assets/minecraft/textures/blocks/fire_layer_1.png" ||
filename == "assets/minecraft/textures/entity/chicken.png" );
}
void ProcessZipEntry_Modern( string filename, byte[] data, ZipEntry entry ) {
switch( filename ) {
case "assets/minecraft/textures/environment/snow.png":
entry.Filename = "snow.png";
writer.WriteZipEntry( entry, data );
break;
case "assets/minecraft/textures/entity/chicken.png":
entry.Filename = "mob/chicken.png";
writer.WriteZipEntry( entry, data );
break;
case "assets/minecraft/textures/blocks/water_still.png":
PatchDefault( data, 0 );
break;
case "assets/minecraft/textures/blocks/lava_still.png":
PatchCycle( data, 16 );
break;
case "assets/minecraft/textures/blocks/fire_layer_1.png":
PatchDefault( data, 32 );
break;
}
}
unsafe void PatchImage( Bitmap dstBitmap, Bitmap maskBitmap ) {
using( FastBitmap dst = new FastBitmap( dstBitmap, true ),
src = new FastBitmap( maskBitmap, true ) ) {
int size = src.Width, tileSize = size / 16;
for( int y = 0; y < size; y += tileSize ) {
int* row = src.GetRowPtr( y );
for( int x = 0; x < size; x += tileSize ) {
if( row[x] != unchecked((int)0x80000000) ) {
FastBitmap.MovePortion( x, y, x, y, src, dst, tileSize );
}
}
}
}
}
void CopyTile( int src, int dst, int y, Bitmap bmp ) {
for( int yy = 0; yy < 16; yy++ ) {
for( int xx = 0; xx < 16; xx++ ) {
animBitmap.SetPixel( dst + xx, y + yy,
bmp.GetPixel( xx, src + yy ) );
}
}
}
public bool CheckAllResourcesExist() {
return File.Exists( "default.zip" );
}
class GZipWebClient : WebClient {
protected override WebRequest GetWebRequest( Uri address ) {
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest( address );
request.AutomaticDecompression = DecompressionMethods.GZip;
return request;
}
}
static bool DownloadData( string uri, WebClient client, string output,
NativeWindow window, ref int i ) {
i++;
if( File.Exists( output ) ) return true;
window.Title = Program.AppName + " - fetching " + output + "(" + i + "/" + resourcesCount + ")";
try {
client.DownloadFile( uri, output );
} catch( WebException ex ) {
//Program.LogException( ex );
//MessageBox.Show( "Unable to download or save " + output, "Failed to download or save resource",
// MessageBoxButtons.OK, MessageBoxIcon.Error );
// TODO: show error
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,122 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using ClassicalSharp.TexturePack;
namespace Launcher2 {
public sealed class ZipWriter {
BinaryWriter writer;
Stream stream;
public ZipWriter( Stream stream ) {
this.stream = stream;
writer = new BinaryWriter( stream );
}
internal ZipEntry[] entries;
internal int entriesCount;
public void WriteZipEntry( ZipEntry entry, byte[] data ) {
entry.CompressedDataSize = (int)entry.UncompressedDataSize;
entry.LocalHeaderOffset = (int)stream.Position;
entries[entriesCount++] = entry;
WriteLocalFileEntry( entry, data, data.Length );
}
public void WriteNewImage( Bitmap bmp, string filename ) {
MemoryStream data = new MemoryStream();
bmp.Save( data, ImageFormat.Png );
byte[] buffer = data.GetBuffer();
WriteNewEntry( filename, buffer, (int)data.Length );
}
public void WriteNewString( string text, string filename ) {
byte[] data = Encoding.ASCII.GetBytes( text );
WriteNewEntry( filename, data, data.Length );
}
public void WriteNewEntry( string filename, byte[] data, int dataLength ) {
ZipEntry entry = new ZipEntry();
entry.UncompressedDataSize = dataLength;
entry.Crc32 = CRC32( data, dataLength );
entry.CompressedDataSize = dataLength;
entry.LocalHeaderOffset = (int)stream.Position;
entry.Filename = filename;
entries[entriesCount++] = entry;
WriteLocalFileEntry( entry, data, dataLength );
}
public void WriteCentralDirectoryRecords() {
int dirOffset = (int)stream.Position;
for( int i = 0; i < entriesCount; i++ ) {
WriteCentralDirectoryHeaderEntry( entries[i] );
}
int dirSize = (int)( stream.Position - dirOffset );
WriteEndOfCentralDirectoryRecord( (ushort)entriesCount, dirSize, dirOffset );
}
void WriteLocalFileEntry( ZipEntry entry, byte[] data, int length ) {
writer.Write( 0x04034b50 ); // signature
writer.Write( (ushort)20 ); // version needed
writer.Write( (ushort)0 ); // bitflags
writer.Write( (ushort)0 ); // compression method
writer.Write( 0 ); // last modified
writer.Write( entry.Crc32 );
writer.Write( entry.CompressedDataSize );
writer.Write( entry.UncompressedDataSize );
writer.Write( (ushort)entry.Filename.Length );
writer.Write( (ushort)0 ); // extra field length
for( int i = 0; i < entry.Filename.Length; i++ )
writer.Write( (byte)entry.Filename[i] );
writer.Write( data, 0, length );
}
void WriteCentralDirectoryHeaderEntry( ZipEntry entry ) {
writer.Write( 0x02014b50 ); // signature
writer.Write( (ushort)20 ); // version
writer.Write( (ushort)20 ); // version needed
writer.Write( (ushort)0 ); // bitflags
writer.Write( (ushort)0 ); // compression method
writer.Write( 0 ); // last modified
writer.Write( entry.Crc32 );
writer.Write( entry.CompressedDataSize );
writer.Write( entry.UncompressedDataSize );
writer.Write( (ushort)entry.Filename.Length );
writer.Write( (ushort)0 ); // extra field length
writer.Write( (ushort)0 ); // file comment length
writer.Write( (ushort)0 ); // disk number
writer.Write( (ushort)0 ); // internal attributes
writer.Write( 0 ); // external attributes
writer.Write( entry.LocalHeaderOffset );
for( int i = 0; i < entry.Filename.Length; i++ )
writer.Write( (byte)entry.Filename[i] );
}
void WriteEndOfCentralDirectoryRecord( ushort entries, int centralDirSize, int centralDirOffset ) {
writer.Write( 0x06054b50 ); // signature
writer.Write( (ushort)0 ); // disk number
writer.Write( (ushort)0 ); // disk number of start
writer.Write( entries ); // disk entries
writer.Write( entries ); // total entries
writer.Write( centralDirSize );
writer.Write( centralDirOffset );
writer.Write( (ushort)0 ); // comment length
}
static uint CRC32( byte[] data, int length ) {
uint crc = 0xffffffffU;
for( int i = 0; i < length; i++ ) {
crc ^= data[i];
for( int j = 0; j < 8; j++ )
crc = (crc >> 1) ^ (crc & 1) * 0xEDB88320;
}
return crc ^ 0xffffffffU;
}
}
}

View File

@ -2,6 +2,8 @@
using ClassicalSharp;
using OpenTK;
using OpenTK.Graphics;
using System.IO;
using System.Diagnostics;
namespace Launcher2 {
@ -18,15 +20,37 @@ namespace Launcher2 {
MainScreen screen = new MainScreen();
screen.Drawer = new GdiPlusDrawer2D( null );
screen.Window = window;
screen.RecreateBackground();
screen.Init();
screen.RecreateBackground();
while( true ) {
window.ProcessEvents();
if( !window.Exists ) break;
screen.Display();
System.Threading.Thread.Sleep( 10 );
}
}
static string missingExeMessage = "Failed to start ClassicalSharp. (classicalsharp.exe was not found)"
+ Environment.NewLine + Environment.NewLine +
"This application is only the launcher, it is not the actual client. " +
"Please place the launcher in the same directory as the client (classicalsharp.exe).";
public static bool StartClient( string args ) {
Process process = null;
if( !File.Exists( "ClassicalSharp.exe" ) ) {
// TODO: show message popup
return false;
}
if( Type.GetType( "Mono.Runtime" ) != null ) {
process = Process.Start( "mono", "\"ClassicalSharp.exe\" " + args );
} else {
process = Process.Start( "ClassicalSharp.exe", args );
}
return true;
}
}
}

View File

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
namespace Launcher2 {
public class ClassicubeSession : GameSession {
const string classicubeNetUri = "https://www.classicube.net/",
loginUri = "https://www.classicube.net/acc/login",
publicServersUri = "https://www.classicube.net/server/list",
playUri = "https://www.classicube.net/server/play/";
const string wrongCredentialsMessage = "Login failed";
const string loggedInAs = @"<a href=""/acc"" class=""button"">";
StringComparison ordinal = StringComparison.Ordinal;
public override void Login( string user, string password ) {
Username = user;
// Step 1: GET csrf token from login page.
var swGet = System.Diagnostics.Stopwatch.StartNew();
var getResponse = GetHtml( loginUri, classicubeNetUri );
string token = null;
foreach( string line in getResponse ) {
//Console.WriteLine( line );
if( line.StartsWith( @" <input id=""csrf_token""", ordinal ) ) {
const int tokenStart = 68;
int tokenEnd = line.IndexOf( '"', tokenStart );
token = line.Substring( tokenStart, tokenEnd - tokenStart );
Log( "cc token get took " + swGet.ElapsedMilliseconds );
swGet.Stop();
break;
}
}
// Step 2: POST to login page with csrf token.
string loginData = String.Format(
"csrf_token={0}&username={1}&password={2}",
Uri.EscapeDataString( token ),
Uri.EscapeDataString( user ),
Uri.EscapeDataString( password )
);
var sw = System.Diagnostics.Stopwatch.StartNew();
var response = PostHtml( loginUri, loginUri, loginData );
foreach( string line in response ) {
if( line.Contains( wrongCredentialsMessage ) ) {
throw new InvalidOperationException( "Wrong username or password." );
} else if( line.Contains( loggedInAs ) ) {
Log( "cc login took " + sw.ElapsedMilliseconds );
sw.Stop();
return;
}
}
}
public override GameStartData GetConnectInfo( string hash ) {
string uri = playUri + hash;
var response = GetHtml( uri, classicubeNetUri );
GameStartData data = new GameStartData();
data.Username = Username;
foreach( string line in response ) {
int index = 0;
// Look for <param name="x" value="x"> tags
if( ( index = line.IndexOf( "<param", ordinal ) ) > 0 ) {
int nameStart = index + 13;
int nameEnd = line.IndexOf( '"', nameStart );
string paramName = line.Substring( nameStart, nameEnd - nameStart );
// Don't read param value by default so we avoid allocating unnecessary 'value' strings.
if( paramName == "server" ) {
data.Ip = GetParamValue( line, nameEnd );
} else if( paramName == "port" ) {
data.Port = GetParamValue( line, nameEnd );
} else if( paramName == "mppass" ) {
data.Mppass = GetParamValue( line, nameEnd );
}
}
}
return data;
}
static string GetParamValue( string line, int nameEnd ) {
int valueStart = nameEnd + 9;
int valueEnd = line.IndexOf( '"', valueStart );
return line.Substring( valueStart, valueEnd - valueStart );
}
public override List<ServerListEntry> GetPublicServers() {
var sw = System.Diagnostics.Stopwatch.StartNew();
var response = GetHtml( publicServersUri, classicubeNetUri );
List<ServerListEntry> servers = new List<ServerListEntry>();
int index = -1;
string hash = null;
string name = null;
string players = null;
string maxPlayers = null;
foreach( string line in response ) {
if( line.StartsWith( " <strong><a href", ordinal ) ) {
const int hashStart = 34;
int hashEnd = line.IndexOf( '/', hashStart );
hash = line.Substring( hashStart, hashEnd - hashStart );
int nameStart = hashEnd + 3; // point to first char of name
int nameEnd = line.IndexOf( '<', nameStart );
name = line.Substring( nameStart, nameEnd - nameStart );
name = WebUtility.HtmlDecode( name );
index++;
}
if( index < 0 ) continue;
if( line.StartsWith( @" <td class=""players"">", ordinal ) ) {
const int playersStart = 24;
int playersEnd = line.IndexOf( '/', playersStart );
players = line.Substring( playersStart, playersEnd - playersStart );
int maxPlayersStart = playersEnd + 1;
int maxPlayersEnd = line.IndexOf( ']', playersStart );
maxPlayers = line.Substring( maxPlayersStart, maxPlayersEnd - maxPlayersStart );
servers.Add( new ServerListEntry( hash, name, players, maxPlayers, "" ) );
}
}
Log( "cc servers took " + sw.ElapsedMilliseconds );
sw.Stop();
return servers;
}
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
namespace Launcher2 {
public abstract class GameSession {
public string Username;
public virtual void ResetSession() {
Username = null;
cookies = new CookieContainer();
}
public abstract void Login( string user, string password );
public abstract GameStartData GetConnectInfo( string hash );
public abstract List<ServerListEntry> 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<string> GetHtml( string uri, string referer ) {
HttpWebResponse response = MakeRequest( uri, referer, null );
return GetResponseLines( response );
}
protected IEnumerable<string> PostHtml( string uri, string referer, string data ) {
HttpWebResponse response = MakeRequest( uri, referer, data );
return GetResponseLines( response );
}
protected IEnumerable<string> 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 );
}
}
}

View File

@ -0,0 +1,29 @@
using System;
namespace Launcher2 {
public class ServerListEntry {
public string Hash;
/// <summary> Name of the server. </summary>
public string Name;
/// <summary> Current number of players on the server. </summary>
public string Players;
/// <summary> Maximum number of players that can play on the server. </summary>
public string MaximumPlayers;
/// <summary> How long the server has been 'alive'. </summary>
public string Uptime;
public ServerListEntry( string hash, string name, string players, string maxPlayers, string uptime ) {
Hash = hash;
Name = name;
Players = players;
MaximumPlayers = maxPlayers;
Uptime = uptime;
}
}
}

View File

@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Launcher2 {
public static class WebUtility {
static Dictionary<string, char> _lookupTable = new Dictionary<string, char>( 253, StringComparer.Ordinal ) {
{ "quot", '\x0022' }, { "amp", '\x0026' }, { "apos", '\x0027' }, { "lt", '\x003C' },
{ "gt", '\x003E' }, { "nbsp", '\x00A0' }, { "iexcl", '\x00A1' }, { "cent", '\x00A2' },
{ "pound", '\x00A3' }, { "curren", '\x00A4' }, { "yen", '\x00A5' }, { "brvbar", '\x00A6' },
{ "sect", '\x00A7' }, { "uml", '\x00A8' }, { "copy", '\x00A9' }, { "ordf", '\x00AA' },
{ "laquo", '\x00AB' }, { "not", '\x00AC' }, { "shy", '\x00AD' }, { "reg", '\x00AE' },
{ "macr", '\x00AF' }, { "deg", '\x00B0' }, { "plusmn", '\x00B1' }, { "sup2", '\x00B2' },
{ "sup3", '\x00B3' }, { "acute", '\x00B4' }, { "micro", '\x00B5' }, { "para", '\x00B6' },
{ "middot", '\x00B7' }, { "cedil", '\x00B8' }, { "sup1", '\x00B9' }, { "ordm", '\x00BA' },
{ "raquo", '\x00BB' }, { "frac14", '\x00BC' }, { "frac12", '\x00BD' }, { "frac34", '\x00BE' },
{ "iquest", '\x00BF' }, { "Agrave", '\x00C0' }, { "Aacute", '\x00C1' }, { "Acirc", '\x00C2' },
{ "Atilde", '\x00C3' }, { "Auml", '\x00C4' }, { "Aring", '\x00C5' }, { "AElig", '\x00C6' },
{ "Ccedil", '\x00C7' }, { "Egrave", '\x00C8' }, { "Eacute", '\x00C9' }, { "Ecirc", '\x00CA' },
{ "Euml", '\x00CB' }, { "Igrave", '\x00CC' }, { "Iacute", '\x00CD' }, { "Icirc", '\x00CE' },
{ "Iuml", '\x00CF' }, { "ETH", '\x00D0' }, { "Ntilde", '\x00D1' }, { "Ograve", '\x00D2' },
{ "Oacute", '\x00D3' }, { "Ocirc", '\x00D4' }, { "Otilde", '\x00D5' }, { "Ouml", '\x00D6' },
{ "times", '\x00D7' }, { "Oslash", '\x00D8' }, { "Ugrave", '\x00D9' }, { "Uacute", '\x00DA' },
{ "Ucirc", '\x00DB' }, { "Uuml", '\x00DC' }, { "Yacute", '\x00DD' }, { "THORN", '\x00DE' },
{ "szlig", '\x00DF' }, { "agrave", '\x00E0' }, { "aacute", '\x00E1' }, { "acirc", '\x00E2' },
{ "atilde", '\x00E3' }, { "auml", '\x00E4' }, { "aring", '\x00E5' }, { "aelig", '\x00E6' },
{ "ccedil", '\x00E7' }, { "egrave", '\x00E8' }, { "eacute", '\x00E9' }, { "ecirc", '\x00EA' },
{ "euml", '\x00EB' }, { "igrave", '\x00EC' }, { "iacute", '\x00ED' }, { "icirc", '\x00EE' },
{ "iuml", '\x00EF' }, { "eth", '\x00F0' }, { "ntilde", '\x00F1' }, { "ograve", '\x00F2' },
{ "oacute", '\x00F3' }, { "ocirc", '\x00F4' }, { "otilde", '\x00F5' }, { "ouml", '\x00F6' },
{ "divide", '\x00F7' }, { "oslash", '\x00F8' }, { "ugrave", '\x00F9' }, { "uacute", '\x00FA' },
{ "ucirc", '\x00FB' }, { "uuml", '\x00FC' }, { "yacute", '\x00FD' }, { "thorn", '\x00FE' },
{ "yuml", '\x00FF' }, { "OElig", '\x0152' }, { "oelig", '\x0153' }, { "Scaron", '\x0160' },
{ "scaron", '\x0161' }, { "Yuml", '\x0178' }, { "fnof", '\x0192' }, { "circ", '\x02C6' },
{ "tilde", '\x02DC' }, { "Alpha", '\x0391' }, { "Beta", '\x0392' }, { "Gamma", '\x0393' },
{ "Delta", '\x0394' }, { "Epsilon", '\x0395' }, { "Zeta", '\x0396' }, { "Eta", '\x0397' },
{ "Theta", '\x0398' }, { "Iota", '\x0399' }, { "Kappa", '\x039A' }, { "Lambda", '\x039B' },
{ "Mu", '\x039C' }, { "Nu", '\x039D' }, { "Xi", '\x039E' }, { "Omicron", '\x039F' },
{ "Pi", '\x03A0' }, { "Rho", '\x03A1' }, { "Sigma", '\x03A3' }, { "Tau", '\x03A4' },
{ "Upsilon", '\x03A5' }, { "Phi", '\x03A6' }, { "Chi", '\x03A7' }, { "Psi", '\x03A8' },
{ "Omega", '\x03A9' }, { "alpha", '\x03B1' }, { "beta", '\x03B2' }, { "gamma", '\x03B3' },
{ "delta", '\x03B4' }, { "epsilon", '\x03B5' }, { "zeta", '\x03B6' }, { "eta", '\x03B7' },
{ "theta", '\x03B8' }, { "iota", '\x03B9' }, { "kappa", '\x03BA' }, { "lambda", '\x03BB' },
{ "mu", '\x03BC' }, { "nu", '\x03BD' }, { "xi", '\x03BE' }, { "omicron", '\x03BF' },
{ "pi", '\x03C0' }, { "rho", '\x03C1' }, { "sigmaf", '\x03C2' }, { "sigma", '\x03C3' },
{ "tau", '\x03C4' }, { "upsilon", '\x03C5' }, { "phi", '\x03C6' }, { "chi", '\x03C7' },
{ "psi", '\x03C8' }, { "omega", '\x03C9' }, { "thetasym", '\x03D1' }, { "upsih", '\x03D2' },
{ "piv", '\x03D6' }, { "ensp", '\x2002' }, { "emsp", '\x2003' }, { "thinsp", '\x2009' },
{ "zwnj", '\x200C' }, { "zwj", '\x200D' }, { "lrm", '\x200E' }, { "rlm", '\x200F' },
{ "ndash", '\x2013' }, { "mdash", '\x2014' }, { "lsquo", '\x2018' }, { "rsquo", '\x2019' },
{ "sbquo", '\x201A' }, { "ldquo", '\x201C' }, { "rdquo", '\x201D' }, { "bdquo", '\x201E' },
{ "dagger", '\x2020' }, { "Dagger", '\x2021' }, { "bull", '\x2022' }, { "hellip", '\x2026' },
{ "permil", '\x2030' }, { "prime", '\x2032' }, { "Prime", '\x2033' }, { "lsaquo", '\x2039' },
{ "rsaquo", '\x203A' }, { "oline", '\x203E' }, { "frasl", '\x2044' }, { "euro", '\x20AC' },
{ "image", '\x2111' }, { "weierp", '\x2118' }, { "real", '\x211C' }, { "trade", '\x2122' },
{ "alefsym", '\x2135' }, { "larr", '\x2190' }, { "uarr", '\x2191' }, { "rarr", '\x2192' },
{ "darr", '\x2193' }, { "harr", '\x2194' }, { "crarr", '\x21B5' }, { "lArr", '\x21D0' },
{ "uArr", '\x21D1' }, { "rArr", '\x21D2' }, { "dArr", '\x21D3' }, { "hArr", '\x21D4' },
{ "forall", '\x2200' }, { "part", '\x2202' }, { "exist", '\x2203' }, { "empty", '\x2205' },
{ "nabla", '\x2207' }, { "isin", '\x2208' }, { "notin", '\x2209' }, { "ni", '\x220B' },
{ "prod", '\x220F' }, { "sum", '\x2211' }, { "minus", '\x2212' }, { "lowast", '\x2217' },
{ "radic", '\x221A' }, { "prop", '\x221D' }, { "infin", '\x221E' }, { "ang", '\x2220' },
{ "and", '\x2227' }, { "or", '\x2228' }, { "cap", '\x2229' }, { "cup", '\x222A' },
{ "int", '\x222B' }, { "there4", '\x2234' }, { "sim", '\x223C' }, { "cong", '\x2245' },
{ "asymp", '\x2248' }, { "ne", '\x2260' }, { "equiv", '\x2261' }, { "le", '\x2264' },
{ "ge", '\x2265' }, { "sub", '\x2282' }, { "sup", '\x2283' }, { "nsub", '\x2284' },
{ "sube", '\x2286' }, { "supe", '\x2287' }, { "oplus", '\x2295' }, { "otimes", '\x2297' },
{ "perp", '\x22A5' }, { "sdot", '\x22C5' }, { "lceil", '\x2308' }, { "rceil", '\x2309' },
{ "lfloor", '\x230A' }, { "rfloor", '\x230B' }, { "lang", '\x2329' }, { "rang", '\x232A' },
{ "loz", '\x25CA' }, { "spades", '\x2660' }, { "clubs", '\x2663' }, { "hearts", '\x2665' },
{ "diams", '\x2666' },
};
public static string HtmlDecode( string value ) {
value = value.Replace( "hellip;", "\x2026" ); // minecraft.net doesn't escape this at the end properly.
if( String.IsNullOrEmpty( value ) || value.IndexOf( '&' ) < 0 ) {
return value;
}
StringBuilder sb = new StringBuilder();
WebUtility.HtmlDecode( value, sb );
return sb.ToString();
}
static void HtmlDecode( string value, StringBuilder output ) {
for( int i = 0; i < value.Length; i++ ) {
char token = value[i];
if( token != '&' ) {
output.Append( token );
continue;
}
int entityEnd = value.IndexOf( ';', i + 1 );
if( entityEnd <= 0 ) {
output.Append( token );
continue;
}
string entity = value.Substring( i + 1, entityEnd - i - 1 );
if( entity.Length > 1 && entity[0] == '#' ) {
ushort encodedNumber;
if( entity[1] == 'x' || entity[1] == 'X' ) {
ushort.TryParse( entity.Substring( 2 ), NumberStyles.AllowHexSpecifier, NumberFormatInfo.InvariantInfo, out encodedNumber );
} else {
ushort.TryParse( entity.Substring( 1 ), NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out encodedNumber );
}
if( encodedNumber != 0 ) {
output.Append( (char)encodedNumber );
i = entityEnd;
}
} else {
i = entityEnd;
char decodedEntity;
if( _lookupTable.TryGetValue( entity, out decodedEntity ) ) {
output.Append( decodedEntity );
} else { // Invalid token.
output.Append( '&' );
output.Append( entity );
output.Append( ';' );
}
}
}
}
}
}