Merge pull request #43 from ORelio/Indev

Merging changes from Indev for 1.8.0 release
This commit is contained in:
ORelio 2014-09-04 16:01:07 +02:00
commit 729ae474a1
73 changed files with 17176 additions and 2278 deletions

File diff suppressed because it is too large Load diff

369
MinecraftClient/ChatBot.cs Normal file
View file

@ -0,0 +1,369 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace MinecraftClient
{
///
/// Welcome to the Bot API file !
/// The virtual class "ChatBot" contains anything you need for creating chat bots
/// Inherit from this class while adding your bot class to the folder "ChatBots".
/// Override the methods you want for handling events: Initialize, Update, GetText.
/// Once your bot is created, read the explanations below to start using it in the MinecraftClient app.
///
/// Pieces of code to add in other parts of the program for your bot. Line numbers are approximative.
/// McTcpClient:110 | if (Settings.YourBot_Enabled) { handler.BotLoad(new ChatBots.YourBot()); }
/// Settings.cs:73 | public static bool YourBot_Enabled = false;
/// Settings.cs:74 | private enum ParseMode { /* [...] */, YourBot };
/// Settings.cs:106 | case "yourbot": pMode = ParseMode.YourBot; break;
/// Settings.cs:197 | case ParseMode.YourBot: switch (argName.ToLower()) { case "enabled": YourBot_Enabled = str2bool(argValue); break; } break;
/// Settings.cs:267 | + "[YourBot]\r\n" + "enabled=false\r\n"
/// Here your are. Now you will have a setting in MinecraftClient.ini for enabling your brand new bot.
/// Delete MinecraftClient.ini to re-generate it or add the lines [YourBot] and enabled=true to the existing one.
///
/// <summary>
/// The virtual class containing anything you need for creating chat bots.
/// </summary>
public abstract class ChatBot
{
public enum DisconnectReason { InGameKick, LoginRejected, ConnectionLost };
//Will be automatically set on bot loading, don't worry about this
public void SetHandler(McTcpClient handler) { this.handler = handler; }
private McTcpClient handler;
/* ================================================== */
/* Main methods to override for creating your bot */
/* ================================================== */
/// <summary>
/// Anything you want to initialize your bot, will be called on load by MinecraftCom
/// </summary>
public virtual void Initialize() { }
/// <summary>
/// Will be called every ~100ms (10fps) if loaded in MinecraftCom
/// </summary>
public virtual void Update() { }
/// <summary>
/// Any text sent by the server will be sent here by MinecraftCom
/// </summary>
/// <param name="text">Text from the server</param>
public virtual void GetText(string text) { }
/// <summary>
/// Is called when the client has been disconnected fom the server
/// </summary>
/// <param name="reason">Disconnect Reason</param>
/// <param name="message">Kick message, if any</param>
/// <returns>Return TRUE if the client is about to restart</returns>
public virtual bool OnDisconnect(DisconnectReason reason, string message) { return false; }
/* =================================================================== */
/* ToolBox - Methods below might be useful while creating your bot. */
/* You should not need to interact with other classes of the program. */
/* All the methods in this ChatBot class should do the job for you. */
/* =================================================================== */
/// <summary>
/// Send text to the server. Can be anything such as chat messages or commands
/// </summary>
/// <param name="text">Text to send to the server</param>
/// <returns>True if the text was sent with no error</returns>
protected bool SendText(string text)
{
LogToConsole("Sending '" + text + "'");
return handler.SendText(text);
}
/// <summary>
/// Perform an internal MCC command (not a server command, use SendText() instead for that!)
/// </summary>
/// <param name="command">The command to process</param>
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
protected bool performInternalCommand(string command)
{
string temp = "";
return handler.performInternalCommand(command, ref temp);
}
/// <summary>
/// Perform an internal MCC command (not a server command, use SendText() instead for that!)
/// </summary>
/// <param name="command">The command to process</param>
/// <param name="response_msg">May contain a confirmation or error message after processing the command, or "" otherwise.</param>
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
protected bool performInternalCommand(string command, ref string response_msg)
{
return handler.performInternalCommand(command, ref response_msg);
}
/// <summary>
/// Remove color codes ("§c") from a text message received from the server
/// </summary>
protected static string getVerbatim(string text)
{
if ( String.IsNullOrEmpty(text) )
return String.Empty;
int idx = 0;
var data = new char[text.Length];
for ( int i = 0; i < text.Length; i++ )
if ( text[i] != '§' )
data[idx++] = text[i];
else
i++;
return new string(data, 0, idx);
}
/// <summary>
/// Verify that a string contains only a-z A-Z 0-9 and _ characters.
/// </summary>
protected static bool isValidName(string username)
{
if ( String.IsNullOrEmpty(username) )
return false;
foreach ( char c in username )
if ( !((c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '_') )
return false;
return true;
}
/// <summary>
/// Returns true if the text passed is a private message sent to the bot
/// </summary>
/// <param name="text">text to test</param>
/// <param name="message">if it's a private message, this will contain the message</param>
/// <param name="sender">if it's a private message, this will contain the player name that sends the message</param>
/// <returns>Returns true if the text is a private message</returns>
protected static bool isPrivateMessage(string text, ref string message, ref string sender)
{
text = getVerbatim(text);
if (text == "") { return false; }
string[] tmp = text.Split(' ');
try
{
//Detect vanilla /tell messages
//Someone whispers message (MC 1.5)
//Someone whispers to you: message (MC 1.7)
if (tmp.Length > 2 && tmp[1] == "whispers")
{
if (tmp.Length > 4 && tmp[2] == "to" && tmp[3] == "you:")
{
message = text.Substring(tmp[0].Length + 18); //MC 1.7
}
else message = text.Substring(tmp[0].Length + 10); //MC 1.5
sender = tmp[0];
return isValidName(sender);
}
//Detect Essentials (Bukkit) /m messages
//[Someone -> me] message
//[~Someone -> me] message
else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "->"
&& (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers
{
message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 1);
sender = tmp[0].Substring(1);
if (sender[0] == '~') { sender = sender.Substring(1); }
return isValidName(sender);
}
//Detect Essentials (Bukkit) /me messages with some custom rank
//[Someone [rank] -> me] message
//[~Someone [rank] -> me] message
else if (text[0] == '[' && tmp.Length > 3 && tmp[2] == "->"
&& (tmp[3] == "me]" || tmp[3] == "moi]")) //'me' is replaced by 'moi' in french servers
{
message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1);
sender = tmp[0].Substring(1);
if (sender[0] == '~') { sender = sender.Substring(1); }
return isValidName(sender);
}
else return false;
}
catch (IndexOutOfRangeException) { return false; }
}
/// <summary>
/// Returns true if the text passed is a public message written by a player on the chat
/// </summary>
/// <param name="text">text to test</param>
/// <param name="message">if it's message, this will contain the message</param>
/// <param name="sender">if it's message, this will contain the player name that sends the message</param>
/// <returns>Returns true if the text is a chat message</returns>
protected static bool isChatMessage(string text, ref string message, ref string sender)
{
//Detect chat messages
//<Someone> message
//<*Faction Someone> message
//<*Faction Someone>: message
//<*Faction ~Nicknamed>: message
text = getVerbatim(text);
if (text == "") { return false; }
if (text[0] == '<')
{
try
{
text = text.Substring(1);
string[] tmp = text.Split('>');
sender = tmp[0];
message = text.Substring(sender.Length + 2);
if (message.Length > 1 && message[0] == ' ')
{ message = message.Substring(1); }
tmp = sender.Split(' ');
sender = tmp[tmp.Length - 1];
if (sender[0] == '~') { sender = sender.Substring(1); }
return isValidName(sender);
}
catch (IndexOutOfRangeException) { return false; }
}
else return false;
}
/// <summary>
/// Returns true if the text passed is a teleport request (Essentials)
/// </summary>
/// <param name="text">Text to parse</param>
/// <param name="sender">Will contain the sender's username, if it's a teleport request</param>
/// <returns>Returns true if the text is a teleport request</returns>
protected static bool isTeleportRequest(string text, ref string sender)
{
text = getVerbatim(text);
sender = text.Split(' ')[0];
if (text.EndsWith("has requested to teleport to you.")
|| text.EndsWith("has requested that you teleport to them."))
{
return isValidName(sender);
}
else return false;
}
/// <summary>
/// Writes some text in the console. Nothing will be sent to the server.
/// </summary>
/// <param name="text">Log text to write</param>
public static void LogToConsole(string text)
{
ConsoleIO.WriteLineFormatted("§8[BOT] " + text);
if (!String.IsNullOrEmpty(Settings.chatbotLogFile))
{
if (!File.Exists(Settings.chatbotLogFile))
{
try { Directory.CreateDirectory(Path.GetDirectoryName(Settings.chatbotLogFile)); }
catch { return; /* Invalid path or access denied */ }
try { File.WriteAllText(Settings.chatbotLogFile, ""); }
catch { return; /* Invalid file name or access denied */ }
}
File.AppendAllLines(Settings.chatbotLogFile, new string[] { getTimestamp() + ' ' + text });
}
}
/// <summary>
/// Disconnect from the server and restart the program
/// It will unload & reload all the bots and then reconnect to the server
/// </summary>
protected void ReconnectToTheServer() { ReconnectToTheServer(3); }
/// <summary>
/// Disconnect from the server and restart the program
/// It will unload & reload all the bots and then reconnect to the server
/// </summary>
/// <param name="attempts">If connection fails, the client will make X extra attempts</param>
protected void ReconnectToTheServer(int ExtraAttempts)
{
McTcpClient.AttemptsLeft = ExtraAttempts;
Program.Restart();
}
/// <summary>
/// Disconnect from the server and exit the program
/// </summary>
protected void DisconnectAndExit()
{
Program.Exit();
}
/// <summary>
/// Unload the chatbot, and release associated memory.
/// </summary>
protected void UnloadBot()
{
handler.BotUnLoad(this);
}
/// <summary>
/// Send a private message to a player
/// </summary>
/// <param name="player">Player name</param>
/// <param name="message">Message</param>
protected void SendPrivateMessage(string player, string message)
{
SendText("/tell " + player + ' ' + message);
}
/// <summary>
/// Run a script from a file using a Scripting bot
/// </summary>
/// <param name="filename">File name</param>
/// <param name="playername">Player name to send error messages, if applicable</param>
protected void RunScript(string filename, string playername = "")
{
handler.BotLoad(new ChatBots.Script(filename, playername));
}
/// <summary>
/// Get a D-M-Y h:m:s timestamp representing the current system date and time
/// </summary>
protected static string getTimestamp()
{
DateTime time = DateTime.Now;
string D = time.Day.ToString("00");
string M = time.Month.ToString("00");
string Y = time.Year.ToString("0000");
string h = time.Hour.ToString("00");
string m = time.Minute.ToString("00");
string s = time.Second.ToString("00");
return "" + D + '-' + M + '-' + Y + ' ' + h + ':' + m + ':' + s;
}
}
}

View file

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// This bot make the console beep on some specified words. Useful to detect when someone is talking to you, for example.
/// </summary>
public class Alerts : ChatBot
{
private string[] dictionary = new string[0];
private string[] excludelist = new string[0];
public override void Initialize()
{
if (System.IO.File.Exists(Settings.Alerts_MatchesFile))
{
List<string> tmp_dictionary = new List<string>();
string[] file_lines = System.IO.File.ReadAllLines(Settings.Alerts_MatchesFile);
foreach (string line in file_lines)
if (line.Trim().Length > 0 && !tmp_dictionary.Contains(line.ToLower()))
tmp_dictionary.Add(line.ToLower());
dictionary = tmp_dictionary.ToArray();
}
else LogToConsole("File not found: " + Settings.Alerts_MatchesFile);
if (System.IO.File.Exists(Settings.Alerts_ExcludesFile))
{
List<string> tmp_excludelist = new List<string>();
string[] file_lines = System.IO.File.ReadAllLines(Settings.Alerts_ExcludesFile);
foreach (string line in file_lines)
if (line.Trim().Length > 0 && !tmp_excludelist.Contains(line.Trim().ToLower()))
tmp_excludelist.Add(line.ToLower());
excludelist = tmp_excludelist.ToArray();
}
else LogToConsole("File not found : " + Settings.Alerts_ExcludesFile);
}
public override void GetText(string text)
{
text = getVerbatim(text);
string comp = text.ToLower();
foreach (string alert in dictionary)
{
if (comp.Contains(alert))
{
bool ok = true;
foreach (string exclusion in excludelist)
{
if (comp.Contains(exclusion))
{
ok = false;
break;
}
}
if (ok)
{
if (Settings.Alerts_Beep_Enabled) { Console.Beep(); } //Text found !
if (ConsoleIO.basicIO) { ConsoleIO.WriteLine(comp.Replace(alert, "§c" + alert + "§r")); }
else
{
#region Displaying the text with the interesting part highlighted
Console.BackgroundColor = ConsoleColor.DarkGray;
Console.ForegroundColor = ConsoleColor.White;
//Will be used for text displaying
string[] temp = comp.Split(alert.Split(','), StringSplitOptions.None);
int p = 0;
//Special case : alert in the beginning of the text
string test = "";
for (int i = 0; i < alert.Length; i++)
{
test += comp[i];
}
if (test == alert)
{
Console.BackgroundColor = ConsoleColor.Yellow;
Console.ForegroundColor = ConsoleColor.Red;
for (int i = 0; i < alert.Length; i++)
{
ConsoleIO.Write(text[p]);
p++;
}
}
//Displaying the rest of the text
for (int i = 0; i < temp.Length; i++)
{
Console.BackgroundColor = ConsoleColor.DarkGray;
Console.ForegroundColor = ConsoleColor.White;
for (int j = 0; j < temp[i].Length; j++)
{
ConsoleIO.Write(text[p]);
p++;
}
Console.BackgroundColor = ConsoleColor.Yellow;
Console.ForegroundColor = ConsoleColor.Red;
try
{
for (int j = 0; j < alert.Length; j++)
{
ConsoleIO.Write(text[p]);
p++;
}
}
catch (IndexOutOfRangeException) { }
}
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Gray;
ConsoleIO.Write('\n');
#endregion
}
}
}
}
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// This bot sends a command every 60 seconds in order to stay non-afk.
/// </summary>
public class AntiAFK : ChatBot
{
private int count;
private int timeping;
/// <summary>
/// This bot sends a /ping command every X seconds in order to stay non-afk.
/// </summary>
/// <param name="pingparam">Time amount between each ping (10 = 1s, 600 = 1 minute, etc.)</param>
public AntiAFK(int pingparam)
{
count = 0;
timeping = pingparam;
if (timeping < 10) { timeping = 10; } //To avoid flooding
}
public override void Update()
{
count++;
if (count == timeping)
{
SendText(Settings.AntiAFK_Command);
count = 0;
}
}
}
}

View file

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// This bot automatically re-join the server if kick message contains predefined string (Server is restarting ...)
/// </summary>
public class AutoRelog : ChatBot
{
private string[] dictionary = new string[0];
private int attempts;
private int delay;
/// <summary>
/// This bot automatically re-join the server if kick message contains predefined string
/// </summary>
/// <param name="DelayBeforeRelog">Delay before re-joining the server (in seconds)</param>
/// <param name="retries">Number of retries if connection fails (-1 = infinite)</param>
public AutoRelog(int DelayBeforeRelog, int retries)
{
attempts = retries;
if (attempts == -1) { attempts = int.MaxValue; }
McTcpClient.AttemptsLeft = attempts;
delay = DelayBeforeRelog;
if (delay < 1) { delay = 1; }
}
public override void Initialize()
{
McTcpClient.AttemptsLeft = attempts;
if (System.IO.File.Exists(Settings.AutoRelog_KickMessagesFile))
{
dictionary = System.IO.File.ReadAllLines(Settings.AutoRelog_KickMessagesFile);
for (int i = 0; i < dictionary.Length; i++)
{
dictionary[i] = dictionary[i].ToLower();
}
}
else LogToConsole("File not found: " + Settings.AutoRelog_KickMessagesFile);
}
public override bool OnDisconnect(DisconnectReason reason, string message)
{
message = getVerbatim(message);
string comp = message.ToLower();
foreach (string msg in dictionary)
{
if (comp.Contains(msg))
{
LogToConsole("Waiting " + delay + " seconds before reconnecting...");
System.Threading.Thread.Sleep(delay * 1000);
McTcpClient.AttemptsLeft = attempts;
ReconnectToTheServer();
return true;
}
}
return false;
}
}
}

View file

@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// This bot saves the received messages in a text file.
/// </summary>
public class ChatLog : ChatBot
{
public enum MessageFilter { AllText, AllMessages, OnlyChat, OnlyWhispers };
private bool dateandtime;
private bool saveOther = true;
private bool saveChat = true;
private bool savePrivate = true;
private string logfile;
/// <summary>
/// This bot saves the messages received in the specified file, with some filters and date/time tagging.
/// </summary>
/// <param name="file">The file to save the log in</param>
/// <param name="filter">The kind of messages to save</param>
/// <param name="AddDateAndTime">Add a date and time before each message</param>
public ChatLog(string file, MessageFilter filter, bool AddDateAndTime)
{
dateandtime = AddDateAndTime;
logfile = file;
switch (filter)
{
case MessageFilter.AllText:
saveOther = true;
savePrivate = true;
saveChat = true;
break;
case MessageFilter.AllMessages:
saveOther = false;
savePrivate = true;
saveChat = true;
break;
case MessageFilter.OnlyChat:
saveOther = false;
savePrivate = false;
saveChat = true;
break;
case MessageFilter.OnlyWhispers:
saveOther = false;
savePrivate = true;
saveChat = false;
break;
}
}
public static MessageFilter str2filter(string filtername)
{
switch (filtername.ToLower())
{
case "all": return MessageFilter.AllText;
case "messages": return MessageFilter.AllMessages;
case "chat": return MessageFilter.OnlyChat;
case "private": return MessageFilter.OnlyWhispers;
default: return MessageFilter.AllText;
}
}
public override void GetText(string text)
{
text = getVerbatim(text);
string sender = "";
string message = "";
if (saveChat && isChatMessage(text, ref message, ref sender))
{
save("Chat " + sender + ": " + message);
}
else if (savePrivate && isPrivateMessage(text, ref message, ref sender))
{
save("Private " + sender + ": " + message);
}
else if (saveOther)
{
save("Other: " + text);
}
}
private void save(string tosave)
{
if (dateandtime)
tosave = getTimestamp() + ' ' + tosave;
Directory.CreateDirectory(Path.GetDirectoryName(logfile));
FileStream stream = new FileStream(logfile, FileMode.OpenOrCreate);
StreamWriter writer = new StreamWriter(stream);
stream.Seek(0, SeekOrigin.End);
writer.WriteLine(tosave);
writer.Dispose();
stream.Close();
}
}
}

View file

@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// In-Chat Hangman game
/// </summary>
public class HangmanGame : ChatBot
{
private int vie = 0;
private int vie_param = 10;
private int compteur = 0;
private int compteur_param = 3000; //5 minutes
private bool running = false;
private bool[] discovered;
private string word = "";
private string letters = "";
private bool English;
/// <summary>
/// Le jeu du Pendu / Hangman Game
/// </summary>
/// <param name="english">if true, the game will be in english. If false, the game will be in french.</param>
public HangmanGame(bool english)
{
English = english;
}
public override void Update()
{
if (running)
{
if (compteur > 0)
{
compteur--;
}
else
{
SendText(English ? "You took too long to try a letter." : "Temps imparti écoulé !");
SendText(English ? "Game canceled." : "Partie annulée.");
running = false;
}
}
}
public override void GetText(string text)
{
string message = "";
string username = "";
text = getVerbatim(text);
if (isPrivateMessage(text, ref message, ref username))
{
if (Settings.Bots_Owners.Contains(username.ToLower()))
{
switch (message)
{
case "start":
start();
break;
case "stop":
running = false;
break;
default:
break;
}
}
}
else
{
if (running && isChatMessage(text, ref message, ref username))
{
if (message.Length == 1)
{
char letter = message.ToUpper()[0];
if (letter >= 'A' && letter <= 'Z')
{
if (letters.Contains(letter))
{
SendText(English ? ("Letter " + letter + " has already been tried.") : ("Le " + letter + " a déjà été proposé."));
}
else
{
letters += letter;
compteur = compteur_param;
if (word.Contains(letter))
{
for (int i = 0; i < word.Length; i++) { if (word[i] == letter) { discovered[i] = true; } }
SendText(English ? ("Yes, the word contains a " + letter + '!') : ("Le " + letter + " figurait bien dans le mot :)"));
}
else
{
vie--;
if (vie == 0)
{
SendText(English ? "Game Over! :]" : "Perdu ! Partie terminée :]");
SendText(English ? ("The word was: " + word) : ("Le mot était : " + word));
running = false;
}
else SendText(English ? ("The " + letter + "? No.") : ("Le " + letter + " ? Non."));
}
if (running)
{
SendText(English ? ("Mysterious word: " + word_cached + " (lives : " + vie + ")")
: ("Mot mystère : " + word_cached + " (vie : " + vie + ")"));
}
if (winner)
{
SendText(English ? ("Congrats, " + username + '!') : ("Félicitations, " + username + " !"));
running = false;
}
}
}
}
}
}
}
private void start()
{
vie = vie_param;
running = true;
letters = "";
word = chooseword();
compteur = compteur_param;
discovered = new bool[word.Length];
SendText(English ? "Hangman v1.0 - By ORelio" : "Pendu v1.0 - Par ORelio");
SendText(English ? ("Mysterious word: " + word_cached + " (lives : " + vie + ")")
: ("Mot mystère : " + word_cached + " (vie : " + vie + ")"));
SendText(English ? ("Try some letters ... :)") : ("Proposez une lettre ... :)"));
}
private string chooseword()
{
if (System.IO.File.Exists(English ? Settings.Hangman_FileWords_EN : Settings.Hangman_FileWords_FR))
{
string[] dico = System.IO.File.ReadAllLines(English ? Settings.Hangman_FileWords_EN : Settings.Hangman_FileWords_FR);
return dico[new Random().Next(dico.Length)];
}
else
{
LogToConsole(English ? "File not found: " + Settings.Hangman_FileWords_EN : "Fichier introuvable : " + Settings.Hangman_FileWords_FR);
return English ? "WORDSAREMISSING" : "DICOMANQUANT";
}
}
private string word_cached
{
get
{
string printed = "";
for (int i = 0; i < word.Length; i++)
{
if (discovered[i])
{
printed += word[i];
}
else printed += '_';
}
return printed;
}
}
private bool winner
{
get
{
for (int i = 0; i < discovered.Length; i++)
{
if (!discovered[i])
{
return false;
}
}
return true;
}
}
}
}

View file

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// This bot sends a /list command every X seconds and save the result.
/// </summary>
public class PlayerListLogger : ChatBot
{
private int count;
private int timeping;
private string file;
/// <summary>
/// This bot sends a /list command every X seconds and save the result.
/// </summary>
/// <param name="pingparam">Time amount between each list ping (10 = 1s, 600 = 1 minute, etc.)</param>
public PlayerListLogger(int pingparam, string filetosavein)
{
count = 0;
file = filetosavein;
timeping = pingparam;
if (timeping < 10) { timeping = 10; } //To avoid flooding
}
public override void Update()
{
count++;
if (count == timeping)
{
SendText("/list");
count = 0;
}
}
public override void GetText(string text)
{
if (text.Contains("Joueurs en ligne") || text.Contains("Connected:") || text.Contains("online:"))
{
LogToConsole("Saving Player List");
DateTime now = DateTime.Now;
string TimeStamp = "[" + now.Year + '/' + now.Month + '/' + now.Day + ' ' + now.Hour + ':' + now.Minute + ']';
System.IO.File.AppendAllText(file, TimeStamp + "\n" + getVerbatim(text) + "\n\n");
}
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// Allow to perform operations using whispers to the bot
/// </summary>
public class RemoteControl : ChatBot
{
public override void GetText(string text)
{
text = getVerbatim(text);
string command = "", sender = "";
if (isPrivateMessage(text, ref command, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower()))
{
string response = "";
performInternalCommand(command, ref response);
if (response.Length > 0)
{
SendPrivateMessage(sender, response);
}
}
else if (Settings.RemoteCtrl_AutoTpaccept && isTeleportRequest(text, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower()))
{
SendText("/tpaccept");
}
}
}
}

View file

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// Runs a list of commands
/// </summary>
public class Script : ChatBot
{
private string file;
private string[] lines = new string[0];
private int sleepticks = 10;
private int sleepticks_interval = 10;
private int nextline = 0;
private string owner;
public Script(string filename)
{
file = filename;
}
public Script(string filename, string ownername)
: this(filename)
{
if (ownername != "")
owner = ownername;
}
public static bool lookForScript(ref string filename)
{
//Automatically look in subfolders and try to add ".txt" file extension
char dir_slash = Program.isUsingMono ? '/' : '\\';
string[] files = new string[]
{
filename,
filename + ".txt",
"scripts" + dir_slash + filename,
"scripts" + dir_slash + filename + ".txt",
"config" + dir_slash + filename,
"config" + dir_slash + filename + ".txt",
};
foreach (string possible_file in files)
{
if (System.IO.File.Exists(possible_file))
{
filename = possible_file;
return true;
}
}
return false;
}
public override void Initialize()
{
//Load the given file from the startup parameters
if (lookForScript(ref file))
{
lines = System.IO.File.ReadAllLines(file);
if (owner != null) { SendPrivateMessage(owner, "Script '" + file + "' loaded."); }
}
else
{
LogToConsole("File not found: '" + file + "'");
if (owner != null)
SendPrivateMessage(owner, "File not found: '" + file + "'");
UnloadBot(); //No need to keep the bot active
}
}
public override void Update()
{
if (sleepticks > 0) { sleepticks--; }
else
{
if (nextline < lines.Length) //Is there an instruction left to interpret?
{
string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line
nextline++; //Move the cursor so that the next time the following line will be interpreted
sleepticks = sleepticks_interval; //Used to delay next command sending and prevent from beign kicked for spamming
if (instruction_line.Length > 1)
{
if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/')
{
instruction_line = Settings.expandVars(instruction_line);
string instruction_name = instruction_line.Split(' ')[0];
switch (instruction_name.ToLower())
{
case "wait":
int ticks = 10;
try
{
ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5));
}
catch { }
sleepticks = ticks;
break;
default:
if (!performInternalCommand(instruction_line))
{
sleepticks = 0; Update(); //Unknown command : process next line immediately
}
else if (instruction_name.ToLower() != "log") { LogToConsole(instruction_line); }
break;
}
}
else { sleepticks = 0; Update(); } //Comment: process next line immediately
}
}
else
{
//No more instructions to interpret
UnloadBot();
}
}
}
}
}

View file

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// Trigger scripts on specific events
/// </summary>
public class ScriptScheduler : ChatBot
{
private class TaskDesc
{
public string script_file = null;
public bool triggerOnFirstLogin = false;
public bool triggerOnLogin = false;
public bool triggerOnTime = false;
public bool triggerOnInterval = false;
public int triggerOnInterval_Interval = 0;
public int triggerOnInterval_Interval_Countdown = 0;
public List<DateTime> triggerOnTime_Times = new List<DateTime>();
public bool triggerOnTime_alreadyTriggered = false;
}
private static bool firstlogin_done = false;
private string tasksfile;
private bool serverlogin_done;
private List<TaskDesc> tasks = new List<TaskDesc>();
private int verifytasks_timeleft = 10;
private int verifytasks_delay = 10;
public ScriptScheduler(string tasksfile)
{
this.tasksfile = tasksfile;
serverlogin_done = false;
}
public override void Initialize()
{
//Load the given file from the startup parameters
if (System.IO.File.Exists(tasksfile))
{
TaskDesc current_task = null;
String[] lines = System.IO.File.ReadAllLines(tasksfile);
foreach (string lineRAW in lines)
{
string line = lineRAW.Split('#')[0].Trim();
if (line.Length > 0)
{
if (line[0] == '[' && line[line.Length - 1] == ']')
{
switch (line.Substring(1, line.Length - 2).ToLower())
{
case "task":
checkAddTask(current_task);
current_task = new TaskDesc(); //Create a blank task
break;
}
}
else if (current_task != null)
{
string argName = line.Split('=')[0];
if (line.Length > (argName.Length + 1))
{
string argValue = line.Substring(argName.Length + 1);
switch (argName.ToLower())
{
case "triggeronfirstlogin": current_task.triggerOnFirstLogin = Settings.str2bool(argValue); break;
case "triggeronlogin": current_task.triggerOnLogin = Settings.str2bool(argValue); break;
case "triggerontime": current_task.triggerOnTime = Settings.str2bool(argValue); break;
case "triggeroninterval": current_task.triggerOnInterval = Settings.str2bool(argValue); break;
case "timevalue": try { current_task.triggerOnTime_Times.Add(DateTime.ParseExact(argValue, "HH:mm", CultureInfo.InvariantCulture)); } catch { } break;
case "timeinterval": int interval = 1; int.TryParse(argValue, out interval); current_task.triggerOnInterval_Interval = interval; break;
case "script": current_task.script_file = argValue; break;
}
}
}
}
}
checkAddTask(current_task);
}
else
{
LogToConsole("File not found: '" + tasksfile + "'");
UnloadBot(); //No need to keep the bot active
}
}
private void checkAddTask(TaskDesc current_task)
{
if (current_task != null)
{
//Check if we built a valid task before adding it
if (current_task.script_file != null && Script.lookForScript(ref current_task.script_file) //Check if file exists
&& (current_task.triggerOnLogin
|| (current_task.triggerOnTime && current_task.triggerOnTime_Times.Count > 0))
|| (current_task.triggerOnInterval && current_task.triggerOnInterval_Interval > 0)) //Look for a valid trigger
{
current_task.triggerOnInterval_Interval_Countdown = current_task.triggerOnInterval_Interval; //Init countdown for interval
tasks.Add(current_task);
}
}
}
public override void Update()
{
if (verifytasks_timeleft <= 0)
{
verifytasks_timeleft = verifytasks_delay;
if (serverlogin_done)
{
foreach (TaskDesc task in tasks)
{
if (task.triggerOnTime)
{
bool matching_time_found = false;
foreach (DateTime time in task.triggerOnTime_Times)
{
if (time.Hour == DateTime.Now.Hour && time.Minute == DateTime.Now.Minute)
{
matching_time_found = true;
if (!task.triggerOnTime_alreadyTriggered)
{
task.triggerOnTime_alreadyTriggered = true;
RunScript(task.script_file);
}
}
}
if (!matching_time_found)
task.triggerOnTime_alreadyTriggered = false;
}
if (task.triggerOnInterval)
{
if (task.triggerOnInterval_Interval_Countdown == 0)
{
task.triggerOnInterval_Interval_Countdown = task.triggerOnInterval_Interval;
RunScript(task.script_file);
}
else task.triggerOnInterval_Interval_Countdown--;
}
}
}
else
{
foreach (TaskDesc task in tasks)
{
if (task.triggerOnLogin || (firstlogin_done == false && task.triggerOnFirstLogin))
RunScript(task.script_file);
}
firstlogin_done = true;
serverlogin_done = true;
}
}
else verifytasks_timeleft--;
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.ChatBots
{
/// <summary>
/// Example of message receiving.
/// </summary>
public class TestBot : ChatBot
{
public override void GetText(string text)
{
string message = "";
string username = "";
text = getVerbatim(text);
if (isPrivateMessage(text, ref message, ref username))
{
ConsoleIO.WriteLine("Bot: " + username + " told me : " + message);
}
else if (isChatMessage(text, ref message, ref username))
{
ConsoleIO.WriteLine("Bot: " + username + " said : " + message);
}
}
}
}

View file

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient
{
/// <summary>
/// Represents an internal MCC command: Command name, source code and usage message
/// To add a new command, inherit from this class while adding the command class to the folder "Commands".
/// If inheriting from the 'Command' class and placed in the 'Commands' namespace, the command will be
/// automatically loaded and available in main chat prompt, scripts, remote control and command help.
/// </summary>
public abstract class Command
{
/// <summary>
/// The command name
/// </summary>
public abstract string CMDName { get; }
/// <summary>
/// Usage message, eg: 'name [args]: do something'
/// </summary>
public abstract string CMDDesc { get; }
/// <summary>
/// Perform the command
/// </summary>
/// <param name="command">The full command, eg: 'mycommand arg1 arg2'</param>
/// <returns>A confirmation/error message, or "" if no message</returns>
public abstract string Run(McTcpClient handler, string command);
/// <summary>
/// Return a list of aliases for this command.
/// Override this method if you wish to put aliases to the command
/// </summary>
public virtual IEnumerable<string> getCMDAliases() { return new string[0]; }
/// <summary>
/// Check if at least one argument has been passed to the command
/// </summary>
public static bool hasArg(string command)
{
int first_space = command.IndexOf(' ');
return (first_space > 0 && first_space < command.Length - 1);
}
/// <summary>
/// Extract the argument string from the command
/// </summary>
/// <returns>Argument or "" if no argument</returns>
public static string getArg(string command)
{
if (hasArg(command))
{
return command.Substring(command.IndexOf(' ') + 1);
}
else return "";
}
/// <summary>
/// Extract the arguments as a string array from the command
/// </summary>
/// <returns>Argument array or empty array if no arguments</returns>
public static string[] getArgs(string command)
{
string[] args = getArg(command).Split(' ');
if (args.Length == 1 && args[0] == "")
{
return new string[] { };
}
else
{
return args;
}
}
}
}

View file

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Commands
{
public class Connect : Command
{
public override string CMDName { get { return "connect"; } }
public override string CMDDesc { get { return "connect <server> [account]: connect to the specified server."; } }
public override string Run(McTcpClient handler, string command)
{
if (hasArg(command))
{
string[] args = getArgs(command);
if (args.Length > 1)
{
if (!Settings.setAccount(args[1]))
{
return "Unknown account '" + args[1] + "'.";
}
}
if (Settings.setServerIP(args[0]))
{
Program.Restart();
return "";
}
else return "Invalid server IP '" + args[0] + "'.";
}
else return CMDDesc;
}
}
}

View file

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Commands
{
public class Exit : Command
{
public override string CMDName { get { return "exit"; } }
public override string CMDDesc { get { return "exit: disconnect from the server."; } }
public override string Run(McTcpClient handler, string command)
{
Program.Exit();
return "";
}
public override IEnumerable<string> getCMDAliases()
{
return new string[] { "quit" };
}
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Commands
{
public class Log : Command
{
public override string CMDName { get { return "log"; } }
public override string CMDDesc { get { return "log <text>: log some text to the console."; } }
public override string Run(McTcpClient handler, string command)
{
if (hasArg(command))
{
ChatBot.LogToConsole(getArg(command));
return "";
}
else return CMDDesc;
}
}
}

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Commands
{
public class Reco : Command
{
public override string CMDName { get { return "reco"; } }
public override string CMDDesc { get { return "reco [account]: restart and reconnect to the server."; } }
public override string Run(McTcpClient handler, string command)
{
string[] args = getArgs(command);
if (args.Length > 0)
{
if (!Settings.setAccount(args[0]))
{
return "Unknown account '" + args[0] + "'.";
}
}
Program.Restart();
return "";
}
}
}

View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Commands
{
public class Respawn : Command
{
public override string CMDName { get { return "respawn"; } }
public override string CMDDesc { get { return "respawn: Use this to respawn if you are dead."; } }
public override string Run(McTcpClient handler, string command)
{
handler.SendRespawnPacket();
return "You have respawned.";
}
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Commands
{
public class Script : Command
{
public override string CMDName { get { return "script"; } }
public override string CMDDesc { get { return "script <scriptname>: run a script file."; } }
public override string Run(McTcpClient handler, string command)
{
if (hasArg(command))
{
handler.BotLoad(new ChatBots.Script(getArg(command)));
return "";
}
else return CMDDesc;
}
}
}

View file

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Commands
{
public class Send : Command
{
public override string CMDName { get { return "send"; } }
public override string CMDDesc { get { return "send <text>: send a chat message or command."; } }
public override string Run(McTcpClient handler, string command)
{
if (hasArg(command))
{
handler.SendText(getArg(command));
return "";
}
else return CMDDesc;
}
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Commands
{
public class Set : Command
{
public override string CMDName { get { return "set"; } }
public override string CMDDesc { get { return "set varname=value: set a custom %variable%."; } }
public override string Run(McTcpClient handler, string command)
{
if (hasArg(command))
{
string[] temp = getArg(command).Split('=');
if (temp.Length > 1)
{
if (Settings.setVar(temp[0], getArg(command).Substring(temp[0].Length + 1)))
{
return ""; //Success
}
else return "variable name must be A-Za-z0-9.";
}
else return CMDDesc;
}
else return CMDDesc;
}
}
}

View file

@ -25,7 +25,10 @@ namespace MinecraftClient
private static bool reading_lock = false;
private static bool writing_lock = false;
#region Read User Input
/// <summary>
/// Read a password from the standard input
/// </summary>
public static string ReadPassword()
{
string password = "";
@ -71,6 +74,10 @@ namespace MinecraftClient
return password;
}
/// <summary>
/// Read a line from the standard input
/// </summary>
public static string ReadLine()
{
if (basicIO) { return Console.ReadLine(); }
@ -152,7 +159,7 @@ namespace MinecraftClient
if (tmp.Length > 0)
{
string word_tocomplete = tmp[tmp.Length - 1];
string word_autocomplete = autocomplete_engine.AutoComplete(word_tocomplete);
string word_autocomplete = autocomplete_engine.AutoComplete(buffer);
if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != word_tocomplete)
{
while (buffer.Length > 0 && buffer[buffer.Length - 1] != ' ') { RemoveOneChar(); }
@ -174,9 +181,11 @@ namespace MinecraftClient
previous.AddLast(buffer + buffer2);
return buffer + buffer2;
}
#endregion
#region Console Output
/// <summary>
/// Write a string to the standard output, without newline character
/// </summary>
public static void Write(string text)
{
if (basicIO) { Console.Write(text); return; }
@ -216,16 +225,79 @@ namespace MinecraftClient
writing_lock = false;
}
/// <summary>
/// Write a string to the standard output with a trailing newline
/// </summary>
public static void WriteLine(string line)
{
Write(line + '\n');
}
/// <summary>
/// Write a single character to the standard output
/// </summary>
public static void Write(char c)
{
Write("" + c);
}
#endregion
/// <summary>
/// Write a Minecraft-Formatted string to the standard output, using §c color codes
/// </summary>
/// <param name="str">String to write</param>
/// <param name="acceptnewlines">If false, space are printed instead of newlines</param>
public static void WriteLineFormatted(string str, bool acceptnewlines = true)
{
if (basicIO) { Console.WriteLine(str); return; }
if (!String.IsNullOrEmpty(str))
{
if (Settings.chatTimeStamps)
{
int hour = DateTime.Now.Hour, minute = DateTime.Now.Minute, second = DateTime.Now.Second;
ConsoleIO.Write(hour.ToString("00") + ':' + minute.ToString("00") + ':' + second.ToString("00") + ' ');
}
if (!acceptnewlines) { str = str.Replace('\n', ' '); }
if (ConsoleIO.basicIO) { ConsoleIO.WriteLine(str); return; }
string[] subs = str.Split(new char[] { '§' });
if (subs[0].Length > 0) { ConsoleIO.Write(subs[0]); }
for (int i = 1; i < subs.Length; i++)
{
if (subs[i].Length > 0)
{
switch (subs[i][0])
{
case '0': Console.ForegroundColor = ConsoleColor.Gray; break; //Should be Black but Black is non-readable on a black background
case '1': Console.ForegroundColor = ConsoleColor.DarkBlue; break;
case '2': Console.ForegroundColor = ConsoleColor.DarkGreen; break;
case '3': Console.ForegroundColor = ConsoleColor.DarkCyan; break;
case '4': Console.ForegroundColor = ConsoleColor.DarkRed; break;
case '5': Console.ForegroundColor = ConsoleColor.DarkMagenta; break;
case '6': Console.ForegroundColor = ConsoleColor.DarkYellow; break;
case '7': Console.ForegroundColor = ConsoleColor.Gray; break;
case '8': Console.ForegroundColor = ConsoleColor.DarkGray; break;
case '9': Console.ForegroundColor = ConsoleColor.Blue; break;
case 'a': Console.ForegroundColor = ConsoleColor.Green; break;
case 'b': Console.ForegroundColor = ConsoleColor.Cyan; break;
case 'c': Console.ForegroundColor = ConsoleColor.Red; break;
case 'd': Console.ForegroundColor = ConsoleColor.Magenta; break;
case 'e': Console.ForegroundColor = ConsoleColor.Yellow; break;
case 'f': Console.ForegroundColor = ConsoleColor.White; break;
case 'r': Console.ForegroundColor = ConsoleColor.White; break;
}
if (subs[i].Length > 1)
{
ConsoleIO.Write(subs[i].Substring(1, subs[i].Length - 1));
}
}
}
ConsoleIO.Write('\n');
}
Console.ForegroundColor = ConsoleColor.Gray;
}
#region Subfunctions
private static void ClearLineAndBuffer()

View file

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Net;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
namespace MinecraftClient
{
/// <summary>
/// Allow to set the player skin as console icon, on Windows only.
/// See StackOverflow no. 2986853
/// </summary>
public static class ConsoleIcon
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleIcon(IntPtr hIcon);
/// <summary>
/// Asynchronously download the player's skin and set the head as console icon
/// </summary>
public static void setPlayerIconAsync(string playerName)
{
if (!Program.isUsingMono) //Windows Only
{
Thread t = new Thread(new ThreadStart(delegate
{
HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://skins.minecraft.net/MinecraftSkins/" + playerName + ".png");
try
{
using (HttpWebResponse httpWebReponse = (HttpWebResponse)httpWebRequest.GetResponse())
{
Bitmap skin = new Bitmap(Image.FromStream(httpWebReponse.GetResponseStream())); //Read skin from network
skin = skin.Clone(new Rectangle(8, 8, 8, 8), skin.PixelFormat); //Crop skin
SetConsoleIcon(skin.GetHicon()); //Set skin as icon
}
}
catch (WebException) //Skin not found? Reset to default icon
{
try
{
SetConsoleIcon(Icon.ExtractAssociatedIcon(Application.ExecutablePath).Handle);
}
catch { }
}
}
));
t.Name = "Player skin icon setter";
t.Start();
}
}
/// <summary>
/// Set the icon back to the default CMD icon
/// </summary>
public static void revertToCMDIcon()
{
if (!Program.isUsingMono) //Windows Only
{
try
{
SetConsoleIcon(Icon.ExtractAssociatedIcon(Environment.SystemDirectory + "\\cmd.exe").Handle);
}
catch { }
}
}
}
}

View file

@ -3,14 +3,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace MinecraftClient
namespace MinecraftClient.Crypto
{
/// <summary>
/// Methods for handling all the crypto stuff: RSA (Encryption Key Request), AES (Encrypted Stream), SHA-1 (Server Hash).
/// </summary>
public class Crypto
public class CryptoHandler
{
/// <summary>
/// Get a cryptographic service for encrypting data using the server's RSA public key
@ -192,249 +193,20 @@ namespace MinecraftClient
}
/// <summary>
/// Interface for AES stream
/// Allows to use any object which has a Read() and Write() method.
/// Get a new AES-encrypted stream for wrapping a non-encrypted stream.
/// </summary>
/// <param name="underlyingStream">Stream to encrypt</param>
/// <param name="AesKey">Key to use</param>
/// <param name="paddingProvider">Padding provider for Mono implementation</param>
/// <returns>Return an appropriate stream depending on the framework being used</returns>
public interface IAesStream
public static IAesStream getAesStream(Stream underlyingStream, byte[] AesKey, IPaddingProvider paddingProvider)
{
int Read(byte[] buffer, int offset, int count);
void Write(byte[] buffer, int offset, int count);
}
/// <summary>
/// An encrypted stream using AES, used for encrypting network data on the fly using AES.
/// This is the regular AesStream class used with the regular .NET framework from Microsoft.
/// </summary>
public class AesStream : System.IO.Stream, IAesStream
{
CryptoStream enc;
CryptoStream dec;
public AesStream(System.IO.Stream stream, byte[] key)
if (Program.isUsingMono)
{
BaseStream = stream;
enc = new CryptoStream(stream, GenerateAES(key).CreateEncryptor(), CryptoStreamMode.Write);
dec = new CryptoStream(stream, GenerateAES(key).CreateDecryptor(), CryptoStreamMode.Read);
}
public System.IO.Stream BaseStream { get; set; }
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
BaseStream.Flush();
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override int ReadByte()
{
return dec.ReadByte();
}
public override int Read(byte[] buffer, int offset, int count)
{
return dec.Read(buffer, offset, count);
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void WriteByte(byte b)
{
enc.WriteByte(b);
}
public override void Write(byte[] buffer, int offset, int count)
{
enc.Write(buffer, offset, count);
}
private RijndaelManaged GenerateAES(byte[] key)
{
RijndaelManaged cipher = new RijndaelManaged();
cipher.Mode = CipherMode.CFB;
cipher.Padding = PaddingMode.None;
cipher.KeySize = 128;
cipher.FeedbackSize = 8;
cipher.Key = key;
cipher.IV = key;
return cipher;
}
}
/// <summary>
/// An encrypted stream using AES, used for encrypting network data on the fly using AES.
/// This is a mono-compatible adaptation which only sends and receive 16 bytes at a time, and manually transforms blocks.
/// Data is cached before reaching the 128bits block size necessary for mono which is not CFB-8 compatible.
/// </summary>
public class MonoAesStream : System.IO.Stream, IAesStream
{
ICryptoTransform enc;
ICryptoTransform dec;
List<byte> dec_cache = new List<byte>();
List<byte> tosend_cache = new List<byte>();
public MonoAesStream(System.IO.Stream stream, byte[] key)
{
BaseStream = stream;
RijndaelManaged aes = GenerateAES(key);
enc = aes.CreateEncryptor();
dec = aes.CreateDecryptor();
}
public System.IO.Stream BaseStream { get; set; }
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
BaseStream.Flush();
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override int ReadByte()
{
byte[] temp = new byte[1];
Read(temp, 0, 1);
return temp[0];
}
public override int Read(byte[] buffer, int offset, int count)
{
while (dec_cache.Count < count)
{
byte[] temp_in = new byte[16];
byte[] temp_out = new byte[16];
int read = 0;
while (read < 16)
read += BaseStream.Read(temp_in, read, 16 - read);
dec.TransformBlock(temp_in, 0, 16, temp_out, 0);
foreach (byte b in temp_out)
dec_cache.Add(b);
}
for (int i = offset; i - offset < count; i++)
{
buffer[i] = dec_cache[0];
dec_cache.RemoveAt(0);
}
return count;
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void WriteByte(byte b)
{
Write(new byte[] { b }, 0, 1);
}
public override void Write(byte[] buffer, int offset, int count)
{
for (int i = offset; i - offset < count; i++)
tosend_cache.Add(buffer[i]);
if (tosend_cache.Count < 16)
tosend_cache.AddRange(MinecraftCom.getPaddingPacket());
while (tosend_cache.Count > 16)
{
byte[] temp_in = new byte[16];
byte[] temp_out = new byte[16];
for (int i = 0; i < 16; i++)
{
temp_in[i] = tosend_cache[0];
tosend_cache.RemoveAt(0);
}
enc.TransformBlock(temp_in, 0, 16, temp_out, 0);
BaseStream.Write(temp_out, 0, 16);
}
}
private RijndaelManaged GenerateAES(byte[] key)
{
RijndaelManaged cipher = new RijndaelManaged();
cipher.Mode = CipherMode.CFB;
cipher.Padding = PaddingMode.None;
cipher.KeySize = 128;
cipher.FeedbackSize = 8;
cipher.Key = key;
cipher.IV = key;
return cipher;
return new Streams.MonoAesStream(underlyingStream, AesKey, paddingProvider);
}
else return new Streams.RegularAesStream(underlyingStream, AesKey);
}
}
}

View file

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Crypto
{
/// <summary>
/// Interface for AES stream
/// Allows to use a different implementation depending on the framework being used.
/// </summary>
public interface IAesStream
{
int Read(byte[] buffer, int offset, int count);
void Write(byte[] buffer, int offset, int count);
}
}

View file

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Crypto
{
/// <summary>
/// Interface for padding provider
/// Allow to get a padding plugin message from the current network protocol implementation.
/// </summary>
public interface IPaddingProvider
{
byte[] getPaddingPacket();
}
}

View file

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace MinecraftClient.Crypto.Streams
{
/// <summary>
/// An encrypted stream using AES, used for encrypting network data on the fly using AES.
/// This is a mono-compatible adaptation which only sends and receive 16 bytes at a time, and manually transforms blocks.
/// Data is cached before reaching the 128bits block size necessary for mono which is not CFB-8 compatible.
/// </summary>
public class MonoAesStream : Stream, IAesStream
{
IPaddingProvider pad;
ICryptoTransform enc;
ICryptoTransform dec;
List<byte> dec_cache = new List<byte>();
List<byte> tosend_cache = new List<byte>();
public MonoAesStream(System.IO.Stream stream, byte[] key, IPaddingProvider provider)
{
BaseStream = stream;
RijndaelManaged aes = GenerateAES(key);
enc = aes.CreateEncryptor();
dec = aes.CreateDecryptor();
pad = provider;
}
public System.IO.Stream BaseStream { get; set; }
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
BaseStream.Flush();
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override int ReadByte()
{
byte[] temp = new byte[1];
Read(temp, 0, 1);
return temp[0];
}
public override int Read(byte[] buffer, int offset, int count)
{
while (dec_cache.Count < count)
{
byte[] temp_in = new byte[16];
byte[] temp_out = new byte[16];
int read = 0;
while (read < 16)
read += BaseStream.Read(temp_in, read, 16 - read);
dec.TransformBlock(temp_in, 0, 16, temp_out, 0);
foreach (byte b in temp_out)
dec_cache.Add(b);
}
for (int i = offset; i - offset < count; i++)
{
buffer[i] = dec_cache[0];
dec_cache.RemoveAt(0);
}
return count;
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void WriteByte(byte b)
{
Write(new byte[] { b }, 0, 1);
}
public override void Write(byte[] buffer, int offset, int count)
{
for (int i = offset; i - offset < count; i++)
tosend_cache.Add(buffer[i]);
if (tosend_cache.Count < 16)
tosend_cache.AddRange(pad.getPaddingPacket());
while (tosend_cache.Count > 16)
{
byte[] temp_in = new byte[16];
byte[] temp_out = new byte[16];
for (int i = 0; i < 16; i++)
{
temp_in[i] = tosend_cache[0];
tosend_cache.RemoveAt(0);
}
enc.TransformBlock(temp_in, 0, 16, temp_out, 0);
BaseStream.Write(temp_out, 0, 16);
}
}
private RijndaelManaged GenerateAES(byte[] key)
{
RijndaelManaged cipher = new RijndaelManaged();
cipher.Mode = CipherMode.CFB;
cipher.Padding = PaddingMode.None;
cipher.KeySize = 128;
cipher.FeedbackSize = 8;
cipher.Key = key;
cipher.IV = key;
return cipher;
}
}
}

View file

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace MinecraftClient.Crypto.Streams
{
/// <summary>
/// An encrypted stream using AES, used for encrypting network data on the fly using AES.
/// This is the regular AesStream class used with the regular .NET framework from Microsoft.
/// </summary>
public class RegularAesStream : Stream, IAesStream
{
CryptoStream enc;
CryptoStream dec;
public RegularAesStream(Stream stream, byte[] key)
{
BaseStream = stream;
enc = new CryptoStream(stream, GenerateAES(key).CreateEncryptor(), CryptoStreamMode.Write);
dec = new CryptoStream(stream, GenerateAES(key).CreateDecryptor(), CryptoStreamMode.Read);
}
public System.IO.Stream BaseStream { get; set; }
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
BaseStream.Flush();
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override int ReadByte()
{
return dec.ReadByte();
}
public override int Read(byte[] buffer, int offset, int count)
{
return dec.Read(buffer, offset, count);
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void WriteByte(byte b)
{
enc.WriteByte(b);
}
public override void Write(byte[] buffer, int offset, int count)
{
enc.Write(buffer, offset, count);
}
private RijndaelManaged GenerateAES(byte[] key)
{
RijndaelManaged cipher = new RijndaelManaged();
cipher.Mode = CipherMode.CFB;
cipher.Padding = PaddingMode.None;
cipher.KeySize = 128;
cipher.FeedbackSize = 8;
cipher.Key = key;
cipher.IV = key;
return cipher;
}
}
}

View file

@ -6,50 +6,72 @@ using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Net;
using MinecraftClient.Protocol;
using MinecraftClient.Proxy;
namespace MinecraftClient
{
/// <summary>
/// The main client class, used to connect to a Minecraft server.
/// It allows message sending and text receiving.
/// </summary>
class McTcpClient
public class McTcpClient : IMinecraftComHandler
{
private static List<string> cmd_names = new List<string>();
private static Dictionary<string, Command> cmds = new Dictionary<string, Command>();
private List<ChatBot> bots = new List<ChatBot>();
private static List<ChatBots.Script> scripts_on_hold = new List<ChatBots.Script>();
public void BotLoad(ChatBot b) { b.SetHandler(this); bots.Add(b); b.Initialize(); Settings.SingleCommand = ""; }
public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); }
public void BotClear() { bots.Clear(); }
public static int AttemptsLeft = 0;
string host;
int port;
string username;
string text;
Thread t_updater;
Thread t_sender;
private string host;
private int port;
private string username;
private string uuid;
private string sessionid;
public int getServerPort() { return port; }
public string getServerHost() { return host; }
public string getUsername() { return username; }
public string getUserUUID() { return uuid; }
public string getSessionID() { return sessionid; }
TcpClient client;
MinecraftCom handler;
IMinecraftCom handler;
Thread cmdprompt;
/// <summary>
/// Starts the main chat client, wich will login to the server using the MinecraftCom class.
/// Starts the main chat client
/// </summary>
/// <param name="username">The chosen username of a premium Minecraft Account</param>
/// <param name="sessionID">A valid sessionID obtained with MinecraftCom.GetLogin()</param>
/// <param name="server_port">The server IP (serveradress or serveradress:port)</param>
/// <param name="uuid">The player's UUID for online-mode authentication</param>
/// <param name="sessionID">A valid sessionID obtained after logging in</param>
/// <param name="server_ip">The server IP</param>
/// <param name="port">The server port to use</param>
/// <param name="protocolversion">Minecraft protocol version to use</param>
public McTcpClient(string username, string uuid, string sessionID, string server_port, MinecraftCom handler)
public McTcpClient(string username, string uuid, string sessionID, int protocolversion, string server_ip, short port)
{
StartClient(username, uuid, sessionID, server_port, false, handler, "");
StartClient(username, uuid, sessionID, server_ip, port, protocolversion, false, "");
}
/// <summary>
/// Starts the main chat client in single command sending mode, wich will login to the server using the MinecraftCom class, send the command and close.
/// Starts the main chat client in single command sending mode
/// </summary>
/// <param name="username">The chosen username of a premium Minecraft Account</param>
/// <param name="sessionID">A valid sessionID obtained with MinecraftCom.GetLogin()</param>
/// <param name="server_port">The server IP (serveradress or serveradress:port)</param>
/// <param name="uuid">The player's UUID for online-mode authentication</param>
/// <param name="sessionID">A valid sessionID obtained after logging in</param>
/// <param name="server_ip">The server IP</param>
/// <param name="port">The server port to use</param>
/// <param name="protocolversion">Minecraft protocol version to use</param>
/// <param name="command">The text or command to send.</param>
public McTcpClient(string username, string uuid, string sessionID, string server_port, MinecraftCom handler, string command)
public McTcpClient(string username, string uuid, string sessionID, string server_ip, short port, int protocolversion, string command)
{
StartClient(username, uuid, sessionID, server_port, true, handler, command);
StartClient(username, uuid, sessionID, server_ip, port, protocolversion, true, command);
}
/// <summary>
@ -57,70 +79,64 @@ namespace MinecraftClient
/// </summary>
/// <param name="user">The chosen username of a premium Minecraft Account</param>
/// <param name="sessionID">A valid sessionID obtained with MinecraftCom.GetLogin()</param>
/// <param name="server_port">The server IP (serveradress or serveradress:port)/param>
/// <param name="server_ip">The server IP</param>
/// <param name="port">The server port to use</param>
/// <param name="protocolversion">Minecraft protocol version to use</param>
/// <param name="uuid">The player's UUID for online-mode authentication</param>
/// <param name="singlecommand">If set to true, the client will send a single command and then disconnect from the server</param>
/// <param name="command">The text or command to send. Will only be sent if singlecommand is set to true.</param>
private void StartClient(string user, string uuid, string sessionID, string server_port, bool singlecommand, MinecraftCom handler, string command)
private void StartClient(string user, string uuid, string sessionID, string server_ip, short port, int protocolversion, bool singlecommand, string command)
{
this.handler = handler;
username = user;
string[] sip = server_port.Split(':');
host = sip[0];
if (sip.Length == 1)
this.sessionid = sessionID;
this.uuid = uuid;
this.username = user;
this.host = server_ip;
this.port = port;
if (!singlecommand)
{
port = 25565;
}
else
{
try
{
port = Convert.ToInt32(sip[1]);
}
catch (FormatException) { port = 25565; }
if (Settings.AntiAFK_Enabled) { BotLoad(new ChatBots.AntiAFK(Settings.AntiAFK_Delay)); }
if (Settings.Hangman_Enabled) { BotLoad(new ChatBots.HangmanGame(Settings.Hangman_English)); }
if (Settings.Alerts_Enabled) { BotLoad(new ChatBots.Alerts()); }
if (Settings.ChatLog_Enabled) { BotLoad(new ChatBots.ChatLog(Settings.expandVars(Settings.ChatLog_File), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); }
if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.expandVars(Settings.PlayerLog_File))); }
if (Settings.AutoRelog_Enabled) { BotLoad(new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries)); }
if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.expandVars(Settings.ScriptScheduler_TasksFile))); }
if (Settings.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); }
}
try
{
Console.WriteLine("Logging in...");
client = new TcpClient(host, port);
client = ProxyHandler.newTcpClient(host, port);
client.ReceiveBufferSize = 1024 * 1024;
handler.setClient(client);
if (handler.Login(user, uuid, sessionID, host, port))
handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, this);
Console.WriteLine("Version is supported.\nLogging in...");
if (handler.Login())
{
//Single command sending
if (singlecommand)
{
handler.SendChatMessage(command);
Console.Write("Command ");
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.Write(command);
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine(" sent.");
ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent.");
Thread.Sleep(5000);
handler.Disconnect("disconnect.quitting");
handler.Disconnect();
Thread.Sleep(1000);
}
else
{
Console.WriteLine("Server was successfully joined.\nType '/quit' to leave the server.");
foreach (ChatBot bot in scripts_on_hold) { bots.Add(bot); }
scripts_on_hold.Clear();
//Command sending thread, allowing user input
t_sender = new Thread(new ThreadStart(StartTalk));
t_sender.Name = "CommandSender";
t_sender.Start();
Console.WriteLine("Server was successfully joined.\nType '"
+ (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar)
+ "quit' to leave the server.");
//Data receiving thread, allowing text receiving
t_updater = new Thread(new ThreadStart(Updater));
t_updater.Name = "PacketHandler";
t_updater.Start();
cmdprompt = new Thread(new ThreadStart(CommandPrompt));
cmdprompt.Name = "MCC Command prompt";
cmdprompt.Start();
}
}
else
{
Console.WriteLine("Login failed.");
if (!singlecommand && !handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Login failed.")) { Program.ReadLineReconnect(); }
}
}
catch (SocketException)
{
@ -136,14 +152,14 @@ namespace MinecraftClient
/// <summary>
/// Allows the user to send chat messages, commands, and to leave the server.
/// Will be automatically called on a separate Thread by StartClient()
/// </summary>
private void StartTalk()
private void CommandPrompt()
{
try
{
//Needed if the player is dead
string text = "";
Thread.Sleep(500);
handler.SendRespawnPacket();
while (client.Client.Connected)
@ -164,80 +180,94 @@ namespace MinecraftClient
else
{
text = text.Trim();
if (text.ToLower() == "/quit" || text.ToLower() == "/reco")
if (text.Length > 0)
{
break;
}
else if (text.ToLower() == "/respawn")
{
handler.SendRespawnPacket();
ConsoleIO.WriteLine("You have respawned.");
}
else if (text.ToLower().StartsWith("/script "))
{
handler.BotLoad(new Bots.Script(text.Substring(8)));
}
else if (text != "")
{
//Message is too long
if (text.Length > 100)
if (Settings.internalCmdChar == ' ' || text[0] == Settings.internalCmdChar)
{
if (text[0] == '/')
string response_msg = "";
string command = Settings.internalCmdChar == ' ' ? text : text.Substring(1);
if (!performInternalCommand(Settings.expandVars(command), ref response_msg) && Settings.internalCmdChar == '/')
{
//Send the first 100 chars of the command
text = text.Substring(0, 100);
handler.SendChatMessage(text);
SendText(text);
}
else
else if (response_msg.Length > 0)
{
//Send the message splitted in several messages
while (text.Length > 100)
{
handler.SendChatMessage(text.Substring(0, 100));
text = text.Substring(100, text.Length - 100);
}
handler.SendChatMessage(text);
ConsoleIO.WriteLineFormatted("§8MCC: " + response_msg);
}
}
else handler.SendChatMessage(text);
else SendText(text);
}
}
}
switch (text.ToLower())
{
case "/quit": Program.Exit(); break;
case "/reco": Program.Restart(); break;
}
}
catch (IOException) { }
}
/// <summary>
/// Receive the data (including chat messages) from the server, and keep the connection alive.
/// Will be automatically called on a separate Thread by StartClient()
/// Perform an internal MCC command (not a server command, use SendText() instead for that!)
/// </summary>
/// <param name="command">The command</param>
/// <param name="interactive_mode">Set to true if command was sent by the user using the command prompt</param>
/// <param name="response_msg">May contain a confirmation or error message after processing the command, or "" otherwise.</param>
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
private void Updater()
public bool performInternalCommand(string command, ref string response_msg)
{
try
{
//handler.DebugDump();
do
{
Thread.Sleep(100);
} while (handler.Update());
}
catch (IOException) { }
catch (SocketException) { }
catch (ObjectDisposedException) { }
/* Load commands from the 'Commands' namespace */
if (!handler.HasBeenKicked)
if (cmds.Count == 0)
{
ConsoleIO.WriteLine("Connection has been lost.");
if (!handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "Connection has been lost.") && !Program.ReadLineReconnect()) { t_sender.Abort(); }
Type[] cmds_classes = Program.GetTypesInNamespace("MinecraftClient.Commands");
foreach (Type type in cmds_classes)
{
if (type.IsSubclassOf(typeof(Command)))
{
try
{
Command cmd = (Command)Activator.CreateInstance(type);
cmds[cmd.CMDName.ToLower()] = cmd;
cmd_names.Add(cmd.CMDName.ToLower());
foreach (string alias in cmd.getCMDAliases())
cmds[alias.ToLower()] = cmd;
}
catch (Exception e)
{
ConsoleIO.WriteLine(e.Message);
}
}
}
}
else if (Program.ReadLineReconnect()) { t_sender.Abort(); }
/* Process the provided command */
string command_name = command.Split(' ')[0].ToLower();
if (command_name == "help")
{
if (Command.hasArg(command))
{
string help_cmdname = Command.getArgs(command)[0].ToLower();
if (help_cmdname == "help")
{
response_msg = "help <cmdname>: show brief help about a command.";
}
else if (cmds.ContainsKey(help_cmdname))
{
response_msg = cmds[help_cmdname].CMDDesc;
}
else response_msg = "Unknown command '" + command_name + "'. Use 'help' for command list.";
}
else response_msg = "help <cmdname>. Available commands: " + String.Join(", ", cmd_names.ToArray());
}
else if (cmds.ContainsKey(command_name))
{
response_msg = cmds[command_name].Run(this, command);
}
else
{
response_msg = "Unknown command '" + command_name + "'. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help.";
return false;
}
return true;
}
/// <summary>
@ -246,11 +276,122 @@ namespace MinecraftClient
public void Disconnect()
{
handler.Disconnect("disconnect.quitting");
foreach (ChatBot bot in bots)
if (bot is ChatBots.Script)
scripts_on_hold.Add((ChatBots.Script)bot);
handler.Disconnect();
handler.Dispose();
if (cmdprompt != null)
cmdprompt.Abort();
Thread.Sleep(1000);
if (t_updater != null) { t_updater.Abort(); }
if (t_sender != null) { t_sender.Abort(); }
if (client != null) { client.Close(); }
}
/// <summary>
/// Received some text from the server
/// </summary>
/// <param name="text">Text received</param>
public void OnTextReceived(string text)
{
ConsoleIO.WriteLineFormatted(text, false);
foreach (ChatBot bot in bots)
bot.GetText(text);
}
/// <summary>
/// When connection has been lost
/// </summary>
public void OnConnectionLost(ChatBot.DisconnectReason reason, string message)
{
bool will_restart = false;
switch (reason)
{
case ChatBot.DisconnectReason.ConnectionLost:
message = "Connection has been lost.";
ConsoleIO.WriteLine(message);
break;
case ChatBot.DisconnectReason.InGameKick:
ConsoleIO.WriteLine("Disconnected by Server :");
ConsoleIO.WriteLineFormatted(message);
break;
case ChatBot.DisconnectReason.LoginRejected:
ConsoleIO.WriteLine("Login failed :");
ConsoleIO.WriteLineFormatted(message);
break;
}
foreach (ChatBot bot in bots)
will_restart |= bot.OnDisconnect(reason, message);
if (!will_restart) { Program.OfflineCommandPrompt(); }
}
/// <summary>
/// Called ~10 times per second by the protocol handler
/// </summary>
public void OnUpdate()
{
for (int i = 0; i < bots.Count; i++)
{
try
{
bots[i].Update();
}
catch (Exception e)
{
ConsoleIO.WriteLineFormatted("§8Got error from " + bots[i].ToString() + ": " + e.ToString());
}
}
}
/// <summary>
/// Send a chat message or command to the server
/// </summary>
/// <param name="text">Text to send to the server</param>
/// <returns>True if the text was sent with no error</returns>
public bool SendText(string text)
{
if (text.Length > 100) //Message is too long?
{
if (text[0] == '/')
{
//Send the first 100 chars of the command
text = text.Substring(0, 100);
return handler.SendChatMessage(text);
}
else
{
//Send the message splitted into several messages
while (text.Length > 100)
{
handler.SendChatMessage(text.Substring(0, 100));
text = text.Substring(100, text.Length - 100);
}
return handler.SendChatMessage(text);
}
}
else return handler.SendChatMessage(text);
}
/// <summary>
/// Allow to respawn after death
/// </summary>
/// <returns>True if packet successfully sent</returns>
public bool SendRespawnPacket()
{
return handler.SendRespawnPacket();
}
}
}

View file

@ -53,7 +53,7 @@
<SignManifests>false</SignManifests>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>resources\appicon.ico</ApplicationIcon>
<ApplicationIcon>Resources\AppIcon.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<StartupObject>MinecraftClient.Program</StartupObject>
@ -62,6 +62,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@ -70,14 +71,65 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Bots.cs" />
<Compile Include="ChatBots\Alerts.cs" />
<Compile Include="ChatBots\AntiAFK.cs" />
<Compile Include="ChatBots\AutoRelog.cs" />
<Compile Include="ChatBots\ChatLog.cs" />
<Compile Include="ChatBots\HangmanGame.cs" />
<Compile Include="ChatBots\PlayerListLogger.cs" />
<Compile Include="ChatBots\RemoteControl.cs" />
<Compile Include="ChatBots\Script.cs" />
<Compile Include="ChatBots\ScriptScheduler.cs" />
<Compile Include="ChatBots\TestBot.cs" />
<Compile Include="ChatBot.cs" />
<Compile Include="Command.cs" />
<Compile Include="Commands\Connect.cs" />
<Compile Include="Commands\Exit.cs" />
<Compile Include="Commands\Log.cs" />
<Compile Include="Commands\Reco.cs" />
<Compile Include="Commands\Respawn.cs" />
<Compile Include="Commands\Script.cs" />
<Compile Include="Commands\Send.cs" />
<Compile Include="Commands\Set.cs" />
<Compile Include="ConsoleIcon.cs" />
<Compile Include="ConsoleIO.cs" />
<Compile Include="Crypto.cs" />
<Compile Include="ChatParser.cs" />
<Compile Include="MinecraftCom.cs" />
<Compile Include="Crypto\Streams\MonoAesStream.cs" />
<Compile Include="Crypto\Streams\RegularAesStream.cs" />
<Compile Include="Crypto\CryptoHandler.cs" />
<Compile Include="Protocol\Handlers\Compression\CRC32.cs" />
<Compile Include="Protocol\Handlers\Compression\Deflate.cs" />
<Compile Include="Protocol\Handlers\Compression\GZipStream.cs" />
<Compile Include="Protocol\Handlers\Compression\Inflate.cs" />
<Compile Include="Protocol\Handlers\Compression\InfTree.cs" />
<Compile Include="Protocol\Handlers\Compression\Tree.cs" />
<Compile Include="Protocol\Handlers\Compression\Zlib.cs" />
<Compile Include="Protocol\Handlers\Compression\ZlibBaseStream.cs" />
<Compile Include="Protocol\Handlers\Compression\ZlibCodec.cs" />
<Compile Include="Protocol\Handlers\Compression\ZlibConstants.cs" />
<Compile Include="Protocol\Handlers\ZlibUtils.cs" />
<Compile Include="Protocol\Handlers\ChatParser.cs" />
<Compile Include="Crypto\IAesStream.cs" />
<Compile Include="Crypto\IPaddingProvider.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="McTcpClient.cs" />
<Compile Include="Protocol\Handlers\Compression\ZlibStream.cs" />
<Compile Include="Protocol\Handlers\Protocol18.cs" />
<Compile Include="Protocol\Handlers\Protocol16.cs" />
<Compile Include="Protocol\IMinecraftCom.cs" />
<Compile Include="Protocol\IMinecraftComHandler.cs" />
<Compile Include="Protocol\Handlers\Protocol17.cs" />
<Compile Include="Protocol\ProtocolHandler.cs" />
<Compile Include="Proxy\ProxyHandler.cs" />
<Compile Include="Proxy\Handlers\EventArgs\CreateConnectionAsyncCompletedEventArgs.cs" />
<Compile Include="Proxy\Handlers\Exceptions\ProxyException.cs" />
<Compile Include="Proxy\Handlers\HttpProxyClient.cs" />
<Compile Include="Proxy\Handlers\IProxyClient.cs" />
<Compile Include="Proxy\Handlers\ProxyClientFactory.cs" />
<Compile Include="Proxy\Handlers\Socks4aProxyClient.cs" />
<Compile Include="Proxy\Handlers\Socks4ProxyClient.cs" />
<Compile Include="Proxy\Handlers\Socks5ProxyClient.cs" />
<Compile Include="Proxy\Handlers\Utils.cs" />
<Compile Include="Settings.cs" />
</ItemGroup>
<ItemGroup>
@ -103,7 +155,7 @@
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Content Include="resources\appicon.ico" />
<Content Include="Resources\AppIcon.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View file

@ -1,610 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace MinecraftClient
{
/// <summary>
/// The class containing all the core functions needed to communicate with a Minecraft server.
/// </summary>
public class MinecraftCom : IAutoComplete
{
#region Login to Minecraft.net and get a new session ID
public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, Blocked, AccountMigrated, NotPremium };
/// <summary>
/// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme.
/// </summary>
/// <param name="user">Login</param>
/// <param name="pass">Password</param>
/// <param name="accesstoken">Will contain the access token returned by Minecraft.net, if the login is successful</param>
/// <param name="uuid">Will contain the player's UUID, needed for multiplayer</param>
/// <returns>Returns the status of the login (Success, Failure, etc.)</returns>
public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string uuid)
{
try
{
WebClient wClient = new WebClient();
wClient.Headers.Add("Content-Type: application/json");
string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + user + "\", \"password\": \"" + pass + "\" }";
string result = wClient.UploadString("https://authserver.mojang.com/authenticate", json_request);
if (result.Contains("availableProfiles\":[]}"))
{
return LoginResult.NotPremium;
}
else
{
string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { user = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; }
return LoginResult.Success;
}
}
catch (WebException e)
{
if (e.Status == WebExceptionStatus.ProtocolError)
{
HttpWebResponse response = (HttpWebResponse)e.Response;
if ((int)response.StatusCode == 403)
{
using (System.IO.StreamReader sr = new System.IO.StreamReader(response.GetResponseStream()))
{
string result = sr.ReadToEnd();
if (result.Contains("UserMigratedException"))
{
return LoginResult.AccountMigrated;
}
else return LoginResult.WrongPassword;
}
}
else if ((int)response.StatusCode == 503)
{
return LoginResult.ServiceUnavailable;
}
else return LoginResult.Blocked;
}
else if (e.Status == WebExceptionStatus.SendFailure)
{
return LoginResult.SSLError;
}
else return LoginResult.OtherError;
}
}
#endregion
#region Session checking when joining a server in online mode
/// <summary>
/// Check session using the Yggdrasil authentication scheme. Allow to join an online-mode server
/// </summary>
/// <param name="user">Username</param>
/// <param name="accesstoken">Session ID</param>
/// <param name="serverhash">Server ID</param>
/// <returns>TRUE if session was successfully checked</returns>
public static bool SessionCheck(string uuid, string accesstoken, string serverhash)
{
try
{
WebClient wClient = new WebClient();
wClient.Headers.Add("Content-Type: application/json");
string json_request = "{\"accessToken\":\"" + accesstoken + "\",\"selectedProfile\":\"" + uuid + "\",\"serverId\":\"" + serverhash + "\"}";
return (wClient.UploadString("https://sessionserver.mojang.com/session/minecraft/join", json_request) == "");
}
catch (WebException) { return false; }
}
#endregion
TcpClient c = new TcpClient();
Crypto.IAesStream s;
public bool HasBeenKicked { get { return connectionlost; } }
bool connectionlost = false;
bool encrypted = false;
int protocolversion;
public MinecraftCom()
{
foreach (ChatBot bot in scripts_on_hold) { bots.Add(bot); }
scripts_on_hold.Clear();
}
public bool Update()
{
for (int i = 0; i < bots.Count; i++) { bots[i].Update(); }
if (c.Client == null || !c.Connected) { return false; }
int id = 0, size = 0;
try
{
while (c.Client.Available > 0)
{
size = readNextVarInt(); //Packet size
id = readNextVarInt(); //Packet ID
switch (id)
{
case 0x00:
byte[] keepalive = new byte[4] { 0, 0, 0, 0 };
Receive(keepalive, 0, 4, SocketFlags.None);
byte[] keepalive_packet = concatBytes(getVarInt(0x00), keepalive);
byte[] keepalive_tosend = concatBytes(getVarInt(keepalive_packet.Length), keepalive_packet);
Send(keepalive_tosend);
break;
case 0x02:
string message = readNextString();
//printstring("§8" + message, false); //Debug : Show the RAW JSON data
message = ChatParser.ParseText(message);
printstring(message, false);
for (int i = 0; i < bots.Count; i++) { bots[i].GetText(message); } break;
case 0x3A:
int autocomplete_count = readNextVarInt();
string tab_list = "";
for (int i = 0; i < autocomplete_count; i++)
{
autocomplete_result = readNextString();
if (autocomplete_result != "")
tab_list = tab_list + autocomplete_result + " ";
}
autocomplete_received = true;
tab_list = tab_list.Trim();
if (tab_list.Length > 0)
printstring("§8" + tab_list, false);
break;
case 0x40: string reason = ChatParser.ParseText(readNextString());
ConsoleIO.WriteLine("Disconnected by Server :");
printstring(reason, true);
connectionlost = true;
for (int i = 0; i < bots.Count; i++)
bots[i].OnDisconnect(ChatBot.DisconnectReason.InGameKick, reason);
return false;
default:
readData(size - getVarInt(id).Length); //Skip packet
break;
}
}
}
catch (SocketException) { return false; }
return true;
}
public void DebugDump()
{
byte[] cache = new byte[128000];
Receive(cache, 0, 128000, SocketFlags.None);
string dump = BitConverter.ToString(cache);
System.IO.File.WriteAllText("debug.txt", dump);
System.Diagnostics.Process.Start("debug.txt");
}
public bool OnConnectionLost(ChatBot.DisconnectReason reason, string reason_message)
{
if (!connectionlost)
{
connectionlost = true;
for (int i = 0; i < bots.Count; i++)
{
if (bots[i].OnDisconnect(reason, reason_message))
{
return true; //The client is about to restart
}
}
}
return false;
}
private void readData(int offset)
{
if (offset > 0)
{
try
{
byte[] cache = new byte[offset];
Receive(cache, 0, offset, SocketFlags.None);
}
catch (OutOfMemoryException) { }
}
}
private string readNextString()
{
int length = readNextVarInt();
if (length > 0)
{
byte[] cache = new byte[length];
Receive(cache, 0, length, SocketFlags.None);
string result = Encoding.UTF8.GetString(cache);
return result;
}
else return "";
}
private byte[] readNextByteArray()
{
byte[] tmp = new byte[2];
Receive(tmp, 0, 2, SocketFlags.None);
Array.Reverse(tmp);
short len = BitConverter.ToInt16(tmp, 0);
byte[] data = new byte[len];
Receive(data, 0, len, SocketFlags.None);
return data;
}
private int readNextVarInt()
{
int i = 0;
int j = 0;
int k = 0;
byte[] tmp = new byte[1];
while (true)
{
Receive(tmp, 0, 1, SocketFlags.None);
k = tmp[0];
i |= (k & 0x7F) << j++ * 7;
if (j > 5) throw new OverflowException("VarInt too big");
if ((k & 0x80) != 128) break;
}
return i;
}
public static byte[] getPaddingPacket()
{
//Will generate a 15-bytes long padding packet
byte[] id = getVarInt(0x17); //Plugin Message
byte[] channel_name = Encoding.UTF8.GetBytes("MCC|Pad");
byte[] channel_name_len = getVarInt(channel_name.Length);
byte[] data = new byte[] { 0x00, 0x00, 0x00 };
byte[] data_len = BitConverter.GetBytes((short)data.Length); Array.Reverse(data_len);
byte[] packet_data = concatBytes(id, channel_name_len, channel_name, data_len, data);
byte[] packet_length = getVarInt(packet_data.Length);
return concatBytes(packet_length, packet_data);
}
private static byte[] getVarInt(int paramInt)
{
List<byte> bytes = new List<byte>();
while ((paramInt & -128) != 0)
{
bytes.Add((byte)(paramInt & 127 | 128));
paramInt = (int)(((uint)paramInt) >> 7);
}
bytes.Add((byte)paramInt);
return bytes.ToArray();
}
private static byte[] concatBytes(params byte[][] bytes)
{
List<byte> result = new List<byte>();
foreach (byte[] array in bytes)
result.AddRange(array);
return result.ToArray();
}
private static int atoi(string str)
{
return int.Parse(new string(str.Trim().TakeWhile(char.IsDigit).ToArray()));
}
private static void setcolor(char c)
{
switch (c)
{
case '0': Console.ForegroundColor = ConsoleColor.Gray; break; //Should be Black but Black is non-readable on a black background
case '1': Console.ForegroundColor = ConsoleColor.DarkBlue; break;
case '2': Console.ForegroundColor = ConsoleColor.DarkGreen; break;
case '3': Console.ForegroundColor = ConsoleColor.DarkCyan; break;
case '4': Console.ForegroundColor = ConsoleColor.DarkRed; break;
case '5': Console.ForegroundColor = ConsoleColor.DarkMagenta; break;
case '6': Console.ForegroundColor = ConsoleColor.DarkYellow; break;
case '7': Console.ForegroundColor = ConsoleColor.Gray; break;
case '8': Console.ForegroundColor = ConsoleColor.DarkGray; break;
case '9': Console.ForegroundColor = ConsoleColor.Blue; break;
case 'a': Console.ForegroundColor = ConsoleColor.Green; break;
case 'b': Console.ForegroundColor = ConsoleColor.Cyan; break;
case 'c': Console.ForegroundColor = ConsoleColor.Red; break;
case 'd': Console.ForegroundColor = ConsoleColor.Magenta; break;
case 'e': Console.ForegroundColor = ConsoleColor.Yellow; break;
case 'f': Console.ForegroundColor = ConsoleColor.White; break;
case 'r': Console.ForegroundColor = ConsoleColor.White; break;
}
}
private static void printstring(string str, bool acceptnewlines)
{
if (!String.IsNullOrEmpty(str))
{
if (Settings.chatTimeStamps)
{
int hour = DateTime.Now.Hour, minute = DateTime.Now.Minute, second = DateTime.Now.Second;
ConsoleIO.Write(hour.ToString("00") + ':' + minute.ToString("00") + ':' + second.ToString("00") + ' ');
}
if (!acceptnewlines) { str = str.Replace('\n', ' '); }
if (ConsoleIO.basicIO) { ConsoleIO.WriteLine(str); return; }
string[] subs = str.Split(new char[] { '§' });
if (subs[0].Length > 0) { ConsoleIO.Write(subs[0]); }
for (int i = 1; i < subs.Length; i++)
{
if (subs[i].Length > 0)
{
setcolor(subs[i][0]);
if (subs[i].Length > 1)
{
ConsoleIO.Write(subs[i].Substring(1, subs[i].Length - 1));
}
}
}
ConsoleIO.Write('\n');
}
Console.ForegroundColor = ConsoleColor.Gray;
}
private bool autocomplete_received = false;
private string autocomplete_result = "";
public string AutoComplete(string behindcursor)
{
if (String.IsNullOrEmpty(behindcursor))
return "";
byte[] packet_id = getVarInt(0x14);
byte[] tocomplete_val = Encoding.UTF8.GetBytes(behindcursor);
byte[] tocomplete_len = getVarInt(tocomplete_val.Length);
byte[] tabcomplete_packet = concatBytes(packet_id, tocomplete_len, tocomplete_val);
byte[] tabcomplete_packet_tosend = concatBytes(getVarInt(tabcomplete_packet.Length), tabcomplete_packet);
autocomplete_received = false;
autocomplete_result = behindcursor;
Send(tabcomplete_packet_tosend);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms)
while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }
return autocomplete_result;
}
public void setVersion(int ver) { protocolversion = ver; }
public void setClient(TcpClient n) { c = n; }
private void setEncryptedClient(Crypto.IAesStream n) { s = n; encrypted = true; }
private void Receive(byte[] buffer, int start, int offset, SocketFlags f)
{
if (encrypted)
{
s.Read(buffer, start, offset);
}
else c.Client.Receive(buffer, start, offset, f);
}
private void Send(byte[] buffer)
{
if (encrypted)
{
s.Write(buffer, 0, buffer.Length);
}
else c.Client.Send(buffer);
}
public static bool GetServerInfo(string serverIP, ref int protocolversion, ref string version)
{
try
{
string host; int port;
string[] sip = serverIP.Split(':');
host = sip[0];
if (sip.Length == 1)
{
port = 25565;
}
else
{
try
{
port = Convert.ToInt32(sip[1]);
}
catch (FormatException) { port = 25565; }
}
TcpClient tcp = new TcpClient(host, port);
byte[] packet_id = getVarInt(0);
byte[] protocol_version = getVarInt(4);
byte[] server_adress_val = Encoding.UTF8.GetBytes(host);
byte[] server_adress_len = getVarInt(server_adress_val.Length);
byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port);
byte[] next_state = getVarInt(1);
byte[] packet = concatBytes(packet_id, protocol_version, server_adress_len, server_adress_val, server_port, next_state);
byte[] tosend = concatBytes(getVarInt(packet.Length), packet);
tcp.Client.Send(tosend, SocketFlags.None);
byte[] status_request = getVarInt(0);
byte[] request_packet = concatBytes(getVarInt(status_request.Length), status_request);
tcp.Client.Send(request_packet, SocketFlags.None);
MinecraftCom ComTmp = new MinecraftCom();
ComTmp.setClient(tcp);
if (ComTmp.readNextVarInt() > 0) //Read Response length
{
if (ComTmp.readNextVarInt() == 0x00) //Read Packet ID
{
string result = ComTmp.readNextString(); //Get the Json data
if (result[0] == '{' && result.Contains("protocol\":") && result.Contains("name\":\""))
{
string[] tmp_ver = result.Split(new string[] { "protocol\":" }, StringSplitOptions.None);
string[] tmp_name = result.Split(new string[] { "name\":\"" }, StringSplitOptions.None);
if (tmp_ver.Length >= 2 && tmp_name.Length >= 2)
{
protocolversion = atoi(tmp_ver[1]);
version = tmp_name[1].Split('"')[0];
if (result.Contains("modinfo\":"))
{
//Server is running Forge (which is not supported)
version = "Forge " + version;
protocolversion = 0;
}
Console.ForegroundColor = ConsoleColor.DarkGray;
//Console.WriteLine(result); //Debug: show the full Json string
Console.WriteLine("Server version : " + version + " (protocol v" + protocolversion + ").");
Console.ForegroundColor = ConsoleColor.Gray;
return true;
}
}
}
}
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine("Unexpected answer from the server (is that a MC 1.7+ server ?)");
Console.ForegroundColor = ConsoleColor.Gray;
return false;
}
catch
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine("An error occured while attempting to connect to this IP.");
Console.ForegroundColor = ConsoleColor.Gray;
return false;
}
}
public bool Login(string username, string uuid, string sessionID, string host, int port)
{
byte[] packet_id = getVarInt(0);
byte[] protocol_version = getVarInt(protocolversion);
byte[] server_adress_val = Encoding.UTF8.GetBytes(host);
byte[] server_adress_len = getVarInt(server_adress_val.Length);
byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port);
byte[] next_state = getVarInt(2);
byte[] handshake_packet = concatBytes(packet_id, protocol_version, server_adress_len, server_adress_val, server_port, next_state);
byte[] handshake_packet_tosend = concatBytes(getVarInt(handshake_packet.Length), handshake_packet);
Send(handshake_packet_tosend);
byte[] username_val = Encoding.UTF8.GetBytes(username);
byte[] username_len = getVarInt(username_val.Length);
byte[] login_packet = concatBytes(packet_id, username_len, username_val);
byte[] login_packet_tosend = concatBytes(getVarInt(login_packet.Length), login_packet);
Send(login_packet_tosend);
readNextVarInt(); //Packet size
int pid = readNextVarInt(); //Packet ID
if (pid == 0x00) //Login rejected
{
Console.WriteLine("Login rejected by Server :");
printstring(ChatParser.ParseText(readNextString()), true);
return false;
}
else if (pid == 0x01) //Encryption request
{
string serverID = readNextString();
byte[] Serverkey = readNextByteArray();
byte[] token = readNextByteArray();
return StartEncryption(uuid, sessionID, token, serverID, Serverkey);
}
else if (pid == 0x02) //Login successfull
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine("Server is in offline mode.");
Console.ForegroundColor = ConsoleColor.Gray;
return true; //No need to check session or start encryption
}
else return false;
}
public bool StartEncryption(string uuid, string sessionID, byte[] token, string serverIDhash, byte[] serverKey)
{
System.Security.Cryptography.RSACryptoServiceProvider RSAService = Crypto.DecodeRSAPublicKey(serverKey);
byte[] secretKey = Crypto.GenerateAESPrivateKey();
Console.ForegroundColor = ConsoleColor.DarkGray;
ConsoleIO.WriteLine("Crypto keys & hash generated.");
Console.ForegroundColor = ConsoleColor.Gray;
if (serverIDhash != "-")
{
Console.WriteLine("Checking Session...");
if (!SessionCheck(uuid, sessionID, Crypto.getServerHash(serverIDhash, serverKey, secretKey)))
{
return false;
}
}
//Encrypt the data
byte[] key_enc = RSAService.Encrypt(secretKey, false);
byte[] token_enc = RSAService.Encrypt(token, false);
byte[] key_len = BitConverter.GetBytes((short)key_enc.Length); Array.Reverse(key_len);
byte[] token_len = BitConverter.GetBytes((short)token_enc.Length); Array.Reverse(token_len);
//Encryption Response packet
byte[] packet_id = getVarInt(0x01);
byte[] encryption_response = concatBytes(packet_id, key_len, key_enc, token_len, token_enc);
byte[] encryption_response_tosend = concatBytes(getVarInt(encryption_response.Length), encryption_response);
Send(encryption_response_tosend);
//Start client-side encryption
Crypto.IAesStream encrypted;
if (Program.isUsingMono) { encrypted = new Crypto.MonoAesStream(c.GetStream(), secretKey); }
else encrypted = new Crypto.AesStream(c.GetStream(), secretKey);
setEncryptedClient(encrypted);
//Get the next packet
readNextVarInt(); //Skip Packet size (not needed)
return (readNextVarInt() == 0x02); //Packet ID. 0x02 = Login Success
}
public bool SendChatMessage(string message)
{
if (String.IsNullOrEmpty(message))
return true;
try
{
byte[] packet_id = getVarInt(0x01);
byte[] message_val = Encoding.UTF8.GetBytes(message);
byte[] message_len = getVarInt(message_val.Length);
byte[] message_packet = concatBytes(packet_id, message_len, message_val);
byte[] message_packet_tosend = concatBytes(getVarInt(message_packet.Length), message_packet);
Send(message_packet_tosend);
return true;
}
catch (SocketException) { return false; }
}
public bool SendRespawnPacket()
{
try
{
byte[] packet_id = getVarInt(0x16);
byte[] action_id = new byte[] { 0 };
byte[] respawn_packet = concatBytes(getVarInt(packet_id.Length + 1), packet_id, action_id);
Send(respawn_packet);
return true;
}
catch (SocketException) { return false; }
}
public void Disconnect(string message)
{
if (message == null)
message = "";
message.Replace("\"", "\\\"");
message = "\"" + message + "\"";
try
{
byte[] packet_id = getVarInt(0x40);
byte[] message_val = Encoding.UTF8.GetBytes(message);
byte[] message_len = getVarInt(message_val.Length);
byte[] disconnect_packet = concatBytes(packet_id, message_len, message_val);
byte[] disconnect_packet_tosend = concatBytes(getVarInt(disconnect_packet.Length), disconnect_packet);
Send(disconnect_packet_tosend);
}
catch (SocketException) { }
catch (System.IO.IOException) { }
catch (NullReferenceException) { }
catch (ObjectDisposedException) { }
foreach (ChatBot bot in bots)
if (bot is Bots.Script)
scripts_on_hold.Add((Bots.Script)bot);
}
private List<ChatBot> bots = new List<ChatBot>();
private static List<Bots.Script> scripts_on_hold = new List<Bots.Script>();
public void BotLoad(ChatBot b) { b.SetHandler(this); bots.Add(b); b.Initialize(); Settings.SingleCommand = ""; }
public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); }
public void BotClear() { bots.Clear(); }
}
}

View file

@ -2,6 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Protocol;
using System.Reflection;
using System.Threading;
namespace MinecraftClient
{
@ -11,11 +14,12 @@ namespace MinecraftClient
/// This source code is released under the CDDL 1.0 License.
/// </summary>
class Program
static class Program
{
private static McTcpClient Client;
public static string[] startupargs;
public const string Version = "1.7.3";
public const string Version = "1.8.0";
private static Thread offlinePrompt = null;
/// <summary>
/// The main entry point of Minecraft Console Client
@ -23,7 +27,7 @@ namespace MinecraftClient
static void Main(string[] args)
{
Console.WriteLine("Console Client for MC 1.7.2 to 1.7.9 - v" + Version + " - By ORelio & Contributors");
Console.WriteLine("Console Client for MC 1.4.6 to 1.8.0 - v" + Version + " - By ORelio & Contributors");
//Basic Input/Output ?
if (args.Length >= 1 && args[args.Length - 1] == "BasicIO")
@ -58,7 +62,7 @@ namespace MinecraftClient
Settings.Password = args[1];
if (args.Length >= 3)
{
Settings.ServerIP = args[2];
Settings.setServerIP(args[2]);
//Single command?
if (args.Length >= 4)
@ -71,7 +75,8 @@ namespace MinecraftClient
if (Settings.ConsoleTitle != "")
{
Console.Title = Settings.ConsoleTitle.Replace("%username%", "New Window");
Settings.Username = "New Window";
Console.Title = Settings.expandVars(Settings.ConsoleTitle);
}
//Asking the user to type in missing data such as Username and Password
@ -104,84 +109,89 @@ namespace MinecraftClient
private static void InitializeClient()
{
MinecraftCom.LoginResult result;
ProtocolHandler.LoginResult result;
Settings.Username = Settings.Login;
string sessionID = "";
string UUID = "";
if (Settings.Password == "-")
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine("You chose to run in offline mode.");
Console.ForegroundColor = ConsoleColor.Gray;
result = MinecraftCom.LoginResult.Success;
ConsoleIO.WriteLineFormatted("§8You chose to run in offline mode.");
result = ProtocolHandler.LoginResult.Success;
sessionID = "0";
}
else
{
Console.WriteLine("Connecting to Minecraft.net...");
result = MinecraftCom.GetLogin(ref Settings.Username, Settings.Password, ref sessionID, ref UUID);
result = ProtocolHandler.GetLogin(ref Settings.Username, Settings.Password, ref sessionID, ref UUID);
}
if (result == MinecraftCom.LoginResult.Success)
if (result == ProtocolHandler.LoginResult.Success)
{
if (Settings.ConsoleTitle != "")
{
Console.Title = Settings.ConsoleTitle.Replace("%username%", Settings.Username);
}
Console.Title = Settings.expandVars(Settings.ConsoleTitle);
if (Settings.playerHeadAsIcon)
ConsoleIcon.setPlayerIconAsync(Settings.Username);
Console.WriteLine("Success. (session ID: " + sessionID + ')');
if (Settings.ServerIP == "")
{
Console.Write("Server IP : ");
Settings.ServerIP = Console.ReadLine();
Settings.setServerIP(Console.ReadLine());
}
//Get server version
Console.WriteLine("Retrieving Server Info...");
int protocolversion = 0; string version = "";
if (MinecraftCom.GetServerInfo(Settings.ServerIP, ref protocolversion, ref version))
int protocolversion = 0;
if (Settings.ServerVersion != "" && Settings.ServerVersion.ToLower() != "auto")
{
//Supported protocol version ?
int[] supportedVersions = { 4, 5 };
if (Array.IndexOf(supportedVersions, protocolversion) > -1)
protocolversion = Protocol.ProtocolHandler.MCVer2ProtocolVersion(Settings.ServerVersion);
if (protocolversion != 0)
{
//Load translations (Minecraft 1.6+)
ChatParser.InitTranslations();
ConsoleIO.WriteLineFormatted("§8Using Minecraft version " + Settings.ServerVersion + " (protocol v" + protocolversion + ')');
}
else ConsoleIO.WriteLineFormatted("§8Unknown or not supported MC version '" + Settings.ServerVersion + "'.\nSwitching to autodetection mode.");
}
//Will handle the connection for this client
Console.WriteLine("Version is supported.");
MinecraftCom handler = new MinecraftCom();
ConsoleIO.SetAutoCompleteEngine(handler);
handler.setVersion(protocolversion);
//Load & initialize bots if needed
if (Settings.AntiAFK_Enabled) { handler.BotLoad(new Bots.AntiAFK(Settings.AntiAFK_Delay)); }
if (Settings.Hangman_Enabled) { handler.BotLoad(new Bots.Pendu(Settings.Hangman_English)); }
if (Settings.Alerts_Enabled) { handler.BotLoad(new Bots.Alerts()); }
if (Settings.ChatLog_Enabled) { handler.BotLoad(new Bots.ChatLog(Settings.ChatLog_File.Replace("%username%", Settings.Username), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); }
if (Settings.PlayerLog_Enabled) { handler.BotLoad(new Bots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.PlayerLog_File.Replace("%username%", Settings.Username))); }
if (Settings.AutoRelog_Enabled) { handler.BotLoad(new Bots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries)); }
if (Settings.ScriptScheduler_Enabled) { handler.BotLoad(new Bots.ScriptScheduler(Settings.ScriptScheduler_TasksFile.Replace("%username%", Settings.Username))); }
if (Settings.RemoteCtrl_Enabled) { handler.BotLoad(new Bots.RemoteControl()); }
if (protocolversion == 0)
{
Console.WriteLine("Retrieving Server Info...");
if (!ProtocolHandler.GetServerInfo(Settings.ServerIP, Settings.ServerPort, ref protocolversion))
{
Console.WriteLine("Failed to ping this IP.");
if (Settings.AutoRelog_Enabled)
{
ChatBots.AutoRelog bot = new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries);
if (!bot.OnDisconnect(ChatBot.DisconnectReason.ConnectionLost, "Failed to ping this IP.")) { OfflineCommandPrompt(); }
}
else OfflineCommandPrompt();
return;
}
}
if (protocolversion != 0)
{
try
{
//Start the main TCP client
if (Settings.SingleCommand != "")
{
Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, handler, Settings.SingleCommand);
Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, Settings.ServerPort, protocolversion, Settings.SingleCommand);
}
else Client = new McTcpClient(Settings.Username, UUID, sessionID, Settings.ServerIP, handler);
else Client = new McTcpClient(Settings.Username, UUID, sessionID, protocolversion, Settings.ServerIP, Settings.ServerPort);
}
else
catch (NotSupportedException)
{
Console.WriteLine("Cannot connect to the server : This version is not supported !");
ReadLineReconnect();
OfflineCommandPrompt();
}
}
else
{
Console.WriteLine("Failed to ping this IP.");
ReadLineReconnect();
Console.WriteLine("Failed to determine server version.");
OfflineCommandPrompt();
}
}
else
@ -190,26 +200,23 @@ namespace MinecraftClient
Console.Write("Connection failed : ");
switch (result)
{
case MinecraftCom.LoginResult.AccountMigrated: Console.WriteLine("Account migrated, use e-mail as username."); break;
case MinecraftCom.LoginResult.Blocked: Console.WriteLine("Too many failed logins. Please try again later."); break;
case MinecraftCom.LoginResult.ServiceUnavailable: Console.WriteLine("Login servers are unavailable. Please try again later."); break;
case MinecraftCom.LoginResult.WrongPassword: Console.WriteLine("Incorrect password."); break;
case MinecraftCom.LoginResult.NotPremium: Console.WriteLine("User not premium."); break;
case MinecraftCom.LoginResult.OtherError: Console.WriteLine("Network error."); break;
case MinecraftCom.LoginResult.SSLError: Console.WriteLine("SSL Error.");
case ProtocolHandler.LoginResult.AccountMigrated: Console.WriteLine("Account migrated, use e-mail as username."); break;
case ProtocolHandler.LoginResult.ServiceUnavailable: Console.WriteLine("Login servers are unavailable. Please try again later."); break;
case ProtocolHandler.LoginResult.WrongPassword: Console.WriteLine("Incorrect password."); break;
case ProtocolHandler.LoginResult.NotPremium: Console.WriteLine("User not premium."); break;
case ProtocolHandler.LoginResult.OtherError: Console.WriteLine("Network error."); break;
case ProtocolHandler.LoginResult.SSLError: Console.WriteLine("SSL Error.");
if (isUsingMono)
{
Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine("It appears that you are using Mono to run this program."
ConsoleIO.WriteLineFormatted("§8It appears that you are using Mono to run this program."
+ '\n' + "The first time, you have to import HTTPS certificates using:"
+ '\n' + "mozroots --import --ask-remove");
Console.ForegroundColor = ConsoleColor.Gray;
return;
}
break;
}
while (Console.KeyAvailable) { Console.ReadKey(false); }
if (Settings.SingleCommand == "") { ReadLineReconnect(); }
if (Settings.SingleCommand == "") { OfflineCommandPrompt(); }
}
}
@ -219,7 +226,13 @@ namespace MinecraftClient
public static void Restart()
{
new System.Threading.Thread(new System.Threading.ThreadStart(t_restart)).Start();
new Thread(new ThreadStart(delegate
{
if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); }
if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); }
Console.WriteLine("Restarting Minecraft Console Client...");
InitializeClient();
})).Start();
}
/// <summary>
@ -228,23 +241,64 @@ namespace MinecraftClient
public static void Exit()
{
new System.Threading.Thread(new System.Threading.ThreadStart(t_exit)).Start();
new Thread(new ThreadStart(delegate
{
if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); }
if (offlinePrompt != null) { offlinePrompt.Abort(); offlinePrompt = null; ConsoleIO.Reset(); }
if (Settings.playerHeadAsIcon) { ConsoleIcon.revertToCMDIcon(); }
Environment.Exit(0);
})).Start();
}
/// <summary>
/// Pause the program, usually when an error or a kick occured, letting the user press Enter to quit OR type /reconnect
/// </summary>
/// <returns>Return True if the user typed "/reconnect"</returns>
public static bool ReadLineReconnect()
public static void OfflineCommandPrompt()
{
string text = Console.ReadLine();
if (text == "reco" || text == "reconnect" || text == "/reco" || text == "/reconnect")
if (!Settings.exitOnFailure && offlinePrompt == null)
{
Program.Restart();
return true;
offlinePrompt = new Thread(new ThreadStart(delegate
{
string command = " ";
ConsoleIO.WriteLineFormatted("Not connected to any server. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help.");
ConsoleIO.WriteLineFormatted("Or press Enter to exit Minecraft Console Client.");
while (command.Length > 0)
{
if (!ConsoleIO.basicIO) { ConsoleIO.Write('>'); }
command = Console.ReadLine().Trim();
if (command.Length > 0)
{
if (Settings.internalCmdChar != ' ' && command[0] == Settings.internalCmdChar)
{
string message = "";
command = command.Substring(1);
if (command.StartsWith("reco"))
{
message = new Commands.Reco().Run(null, Settings.expandVars(command));
}
else if (command.StartsWith("connect"))
{
message = new Commands.Connect().Run(null, Settings.expandVars(command));
}
else if (command.StartsWith("exit") || command.StartsWith("quit"))
{
message = new Commands.Exit().Run(null, Settings.expandVars(command));
}
else if (command.StartsWith("help"))
{
ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Reco().CMDDesc);
ConsoleIO.WriteLineFormatted("§8MCC: " + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + new Commands.Connect().CMDDesc);
}
else ConsoleIO.WriteLineFormatted("§8Unknown command '" + command.Split(' ')[0] + "'.");
if (message != "") { ConsoleIO.WriteLineFormatted("§8MCC: " + message); }
}
else ConsoleIO.WriteLineFormatted("§8Please type a command or press Enter to exit.");
}
}
}));
offlinePrompt.Start();
}
else return false;
}
/// <summary>
@ -260,24 +314,16 @@ namespace MinecraftClient
}
/// <summary>
/// Private thread for restarting the program. Called through Restart()
/// Enumerate types in namespace through reflection
/// </summary>
/// <param name="nameSpace">Namespace to process</param>
/// <param name="assembly">Assembly to use. Default is Assembly.GetExecutingAssembly()</param>
/// <returns></returns>
private static void t_restart()
public static Type[] GetTypesInNamespace(string nameSpace, Assembly assembly = null)
{
if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); }
Console.WriteLine("Restarting Minecraft Console Client...");
InitializeClient();
}
/// <summary>
/// Private thread for exiting the program. Called through Exit()
/// </summary>
private static void t_exit()
{
if (Client != null) { Client.Disconnect(); ConsoleIO.Reset(); }
Environment.Exit(0);
if (assembly == null) { assembly = Assembly.GetExecutingAssembly(); }
return assembly.GetTypes().Where(t => String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal)).ToArray();
}
}
}

View file

@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MinecraftClient")]
[assembly: AssemblyCopyright("Copyright © ORelio 2012-2013")]
[assembly: AssemblyCopyright("Copyright © ORelio 2012-2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.6.1")]
[assembly: AssemblyFileVersion("1.6.1")]
[assembly: AssemblyVersion("1.8.0")]
[assembly: AssemblyFileVersion("1.8.0")]

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// This class parses JSON chat data from MC 1.6+ and returns the appropriate string to be printed.
@ -56,22 +56,23 @@ namespace MinecraftClient
{
switch (colorname.ToLower())
{
case "black": return "§0";
case "dark_blue": return "§1";
case "dark_green": return "§2";
case "dark_aqua": return "§3";
case "dark_red": return "§4";
case "dark_purple": return "§5";
case "gold": return "§6";
case "gray": return "§7";
case "dark_gray": return "§8";
case "blue": return "§9";
case "green": return "§a";
case "aqua": return "§b";
case "red": return "§c";
case "light_purple": return "§d";
case "yellow": return "§e";
case "white": return "§f";
/* MC 1.7+ Name MC 1.6 Name Classic tag */
case "black": /* Blank if same */ return "§0";
case "dark_blue": return "§1";
case "dark_green": return "§2";
case "dark_aqua": case "dark_cyan": return "§3";
case "dark_red": return "§4";
case "dark_purple": case "dark_magenta": return "§5";
case "gold": case "dark_yellow": return "§6";
case "gray": return "§7";
case "dark_gray": return "§8";
case "blue": return "§9";
case "green": return "§a";
case "aqua": case "cyan": return "§b";
case "red": return "§c";
case "light_purple": case "magenta": return "§d";
case "yellow": return "§e";
case "white": return "§f";
default: return "";
}
}
@ -99,7 +100,7 @@ namespace MinecraftClient
if (!System.IO.Directory.Exists("lang"))
System.IO.Directory.CreateDirectory("lang");
string Language_File = "lang\\" + Settings.Language + ".lang";
string Language_File = "lang" + (Program.isUsingMono ? '/' : '\\') + Settings.Language + ".lang";
//File not found? Try downloading language file from Mojang's servers?
if (!System.IO.File.Exists(Language_File))
@ -127,9 +128,7 @@ namespace MinecraftClient
&& System.IO.File.Exists(Settings.TranslationsFile_FromMCDir))
{
Language_File = Settings.TranslationsFile_FromMCDir;
Console.ForegroundColor = ConsoleColor.DarkGray;
ConsoleIO.WriteLine("Defaulting to en_GB.lang from your Minecraft directory.");
Console.ForegroundColor = ConsoleColor.Gray;
ConsoleIO.WriteLineFormatted("§8Defaulting to en_GB.lang from your Minecraft directory.");
}
//Load the external dictionnary of translation rules or display an error message
@ -148,16 +147,12 @@ namespace MinecraftClient
}
}
Console.ForegroundColor = ConsoleColor.DarkGray;
ConsoleIO.WriteLine("Translations file loaded.");
Console.ForegroundColor = ConsoleColor.Gray;
ConsoleIO.WriteLineFormatted("§8Translations file loaded.");
}
else //No external dictionnary found.
{
Console.ForegroundColor = ConsoleColor.DarkGray;
ConsoleIO.WriteLine("Translations file not found: \"" + Language_File + "\""
ConsoleIO.WriteLineFormatted("§8Translations file not found: \"" + Language_File + "\""
+ "\nSome messages won't be properly printed without this file.");
Console.ForegroundColor = ConsoleColor.Gray;
}
}

View file

@ -0,0 +1,814 @@
// CRC32.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2011 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// Last Saved: <2011-August-02 18:25:54>
//
// ------------------------------------------------------------------
//
// This module defines the CRC32 class, which can do the CRC32 algorithm, using
// arbitrary starting polynomials, and bit reversal. The bit reversal is what
// distinguishes this CRC-32 used in BZip2 from the CRC-32 that is used in PKZIP
// files, or GZIP files. This class does both.
//
// ------------------------------------------------------------------
using System;
using Interop = System.Runtime.InteropServices;
namespace Ionic.Crc
{
/// <summary>
/// Computes a CRC-32. The CRC-32 algorithm is parameterized - you
/// can set the polynomial and enable or disable bit
/// reversal. This can be used for GZIP, BZip2, or ZIP.
/// </summary>
/// <remarks>
/// This type is used internally by DotNetZip; it is generally not used
/// directly by applications wishing to create, read, or manipulate zip
/// archive files.
/// </remarks>
[Interop.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000C")]
[Interop.ComVisible(true)]
#if !NETCF
[Interop.ClassInterface(Interop.ClassInterfaceType.AutoDispatch)]
#endif
public class CRC32
{
/// <summary>
/// Indicates the total number of bytes applied to the CRC.
/// </summary>
public Int64 TotalBytesRead
{
get
{
return _TotalBytesRead;
}
}
/// <summary>
/// Indicates the current CRC for all blocks slurped in.
/// </summary>
public Int32 Crc32Result
{
get
{
return unchecked((Int32)(~_register));
}
}
/// <summary>
/// Returns the CRC32 for the specified stream.
/// </summary>
/// <param name="input">The stream over which to calculate the CRC32</param>
/// <returns>the CRC32 calculation</returns>
public Int32 GetCrc32(System.IO.Stream input)
{
return GetCrc32AndCopy(input, null);
}
/// <summary>
/// Returns the CRC32 for the specified stream, and writes the input into the
/// output stream.
/// </summary>
/// <param name="input">The stream over which to calculate the CRC32</param>
/// <param name="output">The stream into which to deflate the input</param>
/// <returns>the CRC32 calculation</returns>
public Int32 GetCrc32AndCopy(System.IO.Stream input, System.IO.Stream output)
{
if (input == null)
throw new Exception("The input stream must not be null.");
unchecked
{
byte[] buffer = new byte[BUFFER_SIZE];
int readSize = BUFFER_SIZE;
_TotalBytesRead = 0;
int count = input.Read(buffer, 0, readSize);
if (output != null) output.Write(buffer, 0, count);
_TotalBytesRead += count;
while (count > 0)
{
SlurpBlock(buffer, 0, count);
count = input.Read(buffer, 0, readSize);
if (output != null) output.Write(buffer, 0, count);
_TotalBytesRead += count;
}
return (Int32)(~_register);
}
}
/// <summary>
/// Get the CRC32 for the given (word,byte) combo. This is a
/// computation defined by PKzip for PKZIP 2.0 (weak) encryption.
/// </summary>
/// <param name="W">The word to start with.</param>
/// <param name="B">The byte to combine it with.</param>
/// <returns>The CRC-ized result.</returns>
public Int32 ComputeCrc32(Int32 W, byte B)
{
return _InternalComputeCrc32((UInt32)W, B);
}
internal Int32 _InternalComputeCrc32(UInt32 W, byte B)
{
return (Int32)(crc32Table[(W ^ B) & 0xFF] ^ (W >> 8));
}
/// <summary>
/// Update the value for the running CRC32 using the given block of bytes.
/// This is useful when using the CRC32() class in a Stream.
/// </summary>
/// <param name="block">block of bytes to slurp</param>
/// <param name="offset">starting point in the block</param>
/// <param name="count">how many bytes within the block to slurp</param>
public void SlurpBlock(byte[] block, int offset, int count)
{
if (block == null)
throw new Exception("The data buffer must not be null.");
// bzip algorithm
for (int i = 0; i < count; i++)
{
int x = offset + i;
byte b = block[x];
if (this.reverseBits)
{
UInt32 temp = (_register >> 24) ^ b;
_register = (_register << 8) ^ crc32Table[temp];
}
else
{
UInt32 temp = (_register & 0x000000FF) ^ b;
_register = (_register >> 8) ^ crc32Table[temp];
}
}
_TotalBytesRead += count;
}
/// <summary>
/// Process one byte in the CRC.
/// </summary>
/// <param name = "b">the byte to include into the CRC . </param>
public void UpdateCRC(byte b)
{
if (this.reverseBits)
{
UInt32 temp = (_register >> 24) ^ b;
_register = (_register << 8) ^ crc32Table[temp];
}
else
{
UInt32 temp = (_register & 0x000000FF) ^ b;
_register = (_register >> 8) ^ crc32Table[temp];
}
}
/// <summary>
/// Process a run of N identical bytes into the CRC.
/// </summary>
/// <remarks>
/// <para>
/// This method serves as an optimization for updating the CRC when a
/// run of identical bytes is found. Rather than passing in a buffer of
/// length n, containing all identical bytes b, this method accepts the
/// byte value and the length of the (virtual) buffer - the length of
/// the run.
/// </para>
/// </remarks>
/// <param name = "b">the byte to include into the CRC. </param>
/// <param name = "n">the number of times that byte should be repeated. </param>
public void UpdateCRC(byte b, int n)
{
while (n-- > 0)
{
if (this.reverseBits)
{
uint temp = (_register >> 24) ^ b;
_register = (_register << 8) ^ crc32Table[(temp >= 0)
? temp
: (temp + 256)];
}
else
{
UInt32 temp = (_register & 0x000000FF) ^ b;
_register = (_register >> 8) ^ crc32Table[(temp >= 0)
? temp
: (temp + 256)];
}
}
}
private static uint ReverseBits(uint data)
{
unchecked
{
uint ret = data;
ret = (ret & 0x55555555) << 1 | (ret >> 1) & 0x55555555;
ret = (ret & 0x33333333) << 2 | (ret >> 2) & 0x33333333;
ret = (ret & 0x0F0F0F0F) << 4 | (ret >> 4) & 0x0F0F0F0F;
ret = (ret << 24) | ((ret & 0xFF00) << 8) | ((ret >> 8) & 0xFF00) | (ret >> 24);
return ret;
}
}
private static byte ReverseBits(byte data)
{
unchecked
{
uint u = (uint)data * 0x00020202;
uint m = 0x01044010;
uint s = u & m;
uint t = (u << 2) & (m << 1);
return (byte)((0x01001001 * (s + t)) >> 24);
}
}
private void GenerateLookupTable()
{
crc32Table = new UInt32[256];
unchecked
{
UInt32 dwCrc;
byte i = 0;
do
{
dwCrc = i;
for (byte j = 8; j > 0; j--)
{
if ((dwCrc & 1) == 1)
{
dwCrc = (dwCrc >> 1) ^ dwPolynomial;
}
else
{
dwCrc >>= 1;
}
}
if (reverseBits)
{
crc32Table[ReverseBits(i)] = ReverseBits(dwCrc);
}
else
{
crc32Table[i] = dwCrc;
}
i++;
} while (i!=0);
}
#if VERBOSE
Console.WriteLine();
Console.WriteLine("private static readonly UInt32[] crc32Table = {");
for (int i = 0; i < crc32Table.Length; i+=4)
{
Console.Write(" ");
for (int j=0; j < 4; j++)
{
Console.Write(" 0x{0:X8}U,", crc32Table[i+j]);
}
Console.WriteLine();
}
Console.WriteLine("};");
Console.WriteLine();
#endif
}
private uint gf2_matrix_times(uint[] matrix, uint vec)
{
uint sum = 0;
int i=0;
while (vec != 0)
{
if ((vec & 0x01)== 0x01)
sum ^= matrix[i];
vec >>= 1;
i++;
}
return sum;
}
private void gf2_matrix_square(uint[] square, uint[] mat)
{
for (int i = 0; i < 32; i++)
square[i] = gf2_matrix_times(mat, mat[i]);
}
/// <summary>
/// Combines the given CRC32 value with the current running total.
/// </summary>
/// <remarks>
/// This is useful when using a divide-and-conquer approach to
/// calculating a CRC. Multiple threads can each calculate a
/// CRC32 on a segment of the data, and then combine the
/// individual CRC32 values at the end.
/// </remarks>
/// <param name="crc">the crc value to be combined with this one</param>
/// <param name="length">the length of data the CRC value was calculated on</param>
public void Combine(int crc, int length)
{
uint[] even = new uint[32]; // even-power-of-two zeros operator
uint[] odd = new uint[32]; // odd-power-of-two zeros operator
if (length == 0)
return;
uint crc1= ~_register;
uint crc2= (uint) crc;
// put operator for one zero bit in odd
odd[0] = this.dwPolynomial; // the CRC-32 polynomial
uint row = 1;
for (int i = 1; i < 32; i++)
{
odd[i] = row;
row <<= 1;
}
// put operator for two zero bits in even
gf2_matrix_square(even, odd);
// put operator for four zero bits in odd
gf2_matrix_square(odd, even);
uint len2 = (uint) length;
// apply len2 zeros to crc1 (first square will put the operator for one
// zero byte, eight zero bits, in even)
do {
// apply zeros operator for this bit of len2
gf2_matrix_square(even, odd);
if ((len2 & 1)== 1)
crc1 = gf2_matrix_times(even, crc1);
len2 >>= 1;
if (len2 == 0)
break;
// another iteration of the loop with odd and even swapped
gf2_matrix_square(odd, even);
if ((len2 & 1)==1)
crc1 = gf2_matrix_times(odd, crc1);
len2 >>= 1;
} while (len2 != 0);
crc1 ^= crc2;
_register= ~crc1;
//return (int) crc1;
return;
}
/// <summary>
/// Create an instance of the CRC32 class using the default settings: no
/// bit reversal, and a polynomial of 0xEDB88320.
/// </summary>
public CRC32() : this(false)
{
}
/// <summary>
/// Create an instance of the CRC32 class, specifying whether to reverse
/// data bits or not.
/// </summary>
/// <param name='reverseBits'>
/// specify true if the instance should reverse data bits.
/// </param>
/// <remarks>
/// <para>
/// In the CRC-32 used by BZip2, the bits are reversed. Therefore if you
/// want a CRC32 with compatibility with BZip2, you should pass true
/// here. In the CRC-32 used by GZIP and PKZIP, the bits are not
/// reversed; Therefore if you want a CRC32 with compatibility with
/// those, you should pass false.
/// </para>
/// </remarks>
public CRC32(bool reverseBits) :
this( unchecked((int)0xEDB88320), reverseBits)
{
}
/// <summary>
/// Create an instance of the CRC32 class, specifying the polynomial and
/// whether to reverse data bits or not.
/// </summary>
/// <param name='polynomial'>
/// The polynomial to use for the CRC, expressed in the reversed (LSB)
/// format: the highest ordered bit in the polynomial value is the
/// coefficient of the 0th power; the second-highest order bit is the
/// coefficient of the 1 power, and so on. Expressed this way, the
/// polynomial for the CRC-32C used in IEEE 802.3, is 0xEDB88320.
/// </param>
/// <param name='reverseBits'>
/// specify true if the instance should reverse data bits.
/// </param>
///
/// <remarks>
/// <para>
/// In the CRC-32 used by BZip2, the bits are reversed. Therefore if you
/// want a CRC32 with compatibility with BZip2, you should pass true
/// here for the <c>reverseBits</c> parameter. In the CRC-32 used by
/// GZIP and PKZIP, the bits are not reversed; Therefore if you want a
/// CRC32 with compatibility with those, you should pass false for the
/// <c>reverseBits</c> parameter.
/// </para>
/// </remarks>
public CRC32(int polynomial, bool reverseBits)
{
this.reverseBits = reverseBits;
this.dwPolynomial = (uint) polynomial;
this.GenerateLookupTable();
}
/// <summary>
/// Reset the CRC-32 class - clear the CRC "remainder register."
/// </summary>
/// <remarks>
/// <para>
/// Use this when employing a single instance of this class to compute
/// multiple, distinct CRCs on multiple, distinct data blocks.
/// </para>
/// </remarks>
public void Reset()
{
_register = 0xFFFFFFFFU;
}
// private member vars
private UInt32 dwPolynomial;
private Int64 _TotalBytesRead;
private bool reverseBits;
private UInt32[] crc32Table;
private const int BUFFER_SIZE = 8192;
private UInt32 _register = 0xFFFFFFFFU;
}
/// <summary>
/// A Stream that calculates a CRC32 (a checksum) on all bytes read,
/// or on all bytes written.
/// </summary>
///
/// <remarks>
/// <para>
/// This class can be used to verify the CRC of a ZipEntry when
/// reading from a stream, or to calculate a CRC when writing to a
/// stream. The stream should be used to either read, or write, but
/// not both. If you intermix reads and writes, the results are not
/// defined.
/// </para>
///
/// <para>
/// This class is intended primarily for use internally by the
/// DotNetZip library.
/// </para>
/// </remarks>
public class CrcCalculatorStream : System.IO.Stream, System.IDisposable
{
private static readonly Int64 UnsetLengthLimit = -99;
internal System.IO.Stream _innerStream;
private CRC32 _Crc32;
private Int64 _lengthLimit = -99;
private bool _leaveOpen;
/// <summary>
/// The default constructor.
/// </summary>
/// <remarks>
/// <para>
/// Instances returned from this constructor will leave the underlying
/// stream open upon Close(). The stream uses the default CRC32
/// algorithm, which implies a polynomial of 0xEDB88320.
/// </para>
/// </remarks>
/// <param name="stream">The underlying stream</param>
public CrcCalculatorStream(System.IO.Stream stream)
: this(true, CrcCalculatorStream.UnsetLengthLimit, stream, null)
{
}
/// <summary>
/// The constructor allows the caller to specify how to handle the
/// underlying stream at close.
/// </summary>
/// <remarks>
/// <para>
/// The stream uses the default CRC32 algorithm, which implies a
/// polynomial of 0xEDB88320.
/// </para>
/// </remarks>
/// <param name="stream">The underlying stream</param>
/// <param name="leaveOpen">true to leave the underlying stream
/// open upon close of the <c>CrcCalculatorStream</c>; false otherwise.</param>
public CrcCalculatorStream(System.IO.Stream stream, bool leaveOpen)
: this(leaveOpen, CrcCalculatorStream.UnsetLengthLimit, stream, null)
{
}
/// <summary>
/// A constructor allowing the specification of the length of the stream
/// to read.
/// </summary>
/// <remarks>
/// <para>
/// The stream uses the default CRC32 algorithm, which implies a
/// polynomial of 0xEDB88320.
/// </para>
/// <para>
/// Instances returned from this constructor will leave the underlying
/// stream open upon Close().
/// </para>
/// </remarks>
/// <param name="stream">The underlying stream</param>
/// <param name="length">The length of the stream to slurp</param>
public CrcCalculatorStream(System.IO.Stream stream, Int64 length)
: this(true, length, stream, null)
{
if (length < 0)
throw new ArgumentException("length");
}
/// <summary>
/// A constructor allowing the specification of the length of the stream
/// to read, as well as whether to keep the underlying stream open upon
/// Close().
/// </summary>
/// <remarks>
/// <para>
/// The stream uses the default CRC32 algorithm, which implies a
/// polynomial of 0xEDB88320.
/// </para>
/// </remarks>
/// <param name="stream">The underlying stream</param>
/// <param name="length">The length of the stream to slurp</param>
/// <param name="leaveOpen">true to leave the underlying stream
/// open upon close of the <c>CrcCalculatorStream</c>; false otherwise.</param>
public CrcCalculatorStream(System.IO.Stream stream, Int64 length, bool leaveOpen)
: this(leaveOpen, length, stream, null)
{
if (length < 0)
throw new ArgumentException("length");
}
/// <summary>
/// A constructor allowing the specification of the length of the stream
/// to read, as well as whether to keep the underlying stream open upon
/// Close(), and the CRC32 instance to use.
/// </summary>
/// <remarks>
/// <para>
/// The stream uses the specified CRC32 instance, which allows the
/// application to specify how the CRC gets calculated.
/// </para>
/// </remarks>
/// <param name="stream">The underlying stream</param>
/// <param name="length">The length of the stream to slurp</param>
/// <param name="leaveOpen">true to leave the underlying stream
/// open upon close of the <c>CrcCalculatorStream</c>; false otherwise.</param>
/// <param name="crc32">the CRC32 instance to use to calculate the CRC32</param>
public CrcCalculatorStream(System.IO.Stream stream, Int64 length, bool leaveOpen,
CRC32 crc32)
: this(leaveOpen, length, stream, crc32)
{
if (length < 0)
throw new ArgumentException("length");
}
// This ctor is private - no validation is done here. This is to allow the use
// of a (specific) negative value for the _lengthLimit, to indicate that there
// is no length set. So we validate the length limit in those ctors that use an
// explicit param, otherwise we don't validate, because it could be our special
// value.
private CrcCalculatorStream
(bool leaveOpen, Int64 length, System.IO.Stream stream, CRC32 crc32)
: base()
{
_innerStream = stream;
_Crc32 = crc32 ?? new CRC32();
_lengthLimit = length;
_leaveOpen = leaveOpen;
}
/// <summary>
/// Gets the total number of bytes run through the CRC32 calculator.
/// </summary>
///
/// <remarks>
/// This is either the total number of bytes read, or the total number of
/// bytes written, depending on the direction of this stream.
/// </remarks>
public Int64 TotalBytesSlurped
{
get { return _Crc32.TotalBytesRead; }
}
/// <summary>
/// Provides the current CRC for all blocks slurped in.
/// </summary>
/// <remarks>
/// <para>
/// The running total of the CRC is kept as data is written or read
/// through the stream. read this property after all reads or writes to
/// get an accurate CRC for the entire stream.
/// </para>
/// </remarks>
public Int32 Crc
{
get { return _Crc32.Crc32Result; }
}
/// <summary>
/// Indicates whether the underlying stream will be left open when the
/// <c>CrcCalculatorStream</c> is Closed.
/// </summary>
/// <remarks>
/// <para>
/// Set this at any point before calling <see cref="Close()"/>.
/// </para>
/// </remarks>
public bool LeaveOpen
{
get { return _leaveOpen; }
set { _leaveOpen = value; }
}
/// <summary>
/// Read from the stream
/// </summary>
/// <param name="buffer">the buffer to read</param>
/// <param name="offset">the offset at which to start</param>
/// <param name="count">the number of bytes to read</param>
/// <returns>the number of bytes actually read</returns>
public override int Read(byte[] buffer, int offset, int count)
{
int bytesToRead = count;
// Need to limit the # of bytes returned, if the stream is intended to have
// a definite length. This is especially useful when returning a stream for
// the uncompressed data directly to the application. The app won't
// necessarily read only the UncompressedSize number of bytes. For example
// wrapping the stream returned from OpenReader() into a StreadReader() and
// calling ReadToEnd() on it, We can "over-read" the zip data and get a
// corrupt string. The length limits that, prevents that problem.
if (_lengthLimit != CrcCalculatorStream.UnsetLengthLimit)
{
if (_Crc32.TotalBytesRead >= _lengthLimit) return 0; // EOF
Int64 bytesRemaining = _lengthLimit - _Crc32.TotalBytesRead;
if (bytesRemaining < count) bytesToRead = (int)bytesRemaining;
}
int n = _innerStream.Read(buffer, offset, bytesToRead);
if (n > 0) _Crc32.SlurpBlock(buffer, offset, n);
return n;
}
/// <summary>
/// Write to the stream.
/// </summary>
/// <param name="buffer">the buffer from which to write</param>
/// <param name="offset">the offset at which to start writing</param>
/// <param name="count">the number of bytes to write</param>
public override void Write(byte[] buffer, int offset, int count)
{
if (count > 0) _Crc32.SlurpBlock(buffer, offset, count);
_innerStream.Write(buffer, offset, count);
}
/// <summary>
/// Indicates whether the stream supports reading.
/// </summary>
public override bool CanRead
{
get { return _innerStream.CanRead; }
}
/// <summary>
/// Indicates whether the stream supports seeking.
/// </summary>
/// <remarks>
/// <para>
/// Always returns false.
/// </para>
/// </remarks>
public override bool CanSeek
{
get { return false; }
}
/// <summary>
/// Indicates whether the stream supports writing.
/// </summary>
public override bool CanWrite
{
get { return _innerStream.CanWrite; }
}
/// <summary>
/// Flush the stream.
/// </summary>
public override void Flush()
{
_innerStream.Flush();
}
/// <summary>
/// Returns the length of the underlying stream.
/// </summary>
public override long Length
{
get
{
if (_lengthLimit == CrcCalculatorStream.UnsetLengthLimit)
return _innerStream.Length;
else return _lengthLimit;
}
}
/// <summary>
/// The getter for this property returns the total bytes read.
/// If you use the setter, it will throw
/// <see cref="NotSupportedException"/>.
/// </summary>
public override long Position
{
get { return _Crc32.TotalBytesRead; }
set { throw new NotSupportedException(); }
}
/// <summary>
/// Seeking is not supported on this stream. This method always throws
/// <see cref="NotSupportedException"/>
/// </summary>
/// <param name="offset">N/A</param>
/// <param name="origin">N/A</param>
/// <returns>N/A</returns>
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotSupportedException();
}
/// <summary>
/// This method always throws
/// <see cref="NotSupportedException"/>
/// </summary>
/// <param name="value">N/A</param>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
void IDisposable.Dispose()
{
Close();
}
/// <summary>
/// Closes the stream.
/// </summary>
public override void Close()
{
base.Close();
if (!_leaveOpen)
_innerStream.Close();
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,436 @@
// Inftree.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-October-28 12:43:54>
//
// ------------------------------------------------------------------
//
// This module defines classes used in decompression. This code is derived
// from the jzlib implementation of zlib. In keeping with the license for jzlib,
// the copyright to that code is below.
//
// ------------------------------------------------------------------
//
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the distribution.
//
// 3. The names of the authors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------
//
// This program is based on zlib-1.1.3; credit to authors
// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
// and contributors of zlib.
//
// -----------------------------------------------------------------------
using System;
namespace Ionic.Zlib
{
sealed class InfTree
{
private const int MANY = 1440;
private const int Z_OK = 0;
private const int Z_STREAM_END = 1;
private const int Z_NEED_DICT = 2;
private const int Z_ERRNO = - 1;
private const int Z_STREAM_ERROR = - 2;
private const int Z_DATA_ERROR = - 3;
private const int Z_MEM_ERROR = - 4;
private const int Z_BUF_ERROR = - 5;
private const int Z_VERSION_ERROR = - 6;
internal const int fixed_bl = 9;
internal const int fixed_bd = 5;
//UPGRADE_NOTE: Final was removed from the declaration of 'fixed_tl'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
internal static readonly int[] fixed_tl = new int[]{96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 192, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 160, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 224, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 144, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 208, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 176, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 240, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 200, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 168, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 232, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 152, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 216, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 184, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 248, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 196, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 164, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 228, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 148, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 212, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 180, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 244, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 204, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 172, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 236, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 156, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 220, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 188, 0, 8, 14, 0, 8, 142, 0, 8, 78, 0, 9, 252, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 194, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 162, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 226, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 146, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 210, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 178, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 242, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 202, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 170, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 234, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 154, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 218, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 186,
0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 250, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 198, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 166, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 230, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 150, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 214, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 182, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 246, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 206, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 174, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 238, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 158, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 222, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 190, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 254, 96, 7, 256, 0, 8, 80, 0, 8, 16, 84, 8, 115, 82, 7, 31, 0, 8, 112, 0, 8, 48, 0, 9, 193, 80, 7, 10, 0, 8, 96, 0, 8, 32, 0, 9, 161, 0, 8, 0, 0, 8, 128, 0, 8, 64, 0, 9, 225, 80, 7, 6, 0, 8, 88, 0, 8, 24, 0, 9, 145, 83, 7, 59, 0, 8, 120, 0, 8, 56, 0, 9, 209, 81, 7, 17, 0, 8, 104, 0, 8, 40, 0, 9, 177, 0, 8, 8, 0, 8, 136, 0, 8, 72, 0, 9, 241, 80, 7, 4, 0, 8, 84, 0, 8, 20, 85, 8, 227, 83, 7, 43, 0, 8, 116, 0, 8, 52, 0, 9, 201, 81, 7, 13, 0, 8, 100, 0, 8, 36, 0, 9, 169, 0, 8, 4, 0, 8, 132, 0, 8, 68, 0, 9, 233, 80, 7, 8, 0, 8, 92, 0, 8, 28, 0, 9, 153, 84, 7, 83, 0, 8, 124, 0, 8, 60, 0, 9, 217, 82, 7, 23, 0, 8, 108, 0, 8, 44, 0, 9, 185, 0, 8, 12, 0, 8, 140, 0, 8, 76, 0, 9, 249, 80, 7, 3, 0, 8, 82, 0, 8, 18, 85, 8, 163, 83, 7, 35, 0, 8, 114, 0, 8, 50, 0, 9, 197, 81, 7, 11, 0, 8, 98, 0, 8, 34, 0, 9, 165, 0, 8, 2, 0, 8, 130, 0, 8, 66, 0, 9, 229, 80, 7, 7, 0, 8, 90, 0, 8, 26, 0, 9, 149, 84, 7, 67, 0, 8, 122, 0, 8, 58, 0, 9, 213, 82, 7, 19, 0, 8, 106, 0, 8, 42, 0, 9, 181, 0, 8, 10, 0, 8, 138, 0, 8, 74, 0, 9, 245, 80, 7, 5, 0, 8, 86, 0, 8, 22, 192, 8, 0, 83, 7, 51, 0, 8, 118, 0, 8, 54, 0, 9, 205, 81, 7, 15, 0, 8, 102, 0, 8, 38, 0, 9, 173, 0, 8, 6, 0, 8, 134, 0, 8, 70, 0, 9, 237, 80, 7, 9, 0, 8, 94, 0, 8, 30, 0, 9, 157, 84, 7, 99, 0, 8, 126, 0, 8, 62, 0, 9, 221, 82, 7, 27, 0, 8, 110, 0, 8, 46, 0, 9, 189, 0, 8,
14, 0, 8, 142, 0, 8, 78, 0, 9, 253, 96, 7, 256, 0, 8, 81, 0, 8, 17, 85, 8, 131, 82, 7, 31, 0, 8, 113, 0, 8, 49, 0, 9, 195, 80, 7, 10, 0, 8, 97, 0, 8, 33, 0, 9, 163, 0, 8, 1, 0, 8, 129, 0, 8, 65, 0, 9, 227, 80, 7, 6, 0, 8, 89, 0, 8, 25, 0, 9, 147, 83, 7, 59, 0, 8, 121, 0, 8, 57, 0, 9, 211, 81, 7, 17, 0, 8, 105, 0, 8, 41, 0, 9, 179, 0, 8, 9, 0, 8, 137, 0, 8, 73, 0, 9, 243, 80, 7, 4, 0, 8, 85, 0, 8, 21, 80, 8, 258, 83, 7, 43, 0, 8, 117, 0, 8, 53, 0, 9, 203, 81, 7, 13, 0, 8, 101, 0, 8, 37, 0, 9, 171, 0, 8, 5, 0, 8, 133, 0, 8, 69, 0, 9, 235, 80, 7, 8, 0, 8, 93, 0, 8, 29, 0, 9, 155, 84, 7, 83, 0, 8, 125, 0, 8, 61, 0, 9, 219, 82, 7, 23, 0, 8, 109, 0, 8, 45, 0, 9, 187, 0, 8, 13, 0, 8, 141, 0, 8, 77, 0, 9, 251, 80, 7, 3, 0, 8, 83, 0, 8, 19, 85, 8, 195, 83, 7, 35, 0, 8, 115, 0, 8, 51, 0, 9, 199, 81, 7, 11, 0, 8, 99, 0, 8, 35, 0, 9, 167, 0, 8, 3, 0, 8, 131, 0, 8, 67, 0, 9, 231, 80, 7, 7, 0, 8, 91, 0, 8, 27, 0, 9, 151, 84, 7, 67, 0, 8, 123, 0, 8, 59, 0, 9, 215, 82, 7, 19, 0, 8, 107, 0, 8, 43, 0, 9, 183, 0, 8, 11, 0, 8, 139, 0, 8, 75, 0, 9, 247, 80, 7, 5, 0, 8, 87, 0, 8, 23, 192, 8, 0, 83, 7, 51, 0, 8, 119, 0, 8, 55, 0, 9, 207, 81, 7, 15, 0, 8, 103, 0, 8, 39, 0, 9, 175, 0, 8, 7, 0, 8, 135, 0, 8, 71, 0, 9, 239, 80, 7, 9, 0, 8, 95, 0, 8, 31, 0, 9, 159, 84, 7, 99, 0, 8, 127, 0, 8, 63, 0, 9, 223, 82, 7, 27, 0, 8, 111, 0, 8, 47, 0, 9, 191, 0, 8, 15, 0, 8, 143, 0, 8, 79, 0, 9, 255};
//UPGRADE_NOTE: Final was removed from the declaration of 'fixed_td'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
internal static readonly int[] fixed_td = new int[]{80, 5, 1, 87, 5, 257, 83, 5, 17, 91, 5, 4097, 81, 5, 5, 89, 5, 1025, 85, 5, 65, 93, 5, 16385, 80, 5, 3, 88, 5, 513, 84, 5, 33, 92, 5, 8193, 82, 5, 9, 90, 5, 2049, 86, 5, 129, 192, 5, 24577, 80, 5, 2, 87, 5, 385, 83, 5, 25, 91, 5, 6145, 81, 5, 7, 89, 5, 1537, 85, 5, 97, 93, 5, 24577, 80, 5, 4, 88, 5, 769, 84, 5, 49, 92, 5, 12289, 82, 5, 13, 90, 5, 3073, 86, 5, 193, 192, 5, 24577};
// Tables for deflate from PKZIP's appnote.txt.
//UPGRADE_NOTE: Final was removed from the declaration of 'cplens'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
internal static readonly int[] cplens = new int[]{3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
// see note #13 above about 258
//UPGRADE_NOTE: Final was removed from the declaration of 'cplext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
internal static readonly int[] cplext = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112};
//UPGRADE_NOTE: Final was removed from the declaration of 'cpdist'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
internal static readonly int[] cpdist = new int[]{1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
//UPGRADE_NOTE: Final was removed from the declaration of 'cpdext'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
internal static readonly int[] cpdext = new int[]{0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
// If BMAX needs to be larger than 16, then h and x[] should be uLong.
internal const int BMAX = 15; // maximum bit length of any code
internal int[] hn = null; // hufts used in space
internal int[] v = null; // work area for huft_build
internal int[] c = null; // bit length count table
internal int[] r = null; // table entry for structure assignment
internal int[] u = null; // table stack
internal int[] x = null; // bit offsets, then code stack
private int huft_build(int[] b, int bindex, int n, int s, int[] d, int[] e, int[] t, int[] m, int[] hp, int[] hn, int[] v)
{
// Given a list of code lengths and a maximum table size, make a set of
// tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR
// if the given code set is incomplete (the tables are still built in this
// case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of
// lengths), or Z_MEM_ERROR if not enough memory.
int a; // counter for codes of length k
int f; // i repeats in table every f entries
int g; // maximum code length
int h; // table level
int i; // counter, current code
int j; // counter
int k; // number of bits in current code
int l; // bits per table (returned in m)
int mask; // (1 << w) - 1, to avoid cc -O bug on HP
int p; // pointer into c[], b[], or v[]
int q; // points to current table
int w; // bits before this table == (l * h)
int xp; // pointer into x
int y; // number of dummy codes added
int z; // number of entries in current table
// Generate counts for each bit length
p = 0; i = n;
do
{
c[b[bindex + p]]++; p++; i--; // assume all entries <= BMAX
}
while (i != 0);
if (c[0] == n)
{
// null input--all zero length codes
t[0] = - 1;
m[0] = 0;
return Z_OK;
}
// Find minimum and maximum length, bound *m by those
l = m[0];
for (j = 1; j <= BMAX; j++)
if (c[j] != 0)
break;
k = j; // minimum code length
if (l < j)
{
l = j;
}
for (i = BMAX; i != 0; i--)
{
if (c[i] != 0)
break;
}
g = i; // maximum code length
if (l > i)
{
l = i;
}
m[0] = l;
// Adjust last length count to fill out codes, if needed
for (y = 1 << j; j < i; j++, y <<= 1)
{
if ((y -= c[j]) < 0)
{
return Z_DATA_ERROR;
}
}
if ((y -= c[i]) < 0)
{
return Z_DATA_ERROR;
}
c[i] += y;
// Generate starting offsets into the value table for each length
x[1] = j = 0;
p = 1; xp = 2;
while (--i != 0)
{
// note that i == g from above
x[xp] = (j += c[p]);
xp++;
p++;
}
// Make a table of values in order of bit lengths
i = 0; p = 0;
do
{
if ((j = b[bindex + p]) != 0)
{
v[x[j]++] = i;
}
p++;
}
while (++i < n);
n = x[g]; // set n to length of v
// Generate the Huffman codes and for each, make the table entries
x[0] = i = 0; // first Huffman code is zero
p = 0; // grab values in bit order
h = - 1; // no tables yet--level -1
w = - l; // bits decoded == (l * h)
u[0] = 0; // just to keep compilers happy
q = 0; // ditto
z = 0; // ditto
// go through the bit lengths (k already is bits in shortest code)
for (; k <= g; k++)
{
a = c[k];
while (a-- != 0)
{
// here i is the Huffman code of length k bits for value *p
// make tables up to required level
while (k > w + l)
{
h++;
w += l; // previous table always l bits
// compute minimum size table less than or equal to l bits
z = g - w;
z = (z > l)?l:z; // table size upper limit
if ((f = 1 << (j = k - w)) > a + 1)
{
// try a k-w bit table
// too few codes for k-w bit table
f -= (a + 1); // deduct codes from patterns left
xp = k;
if (j < z)
{
while (++j < z)
{
// try smaller tables up to z bits
if ((f <<= 1) <= c[++xp])
break; // enough codes to use up j bits
f -= c[xp]; // else deduct codes from patterns
}
}
}
z = 1 << j; // table entries for j-bit table
// allocate new table
if (hn[0] + z > MANY)
{
// (note: doesn't matter for fixed)
return Z_DATA_ERROR; // overflow of MANY
}
u[h] = q = hn[0]; // DEBUG
hn[0] += z;
// connect to last table, if there is one
if (h != 0)
{
x[h] = i; // save pattern for backing up
r[0] = (sbyte) j; // bits in this table
r[1] = (sbyte) l; // bits to dump before this table
j = SharedUtils.URShift(i, (w - l));
r[2] = (int) (q - u[h - 1] - j); // offset to this table
Array.Copy(r, 0, hp, (u[h - 1] + j) * 3, 3); // connect to last table
}
else
{
t[0] = q; // first table is returned result
}
}
// set up table entry in r
r[1] = (sbyte) (k - w);
if (p >= n)
{
r[0] = 128 + 64; // out of values--invalid code
}
else if (v[p] < s)
{
r[0] = (sbyte) (v[p] < 256?0:32 + 64); // 256 is end-of-block
r[2] = v[p++]; // simple code is just the value
}
else
{
r[0] = (sbyte) (e[v[p] - s] + 16 + 64); // non-simple--look up in lists
r[2] = d[v[p++] - s];
}
// fill code-like entries with r
f = 1 << (k - w);
for (j = SharedUtils.URShift(i, w); j < z; j += f)
{
Array.Copy(r, 0, hp, (q + j) * 3, 3);
}
// backwards increment the k-bit code i
for (j = 1 << (k - 1); (i & j) != 0; j = SharedUtils.URShift(j, 1))
{
i ^= j;
}
i ^= j;
// backup over finished tables
mask = (1 << w) - 1; // needed on HP, cc -O bug
while ((i & mask) != x[h])
{
h--; // don't need to update q
w -= l;
mask = (1 << w) - 1;
}
}
}
// Return Z_BUF_ERROR if we were given an incomplete table
return y != 0 && g != 1?Z_BUF_ERROR:Z_OK;
}
internal int inflate_trees_bits(int[] c, int[] bb, int[] tb, int[] hp, ZlibCodec z)
{
int result;
initWorkArea(19);
hn[0] = 0;
result = huft_build(c, 0, 19, 19, null, null, tb, bb, hp, hn, v);
if (result == Z_DATA_ERROR)
{
z.Message = "oversubscribed dynamic bit lengths tree";
}
else if (result == Z_BUF_ERROR || bb[0] == 0)
{
z.Message = "incomplete dynamic bit lengths tree";
result = Z_DATA_ERROR;
}
return result;
}
internal int inflate_trees_dynamic(int nl, int nd, int[] c, int[] bl, int[] bd, int[] tl, int[] td, int[] hp, ZlibCodec z)
{
int result;
// build literal/length tree
initWorkArea(288);
hn[0] = 0;
result = huft_build(c, 0, nl, 257, cplens, cplext, tl, bl, hp, hn, v);
if (result != Z_OK || bl[0] == 0)
{
if (result == Z_DATA_ERROR)
{
z.Message = "oversubscribed literal/length tree";
}
else if (result != Z_MEM_ERROR)
{
z.Message = "incomplete literal/length tree";
result = Z_DATA_ERROR;
}
return result;
}
// build distance tree
initWorkArea(288);
result = huft_build(c, nl, nd, 0, cpdist, cpdext, td, bd, hp, hn, v);
if (result != Z_OK || (bd[0] == 0 && nl > 257))
{
if (result == Z_DATA_ERROR)
{
z.Message = "oversubscribed distance tree";
}
else if (result == Z_BUF_ERROR)
{
z.Message = "incomplete distance tree";
result = Z_DATA_ERROR;
}
else if (result != Z_MEM_ERROR)
{
z.Message = "empty distance tree with lengths";
result = Z_DATA_ERROR;
}
return result;
}
return Z_OK;
}
internal static int inflate_trees_fixed(int[] bl, int[] bd, int[][] tl, int[][] td, ZlibCodec z)
{
bl[0] = fixed_bl;
bd[0] = fixed_bd;
tl[0] = fixed_tl;
td[0] = fixed_td;
return Z_OK;
}
private void initWorkArea(int vsize)
{
if (hn == null)
{
hn = new int[1];
v = new int[vsize];
c = new int[BMAX + 1];
r = new int[3];
u = new int[BMAX];
x = new int[BMAX + 1];
}
else
{
if (v.Length < vsize)
{
v = new int[vsize];
}
Array.Clear(v,0,vsize);
Array.Clear(c,0,BMAX+1);
r[0]=0; r[1]=0; r[2]=0;
// for(int i=0; i<BMAX; i++){u[i]=0;}
//Array.Copy(c, 0, u, 0, BMAX);
Array.Clear(u,0,BMAX);
// for(int i=0; i<BMAX+1; i++){x[i]=0;}
//Array.Copy(c, 0, x, 0, BMAX + 1);
Array.Clear(x,0,BMAX+1);
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,423 @@
// Tree.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-October-28 13:29:50>
//
// ------------------------------------------------------------------
//
// This module defines classes for zlib compression and
// decompression. This code is derived from the jzlib implementation of
// zlib. In keeping with the license for jzlib, the copyright to that
// code is below.
//
// ------------------------------------------------------------------
//
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the distribution.
//
// 3. The names of the authors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------
//
// This program is based on zlib-1.1.3; credit to authors
// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
// and contributors of zlib.
//
// -----------------------------------------------------------------------
using System;
namespace Ionic.Zlib
{
sealed class Tree
{
private static readonly int HEAP_SIZE = (2 * InternalConstants.L_CODES + 1);
// extra bits for each length code
internal static readonly int[] ExtraLengthBits = new int[]
{
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0
};
// extra bits for each distance code
internal static readonly int[] ExtraDistanceBits = new int[]
{
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
};
// extra bits for each bit length code
internal static readonly int[] extra_blbits = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7};
internal static readonly sbyte[] bl_order = new sbyte[]{16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
// The lengths of the bit length codes are sent in order of decreasing
// probability, to avoid transmitting the lengths for unused bit
// length codes.
internal const int Buf_size = 8 * 2;
// see definition of array dist_code below
//internal const int DIST_CODE_LEN = 512;
private static readonly sbyte[] _dist_code = new sbyte[]
{
0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9,
10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
0, 0, 16, 17, 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21,
22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
};
internal static readonly sbyte[] LengthCode = new sbyte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11,
12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17,
18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19,
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28
};
internal static readonly int[] LengthBase = new int[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28,
32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 0
};
internal static readonly int[] DistanceBase = new int[]
{
0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192,
256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576
};
/// <summary>
/// Map from a distance to a distance code.
/// </summary>
/// <remarks>
/// No side effects. _dist_code[256] and _dist_code[257] are never used.
/// </remarks>
internal static int DistanceCode(int dist)
{
return (dist < 256)
? _dist_code[dist]
: _dist_code[256 + SharedUtils.URShift(dist, 7)];
}
internal short[] dyn_tree; // the dynamic tree
internal int max_code; // largest code with non zero frequency
internal StaticTree staticTree; // the corresponding static tree
// Compute the optimal bit lengths for a tree and update the total bit length
// for the current block.
// IN assertion: the fields freq and dad are set, heap[heap_max] and
// above are the tree nodes sorted by increasing frequency.
// OUT assertions: the field len is set to the optimal bit length, the
// array bl_count contains the frequencies for each bit length.
// The length opt_len is updated; static_len is also updated if stree is
// not null.
internal void gen_bitlen(DeflateManager s)
{
short[] tree = dyn_tree;
short[] stree = staticTree.treeCodes;
int[] extra = staticTree.extraBits;
int base_Renamed = staticTree.extraBase;
int max_length = staticTree.maxLength;
int h; // heap index
int n, m; // iterate over the tree elements
int bits; // bit length
int xbits; // extra bits
short f; // frequency
int overflow = 0; // number of elements with bit length too large
for (bits = 0; bits <= InternalConstants.MAX_BITS; bits++)
s.bl_count[bits] = 0;
// In a first pass, compute the optimal bit lengths (which may
// overflow in the case of the bit length tree).
tree[s.heap[s.heap_max] * 2 + 1] = 0; // root of the heap
for (h = s.heap_max + 1; h < HEAP_SIZE; h++)
{
n = s.heap[h];
bits = tree[tree[n * 2 + 1] * 2 + 1] + 1;
if (bits > max_length)
{
bits = max_length; overflow++;
}
tree[n * 2 + 1] = (short) bits;
// We overwrite tree[n*2+1] which is no longer needed
if (n > max_code)
continue; // not a leaf node
s.bl_count[bits]++;
xbits = 0;
if (n >= base_Renamed)
xbits = extra[n - base_Renamed];
f = tree[n * 2];
s.opt_len += f * (bits + xbits);
if (stree != null)
s.static_len += f * (stree[n * 2 + 1] + xbits);
}
if (overflow == 0)
return ;
// This happens for example on obj2 and pic of the Calgary corpus
// Find the first bit length which could increase:
do
{
bits = max_length - 1;
while (s.bl_count[bits] == 0)
bits--;
s.bl_count[bits]--; // move one leaf down the tree
s.bl_count[bits + 1] = (short) (s.bl_count[bits + 1] + 2); // move one overflow item as its brother
s.bl_count[max_length]--;
// The brother of the overflow item also moves one step up,
// but this does not affect bl_count[max_length]
overflow -= 2;
}
while (overflow > 0);
for (bits = max_length; bits != 0; bits--)
{
n = s.bl_count[bits];
while (n != 0)
{
m = s.heap[--h];
if (m > max_code)
continue;
if (tree[m * 2 + 1] != bits)
{
s.opt_len = (int) (s.opt_len + ((long) bits - (long) tree[m * 2 + 1]) * (long) tree[m * 2]);
tree[m * 2 + 1] = (short) bits;
}
n--;
}
}
}
// Construct one Huffman tree and assigns the code bit strings and lengths.
// Update the total bit length for the current block.
// IN assertion: the field freq is set for all tree elements.
// OUT assertions: the fields len and code are set to the optimal bit length
// and corresponding code. The length opt_len is updated; static_len is
// also updated if stree is not null. The field max_code is set.
internal void build_tree(DeflateManager s)
{
short[] tree = dyn_tree;
short[] stree = staticTree.treeCodes;
int elems = staticTree.elems;
int n, m; // iterate over heap elements
int max_code = -1; // largest code with non zero frequency
int node; // new node being created
// Construct the initial heap, with least frequent element in
// heap[1]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
// heap[0] is not used.
s.heap_len = 0;
s.heap_max = HEAP_SIZE;
for (n = 0; n < elems; n++)
{
if (tree[n * 2] != 0)
{
s.heap[++s.heap_len] = max_code = n;
s.depth[n] = 0;
}
else
{
tree[n * 2 + 1] = 0;
}
}
// The pkzip format requires that at least one distance code exists,
// and that at least one bit should be sent even if there is only one
// possible code. So to avoid special checks later on we force at least
// two codes of non zero frequency.
while (s.heap_len < 2)
{
node = s.heap[++s.heap_len] = (max_code < 2?++max_code:0);
tree[node * 2] = 1;
s.depth[node] = 0;
s.opt_len--;
if (stree != null)
s.static_len -= stree[node * 2 + 1];
// node is 0 or 1 so it does not have extra bits
}
this.max_code = max_code;
// The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
// establish sub-heaps of increasing lengths:
for (n = s.heap_len / 2; n >= 1; n--)
s.pqdownheap(tree, n);
// Construct the Huffman tree by repeatedly combining the least two
// frequent nodes.
node = elems; // next internal node of the tree
do
{
// n = node of least frequency
n = s.heap[1];
s.heap[1] = s.heap[s.heap_len--];
s.pqdownheap(tree, 1);
m = s.heap[1]; // m = node of next least frequency
s.heap[--s.heap_max] = n; // keep the nodes sorted by frequency
s.heap[--s.heap_max] = m;
// Create a new node father of n and m
tree[node * 2] = unchecked((short) (tree[n * 2] + tree[m * 2]));
s.depth[node] = (sbyte) (System.Math.Max((byte) s.depth[n], (byte) s.depth[m]) + 1);
tree[n * 2 + 1] = tree[m * 2 + 1] = (short) node;
// and insert the new node in the heap
s.heap[1] = node++;
s.pqdownheap(tree, 1);
}
while (s.heap_len >= 2);
s.heap[--s.heap_max] = s.heap[1];
// At this point, the fields freq and dad are set. We can now
// generate the bit lengths.
gen_bitlen(s);
// The field len is now set, we can generate the bit codes
gen_codes(tree, max_code, s.bl_count);
}
// Generate the codes for a given tree and bit counts (which need not be
// optimal).
// IN assertion: the array bl_count contains the bit length statistics for
// the given tree and the field len is set for all tree elements.
// OUT assertion: the field code is set for all tree elements of non
// zero code length.
internal static void gen_codes(short[] tree, int max_code, short[] bl_count)
{
short[] next_code = new short[InternalConstants.MAX_BITS + 1]; // next code value for each bit length
short code = 0; // running code value
int bits; // bit index
int n; // code index
// The distribution counts are first used to generate the code values
// without bit reversal.
for (bits = 1; bits <= InternalConstants.MAX_BITS; bits++)
unchecked {
next_code[bits] = code = (short) ((code + bl_count[bits - 1]) << 1);
}
// Check that the bit counts in bl_count are consistent. The last code
// must be all ones.
//Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
// "inconsistent bit counts");
//Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
for (n = 0; n <= max_code; n++)
{
int len = tree[n * 2 + 1];
if (len == 0)
continue;
// Now reverse the bits
tree[n * 2] = unchecked((short) (bi_reverse(next_code[len]++, len)));
}
}
// Reverse the first len bits of a code, using straightforward code (a faster
// method would use a table)
// IN assertion: 1 <= len <= 15
internal static int bi_reverse(int code, int len)
{
int res = 0;
do
{
res |= code & 1;
code >>= 1; //SharedUtils.URShift(code, 1);
res <<= 1;
}
while (--len > 0);
return res >> 1;
}
}
}

View file

@ -0,0 +1,546 @@
// Zlib.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009-2011 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// Last Saved: <2011-August-03 19:52:28>
//
// ------------------------------------------------------------------
//
// This module defines classes for ZLIB compression and
// decompression. This code is derived from the jzlib implementation of
// zlib, but significantly modified. The object model is not the same,
// and many of the behaviors are new or different. Nonetheless, in
// keeping with the license for jzlib, the copyright to that code is
// included below.
//
// ------------------------------------------------------------------
//
// The following notice applies to jzlib:
//
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the distribution.
//
// 3. The names of the authors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------
//
// jzlib is based on zlib-1.1.3.
//
// The following notice applies to zlib:
//
// -----------------------------------------------------------------------
//
// Copyright (C) 1995-2004 Jean-loup Gailly and Mark Adler
//
// The ZLIB software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
//
// Jean-loup Gailly jloup@gzip.org
// Mark Adler madler@alumni.caltech.edu
//
// -----------------------------------------------------------------------
using System;
using Interop=System.Runtime.InteropServices;
namespace Ionic.Zlib
{
/// <summary>
/// Describes how to flush the current deflate operation.
/// </summary>
/// <remarks>
/// The different FlushType values are useful when using a Deflate in a streaming application.
/// </remarks>
public enum FlushType
{
/// <summary>No flush at all.</summary>
None = 0,
/// <summary>Closes the current block, but doesn't flush it to
/// the output. Used internally only in hypothetical
/// scenarios. This was supposed to be removed by Zlib, but it is
/// still in use in some edge cases.
/// </summary>
Partial,
/// <summary>
/// Use this during compression to specify that all pending output should be
/// flushed to the output buffer and the output should be aligned on a byte
/// boundary. You might use this in a streaming communication scenario, so that
/// the decompressor can get all input data available so far. When using this
/// with a ZlibCodec, <c>AvailableBytesIn</c> will be zero after the call if
/// enough output space has been provided before the call. Flushing will
/// degrade compression and so it should be used only when necessary.
/// </summary>
Sync,
/// <summary>
/// Use this during compression to specify that all output should be flushed, as
/// with <c>FlushType.Sync</c>, but also, the compression state should be reset
/// so that decompression can restart from this point if previous compressed
/// data has been damaged or if random access is desired. Using
/// <c>FlushType.Full</c> too often can significantly degrade the compression.
/// </summary>
Full,
/// <summary>Signals the end of the compression/decompression stream.</summary>
Finish,
}
/// <summary>
/// The compression level to be used when using a DeflateStream or ZlibStream with CompressionMode.Compress.
/// </summary>
public enum CompressionLevel
{
/// <summary>
/// None means that the data will be simply stored, with no change at all.
/// If you are producing ZIPs for use on Mac OSX, be aware that archives produced with CompressionLevel.None
/// cannot be opened with the default zip reader. Use a different CompressionLevel.
/// </summary>
None= 0,
/// <summary>
/// Same as None.
/// </summary>
Level0 = 0,
/// <summary>
/// The fastest but least effective compression.
/// </summary>
BestSpeed = 1,
/// <summary>
/// A synonym for BestSpeed.
/// </summary>
Level1 = 1,
/// <summary>
/// A little slower, but better, than level 1.
/// </summary>
Level2 = 2,
/// <summary>
/// A little slower, but better, than level 2.
/// </summary>
Level3 = 3,
/// <summary>
/// A little slower, but better, than level 3.
/// </summary>
Level4 = 4,
/// <summary>
/// A little slower than level 4, but with better compression.
/// </summary>
Level5 = 5,
/// <summary>
/// The default compression level, with a good balance of speed and compression efficiency.
/// </summary>
Default = 6,
/// <summary>
/// A synonym for Default.
/// </summary>
Level6 = 6,
/// <summary>
/// Pretty good compression!
/// </summary>
Level7 = 7,
/// <summary>
/// Better compression than Level7!
/// </summary>
Level8 = 8,
/// <summary>
/// The "best" compression, where best means greatest reduction in size of the input data stream.
/// This is also the slowest compression.
/// </summary>
BestCompression = 9,
/// <summary>
/// A synonym for BestCompression.
/// </summary>
Level9 = 9,
}
/// <summary>
/// Describes options for how the compression algorithm is executed. Different strategies
/// work better on different sorts of data. The strategy parameter can affect the compression
/// ratio and the speed of compression but not the correctness of the compresssion.
/// </summary>
public enum CompressionStrategy
{
/// <summary>
/// The default strategy is probably the best for normal data.
/// </summary>
Default = 0,
/// <summary>
/// The <c>Filtered</c> strategy is intended to be used most effectively with data produced by a
/// filter or predictor. By this definition, filtered data consists mostly of small
/// values with a somewhat random distribution. In this case, the compression algorithm
/// is tuned to compress them better. The effect of <c>Filtered</c> is to force more Huffman
/// coding and less string matching; it is a half-step between <c>Default</c> and <c>HuffmanOnly</c>.
/// </summary>
Filtered = 1,
/// <summary>
/// Using <c>HuffmanOnly</c> will force the compressor to do Huffman encoding only, with no
/// string matching.
/// </summary>
HuffmanOnly = 2,
}
/// <summary>
/// An enum to specify the direction of transcoding - whether to compress or decompress.
/// </summary>
public enum CompressionMode
{
/// <summary>
/// Used to specify that the stream should compress the data.
/// </summary>
Compress= 0,
/// <summary>
/// Used to specify that the stream should decompress the data.
/// </summary>
Decompress = 1,
}
/// <summary>
/// A general purpose exception class for exceptions in the Zlib library.
/// </summary>
[Interop.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000E")]
public class ZlibException : System.Exception
{
/// <summary>
/// The ZlibException class captures exception information generated
/// by the Zlib library.
/// </summary>
public ZlibException()
: base()
{
}
/// <summary>
/// This ctor collects a message attached to the exception.
/// </summary>
/// <param name="s">the message for the exception.</param>
public ZlibException(System.String s)
: base(s)
{
}
}
internal class SharedUtils
{
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
public static int URShift(int number, int bits)
{
return (int)((uint)number >> bits);
}
#if NOT
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
public static long URShift(long number, int bits)
{
return (long) ((UInt64)number >> bits);
}
#endif
/// <summary>
/// Reads a number of characters from the current source TextReader and writes
/// the data to the target array at the specified index.
/// </summary>
///
/// <param name="sourceTextReader">The source TextReader to read from</param>
/// <param name="target">Contains the array of characteres read from the source TextReader.</param>
/// <param name="start">The starting index of the target array.</param>
/// <param name="count">The maximum number of characters to read from the source TextReader.</param>
///
/// <returns>
/// The number of characters read. The number will be less than or equal to
/// count depending on the data available in the source TextReader. Returns -1
/// if the end of the stream is reached.
/// </returns>
public static System.Int32 ReadInput(System.IO.TextReader sourceTextReader, byte[] target, int start, int count)
{
// Returns 0 bytes if not enough space in target
if (target.Length == 0) return 0;
char[] charArray = new char[target.Length];
int bytesRead = sourceTextReader.Read(charArray, start, count);
// Returns -1 if EOF
if (bytesRead == 0) return -1;
for (int index = start; index < start + bytesRead; index++)
target[index] = (byte)charArray[index];
return bytesRead;
}
internal static byte[] ToByteArray(System.String sourceString)
{
return System.Text.UTF8Encoding.UTF8.GetBytes(sourceString);
}
internal static char[] ToCharArray(byte[] byteArray)
{
return System.Text.UTF8Encoding.UTF8.GetChars(byteArray);
}
}
internal static class InternalConstants
{
internal static readonly int MAX_BITS = 15;
internal static readonly int BL_CODES = 19;
internal static readonly int D_CODES = 30;
internal static readonly int LITERALS = 256;
internal static readonly int LENGTH_CODES = 29;
internal static readonly int L_CODES = (LITERALS + 1 + LENGTH_CODES);
// Bit length codes must not exceed MAX_BL_BITS bits
internal static readonly int MAX_BL_BITS = 7;
// repeat previous bit length 3-6 times (2 bits of repeat count)
internal static readonly int REP_3_6 = 16;
// repeat a zero length 3-10 times (3 bits of repeat count)
internal static readonly int REPZ_3_10 = 17;
// repeat a zero length 11-138 times (7 bits of repeat count)
internal static readonly int REPZ_11_138 = 18;
}
internal sealed class StaticTree
{
internal static readonly short[] lengthAndLiteralsTreeCodes = new short[] {
12, 8, 140, 8, 76, 8, 204, 8, 44, 8, 172, 8, 108, 8, 236, 8,
28, 8, 156, 8, 92, 8, 220, 8, 60, 8, 188, 8, 124, 8, 252, 8,
2, 8, 130, 8, 66, 8, 194, 8, 34, 8, 162, 8, 98, 8, 226, 8,
18, 8, 146, 8, 82, 8, 210, 8, 50, 8, 178, 8, 114, 8, 242, 8,
10, 8, 138, 8, 74, 8, 202, 8, 42, 8, 170, 8, 106, 8, 234, 8,
26, 8, 154, 8, 90, 8, 218, 8, 58, 8, 186, 8, 122, 8, 250, 8,
6, 8, 134, 8, 70, 8, 198, 8, 38, 8, 166, 8, 102, 8, 230, 8,
22, 8, 150, 8, 86, 8, 214, 8, 54, 8, 182, 8, 118, 8, 246, 8,
14, 8, 142, 8, 78, 8, 206, 8, 46, 8, 174, 8, 110, 8, 238, 8,
30, 8, 158, 8, 94, 8, 222, 8, 62, 8, 190, 8, 126, 8, 254, 8,
1, 8, 129, 8, 65, 8, 193, 8, 33, 8, 161, 8, 97, 8, 225, 8,
17, 8, 145, 8, 81, 8, 209, 8, 49, 8, 177, 8, 113, 8, 241, 8,
9, 8, 137, 8, 73, 8, 201, 8, 41, 8, 169, 8, 105, 8, 233, 8,
25, 8, 153, 8, 89, 8, 217, 8, 57, 8, 185, 8, 121, 8, 249, 8,
5, 8, 133, 8, 69, 8, 197, 8, 37, 8, 165, 8, 101, 8, 229, 8,
21, 8, 149, 8, 85, 8, 213, 8, 53, 8, 181, 8, 117, 8, 245, 8,
13, 8, 141, 8, 77, 8, 205, 8, 45, 8, 173, 8, 109, 8, 237, 8,
29, 8, 157, 8, 93, 8, 221, 8, 61, 8, 189, 8, 125, 8, 253, 8,
19, 9, 275, 9, 147, 9, 403, 9, 83, 9, 339, 9, 211, 9, 467, 9,
51, 9, 307, 9, 179, 9, 435, 9, 115, 9, 371, 9, 243, 9, 499, 9,
11, 9, 267, 9, 139, 9, 395, 9, 75, 9, 331, 9, 203, 9, 459, 9,
43, 9, 299, 9, 171, 9, 427, 9, 107, 9, 363, 9, 235, 9, 491, 9,
27, 9, 283, 9, 155, 9, 411, 9, 91, 9, 347, 9, 219, 9, 475, 9,
59, 9, 315, 9, 187, 9, 443, 9, 123, 9, 379, 9, 251, 9, 507, 9,
7, 9, 263, 9, 135, 9, 391, 9, 71, 9, 327, 9, 199, 9, 455, 9,
39, 9, 295, 9, 167, 9, 423, 9, 103, 9, 359, 9, 231, 9, 487, 9,
23, 9, 279, 9, 151, 9, 407, 9, 87, 9, 343, 9, 215, 9, 471, 9,
55, 9, 311, 9, 183, 9, 439, 9, 119, 9, 375, 9, 247, 9, 503, 9,
15, 9, 271, 9, 143, 9, 399, 9, 79, 9, 335, 9, 207, 9, 463, 9,
47, 9, 303, 9, 175, 9, 431, 9, 111, 9, 367, 9, 239, 9, 495, 9,
31, 9, 287, 9, 159, 9, 415, 9, 95, 9, 351, 9, 223, 9, 479, 9,
63, 9, 319, 9, 191, 9, 447, 9, 127, 9, 383, 9, 255, 9, 511, 9,
0, 7, 64, 7, 32, 7, 96, 7, 16, 7, 80, 7, 48, 7, 112, 7,
8, 7, 72, 7, 40, 7, 104, 7, 24, 7, 88, 7, 56, 7, 120, 7,
4, 7, 68, 7, 36, 7, 100, 7, 20, 7, 84, 7, 52, 7, 116, 7,
3, 8, 131, 8, 67, 8, 195, 8, 35, 8, 163, 8, 99, 8, 227, 8
};
internal static readonly short[] distTreeCodes = new short[] {
0, 5, 16, 5, 8, 5, 24, 5, 4, 5, 20, 5, 12, 5, 28, 5,
2, 5, 18, 5, 10, 5, 26, 5, 6, 5, 22, 5, 14, 5, 30, 5,
1, 5, 17, 5, 9, 5, 25, 5, 5, 5, 21, 5, 13, 5, 29, 5,
3, 5, 19, 5, 11, 5, 27, 5, 7, 5, 23, 5 };
internal static readonly StaticTree Literals;
internal static readonly StaticTree Distances;
internal static readonly StaticTree BitLengths;
internal short[] treeCodes; // static tree or null
internal int[] extraBits; // extra bits for each code or null
internal int extraBase; // base index for extra_bits
internal int elems; // max number of elements in the tree
internal int maxLength; // max bit length for the codes
private StaticTree(short[] treeCodes, int[] extraBits, int extraBase, int elems, int maxLength)
{
this.treeCodes = treeCodes;
this.extraBits = extraBits;
this.extraBase = extraBase;
this.elems = elems;
this.maxLength = maxLength;
}
static StaticTree()
{
Literals = new StaticTree(lengthAndLiteralsTreeCodes, Tree.ExtraLengthBits, InternalConstants.LITERALS + 1, InternalConstants.L_CODES, InternalConstants.MAX_BITS);
Distances = new StaticTree(distTreeCodes, Tree.ExtraDistanceBits, 0, InternalConstants.D_CODES, InternalConstants.MAX_BITS);
BitLengths = new StaticTree(null, Tree.extra_blbits, 0, InternalConstants.BL_CODES, InternalConstants.MAX_BL_BITS);
}
}
/// <summary>
/// Computes an Adler-32 checksum.
/// </summary>
/// <remarks>
/// The Adler checksum is similar to a CRC checksum, but faster to compute, though less
/// reliable. It is used in producing RFC1950 compressed streams. The Adler checksum
/// is a required part of the "ZLIB" standard. Applications will almost never need to
/// use this class directly.
/// </remarks>
///
/// <exclude/>
public sealed class Adler
{
// largest prime smaller than 65536
private static readonly uint BASE = 65521;
// NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
private static readonly int NMAX = 5552;
#pragma warning disable 3001
#pragma warning disable 3002
/// <summary>
/// Calculates the Adler32 checksum.
/// </summary>
/// <remarks>
/// <para>
/// This is used within ZLIB. You probably don't need to use this directly.
/// </para>
/// </remarks>
/// <example>
/// To compute an Adler32 checksum on a byte array:
/// <code>
/// var adler = Adler.Adler32(0, null, 0, 0);
/// adler = Adler.Adler32(adler, buffer, index, length);
/// </code>
/// </example>
public static uint Adler32(uint adler, byte[] buf, int index, int len)
{
if (buf == null)
return 1;
uint s1 = (uint) (adler & 0xffff);
uint s2 = (uint) ((adler >> 16) & 0xffff);
while (len > 0)
{
int k = len < NMAX ? len : NMAX;
len -= k;
while (k >= 16)
{
//s1 += (buf[index++] & 0xff); s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
s1 += buf[index++]; s2 += s1;
k -= 16;
}
if (k != 0)
{
do
{
s1 += buf[index++];
s2 += s1;
}
while (--k != 0);
}
s1 %= BASE;
s2 %= BASE;
}
return (uint)((s2 << 16) | s1);
}
#pragma warning restore 3001
#pragma warning restore 3002
}
}

View file

@ -0,0 +1,627 @@
// ZlibBaseStream.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-August-06 21:22:38>
//
// ------------------------------------------------------------------
//
// This module defines the ZlibBaseStream class, which is an intnernal
// base class for DeflateStream, ZlibStream and GZipStream.
//
// ------------------------------------------------------------------
using System;
using System.IO;
namespace Ionic.Zlib
{
internal enum ZlibStreamFlavor { ZLIB = 1950, DEFLATE = 1951, GZIP = 1952 }
internal class ZlibBaseStream : System.IO.Stream
{
protected internal ZlibCodec _z = null; // deferred init... new ZlibCodec();
protected internal StreamMode _streamMode = StreamMode.Undefined;
protected internal FlushType _flushMode;
protected internal ZlibStreamFlavor _flavor;
protected internal CompressionMode _compressionMode;
protected internal CompressionLevel _level;
protected internal bool _leaveOpen;
protected internal byte[] _workingBuffer;
protected internal int _bufferSize = ZlibConstants.WorkingBufferSizeDefault;
protected internal byte[] _buf1 = new byte[1];
protected internal System.IO.Stream _stream;
protected internal CompressionStrategy Strategy = CompressionStrategy.Default;
// workitem 7159
Ionic.Crc.CRC32 crc;
protected internal string _GzipFileName;
protected internal string _GzipComment;
protected internal DateTime _GzipMtime;
protected internal int _gzipHeaderByteCount;
internal int Crc32 { get { if (crc == null) return 0; return crc.Crc32Result; } }
public ZlibBaseStream(System.IO.Stream stream,
CompressionMode compressionMode,
CompressionLevel level,
ZlibStreamFlavor flavor,
bool leaveOpen)
: base()
{
this._flushMode = FlushType.None;
//this._workingBuffer = new byte[WORKING_BUFFER_SIZE_DEFAULT];
this._stream = stream;
this._leaveOpen = leaveOpen;
this._compressionMode = compressionMode;
this._flavor = flavor;
this._level = level;
// workitem 7159
if (flavor == ZlibStreamFlavor.GZIP)
{
this.crc = new Ionic.Crc.CRC32();
}
}
protected internal bool _wantCompress
{
get
{
return (this._compressionMode == CompressionMode.Compress);
}
}
private ZlibCodec z
{
get
{
if (_z == null)
{
bool wantRfc1950Header = (this._flavor == ZlibStreamFlavor.ZLIB);
_z = new ZlibCodec();
if (this._compressionMode == CompressionMode.Decompress)
{
_z.InitializeInflate(wantRfc1950Header);
}
else
{
_z.Strategy = Strategy;
_z.InitializeDeflate(this._level, wantRfc1950Header);
}
}
return _z;
}
}
private byte[] workingBuffer
{
get
{
if (_workingBuffer == null)
_workingBuffer = new byte[_bufferSize];
return _workingBuffer;
}
}
public override void Write(System.Byte[] buffer, int offset, int count)
{
// workitem 7159
// calculate the CRC on the unccompressed data (before writing)
if (crc != null)
crc.SlurpBlock(buffer, offset, count);
if (_streamMode == StreamMode.Undefined)
_streamMode = StreamMode.Writer;
else if (_streamMode != StreamMode.Writer)
throw new ZlibException("Cannot Write after Reading.");
if (count == 0)
return;
// first reference of z property will initialize the private var _z
z.InputBuffer = buffer;
_z.NextIn = offset;
_z.AvailableBytesIn = count;
bool done = false;
do
{
_z.OutputBuffer = workingBuffer;
_z.NextOut = 0;
_z.AvailableBytesOut = _workingBuffer.Length;
int rc = (_wantCompress)
? _z.Deflate(_flushMode)
: _z.Inflate(_flushMode);
if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
throw new ZlibException((_wantCompress ? "de" : "in") + "flating: " + _z.Message);
//if (_workingBuffer.Length - _z.AvailableBytesOut > 0)
_stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut);
done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0;
// If GZIP and de-compress, we're done when 8 bytes remain.
if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress)
done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0);
}
while (!done);
}
private void finish()
{
if (_z == null) return;
if (_streamMode == StreamMode.Writer)
{
bool done = false;
do
{
_z.OutputBuffer = workingBuffer;
_z.NextOut = 0;
_z.AvailableBytesOut = _workingBuffer.Length;
int rc = (_wantCompress)
? _z.Deflate(FlushType.Finish)
: _z.Inflate(FlushType.Finish);
if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK)
{
string verb = (_wantCompress ? "de" : "in") + "flating";
if (_z.Message == null)
throw new ZlibException(String.Format("{0}: (rc = {1})", verb, rc));
else
throw new ZlibException(verb + ": " + _z.Message);
}
if (_workingBuffer.Length - _z.AvailableBytesOut > 0)
{
_stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut);
}
done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0;
// If GZIP and de-compress, we're done when 8 bytes remain.
if (_flavor == ZlibStreamFlavor.GZIP && !_wantCompress)
done = (_z.AvailableBytesIn == 8 && _z.AvailableBytesOut != 0);
}
while (!done);
Flush();
// workitem 7159
if (_flavor == ZlibStreamFlavor.GZIP)
{
if (_wantCompress)
{
// Emit the GZIP trailer: CRC32 and size mod 2^32
int c1 = crc.Crc32Result;
_stream.Write(BitConverter.GetBytes(c1), 0, 4);
int c2 = (Int32)(crc.TotalBytesRead & 0x00000000FFFFFFFF);
_stream.Write(BitConverter.GetBytes(c2), 0, 4);
}
else
{
throw new ZlibException("Writing with decompression is not supported.");
}
}
}
// workitem 7159
else if (_streamMode == StreamMode.Reader)
{
if (_flavor == ZlibStreamFlavor.GZIP)
{
if (!_wantCompress)
{
// workitem 8501: handle edge case (decompress empty stream)
if (_z.TotalBytesOut == 0L)
return;
// Read and potentially verify the GZIP trailer:
// CRC32 and size mod 2^32
byte[] trailer = new byte[8];
// workitems 8679 & 12554
if (_z.AvailableBytesIn < 8)
{
// Make sure we have read to the end of the stream
Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, _z.AvailableBytesIn);
int bytesNeeded = 8 - _z.AvailableBytesIn;
int bytesRead = _stream.Read(trailer,
_z.AvailableBytesIn,
bytesNeeded);
if (bytesNeeded != bytesRead)
{
throw new ZlibException(String.Format("Missing or incomplete GZIP trailer. Expected 8 bytes, got {0}.",
_z.AvailableBytesIn + bytesRead));
}
}
else
{
Array.Copy(_z.InputBuffer, _z.NextIn, trailer, 0, trailer.Length);
}
Int32 crc32_expected = BitConverter.ToInt32(trailer, 0);
Int32 crc32_actual = crc.Crc32Result;
Int32 isize_expected = BitConverter.ToInt32(trailer, 4);
Int32 isize_actual = (Int32)(_z.TotalBytesOut & 0x00000000FFFFFFFF);
if (crc32_actual != crc32_expected)
throw new ZlibException(String.Format("Bad CRC32 in GZIP trailer. (actual({0:X8})!=expected({1:X8}))", crc32_actual, crc32_expected));
if (isize_actual != isize_expected)
throw new ZlibException(String.Format("Bad size in GZIP trailer. (actual({0})!=expected({1}))", isize_actual, isize_expected));
}
else
{
throw new ZlibException("Reading with compression is not supported.");
}
}
}
}
private void end()
{
if (z == null)
return;
if (_wantCompress)
{
_z.EndDeflate();
}
else
{
_z.EndInflate();
}
_z = null;
}
public override void Close()
{
if (_stream == null) return;
try
{
finish();
}
finally
{
end();
if (!_leaveOpen) _stream.Close();
_stream = null;
}
}
public override void Flush()
{
_stream.Flush();
}
public override System.Int64 Seek(System.Int64 offset, System.IO.SeekOrigin origin)
{
throw new NotImplementedException();
//_outStream.Seek(offset, origin);
}
public override void SetLength(System.Int64 value)
{
_stream.SetLength(value);
}
#if NOT
public int Read()
{
if (Read(_buf1, 0, 1) == 0)
return 0;
// calculate CRC after reading
if (crc!=null)
crc.SlurpBlock(_buf1,0,1);
return (_buf1[0] & 0xFF);
}
#endif
private bool nomoreinput = false;
private string ReadZeroTerminatedString()
{
var list = new System.Collections.Generic.List<byte>();
bool done = false;
do
{
// workitem 7740
int n = _stream.Read(_buf1, 0, 1);
if (n != 1)
throw new ZlibException("Unexpected EOF reading GZIP header.");
else
{
if (_buf1[0] == 0)
done = true;
else
list.Add(_buf1[0]);
}
} while (!done);
byte[] a = list.ToArray();
return GZipStream.iso8859dash1.GetString(a, 0, a.Length);
}
private int _ReadAndValidateGzipHeader()
{
int totalBytesRead = 0;
// read the header on the first read
byte[] header = new byte[10];
int n = _stream.Read(header, 0, header.Length);
// workitem 8501: handle edge case (decompress empty stream)
if (n == 0)
return 0;
if (n != 10)
throw new ZlibException("Not a valid GZIP stream.");
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
throw new ZlibException("Bad GZIP header.");
Int32 timet = BitConverter.ToInt32(header, 4);
_GzipMtime = GZipStream._unixEpoch.AddSeconds(timet);
totalBytesRead += n;
if ((header[3] & 0x04) == 0x04)
{
// read and discard extra field
n = _stream.Read(header, 0, 2); // 2-byte length field
totalBytesRead += n;
Int16 extraLength = (Int16)(header[0] + header[1] * 256);
byte[] extra = new byte[extraLength];
n = _stream.Read(extra, 0, extra.Length);
if (n != extraLength)
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
totalBytesRead += n;
}
if ((header[3] & 0x08) == 0x08)
_GzipFileName = ReadZeroTerminatedString();
if ((header[3] & 0x10) == 0x010)
_GzipComment = ReadZeroTerminatedString();
if ((header[3] & 0x02) == 0x02)
Read(_buf1, 0, 1); // CRC16, ignore
return totalBytesRead;
}
public override System.Int32 Read(System.Byte[] buffer, System.Int32 offset, System.Int32 count)
{
// According to MS documentation, any implementation of the IO.Stream.Read function must:
// (a) throw an exception if offset & count reference an invalid part of the buffer,
// or if count < 0, or if buffer is null
// (b) return 0 only upon EOF, or if count = 0
// (c) if not EOF, then return at least 1 byte, up to <count> bytes
if (_streamMode == StreamMode.Undefined)
{
if (!this._stream.CanRead) throw new ZlibException("The stream is not readable.");
// for the first read, set up some controls.
_streamMode = StreamMode.Reader;
// (The first reference to _z goes through the private accessor which
// may initialize it.)
z.AvailableBytesIn = 0;
if (_flavor == ZlibStreamFlavor.GZIP)
{
_gzipHeaderByteCount = _ReadAndValidateGzipHeader();
// workitem 8501: handle edge case (decompress empty stream)
if (_gzipHeaderByteCount == 0)
return 0;
}
}
if (_streamMode != StreamMode.Reader)
throw new ZlibException("Cannot Read after Writing.");
if (count == 0) return 0;
if (nomoreinput && _wantCompress) return 0; // workitem 8557
if (buffer == null) throw new ArgumentNullException("buffer");
if (count < 0) throw new ArgumentOutOfRangeException("count");
if (offset < buffer.GetLowerBound(0)) throw new ArgumentOutOfRangeException("offset");
if ((offset + count) > buffer.GetLength(0)) throw new ArgumentOutOfRangeException("count");
int rc = 0;
// set up the output of the deflate/inflate codec:
_z.OutputBuffer = buffer;
_z.NextOut = offset;
_z.AvailableBytesOut = count;
// This is necessary in case _workingBuffer has been resized. (new byte[])
// (The first reference to _workingBuffer goes through the private accessor which
// may initialize it.)
_z.InputBuffer = workingBuffer;
do
{
// need data in _workingBuffer in order to deflate/inflate. Here, we check if we have any.
if ((_z.AvailableBytesIn == 0) && (!nomoreinput))
{
// No data available, so try to Read data from the captive stream.
_z.NextIn = 0;
_z.AvailableBytesIn = _stream.Read(_workingBuffer, 0, _workingBuffer.Length);
if (_z.AvailableBytesIn == 0)
nomoreinput = true;
}
// we have data in InputBuffer; now compress or decompress as appropriate
rc = (_wantCompress)
? _z.Deflate(_flushMode)
: _z.Inflate(_flushMode);
if (nomoreinput && (rc == ZlibConstants.Z_BUF_ERROR))
return 0;
if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
throw new ZlibException(String.Format("{0}flating: rc={1} msg={2}", (_wantCompress ? "de" : "in"), rc, _z.Message));
if ((nomoreinput || rc == ZlibConstants.Z_STREAM_END) && (_z.AvailableBytesOut == count))
break; // nothing more to read
}
//while (_z.AvailableBytesOut == count && rc == ZlibConstants.Z_OK);
while (_z.AvailableBytesOut > 0 && !nomoreinput && rc == ZlibConstants.Z_OK);
// workitem 8557
// is there more room in output?
if (_z.AvailableBytesOut > 0)
{
if (rc == ZlibConstants.Z_OK && _z.AvailableBytesIn == 0)
{
// deferred
}
// are we completely done reading?
if (nomoreinput)
{
// and in compression?
if (_wantCompress)
{
// no more input data available; therefore we flush to
// try to complete the read
rc = _z.Deflate(FlushType.Finish);
if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
throw new ZlibException(String.Format("Deflating: rc={0} msg={1}", rc, _z.Message));
}
}
}
rc = (count - _z.AvailableBytesOut);
// calculate CRC after reading
if (crc != null)
crc.SlurpBlock(buffer, offset, rc);
return rc;
}
public override System.Boolean CanRead
{
get { return this._stream.CanRead; }
}
public override System.Boolean CanSeek
{
get { return this._stream.CanSeek; }
}
public override System.Boolean CanWrite
{
get { return this._stream.CanWrite; }
}
public override System.Int64 Length
{
get { return _stream.Length; }
}
public override long Position
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
internal enum StreamMode
{
Writer,
Reader,
Undefined,
}
public static void CompressString(String s, Stream compressor)
{
byte[] uncompressed = System.Text.Encoding.UTF8.GetBytes(s);
using (compressor)
{
compressor.Write(uncompressed, 0, uncompressed.Length);
}
}
public static void CompressBuffer(byte[] b, Stream compressor)
{
// workitem 8460
using (compressor)
{
compressor.Write(b, 0, b.Length);
}
}
public static String UncompressString(byte[] compressed, Stream decompressor)
{
// workitem 8460
byte[] working = new byte[1024];
var encoding = System.Text.Encoding.UTF8;
using (var output = new MemoryStream())
{
using (decompressor)
{
int n;
while ((n = decompressor.Read(working, 0, working.Length)) != 0)
{
output.Write(working, 0, n);
}
}
// reset to allow read from start
output.Seek(0, SeekOrigin.Begin);
var sr = new StreamReader(output, encoding);
return sr.ReadToEnd();
}
}
public static byte[] UncompressBuffer(byte[] compressed, Stream decompressor)
{
// workitem 8460
byte[] working = new byte[1024];
using (var output = new MemoryStream())
{
using (decompressor)
{
int n;
while ((n = decompressor.Read(working, 0, working.Length)) != 0)
{
output.Write(working, 0, n);
}
}
return output.ToArray();
}
}
}
}

View file

@ -0,0 +1,717 @@
// ZlibCodec.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-November-03 15:40:51>
//
// ------------------------------------------------------------------
//
// This module defines a Codec for ZLIB compression and
// decompression. This code extends code that was based the jzlib
// implementation of zlib, but this code is completely novel. The codec
// class is new, and encapsulates some behaviors that are new, and some
// that were present in other classes in the jzlib code base. In
// keeping with the license for jzlib, the copyright to the jzlib code
// is included below.
//
// ------------------------------------------------------------------
//
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the distribution.
//
// 3. The names of the authors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------
//
// This program is based on zlib-1.1.3; credit to authors
// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
// and contributors of zlib.
//
// -----------------------------------------------------------------------
using System;
using Interop=System.Runtime.InteropServices;
namespace Ionic.Zlib
{
/// <summary>
/// Encoder and Decoder for ZLIB and DEFLATE (IETF RFC1950 and RFC1951).
/// </summary>
///
/// <remarks>
/// This class compresses and decompresses data according to the Deflate algorithm
/// and optionally, the ZLIB format, as documented in <see
/// href="http://www.ietf.org/rfc/rfc1950.txt">RFC 1950 - ZLIB</see> and <see
/// href="http://www.ietf.org/rfc/rfc1951.txt">RFC 1951 - DEFLATE</see>.
/// </remarks>
[Interop.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000D")]
[Interop.ComVisible(true)]
#if !NETCF
[Interop.ClassInterface(Interop.ClassInterfaceType.AutoDispatch)]
#endif
sealed public class ZlibCodec
{
/// <summary>
/// The buffer from which data is taken.
/// </summary>
public byte[] InputBuffer;
/// <summary>
/// An index into the InputBuffer array, indicating where to start reading.
/// </summary>
public int NextIn;
/// <summary>
/// The number of bytes available in the InputBuffer, starting at NextIn.
/// </summary>
/// <remarks>
/// Generally you should set this to InputBuffer.Length before the first Inflate() or Deflate() call.
/// The class will update this number as calls to Inflate/Deflate are made.
/// </remarks>
public int AvailableBytesIn;
/// <summary>
/// Total number of bytes read so far, through all calls to Inflate()/Deflate().
/// </summary>
public long TotalBytesIn;
/// <summary>
/// Buffer to store output data.
/// </summary>
public byte[] OutputBuffer;
/// <summary>
/// An index into the OutputBuffer array, indicating where to start writing.
/// </summary>
public int NextOut;
/// <summary>
/// The number of bytes available in the OutputBuffer, starting at NextOut.
/// </summary>
/// <remarks>
/// Generally you should set this to OutputBuffer.Length before the first Inflate() or Deflate() call.
/// The class will update this number as calls to Inflate/Deflate are made.
/// </remarks>
public int AvailableBytesOut;
/// <summary>
/// Total number of bytes written to the output so far, through all calls to Inflate()/Deflate().
/// </summary>
public long TotalBytesOut;
/// <summary>
/// used for diagnostics, when something goes wrong!
/// </summary>
public System.String Message;
internal DeflateManager dstate;
internal InflateManager istate;
internal uint _Adler32;
/// <summary>
/// The compression level to use in this codec. Useful only in compression mode.
/// </summary>
public CompressionLevel CompressLevel = CompressionLevel.Default;
/// <summary>
/// The number of Window Bits to use.
/// </summary>
/// <remarks>
/// This gauges the size of the sliding window, and hence the
/// compression effectiveness as well as memory consumption. It's best to just leave this
/// setting alone if you don't know what it is. The maximum value is 15 bits, which implies
/// a 32k window.
/// </remarks>
public int WindowBits = ZlibConstants.WindowBitsDefault;
/// <summary>
/// The compression strategy to use.
/// </summary>
/// <remarks>
/// This is only effective in compression. The theory offered by ZLIB is that different
/// strategies could potentially produce significant differences in compression behavior
/// for different data sets. Unfortunately I don't have any good recommendations for how
/// to set it differently. When I tested changing the strategy I got minimally different
/// compression performance. It's best to leave this property alone if you don't have a
/// good feel for it. Or, you may want to produce a test harness that runs through the
/// different strategy options and evaluates them on different file types. If you do that,
/// let me know your results.
/// </remarks>
public CompressionStrategy Strategy = CompressionStrategy.Default;
/// <summary>
/// The Adler32 checksum on the data transferred through the codec so far. You probably don't need to look at this.
/// </summary>
public int Adler32 { get { return (int)_Adler32; } }
/// <summary>
/// Create a ZlibCodec.
/// </summary>
/// <remarks>
/// If you use this default constructor, you will later have to explicitly call
/// InitializeInflate() or InitializeDeflate() before using the ZlibCodec to compress
/// or decompress.
/// </remarks>
public ZlibCodec() { }
/// <summary>
/// Create a ZlibCodec that either compresses or decompresses.
/// </summary>
/// <param name="mode">
/// Indicates whether the codec should compress (deflate) or decompress (inflate).
/// </param>
public ZlibCodec(CompressionMode mode)
{
if (mode == CompressionMode.Compress)
{
int rc = InitializeDeflate();
if (rc != ZlibConstants.Z_OK) throw new ZlibException("Cannot initialize for deflate.");
}
else if (mode == CompressionMode.Decompress)
{
int rc = InitializeInflate();
if (rc != ZlibConstants.Z_OK) throw new ZlibException("Cannot initialize for inflate.");
}
else throw new ZlibException("Invalid ZlibStreamFlavor.");
}
/// <summary>
/// Initialize the inflation state.
/// </summary>
/// <remarks>
/// It is not necessary to call this before using the ZlibCodec to inflate data;
/// It is implicitly called when you call the constructor.
/// </remarks>
/// <returns>Z_OK if everything goes well.</returns>
public int InitializeInflate()
{
return InitializeInflate(this.WindowBits);
}
/// <summary>
/// Initialize the inflation state with an explicit flag to
/// govern the handling of RFC1950 header bytes.
/// </summary>
///
/// <remarks>
/// By default, the ZLIB header defined in <see
/// href="http://www.ietf.org/rfc/rfc1950.txt">RFC 1950</see> is expected. If
/// you want to read a zlib stream you should specify true for
/// expectRfc1950Header. If you have a deflate stream, you will want to specify
/// false. It is only necessary to invoke this initializer explicitly if you
/// want to specify false.
/// </remarks>
///
/// <param name="expectRfc1950Header">whether to expect an RFC1950 header byte
/// pair when reading the stream of data to be inflated.</param>
///
/// <returns>Z_OK if everything goes well.</returns>
public int InitializeInflate(bool expectRfc1950Header)
{
return InitializeInflate(this.WindowBits, expectRfc1950Header);
}
/// <summary>
/// Initialize the ZlibCodec for inflation, with the specified number of window bits.
/// </summary>
/// <param name="windowBits">The number of window bits to use. If you need to ask what that is,
/// then you shouldn't be calling this initializer.</param>
/// <returns>Z_OK if all goes well.</returns>
public int InitializeInflate(int windowBits)
{
this.WindowBits = windowBits;
return InitializeInflate(windowBits, true);
}
/// <summary>
/// Initialize the inflation state with an explicit flag to govern the handling of
/// RFC1950 header bytes.
/// </summary>
///
/// <remarks>
/// If you want to read a zlib stream you should specify true for
/// expectRfc1950Header. In this case, the library will expect to find a ZLIB
/// header, as defined in <see href="http://www.ietf.org/rfc/rfc1950.txt">RFC
/// 1950</see>, in the compressed stream. If you will be reading a DEFLATE or
/// GZIP stream, which does not have such a header, you will want to specify
/// false.
/// </remarks>
///
/// <param name="expectRfc1950Header">whether to expect an RFC1950 header byte pair when reading
/// the stream of data to be inflated.</param>
/// <param name="windowBits">The number of window bits to use. If you need to ask what that is,
/// then you shouldn't be calling this initializer.</param>
/// <returns>Z_OK if everything goes well.</returns>
public int InitializeInflate(int windowBits, bool expectRfc1950Header)
{
this.WindowBits = windowBits;
if (dstate != null) throw new ZlibException("You may not call InitializeInflate() after calling InitializeDeflate().");
istate = new InflateManager(expectRfc1950Header);
return istate.Initialize(this, windowBits);
}
/// <summary>
/// Inflate the data in the InputBuffer, placing the result in the OutputBuffer.
/// </summary>
/// <remarks>
/// You must have set InputBuffer and OutputBuffer, NextIn and NextOut, and AvailableBytesIn and
/// AvailableBytesOut before calling this method.
/// </remarks>
/// <example>
/// <code>
/// private void InflateBuffer()
/// {
/// int bufferSize = 1024;
/// byte[] buffer = new byte[bufferSize];
/// ZlibCodec decompressor = new ZlibCodec();
///
/// Console.WriteLine("\n============================================");
/// Console.WriteLine("Size of Buffer to Inflate: {0} bytes.", CompressedBytes.Length);
/// MemoryStream ms = new MemoryStream(DecompressedBytes);
///
/// int rc = decompressor.InitializeInflate();
///
/// decompressor.InputBuffer = CompressedBytes;
/// decompressor.NextIn = 0;
/// decompressor.AvailableBytesIn = CompressedBytes.Length;
///
/// decompressor.OutputBuffer = buffer;
///
/// // pass 1: inflate
/// do
/// {
/// decompressor.NextOut = 0;
/// decompressor.AvailableBytesOut = buffer.Length;
/// rc = decompressor.Inflate(FlushType.None);
///
/// if (rc != ZlibConstants.Z_OK &amp;&amp; rc != ZlibConstants.Z_STREAM_END)
/// throw new Exception("inflating: " + decompressor.Message);
///
/// ms.Write(decompressor.OutputBuffer, 0, buffer.Length - decompressor.AvailableBytesOut);
/// }
/// while (decompressor.AvailableBytesIn &gt; 0 || decompressor.AvailableBytesOut == 0);
///
/// // pass 2: finish and flush
/// do
/// {
/// decompressor.NextOut = 0;
/// decompressor.AvailableBytesOut = buffer.Length;
/// rc = decompressor.Inflate(FlushType.Finish);
///
/// if (rc != ZlibConstants.Z_STREAM_END &amp;&amp; rc != ZlibConstants.Z_OK)
/// throw new Exception("inflating: " + decompressor.Message);
///
/// if (buffer.Length - decompressor.AvailableBytesOut &gt; 0)
/// ms.Write(buffer, 0, buffer.Length - decompressor.AvailableBytesOut);
/// }
/// while (decompressor.AvailableBytesIn &gt; 0 || decompressor.AvailableBytesOut == 0);
///
/// decompressor.EndInflate();
/// }
///
/// </code>
/// </example>
/// <param name="flush">The flush to use when inflating.</param>
/// <returns>Z_OK if everything goes well.</returns>
public int Inflate(FlushType flush)
{
if (istate == null)
throw new ZlibException("No Inflate State!");
return istate.Inflate(flush);
}
/// <summary>
/// Ends an inflation session.
/// </summary>
/// <remarks>
/// Call this after successively calling Inflate(). This will cause all buffers to be flushed.
/// After calling this you cannot call Inflate() without a intervening call to one of the
/// InitializeInflate() overloads.
/// </remarks>
/// <returns>Z_OK if everything goes well.</returns>
public int EndInflate()
{
if (istate == null)
throw new ZlibException("No Inflate State!");
int ret = istate.End();
istate = null;
return ret;
}
/// <summary>
/// I don't know what this does!
/// </summary>
/// <returns>Z_OK if everything goes well.</returns>
public int SyncInflate()
{
if (istate == null)
throw new ZlibException("No Inflate State!");
return istate.Sync();
}
/// <summary>
/// Initialize the ZlibCodec for deflation operation.
/// </summary>
/// <remarks>
/// The codec will use the MAX window bits and the default level of compression.
/// </remarks>
/// <example>
/// <code>
/// int bufferSize = 40000;
/// byte[] CompressedBytes = new byte[bufferSize];
/// byte[] DecompressedBytes = new byte[bufferSize];
///
/// ZlibCodec compressor = new ZlibCodec();
///
/// compressor.InitializeDeflate(CompressionLevel.Default);
///
/// compressor.InputBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(TextToCompress);
/// compressor.NextIn = 0;
/// compressor.AvailableBytesIn = compressor.InputBuffer.Length;
///
/// compressor.OutputBuffer = CompressedBytes;
/// compressor.NextOut = 0;
/// compressor.AvailableBytesOut = CompressedBytes.Length;
///
/// while (compressor.TotalBytesIn != TextToCompress.Length &amp;&amp; compressor.TotalBytesOut &lt; bufferSize)
/// {
/// compressor.Deflate(FlushType.None);
/// }
///
/// while (true)
/// {
/// int rc= compressor.Deflate(FlushType.Finish);
/// if (rc == ZlibConstants.Z_STREAM_END) break;
/// }
///
/// compressor.EndDeflate();
///
/// </code>
/// </example>
/// <returns>Z_OK if all goes well. You generally don't need to check the return code.</returns>
public int InitializeDeflate()
{
return _InternalInitializeDeflate(true);
}
/// <summary>
/// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel.
/// </summary>
/// <remarks>
/// The codec will use the maximum window bits (15) and the specified
/// CompressionLevel. It will emit a ZLIB stream as it compresses.
/// </remarks>
/// <param name="level">The compression level for the codec.</param>
/// <returns>Z_OK if all goes well.</returns>
public int InitializeDeflate(CompressionLevel level)
{
this.CompressLevel = level;
return _InternalInitializeDeflate(true);
}
/// <summary>
/// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel,
/// and the explicit flag governing whether to emit an RFC1950 header byte pair.
/// </summary>
/// <remarks>
/// The codec will use the maximum window bits (15) and the specified CompressionLevel.
/// If you want to generate a zlib stream, you should specify true for
/// wantRfc1950Header. In this case, the library will emit a ZLIB
/// header, as defined in <see href="http://www.ietf.org/rfc/rfc1950.txt">RFC
/// 1950</see>, in the compressed stream.
/// </remarks>
/// <param name="level">The compression level for the codec.</param>
/// <param name="wantRfc1950Header">whether to emit an initial RFC1950 byte pair in the compressed stream.</param>
/// <returns>Z_OK if all goes well.</returns>
public int InitializeDeflate(CompressionLevel level, bool wantRfc1950Header)
{
this.CompressLevel = level;
return _InternalInitializeDeflate(wantRfc1950Header);
}
/// <summary>
/// Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel,
/// and the specified number of window bits.
/// </summary>
/// <remarks>
/// The codec will use the specified number of window bits and the specified CompressionLevel.
/// </remarks>
/// <param name="level">The compression level for the codec.</param>
/// <param name="bits">the number of window bits to use. If you don't know what this means, don't use this method.</param>
/// <returns>Z_OK if all goes well.</returns>
public int InitializeDeflate(CompressionLevel level, int bits)
{
this.CompressLevel = level;
this.WindowBits = bits;
return _InternalInitializeDeflate(true);
}
/// <summary>
/// Initialize the ZlibCodec for deflation operation, using the specified
/// CompressionLevel, the specified number of window bits, and the explicit flag
/// governing whether to emit an RFC1950 header byte pair.
/// </summary>
///
/// <param name="level">The compression level for the codec.</param>
/// <param name="wantRfc1950Header">whether to emit an initial RFC1950 byte pair in the compressed stream.</param>
/// <param name="bits">the number of window bits to use. If you don't know what this means, don't use this method.</param>
/// <returns>Z_OK if all goes well.</returns>
public int InitializeDeflate(CompressionLevel level, int bits, bool wantRfc1950Header)
{
this.CompressLevel = level;
this.WindowBits = bits;
return _InternalInitializeDeflate(wantRfc1950Header);
}
private int _InternalInitializeDeflate(bool wantRfc1950Header)
{
if (istate != null) throw new ZlibException("You may not call InitializeDeflate() after calling InitializeInflate().");
dstate = new DeflateManager();
dstate.WantRfc1950HeaderBytes = wantRfc1950Header;
return dstate.Initialize(this, this.CompressLevel, this.WindowBits, this.Strategy);
}
/// <summary>
/// Deflate one batch of data.
/// </summary>
/// <remarks>
/// You must have set InputBuffer and OutputBuffer before calling this method.
/// </remarks>
/// <example>
/// <code>
/// private void DeflateBuffer(CompressionLevel level)
/// {
/// int bufferSize = 1024;
/// byte[] buffer = new byte[bufferSize];
/// ZlibCodec compressor = new ZlibCodec();
///
/// Console.WriteLine("\n============================================");
/// Console.WriteLine("Size of Buffer to Deflate: {0} bytes.", UncompressedBytes.Length);
/// MemoryStream ms = new MemoryStream();
///
/// int rc = compressor.InitializeDeflate(level);
///
/// compressor.InputBuffer = UncompressedBytes;
/// compressor.NextIn = 0;
/// compressor.AvailableBytesIn = UncompressedBytes.Length;
///
/// compressor.OutputBuffer = buffer;
///
/// // pass 1: deflate
/// do
/// {
/// compressor.NextOut = 0;
/// compressor.AvailableBytesOut = buffer.Length;
/// rc = compressor.Deflate(FlushType.None);
///
/// if (rc != ZlibConstants.Z_OK &amp;&amp; rc != ZlibConstants.Z_STREAM_END)
/// throw new Exception("deflating: " + compressor.Message);
///
/// ms.Write(compressor.OutputBuffer, 0, buffer.Length - compressor.AvailableBytesOut);
/// }
/// while (compressor.AvailableBytesIn &gt; 0 || compressor.AvailableBytesOut == 0);
///
/// // pass 2: finish and flush
/// do
/// {
/// compressor.NextOut = 0;
/// compressor.AvailableBytesOut = buffer.Length;
/// rc = compressor.Deflate(FlushType.Finish);
///
/// if (rc != ZlibConstants.Z_STREAM_END &amp;&amp; rc != ZlibConstants.Z_OK)
/// throw new Exception("deflating: " + compressor.Message);
///
/// if (buffer.Length - compressor.AvailableBytesOut &gt; 0)
/// ms.Write(buffer, 0, buffer.Length - compressor.AvailableBytesOut);
/// }
/// while (compressor.AvailableBytesIn &gt; 0 || compressor.AvailableBytesOut == 0);
///
/// compressor.EndDeflate();
///
/// ms.Seek(0, SeekOrigin.Begin);
/// CompressedBytes = new byte[compressor.TotalBytesOut];
/// ms.Read(CompressedBytes, 0, CompressedBytes.Length);
/// }
/// </code>
/// </example>
/// <param name="flush">whether to flush all data as you deflate. Generally you will want to
/// use Z_NO_FLUSH here, in a series of calls to Deflate(), and then call EndDeflate() to
/// flush everything.
/// </param>
/// <returns>Z_OK if all goes well.</returns>
public int Deflate(FlushType flush)
{
if (dstate == null)
throw new ZlibException("No Deflate State!");
return dstate.Deflate(flush);
}
/// <summary>
/// End a deflation session.
/// </summary>
/// <remarks>
/// Call this after making a series of one or more calls to Deflate(). All buffers are flushed.
/// </remarks>
/// <returns>Z_OK if all goes well.</returns>
public int EndDeflate()
{
if (dstate == null)
throw new ZlibException("No Deflate State!");
//dinoch Tue, 03 Nov 2009 15:39 (test this)
//int ret = dstate.End();
dstate = null;
return ZlibConstants.Z_OK; //ret;
}
/// <summary>
/// Reset a codec for another deflation session.
/// </summary>
/// <remarks>
/// Call this to reset the deflation state. For example if a thread is deflating
/// non-consecutive blocks, you can call Reset() after the Deflate(Sync) of the first
/// block and before the next Deflate(None) of the second block.
/// </remarks>
/// <returns>Z_OK if all goes well.</returns>
public void ResetDeflate()
{
if (dstate == null)
throw new ZlibException("No Deflate State!");
dstate.Reset();
}
/// <summary>
/// Set the CompressionStrategy and CompressionLevel for a deflation session.
/// </summary>
/// <param name="level">the level of compression to use.</param>
/// <param name="strategy">the strategy to use for compression.</param>
/// <returns>Z_OK if all goes well.</returns>
public int SetDeflateParams(CompressionLevel level, CompressionStrategy strategy)
{
if (dstate == null)
throw new ZlibException("No Deflate State!");
return dstate.SetParams(level, strategy);
}
/// <summary>
/// Set the dictionary to be used for either Inflation or Deflation.
/// </summary>
/// <param name="dictionary">The dictionary bytes to use.</param>
/// <returns>Z_OK if all goes well.</returns>
public int SetDictionary(byte[] dictionary)
{
if (istate != null)
return istate.SetDictionary(dictionary);
if (dstate != null)
return dstate.SetDictionary(dictionary);
throw new ZlibException("No Inflate or Deflate state!");
}
// Flush as much pending output as possible. All deflate() output goes
// through this function so some applications may wish to modify it
// to avoid allocating a large strm->next_out buffer and copying into it.
// (See also read_buf()).
internal void flush_pending()
{
int len = dstate.pendingCount;
if (len > AvailableBytesOut)
len = AvailableBytesOut;
if (len == 0)
return;
if (dstate.pending.Length <= dstate.nextPending ||
OutputBuffer.Length <= NextOut ||
dstate.pending.Length < (dstate.nextPending + len) ||
OutputBuffer.Length < (NextOut + len))
{
throw new ZlibException(String.Format("Invalid State. (pending.Length={0}, pendingCount={1})",
dstate.pending.Length, dstate.pendingCount));
}
Array.Copy(dstate.pending, dstate.nextPending, OutputBuffer, NextOut, len);
NextOut += len;
dstate.nextPending += len;
TotalBytesOut += len;
AvailableBytesOut -= len;
dstate.pendingCount -= len;
if (dstate.pendingCount == 0)
{
dstate.nextPending = 0;
}
}
// Read a new buffer from the current input stream, update the adler32
// and total number of bytes read. All deflate() input goes through
// this function so some applications may wish to modify it to avoid
// allocating a large strm->next_in buffer and copying from it.
// (See also flush_pending()).
internal int read_buf(byte[] buf, int start, int size)
{
int len = AvailableBytesIn;
if (len > size)
len = size;
if (len == 0)
return 0;
AvailableBytesIn -= len;
if (dstate.WantRfc1950HeaderBytes)
{
_Adler32 = Adler.Adler32(_Adler32, InputBuffer, NextIn, len);
}
Array.Copy(InputBuffer, NextIn, buf, start, len);
NextIn += len;
TotalBytesIn += len;
return len;
}
}
}

View file

@ -0,0 +1,127 @@
// ZlibConstants.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-November-03 18:50:19>
//
// ------------------------------------------------------------------
//
// This module defines constants used by the zlib class library. This
// code is derived from the jzlib implementation of zlib, but
// significantly modified. In keeping with the license for jzlib, the
// copyright to that code is included here.
//
// ------------------------------------------------------------------
//
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the distribution.
//
// 3. The names of the authors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------
//
// This program is based on zlib-1.1.3; credit to authors
// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
// and contributors of zlib.
//
// -----------------------------------------------------------------------
using System;
namespace Ionic.Zlib
{
/// <summary>
/// A bunch of constants used in the Zlib interface.
/// </summary>
public static class ZlibConstants
{
/// <summary>
/// The maximum number of window bits for the Deflate algorithm.
/// </summary>
public const int WindowBitsMax = 15; // 32K LZ77 window
/// <summary>
/// The default number of window bits for the Deflate algorithm.
/// </summary>
public const int WindowBitsDefault = WindowBitsMax;
/// <summary>
/// indicates everything is A-OK
/// </summary>
public const int Z_OK = 0;
/// <summary>
/// Indicates that the last operation reached the end of the stream.
/// </summary>
public const int Z_STREAM_END = 1;
/// <summary>
/// The operation ended in need of a dictionary.
/// </summary>
public const int Z_NEED_DICT = 2;
/// <summary>
/// There was an error with the stream - not enough data, not open and readable, etc.
/// </summary>
public const int Z_STREAM_ERROR = -2;
/// <summary>
/// There was an error with the data - not enough data, bad data, etc.
/// </summary>
public const int Z_DATA_ERROR = -3;
/// <summary>
/// There was an error with the working buffer.
/// </summary>
public const int Z_BUF_ERROR = -5;
/// <summary>
/// The size of the working buffer used in the ZlibCodec class. Defaults to 8192 bytes.
/// </summary>
#if NETCF
public const int WorkingBufferSizeDefault = 8192;
#else
public const int WorkingBufferSizeDefault = 16384;
#endif
/// <summary>
/// The minimum size of the working buffer used in the ZlibCodec class. Currently it is 128 bytes.
/// </summary>
public const int WorkingBufferSizeMin = 1024;
}
}

View file

@ -0,0 +1,725 @@
// ZlibStream.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-31 14:53:33>
//
// ------------------------------------------------------------------
//
// This module defines the ZlibStream class, which is similar in idea to
// the System.IO.Compression.DeflateStream and
// System.IO.Compression.GZipStream classes in the .NET BCL.
//
// ------------------------------------------------------------------
using System;
using System.IO;
namespace Ionic.Zlib
{
/// <summary>
/// Represents a Zlib stream for compression or decompression.
/// </summary>
/// <remarks>
///
/// <para>
/// The ZlibStream is a <see
/// href="http://en.wikipedia.org/wiki/Decorator_pattern">Decorator</see> on a <see
/// cref="System.IO.Stream"/>. It adds ZLIB compression or decompression to any
/// stream.
/// </para>
///
/// <para> Using this stream, applications can compress or decompress data via
/// stream <c>Read()</c> and <c>Write()</c> operations. Either compresssion or
/// decompression can occur through either reading or writing. The compression
/// format used is ZLIB, which is documented in <see
/// href="http://www.ietf.org/rfc/rfc1950.txt">IETF RFC 1950</see>, "ZLIB Compressed
/// Data Format Specification version 3.3". This implementation of ZLIB always uses
/// DEFLATE as the compression method. (see <see
/// href="http://www.ietf.org/rfc/rfc1951.txt">IETF RFC 1951</see>, "DEFLATE
/// Compressed Data Format Specification version 1.3.") </para>
///
/// <para>
/// The ZLIB format allows for varying compression methods, window sizes, and dictionaries.
/// This implementation always uses the DEFLATE compression method, a preset dictionary,
/// and 15 window bits by default.
/// </para>
///
/// <para>
/// This class is similar to <see cref="DeflateStream"/>, except that it adds the
/// RFC1950 header and trailer bytes to a compressed stream when compressing, or expects
/// the RFC1950 header and trailer bytes when decompressing. It is also similar to the
/// <see cref="GZipStream"/>.
/// </para>
/// </remarks>
/// <seealso cref="DeflateStream" />
/// <seealso cref="GZipStream" />
public class ZlibStream : System.IO.Stream
{
internal ZlibBaseStream _baseStream;
bool _disposed;
/// <summary>
/// Create a <c>ZlibStream</c> using the specified <c>CompressionMode</c>.
/// </summary>
/// <remarks>
///
/// <para>
/// When mode is <c>CompressionMode.Compress</c>, the <c>ZlibStream</c>
/// will use the default compression level. The "captive" stream will be
/// closed when the <c>ZlibStream</c> is closed.
/// </para>
///
/// </remarks>
///
/// <example>
/// This example uses a <c>ZlibStream</c> to compress a file, and writes the
/// compressed data to another file.
/// <code>
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
/// {
/// using (var raw = System.IO.File.Create(fileToCompress + ".zlib"))
/// {
/// using (Stream compressor = new ZlibStream(raw, CompressionMode.Compress))
/// {
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
/// int n;
/// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
/// {
/// compressor.Write(buffer, 0, n);
/// }
/// }
/// }
/// }
/// </code>
/// <code lang="VB">
/// Using input As Stream = File.OpenRead(fileToCompress)
/// Using raw As FileStream = File.Create(fileToCompress &amp; ".zlib")
/// Using compressor As Stream = New ZlibStream(raw, CompressionMode.Compress)
/// Dim buffer As Byte() = New Byte(4096) {}
/// Dim n As Integer = -1
/// Do While (n &lt;&gt; 0)
/// If (n &gt; 0) Then
/// compressor.Write(buffer, 0, n)
/// End If
/// n = input.Read(buffer, 0, buffer.Length)
/// Loop
/// End Using
/// End Using
/// End Using
/// </code>
/// </example>
///
/// <param name="stream">The stream which will be read or written.</param>
/// <param name="mode">Indicates whether the ZlibStream will compress or decompress.</param>
public ZlibStream(System.IO.Stream stream, CompressionMode mode)
: this(stream, mode, CompressionLevel.Default, false)
{
}
/// <summary>
/// Create a <c>ZlibStream</c> using the specified <c>CompressionMode</c> and
/// the specified <c>CompressionLevel</c>.
/// </summary>
///
/// <remarks>
///
/// <para>
/// When mode is <c>CompressionMode.Decompress</c>, the level parameter is ignored.
/// The "captive" stream will be closed when the <c>ZlibStream</c> is closed.
/// </para>
///
/// </remarks>
///
/// <example>
/// This example uses a <c>ZlibStream</c> to compress data from a file, and writes the
/// compressed data to another file.
///
/// <code>
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
/// {
/// using (var raw = System.IO.File.Create(fileToCompress + ".zlib"))
/// {
/// using (Stream compressor = new ZlibStream(raw,
/// CompressionMode.Compress,
/// CompressionLevel.BestCompression))
/// {
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
/// int n;
/// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
/// {
/// compressor.Write(buffer, 0, n);
/// }
/// }
/// }
/// }
/// </code>
///
/// <code lang="VB">
/// Using input As Stream = File.OpenRead(fileToCompress)
/// Using raw As FileStream = File.Create(fileToCompress &amp; ".zlib")
/// Using compressor As Stream = New ZlibStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression)
/// Dim buffer As Byte() = New Byte(4096) {}
/// Dim n As Integer = -1
/// Do While (n &lt;&gt; 0)
/// If (n &gt; 0) Then
/// compressor.Write(buffer, 0, n)
/// End If
/// n = input.Read(buffer, 0, buffer.Length)
/// Loop
/// End Using
/// End Using
/// End Using
/// </code>
/// </example>
///
/// <param name="stream">The stream to be read or written while deflating or inflating.</param>
/// <param name="mode">Indicates whether the ZlibStream will compress or decompress.</param>
/// <param name="level">A tuning knob to trade speed for effectiveness.</param>
public ZlibStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level)
: this(stream, mode, level, false)
{
}
/// <summary>
/// Create a <c>ZlibStream</c> using the specified <c>CompressionMode</c>, and
/// explicitly specify whether the captive stream should be left open after
/// Deflation or Inflation.
/// </summary>
///
/// <remarks>
///
/// <para>
/// When mode is <c>CompressionMode.Compress</c>, the <c>ZlibStream</c> will use
/// the default compression level.
/// </para>
///
/// <para>
/// This constructor allows the application to request that the captive stream
/// remain open after the deflation or inflation occurs. By default, after
/// <c>Close()</c> is called on the stream, the captive stream is also
/// closed. In some cases this is not desired, for example if the stream is a
/// <see cref="System.IO.MemoryStream"/> that will be re-read after
/// compression. Specify true for the <paramref name="leaveOpen"/> parameter to leave the stream
/// open.
/// </para>
///
/// <para>
/// See the other overloads of this constructor for example code.
/// </para>
///
/// </remarks>
///
/// <param name="stream">The stream which will be read or written. This is called the
/// "captive" stream in other places in this documentation.</param>
/// <param name="mode">Indicates whether the ZlibStream will compress or decompress.</param>
/// <param name="leaveOpen">true if the application would like the stream to remain
/// open after inflation/deflation.</param>
public ZlibStream(System.IO.Stream stream, CompressionMode mode, bool leaveOpen)
: this(stream, mode, CompressionLevel.Default, leaveOpen)
{
}
/// <summary>
/// Create a <c>ZlibStream</c> using the specified <c>CompressionMode</c>
/// and the specified <c>CompressionLevel</c>, and explicitly specify
/// whether the stream should be left open after Deflation or Inflation.
/// </summary>
///
/// <remarks>
///
/// <para>
/// This constructor allows the application to request that the captive
/// stream remain open after the deflation or inflation occurs. By
/// default, after <c>Close()</c> is called on the stream, the captive
/// stream is also closed. In some cases this is not desired, for example
/// if the stream is a <see cref="System.IO.MemoryStream"/> that will be
/// re-read after compression. Specify true for the <paramref
/// name="leaveOpen"/> parameter to leave the stream open.
/// </para>
///
/// <para>
/// When mode is <c>CompressionMode.Decompress</c>, the level parameter is
/// ignored.
/// </para>
///
/// </remarks>
///
/// <example>
///
/// This example shows how to use a ZlibStream to compress the data from a file,
/// and store the result into another file. The filestream remains open to allow
/// additional data to be written to it.
///
/// <code>
/// using (var output = System.IO.File.Create(fileToCompress + ".zlib"))
/// {
/// using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress))
/// {
/// using (Stream compressor = new ZlibStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, true))
/// {
/// byte[] buffer = new byte[WORKING_BUFFER_SIZE];
/// int n;
/// while ((n= input.Read(buffer, 0, buffer.Length)) != 0)
/// {
/// compressor.Write(buffer, 0, n);
/// }
/// }
/// }
/// // can write additional data to the output stream here
/// }
/// </code>
/// <code lang="VB">
/// Using output As FileStream = File.Create(fileToCompress &amp; ".zlib")
/// Using input As Stream = File.OpenRead(fileToCompress)
/// Using compressor As Stream = New ZlibStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, True)
/// Dim buffer As Byte() = New Byte(4096) {}
/// Dim n As Integer = -1
/// Do While (n &lt;&gt; 0)
/// If (n &gt; 0) Then
/// compressor.Write(buffer, 0, n)
/// End If
/// n = input.Read(buffer, 0, buffer.Length)
/// Loop
/// End Using
/// End Using
/// ' can write additional data to the output stream here.
/// End Using
/// </code>
/// </example>
///
/// <param name="stream">The stream which will be read or written.</param>
///
/// <param name="mode">Indicates whether the ZlibStream will compress or decompress.</param>
///
/// <param name="leaveOpen">
/// true if the application would like the stream to remain open after
/// inflation/deflation.
/// </param>
///
/// <param name="level">
/// A tuning knob to trade speed for effectiveness. This parameter is
/// effective only when mode is <c>CompressionMode.Compress</c>.
/// </param>
public ZlibStream(System.IO.Stream stream, CompressionMode mode, CompressionLevel level, bool leaveOpen)
{
_baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.ZLIB, leaveOpen);
}
#region Zlib properties
/// <summary>
/// This property sets the flush behavior on the stream.
/// Sorry, though, not sure exactly how to describe all the various settings.
/// </summary>
virtual public FlushType FlushMode
{
get { return (this._baseStream._flushMode); }
set
{
if (_disposed) throw new ObjectDisposedException("ZlibStream");
this._baseStream._flushMode = value;
}
}
/// <summary>
/// The size of the working buffer for the compression codec.
/// </summary>
///
/// <remarks>
/// <para>
/// The working buffer is used for all stream operations. The default size is
/// 1024 bytes. The minimum size is 128 bytes. You may get better performance
/// with a larger buffer. Then again, you might not. You would have to test
/// it.
/// </para>
///
/// <para>
/// Set this before the first call to <c>Read()</c> or <c>Write()</c> on the
/// stream. If you try to set it afterwards, it will throw.
/// </para>
/// </remarks>
public int BufferSize
{
get
{
return this._baseStream._bufferSize;
}
set
{
if (_disposed) throw new ObjectDisposedException("ZlibStream");
if (this._baseStream._workingBuffer != null)
throw new ZlibException("The working buffer is already set.");
if (value < ZlibConstants.WorkingBufferSizeMin)
throw new ZlibException(String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value, ZlibConstants.WorkingBufferSizeMin));
this._baseStream._bufferSize = value;
}
}
/// <summary> Returns the total number of bytes input so far.</summary>
virtual public long TotalIn
{
get { return this._baseStream._z.TotalBytesIn; }
}
/// <summary> Returns the total number of bytes output so far.</summary>
virtual public long TotalOut
{
get { return this._baseStream._z.TotalBytesOut; }
}
#endregion
#region System.IO.Stream methods
/// <summary>
/// Dispose the stream.
/// </summary>
/// <remarks>
/// <para>
/// This may or may not result in a <c>Close()</c> call on the captive
/// stream. See the constructors that have a <c>leaveOpen</c> parameter
/// for more information.
/// </para>
/// <para>
/// This method may be invoked in two distinct scenarios. If disposing
/// == true, the method has been called directly or indirectly by a
/// user's code, for example via the public Dispose() method. In this
/// case, both managed and unmanaged resources can be referenced and
/// disposed. If disposing == false, the method has been called by the
/// runtime from inside the object finalizer and this method should not
/// reference other objects; in that case only unmanaged resources must
/// be referenced or disposed.
/// </para>
/// </remarks>
/// <param name="disposing">
/// indicates whether the Dispose method was invoked by user code.
/// </param>
protected override void Dispose(bool disposing)
{
try
{
if (!_disposed)
{
if (disposing && (this._baseStream != null))
this._baseStream.Close();
_disposed = true;
}
}
finally
{
base.Dispose(disposing);
}
}
/// <summary>
/// Indicates whether the stream can be read.
/// </summary>
/// <remarks>
/// The return value depends on whether the captive stream supports reading.
/// </remarks>
public override bool CanRead
{
get
{
if (_disposed) throw new ObjectDisposedException("ZlibStream");
return _baseStream._stream.CanRead;
}
}
/// <summary>
/// Indicates whether the stream supports Seek operations.
/// </summary>
/// <remarks>
/// Always returns false.
/// </remarks>
public override bool CanSeek
{
get { return false; }
}
/// <summary>
/// Indicates whether the stream can be written.
/// </summary>
/// <remarks>
/// The return value depends on whether the captive stream supports writing.
/// </remarks>
public override bool CanWrite
{
get
{
if (_disposed) throw new ObjectDisposedException("ZlibStream");
return _baseStream._stream.CanWrite;
}
}
/// <summary>
/// Flush the stream.
/// </summary>
public override void Flush()
{
if (_disposed) throw new ObjectDisposedException("ZlibStream");
_baseStream.Flush();
}
/// <summary>
/// Reading this property always throws a <see cref="NotSupportedException"/>.
/// </summary>
public override long Length
{
get { throw new NotSupportedException(); }
}
/// <summary>
/// The position of the stream pointer.
/// </summary>
///
/// <remarks>
/// Setting this property always throws a <see
/// cref="NotSupportedException"/>. Reading will return the total bytes
/// written out, if used in writing, or the total bytes read in, if used in
/// reading. The count may refer to compressed bytes or uncompressed bytes,
/// depending on how you've used the stream.
/// </remarks>
public override long Position
{
get
{
if (this._baseStream._streamMode == Ionic.Zlib.ZlibBaseStream.StreamMode.Writer)
return this._baseStream._z.TotalBytesOut;
if (this._baseStream._streamMode == Ionic.Zlib.ZlibBaseStream.StreamMode.Reader)
return this._baseStream._z.TotalBytesIn;
return 0;
}
set { throw new NotSupportedException(); }
}
/// <summary>
/// Read data from the stream.
/// </summary>
///
/// <remarks>
///
/// <para>
/// If you wish to use the <c>ZlibStream</c> to compress data while reading,
/// you can create a <c>ZlibStream</c> with <c>CompressionMode.Compress</c>,
/// providing an uncompressed data stream. Then call <c>Read()</c> on that
/// <c>ZlibStream</c>, and the data read will be compressed. If you wish to
/// use the <c>ZlibStream</c> to decompress data while reading, you can create
/// a <c>ZlibStream</c> with <c>CompressionMode.Decompress</c>, providing a
/// readable compressed data stream. Then call <c>Read()</c> on that
/// <c>ZlibStream</c>, and the data will be decompressed as it is read.
/// </para>
///
/// <para>
/// A <c>ZlibStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but
/// not both.
/// </para>
///
/// </remarks>
///
/// <param name="buffer">
/// The buffer into which the read data should be placed.</param>
///
/// <param name="offset">
/// the offset within that data array to put the first byte read.</param>
///
/// <param name="count">the number of bytes to read.</param>
///
/// <returns>the number of bytes read</returns>
public override int Read(byte[] buffer, int offset, int count)
{
if (_disposed) throw new ObjectDisposedException("ZlibStream");
return _baseStream.Read(buffer, offset, count);
}
/// <summary>
/// Calling this method always throws a <see cref="NotSupportedException"/>.
/// </summary>
/// <param name="offset">
/// The offset to seek to....
/// IF THIS METHOD ACTUALLY DID ANYTHING.
/// </param>
/// <param name="origin">
/// The reference specifying how to apply the offset.... IF
/// THIS METHOD ACTUALLY DID ANYTHING.
/// </param>
///
/// <returns>nothing. This method always throws.</returns>
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotSupportedException();
}
/// <summary>
/// Calling this method always throws a <see cref="NotSupportedException"/>.
/// </summary>
/// <param name="value">
/// The new value for the stream length.... IF
/// THIS METHOD ACTUALLY DID ANYTHING.
/// </param>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
/// <summary>
/// Write data to the stream.
/// </summary>
///
/// <remarks>
///
/// <para>
/// If you wish to use the <c>ZlibStream</c> to compress data while writing,
/// you can create a <c>ZlibStream</c> with <c>CompressionMode.Compress</c>,
/// and a writable output stream. Then call <c>Write()</c> on that
/// <c>ZlibStream</c>, providing uncompressed data as input. The data sent to
/// the output stream will be the compressed form of the data written. If you
/// wish to use the <c>ZlibStream</c> to decompress data while writing, you
/// can create a <c>ZlibStream</c> with <c>CompressionMode.Decompress</c>, and a
/// writable output stream. Then call <c>Write()</c> on that stream,
/// providing previously compressed data. The data sent to the output stream
/// will be the decompressed form of the data written.
/// </para>
///
/// <para>
/// A <c>ZlibStream</c> can be used for <c>Read()</c> or <c>Write()</c>, but not both.
/// </para>
/// </remarks>
/// <param name="buffer">The buffer holding data to write to the stream.</param>
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
/// <param name="count">the number of bytes to write.</param>
public override void Write(byte[] buffer, int offset, int count)
{
if (_disposed) throw new ObjectDisposedException("ZlibStream");
_baseStream.Write(buffer, offset, count);
}
#endregion
/// <summary>
/// Compress a string into a byte array using ZLIB.
/// </summary>
///
/// <remarks>
/// Uncompress it with <see cref="ZlibStream.UncompressString(byte[])"/>.
/// </remarks>
///
/// <seealso cref="ZlibStream.UncompressString(byte[])"/>
/// <seealso cref="ZlibStream.CompressBuffer(byte[])"/>
/// <seealso cref="GZipStream.CompressString(string)"/>
///
/// <param name="s">
/// A string to compress. The string will first be encoded
/// using UTF8, then compressed.
/// </param>
///
/// <returns>The string in compressed form</returns>
public static byte[] CompressString(String s)
{
using (var ms = new MemoryStream())
{
Stream compressor =
new ZlibStream(ms, CompressionMode.Compress, CompressionLevel.BestCompression);
ZlibBaseStream.CompressString(s, compressor);
return ms.ToArray();
}
}
/// <summary>
/// Compress a byte array into a new byte array using ZLIB.
/// </summary>
///
/// <remarks>
/// Uncompress it with <see cref="ZlibStream.UncompressBuffer(byte[])"/>.
/// </remarks>
///
/// <seealso cref="ZlibStream.CompressString(string)"/>
/// <seealso cref="ZlibStream.UncompressBuffer(byte[])"/>
///
/// <param name="b">
/// A buffer to compress.
/// </param>
///
/// <returns>The data in compressed form</returns>
public static byte[] CompressBuffer(byte[] b)
{
using (var ms = new MemoryStream())
{
Stream compressor =
new ZlibStream( ms, CompressionMode.Compress, CompressionLevel.BestCompression );
ZlibBaseStream.CompressBuffer(b, compressor);
return ms.ToArray();
}
}
/// <summary>
/// Uncompress a ZLIB-compressed byte array into a single string.
/// </summary>
///
/// <seealso cref="ZlibStream.CompressString(String)"/>
/// <seealso cref="ZlibStream.UncompressBuffer(byte[])"/>
///
/// <param name="compressed">
/// A buffer containing ZLIB-compressed data.
/// </param>
///
/// <returns>The uncompressed string</returns>
public static String UncompressString(byte[] compressed)
{
using (var input = new MemoryStream(compressed))
{
Stream decompressor =
new ZlibStream(input, CompressionMode.Decompress);
return ZlibBaseStream.UncompressString(compressed, decompressor);
}
}
/// <summary>
/// Uncompress a ZLIB-compressed byte array into a byte array.
/// </summary>
///
/// <seealso cref="ZlibStream.CompressBuffer(byte[])"/>
/// <seealso cref="ZlibStream.UncompressString(byte[])"/>
///
/// <param name="compressed">
/// A buffer containing ZLIB-compressed data.
/// </param>
///
/// <returns>The data in uncompressed form</returns>
public static byte[] UncompressBuffer(byte[] compressed)
{
using (var input = new MemoryStream(compressed))
{
Stream decompressor =
new ZlibStream( input, CompressionMode.Decompress );
return ZlibBaseStream.UncompressBuffer(compressed, decompressor);
}
}
}
}

View file

@ -0,0 +1,699 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Implementation for Minecraft 1.4.X, 1.5.X, 1.6.X Protocols
/// </summary>
class Protocol16Handler : IMinecraftCom
{
IMinecraftComHandler handler;
private bool autocomplete_received = false;
private string autocomplete_result = "";
private bool encrypted = false;
private int protocolversion;
private Thread netRead;
Crypto.IAesStream s;
TcpClient c;
public Protocol16Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
{
ConsoleIO.SetAutoCompleteEngine(this);
if (protocolversion >= 72)
ChatParser.InitTranslations();
this.c = Client;
this.protocolversion = ProtocolVersion;
this.handler = Handler;
}
private Protocol16Handler(TcpClient Client)
{
this.c = Client;
}
private void Updater()
{
int keep_alive_interval = 100;
int keep_alive_timer = 100;
try
{
do
{
Thread.Sleep(100);
keep_alive_timer--;
if (keep_alive_timer <= 0)
{
Send(getPaddingPacket());
keep_alive_timer = keep_alive_interval;
}
}
while (Update());
}
catch (System.IO.IOException) { }
catch (SocketException) { }
catch (ObjectDisposedException) { }
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
}
private bool Update()
{
handler.OnUpdate();
bool connection_ok = true;
while (c.Client.Available > 0 && connection_ok)
{
byte id = readNextByte();
connection_ok = processPacket(id);
}
return connection_ok;
}
private bool processPacket(byte id)
{
int nbr = 0;
switch (id)
{
case 0x00: byte[] keepalive = new byte[5] { 0, 0, 0, 0, 0 };
Receive(keepalive, 1, 4, SocketFlags.None);
Send(keepalive); break;
case 0x01: readData(4); readNextString(); readData(5); break;
case 0x02: readData(1); readNextString(); readNextString(); readData(4); break;
case 0x03:
string message = readNextString();
if (protocolversion >= 72) { message = ChatParser.ParseText(message); }
handler.OnTextReceived(message); break;
case 0x04: readData(16); break;
case 0x05: readData(6); readNextItemSlot(); break;
case 0x06: readData(12); break;
case 0x07: readData(9); break;
case 0x08: if (protocolversion >= 72) { readData(10); } else readData(8); break;
case 0x09: readData(8); readNextString(); break;
case 0x0A: readData(1); break;
case 0x0B: readData(33); break;
case 0x0C: readData(9); break;
case 0x0D: readData(41); break;
case 0x0E: readData(11); break;
case 0x0F: readData(10); readNextItemSlot(); readData(3); break;
case 0x10: readData(2); break;
case 0x11: readData(14); break;
case 0x12: readData(5); break;
case 0x13: if (protocolversion >= 72) { readData(9); } else readData(5); break;
case 0x14: readData(4); readNextString(); readData(16); readNextEntityMetaData(); break;
case 0x16: readData(8); break;
case 0x17: readData(19); readNextObjectData(); break;
case 0x18: readData(26); readNextEntityMetaData(); break;
case 0x19: readData(4); readNextString(); readData(16); break;
case 0x1A: readData(18); break;
case 0x1B: if (protocolversion >= 72) { readData(10); } break;
case 0x1C: readData(10); break;
case 0x1D: nbr = (int)readNextByte(); readData(nbr * 4); break;
case 0x1E: readData(4); break;
case 0x1F: readData(7); break;
case 0x20: readData(6); break;
case 0x21: readData(9); break;
case 0x22: readData(18); break;
case 0x23: readData(5); break;
case 0x26: readData(5); break;
case 0x27: if (protocolversion >= 72) { readData(9); } else readData(8); break;
case 0x28: readData(4); readNextEntityMetaData(); break;
case 0x29: readData(8); break;
case 0x2A: readData(5); break;
case 0x2B: readData(8); break;
case 0x2C: if (protocolversion >= 72) { readNextEntityProperties(protocolversion); } break;
case 0x33: readData(13); nbr = readNextInt(); readData(nbr); break;
case 0x34: readData(10); nbr = readNextInt(); readData(nbr); break;
case 0x35: readData(12); break;
case 0x36: readData(14); break;
case 0x37: readData(17); break;
case 0x38: readNextChunkBulkData(); break;
case 0x3C: readData(28); nbr = readNextInt(); readData(3 * nbr); readData(12); break;
case 0x3D: readData(18); break;
case 0x3E: readNextString(); readData(17); break;
case 0x3F: if (protocolversion > 51) { readNextString(); readData(32); } break;
case 0x46: readData(2); break;
case 0x47: readData(17); break;
case 0x64: readNextWindowData(protocolversion); break;
case 0x65: readData(1); break;
case 0x66: readData(7); readNextItemSlot(); break;
case 0x67: readData(3); readNextItemSlot(); break;
case 0x68: readData(1); for (nbr = readNextShort(); nbr > 0; nbr--) { readNextItemSlot(); } break;
case 0x69: readData(5); break;
case 0x6A: readData(4); break;
case 0x6B: readData(2); readNextItemSlot(); break;
case 0x6C: readData(2); break;
case 0x82: readData(10); readNextString(); readNextString(); readNextString(); readNextString(); break;
case 0x83: readData(4); nbr = readNextShort(); readData(nbr); break;
case 0x84: readData(11); nbr = readNextShort(); if (nbr > 0) { readData(nbr); } break;
case 0x85: if (protocolversion >= 74) { readData(13); } break;
case 0xC8:
if (readNextInt() == 2022) { handler.OnTextReceived("You are dead. Type /reco to respawn & reconnect."); }
if (protocolversion >= 72) { readData(4); } else readData(1);
break;
case 0xC9: readNextString(); readData(3); break;
case 0xCA: if (protocolversion >= 72) { readData(9); } else readData(3); break;
case 0xCB: autocomplete_result = readNextString(); autocomplete_received = true; break;
case 0xCC: readNextString(); readData(4); break;
case 0xCD: readData(1); break;
case 0xCE: if (protocolversion > 51) { readNextString(); readNextString(); readData(1); } break;
case 0xCF: if (protocolversion > 51) { readNextString(); readData(1); readNextString(); } readData(4); break;
case 0xD0: if (protocolversion > 51) { readData(1); readNextString(); } break;
case 0xD1: if (protocolversion > 51) { readNextTeamData(); } break;
case 0xFA: readNextString(); nbr = readNextShort(); readData(nbr); break;
case 0xFF: string reason = readNextString();
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, reason); break;
default: return false; //unknown packet!
}
return true; //packet has been successfully skipped
}
private void StartUpdating()
{
netRead = new Thread(new ThreadStart(Updater));
netRead.Name = "ProtocolPacketHandler";
netRead.Start();
}
public void Dispose()
{
try
{
if (netRead != null)
{
netRead.Abort();
c.Close();
}
}
catch { }
}
private void readData(int offset)
{
if (offset > 0)
{
try
{
byte[] cache = new byte[offset];
Receive(cache, 0, offset, SocketFlags.None);
}
catch (OutOfMemoryException) { }
}
}
private string readNextString()
{
ushort length = (ushort)readNextShort();
if (length > 0)
{
byte[] cache = new byte[length * 2];
Receive(cache, 0, length * 2, SocketFlags.None);
string result = Encoding.BigEndianUnicode.GetString(cache);
return result;
}
else return "";
}
private byte[] readNextByteArray()
{
short len = readNextShort();
byte[] data = new byte[len];
Receive(data, 0, len, SocketFlags.None);
return data;
}
private short readNextShort()
{
byte[] tmp = new byte[2];
Receive(tmp, 0, 2, SocketFlags.None);
Array.Reverse(tmp);
return BitConverter.ToInt16(tmp, 0);
}
private int readNextInt()
{
byte[] tmp = new byte[4];
Receive(tmp, 0, 4, SocketFlags.None);
Array.Reverse(tmp);
return BitConverter.ToInt32(tmp, 0);
}
private byte readNextByte()
{
byte[] result = new byte[1];
Receive(result, 0, 1, SocketFlags.None);
return result[0];
}
private void readNextItemSlot()
{
short itemid = readNextShort();
//If slot not empty (item ID != -1)
if (itemid != -1)
{
readData(1); //Item count
readData(2); //Item damage
short length = readNextShort();
//If length of optional NBT data > 0, read it
if (length > 0) { readData(length); }
}
}
private void readNextEntityMetaData()
{
do
{
byte[] id = new byte[1];
Receive(id, 0, 1, SocketFlags.None);
if (id[0] == 0x7F) { break; }
int index = id[0] & 0x1F;
int type = id[0] >> 5;
switch (type)
{
case 0: readData(1); break; //Byte
case 1: readData(2); break; //Short
case 2: readData(4); break; //Int
case 3: readData(4); break; //Float
case 4: readNextString(); break; //String
case 5: readNextItemSlot(); break; //Slot
case 6: readData(12); break; //Vector (3 Int)
}
} while (true);
}
private void readNextObjectData()
{
int id = readNextInt();
if (id != 0) { readData(6); }
}
private void readNextTeamData()
{
readNextString(); //Internal Name
byte mode = readNextByte();
if (mode == 0 || mode == 2)
{
readNextString(); //Display Name
readNextString(); //Prefix
readNextString(); //Suffix
readData(1); //Friendly Fire
}
if (mode == 0 || mode == 3 || mode == 4)
{
short count = readNextShort();
for (int i = 0; i < count; i++)
{
readNextString(); //Players
}
}
}
private void readNextEntityProperties(int protocolversion)
{
if (protocolversion >= 72)
{
if (protocolversion >= 74)
{
//Minecraft 1.6.2
readNextInt(); //Entity ID
int count = readNextInt();
for (int i = 0; i < count; i++)
{
readNextString(); //Property name
readData(8); //Property value (Double)
short othercount = readNextShort();
readData(25 * othercount);
}
}
else
{
//Minecraft 1.6.0 / 1.6.1
readNextInt(); //Entity ID
int count = readNextInt();
for (int i = 0; i < count; i++)
{
readNextString(); //Property name
readData(8); //Property value (Double)
}
}
}
}
private void readNextWindowData(int protocolversion)
{
readData(1);
byte windowtype = readNextByte();
readNextString();
readData(1);
if (protocolversion > 51)
{
readData(1);
if (protocolversion >= 72 && windowtype == 0xb)
{
readNextInt();
}
}
}
private void readNextChunkBulkData()
{
short chunkcount = readNextShort();
int datalen = readNextInt();
readData(1);
readData(datalen);
readData(12 * (chunkcount));
}
private void Receive(byte[] buffer, int start, int offset, SocketFlags f)
{
int read = 0;
while (read < offset)
{
if (encrypted)
{
read += s.Read(buffer, start + read, offset - read);
}
else read += c.Client.Receive(buffer, start + read, offset - read, f);
}
}
private void Send(byte[] buffer)
{
if (encrypted)
{
s.Write(buffer, 0, buffer.Length);
}
else c.Client.Send(buffer);
}
private bool Handshake(string uuid, string username, string sessionID, string host, int port)
{
//array
byte[] data = new byte[10 + (username.Length + host.Length) * 2];
//packet id
data[0] = (byte)2;
//Protocol Version
data[1] = (byte)protocolversion;
//short len
byte[] sh = BitConverter.GetBytes((short)username.Length);
Array.Reverse(sh);
sh.CopyTo(data, 2);
//username
byte[] bname = Encoding.BigEndianUnicode.GetBytes(username);
bname.CopyTo(data, 4);
//short len
sh = BitConverter.GetBytes((short)host.Length);
Array.Reverse(sh);
sh.CopyTo(data, 4 + (username.Length * 2));
//host
byte[] bhost = Encoding.BigEndianUnicode.GetBytes(host);
bhost.CopyTo(data, 6 + (username.Length * 2));
//port
sh = BitConverter.GetBytes(port);
Array.Reverse(sh);
sh.CopyTo(data, 6 + (username.Length * 2) + (host.Length * 2));
Send(data);
byte[] pid = new byte[1];
Receive(pid, 0, 1, SocketFlags.None);
while (pid[0] == 0xFA) //Skip some early plugin messages
{
processPacket(pid[0]);
Receive(pid, 0, 1, SocketFlags.None);
}
if (pid[0] == 0xFD)
{
string serverID = readNextString();
byte[] PublicServerkey = readNextByteArray();
byte[] token = readNextByteArray();
if (serverID == "-")
{
ConsoleIO.WriteLineFormatted("§8Server is in offline mode.");
return true; //No need to check session or start encryption
}
else
{
ConsoleIO.WriteLineFormatted("§8Handshake successful. (Server ID: " + serverID + ')');
return StartEncryption(uuid, username, sessionID, token, serverID, PublicServerkey);
}
}
else return false;
}
private bool StartEncryption(string uuid, string username, string sessionID, byte[] token, string serverIDhash, byte[] serverKey)
{
System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey);
byte[] secretKey = CryptoHandler.GenerateAESPrivateKey();
ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated.");
if (serverIDhash != "-")
{
Console.WriteLine("Checking Session...");
if (!ProtocolHandler.SessionCheck(uuid, sessionID, CryptoHandler.getServerHash(serverIDhash, serverKey, secretKey)))
{
return false;
}
}
//Encrypt the data
byte[] key_enc = RSAService.Encrypt(secretKey, false);
byte[] token_enc = RSAService.Encrypt(token, false);
byte[] keylen = BitConverter.GetBytes((short)key_enc.Length);
byte[] tokenlen = BitConverter.GetBytes((short)token_enc.Length);
Array.Reverse(keylen);
Array.Reverse(tokenlen);
//Building the packet
byte[] data = new byte[5 + (short)key_enc.Length + (short)token_enc.Length];
data[0] = 0xFC;
keylen.CopyTo(data, 1);
key_enc.CopyTo(data, 3);
tokenlen.CopyTo(data, 3 + (short)key_enc.Length);
token_enc.CopyTo(data, 5 + (short)key_enc.Length);
//Send it back
Send(data);
//Getting the next packet
byte[] pid = new byte[1];
Receive(pid, 0, 1, SocketFlags.None);
if (pid[0] == 0xFC)
{
readData(4);
s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this);
encrypted = true;
return true;
}
else return false;
}
public bool Login()
{
if (Handshake(handler.getUserUUID(), handler.getUsername(), handler.getSessionID(), handler.getServerHost(), handler.getServerPort()))
{
Send(new byte[] { 0xCD, 0 });
try
{
byte[] pid = new byte[1];
try
{
if (c.Connected)
{
Receive(pid, 0, 1, SocketFlags.None);
while (pid[0] >= 0xC0 && pid[0] != 0xFF) //Skip some early packets or plugin messages
{
processPacket(pid[0]);
Receive(pid, 0, 1, SocketFlags.None);
}
if (pid[0] == (byte)1)
{
readData(4); readNextString(); readData(5);
StartUpdating();
return true; //The Server accepted the request
}
else if (pid[0] == (byte)0xFF)
{
string reason = readNextString();
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, reason);
return false;
}
}
}
catch
{
//Connection failed
return false;
}
}
catch
{
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
return false;
}
return false; //Login was unsuccessful (received a kick...)
}
else return false;
}
public void Disconnect()
{
const string message = "disconnect.quitting";
try
{
byte[] reason = new byte[3 + (message.Length * 2)];
reason[0] = (byte)0xff;
byte[] msglen;
msglen = BitConverter.GetBytes((short)message.Length);
Array.Reverse(msglen);
msglen.CopyTo(reason, 1);
if (message.Length > 0)
{
byte[] msg;
msg = Encoding.BigEndianUnicode.GetBytes(message);
msg.CopyTo(reason, 3);
}
Send(reason);
}
catch (SocketException) { }
catch (System.IO.IOException) { }
}
public bool SendChatMessage(string message)
{
if (String.IsNullOrEmpty(message))
return true;
try
{
byte[] chat = new byte[3 + (message.Length * 2)];
chat[0] = (byte)3;
byte[] msglen;
msglen = BitConverter.GetBytes((short)message.Length);
Array.Reverse(msglen);
msglen.CopyTo(chat, 1);
byte[] msg;
msg = Encoding.BigEndianUnicode.GetBytes(message);
msg.CopyTo(chat, 3);
Send(chat);
return true;
}
catch (SocketException) { return false; }
catch (System.IO.IOException) { return false; }
}
public bool SendRespawnPacket()
{
try
{
Send(new byte[] { 0xCD, 1 });
return true;
}
catch (SocketException) { return false; }
}
public string AutoComplete(string BehindCursor)
{
if (String.IsNullOrEmpty(BehindCursor))
return "";
byte[] autocomplete = new byte[3 + (BehindCursor.Length * 2)];
autocomplete[0] = 0xCB;
byte[] msglen = BitConverter.GetBytes((short)BehindCursor.Length);
Array.Reverse(msglen); msglen.CopyTo(autocomplete, 1);
byte[] msg = Encoding.BigEndianUnicode.GetBytes(BehindCursor);
msg.CopyTo(autocomplete, 3);
autocomplete_received = false;
autocomplete_result = BehindCursor;
Send(autocomplete);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms)
while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }
string[] results = autocomplete_result.Split((char)0x00);
return results[0];
}
private static byte[] concatBytes(params byte[][] bytes)
{
List<byte> result = new List<byte>();
foreach (byte[] array in bytes)
result.AddRange(array);
return result.ToArray();
}
public byte[] getPaddingPacket()
{
//Will generate a 15-bytes long padding packet
byte[] id = new byte[1] { 0xFA }; //Plugin Message
byte[] channel_name = Encoding.BigEndianUnicode.GetBytes("MCC|");
byte[] channel_name_len = BitConverter.GetBytes((short)channel_name.Length); Array.Reverse(channel_name_len);
byte[] data = new byte[] { 0x00, 0x00 };
byte[] data_len = BitConverter.GetBytes((short)data.Length); Array.Reverse(data_len);
byte[] packet_data = concatBytes(id, channel_name_len, channel_name, data_len, data);
return packet_data;
}
public static bool doPing(string host, int port, ref int protocolversion)
{
try
{
string version = "";
TcpClient tcp = ProxyHandler.newTcpClient(host, port);
tcp.ReceiveTimeout = 5000; //MC 1.7.2+ SpigotMC servers won't answer, so we need a reasonable timeout.
byte[] ping = new byte[2] { 0xfe, 0x01 };
tcp.Client.Send(ping, SocketFlags.None);
tcp.Client.Receive(ping, 0, 1, SocketFlags.None);
if (ping[0] == 0xff)
{
Protocol16Handler ComTmp = new Protocol16Handler(tcp);
string result = ComTmp.readNextString();
if (result.Length > 2 && result[0] == '§' && result[1] == '1')
{
string[] tmp = result.Split((char)0x00);
protocolversion = (byte)Int16.Parse(tmp[1]);
version = tmp[2];
if (protocolversion == 127) //MC 1.7.2+
return false;
}
else
{
protocolversion = (byte)39;
version = "B1.8.1 - 1.3.2";
}
ConsoleIO.WriteLineFormatted("§8Server version : MC " + version + " (protocol v" + protocolversion + ").");
return true;
}
else return false;
}
catch { return false; }
}
}
}

View file

@ -0,0 +1,563 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Implementation for Minecraft 1.7.X Protocol
/// </summary>
class Protocol17Handler : IMinecraftCom
{
IMinecraftComHandler handler;
private bool autocomplete_received = false;
private string autocomplete_result = "";
private bool encrypted = false;
private int protocolversion;
private Thread netRead;
Crypto.IAesStream s;
TcpClient c;
public Protocol17Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
{
ConsoleIO.SetAutoCompleteEngine(this);
ChatParser.InitTranslations();
this.c = Client;
this.protocolversion = ProtocolVersion;
this.handler = Handler;
}
private Protocol17Handler(TcpClient Client)
{
this.c = Client;
}
/// <summary>
/// Separate thread. Network reading loop.
/// </summary>
private void Updater()
{
int keep_alive_interval = 100;
int keep_alive_timer = 100;
try
{
do
{
Thread.Sleep(100);
keep_alive_timer--;
if (keep_alive_timer <= 0)
{
Send(getPaddingPacket());
keep_alive_timer = keep_alive_interval;
}
}
while (Update());
}
catch (System.IO.IOException) { }
catch (SocketException) { }
catch (ObjectDisposedException) { }
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
}
/// <summary>
/// Read and data from the network. Should be called on a separate thread.
/// </summary>
/// <returns></returns>
private bool Update()
{
handler.OnUpdate();
if (c.Client == null || !c.Connected) { return false; }
int id = 0, size = 0;
try
{
while (c.Client.Available > 0)
{
size = readNextVarInt(); //Packet size
id = readNextVarInt(); //Packet ID
switch (id)
{
case 0x00:
byte[] keepalive = new byte[4] { 0, 0, 0, 0 };
Receive(keepalive, 0, 4, SocketFlags.None);
byte[] keepalive_packet = concatBytes(getVarInt(0x00), keepalive);
byte[] keepalive_tosend = concatBytes(getVarInt(keepalive_packet.Length), keepalive_packet);
Send(keepalive_tosend);
break;
case 0x02:
handler.OnTextReceived(ChatParser.ParseText(readNextString()));
break;
case 0x3A:
int autocomplete_count = readNextVarInt();
string tab_list = "";
for (int i = 0; i < autocomplete_count; i++)
{
autocomplete_result = readNextString();
if (autocomplete_result != "")
tab_list = tab_list + autocomplete_result + " ";
}
autocomplete_received = true;
tab_list = tab_list.Trim();
if (tab_list.Length > 0)
ConsoleIO.WriteLineFormatted("§8" + tab_list, false);
break;
case 0x40:
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString()));
return false;
default:
readData(size - getVarInt(id).Length); //Skip packet
break;
}
}
}
catch (SocketException) { return false; }
return true;
}
/// <summary>
/// Start the updating thread. Should be called after login success.
/// </summary>
private void StartUpdating()
{
netRead = new Thread(new ThreadStart(Updater));
netRead.Name = "ProtocolPacketHandler";
netRead.Start();
}
/// <summary>
/// Disconnect from the server, cancel network reading.
/// </summary>
public void Dispose()
{
try
{
if (netRead != null)
{
netRead.Abort();
c.Close();
}
}
catch { }
}
/// <summary>
/// Read some data and discard the result
/// </summary>
/// <param name="offset">Amount of bytes to read</param>
private void readData(int offset)
{
if (offset > 0)
{
try
{
byte[] cache = new byte[offset];
Receive(cache, 0, offset, SocketFlags.None);
}
catch (OutOfMemoryException) { }
}
}
/// <summary>
/// Read a string from the network
/// </summary>
/// <returns>The string</returns>
private string readNextString()
{
int length = readNextVarInt();
if (length > 0)
{
byte[] cache = new byte[length];
Receive(cache, 0, length, SocketFlags.None);
return Encoding.UTF8.GetString(cache);
}
else return "";
}
/// <summary>
/// Read a byte array from the network
/// </summary>
/// <returns>The byte array</returns>
private byte[] readNextByteArray()
{
byte[] tmp = new byte[2];
Receive(tmp, 0, 2, SocketFlags.None);
Array.Reverse(tmp);
short len = BitConverter.ToInt16(tmp, 0);
byte[] data = new byte[len];
Receive(data, 0, len, SocketFlags.None);
return data;
}
/// <summary>
/// Read an integer from the network
/// </summary>
/// <returns>The integer</returns>
private int readNextVarInt()
{
int i = 0;
int j = 0;
int k = 0;
byte[] tmp = new byte[1];
while (true)
{
Receive(tmp, 0, 1, SocketFlags.None);
k = tmp[0];
i |= (k & 0x7F) << j++ * 7;
if (j > 5) throw new OverflowException("VarInt too big");
if ((k & 0x80) != 128) break;
}
return i;
}
/// <summary>
/// Build an integer for sending over the network
/// </summary>
/// <param name="paramInt">Integer to encode</param>
/// <returns>Byte array for this integer</returns>
private static byte[] getVarInt(int paramInt)
{
List<byte> bytes = new List<byte>();
while ((paramInt & -128) != 0)
{
bytes.Add((byte)(paramInt & 127 | 128));
paramInt = (int)(((uint)paramInt) >> 7);
}
bytes.Add((byte)paramInt);
return bytes.ToArray();
}
/// <summary>
/// Easily append several byte arrays
/// </summary>
/// <param name="bytes">Bytes to append</param>
/// <returns>Array containing all the data</returns>
private static byte[] concatBytes(params byte[][] bytes)
{
List<byte> result = new List<byte>();
foreach (byte[] array in bytes)
result.AddRange(array);
return result.ToArray();
}
/// <summary>
/// C-like atoi function for parsing an int from string
/// </summary>
/// <param name="str">String to parse</param>
/// <returns>Int parsed</returns>
private static int atoi(string str)
{
return int.Parse(new string(str.Trim().TakeWhile(char.IsDigit).ToArray()));
}
/// <summary>
/// Network reading method. Read bytes from the socket or encrypted socket.
/// </summary>
private void Receive(byte[] buffer, int start, int offset, SocketFlags f)
{
int read = 0;
while (read < offset)
{
if (encrypted)
{
read += s.Read(buffer, start + read, offset - read);
}
else read += c.Client.Receive(buffer, start + read, offset - read, f);
}
}
/// <summary>
/// Network sending method. Send bytes using the socket or encrypted socket.
/// </summary>
/// <param name="buffer"></param>
private void Send(byte[] buffer)
{
if (encrypted)
{
s.Write(buffer, 0, buffer.Length);
}
else c.Client.Send(buffer);
}
/// <summary>
/// Do the Minecraft login.
/// </summary>
/// <returns>True if login successful</returns>
public bool Login()
{
byte[] packet_id = getVarInt(0);
byte[] protocol_version = getVarInt(protocolversion);
byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.getServerHost());
byte[] server_adress_len = getVarInt(server_adress_val.Length);
byte[] server_port = BitConverter.GetBytes((ushort)handler.getServerPort()); Array.Reverse(server_port);
byte[] next_state = getVarInt(2);
byte[] handshake_packet = concatBytes(packet_id, protocol_version, server_adress_len, server_adress_val, server_port, next_state);
byte[] handshake_packet_tosend = concatBytes(getVarInt(handshake_packet.Length), handshake_packet);
Send(handshake_packet_tosend);
byte[] username_val = Encoding.UTF8.GetBytes(handler.getUsername());
byte[] username_len = getVarInt(username_val.Length);
byte[] login_packet = concatBytes(packet_id, username_len, username_val);
byte[] login_packet_tosend = concatBytes(getVarInt(login_packet.Length), login_packet);
Send(login_packet_tosend);
readNextVarInt(); //Packet size
int pid = readNextVarInt(); //Packet ID
if (pid == 0x00) //Login rejected
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString()));
return false;
}
else if (pid == 0x01) //Encryption request
{
string serverID = readNextString();
byte[] Serverkey = readNextByteArray();
byte[] token = readNextByteArray();
return StartEncryption(handler.getUserUUID(), handler.getSessionID(), token, serverID, Serverkey);
}
else if (pid == 0x02) //Login successful
{
ConsoleIO.WriteLineFormatted("§8Server is in offline mode.");
StartUpdating();
return true; //No need to check session or start encryption
}
else return false;
}
/// <summary>
/// Start network encryption. Automatically called by Login() if the server requests encryption.
/// </summary>
/// <returns>True if encryption was successful</returns>
private bool StartEncryption(string uuid, string sessionID, byte[] token, string serverIDhash, byte[] serverKey)
{
System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey);
byte[] secretKey = CryptoHandler.GenerateAESPrivateKey();
ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated.");
if (serverIDhash != "-")
{
Console.WriteLine("Checking Session...");
if (!ProtocolHandler.SessionCheck(uuid, sessionID, CryptoHandler.getServerHash(serverIDhash, serverKey, secretKey)))
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Failed to check session.");
return false;
}
}
//Encrypt the data
byte[] key_enc = RSAService.Encrypt(secretKey, false);
byte[] token_enc = RSAService.Encrypt(token, false);
byte[] key_len = BitConverter.GetBytes((short)key_enc.Length); Array.Reverse(key_len);
byte[] token_len = BitConverter.GetBytes((short)token_enc.Length); Array.Reverse(token_len);
//Encryption Response packet
byte[] packet_id = getVarInt(0x01);
byte[] encryption_response = concatBytes(packet_id, key_len, key_enc, token_len, token_enc);
byte[] encryption_response_tosend = concatBytes(getVarInt(encryption_response.Length), encryption_response);
Send(encryption_response_tosend);
//Start client-side encryption
s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this);
encrypted = true;
//Read and skip the next packet
int received_packet_size = readNextVarInt();
int received_packet_id = readNextVarInt();
bool encryption_success = (received_packet_id == 0x02);
if (received_packet_id == 0) { handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString())); }
else readData(received_packet_size - getVarInt(received_packet_id).Length);
if (encryption_success) { StartUpdating(); }
return encryption_success;
}
/// <summary>
/// Useless padding packet for solving Mono issue.
/// </summary>
/// <returns>The padding packet</returns>
public byte[] getPaddingPacket()
{
//Will generate a 15-bytes long padding packet
byte[] id = getVarInt(0x17); //Plugin Message
byte[] channel_name = Encoding.UTF8.GetBytes("MCC|Pad");
byte[] channel_name_len = getVarInt(channel_name.Length);
byte[] data = new byte[] { 0x00, 0x00, 0x00 };
byte[] data_len = BitConverter.GetBytes((short)data.Length); Array.Reverse(data_len);
byte[] packet_data = concatBytes(id, channel_name_len, channel_name, data_len, data);
byte[] packet_length = getVarInt(packet_data.Length);
return concatBytes(packet_length, packet_data);
}
/// <summary>
/// Send a chat message to the server
/// </summary>
/// <param name="message">Message</param>
/// <returns>True if properly sent</returns>
public bool SendChatMessage(string message)
{
if (String.IsNullOrEmpty(message))
return true;
try
{
byte[] packet_id = getVarInt(0x01);
byte[] message_val = Encoding.UTF8.GetBytes(message);
byte[] message_len = getVarInt(message_val.Length);
byte[] message_packet = concatBytes(packet_id, message_len, message_val);
byte[] message_packet_tosend = concatBytes(getVarInt(message_packet.Length), message_packet);
Send(message_packet_tosend);
return true;
}
catch (SocketException) { return false; }
catch (System.IO.IOException) { return false; }
}
/// <summary>
/// Send a respawn packet to the server
/// </summary>
/// <param name="message">Message</param>
/// <returns>True if properly sent</returns>
public bool SendRespawnPacket()
{
try
{
byte[] packet_id = getVarInt(0x16);
byte[] action_id = new byte[] { 0 };
byte[] respawn_packet = concatBytes(getVarInt(packet_id.Length + 1), packet_id, action_id);
Send(respawn_packet);
return true;
}
catch (SocketException) { return false; }
}
/// <summary>
/// Disconnect from the server
/// </summary>
/// <param name="message">Optional disconnect reason</param>
public void Disconnect()
{
try
{
byte[] packet_id = getVarInt(0x40);
byte[] message_val = Encoding.UTF8.GetBytes("\"disconnect.quitting\"");
byte[] message_len = getVarInt(message_val.Length);
byte[] disconnect_packet = concatBytes(packet_id, message_len, message_val);
byte[] disconnect_packet_tosend = concatBytes(getVarInt(disconnect_packet.Length), disconnect_packet);
Send(disconnect_packet_tosend);
}
catch (SocketException) { }
catch (System.IO.IOException) { }
catch (NullReferenceException) { }
catch (ObjectDisposedException) { }
}
/// <summary>
/// Autocomplete text while typing username or command
/// </summary>
/// <param name="BehindCursor">Text behind cursor</param>
/// <returns>Completed text</returns>
public string AutoComplete(string BehindCursor)
{
if (String.IsNullOrEmpty(BehindCursor))
return "";
byte[] packet_id = getVarInt(0x14);
byte[] tocomplete_val = Encoding.UTF8.GetBytes(BehindCursor);
byte[] tocomplete_len = getVarInt(tocomplete_val.Length);
byte[] tabcomplete_packet = concatBytes(packet_id, tocomplete_len, tocomplete_val);
byte[] tabcomplete_packet_tosend = concatBytes(getVarInt(tabcomplete_packet.Length), tabcomplete_packet);
autocomplete_received = false;
autocomplete_result = BehindCursor;
Send(tabcomplete_packet_tosend);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms)
while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }
return autocomplete_result;
}
/// <summary>
/// Ping a Minecraft server to get information about the server
/// </summary>
/// <returns>True if ping was successful</returns>
public static bool doPing(string host, int port, ref int protocolversion)
{
string version = "";
TcpClient tcp = ProxyHandler.newTcpClient(host, port);
tcp.ReceiveBufferSize = 1024 * 1024;
byte[] packet_id = getVarInt(0);
byte[] protocol_version = getVarInt(4);
byte[] server_adress_val = Encoding.UTF8.GetBytes(host);
byte[] server_adress_len = getVarInt(server_adress_val.Length);
byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port);
byte[] next_state = getVarInt(1);
byte[] packet = concatBytes(packet_id, protocol_version, server_adress_len, server_adress_val, server_port, next_state);
byte[] tosend = concatBytes(getVarInt(packet.Length), packet);
tcp.Client.Send(tosend, SocketFlags.None);
byte[] status_request = getVarInt(0);
byte[] request_packet = concatBytes(getVarInt(status_request.Length), status_request);
tcp.Client.Send(request_packet, SocketFlags.None);
Protocol17Handler ComTmp = new Protocol17Handler(tcp);
if (ComTmp.readNextVarInt() > 0) //Read Response length
{
if (ComTmp.readNextVarInt() == 0x00) //Read Packet ID
{
string result = ComTmp.readNextString(); //Get the Json data
if (result[0] == '{' && result.Contains("protocol\":") && result.Contains("name\":\""))
{
string[] tmp_ver = result.Split(new string[] { "protocol\":" }, StringSplitOptions.None);
string[] tmp_name = result.Split(new string[] { "name\":\"" }, StringSplitOptions.None);
if (tmp_ver.Length >= 2 && tmp_name.Length >= 2)
{
protocolversion = atoi(tmp_ver[1]);
version = tmp_name[1].Split('"')[0];
if (result.Contains("modinfo\":"))
{
//Server is running Forge (which is not supported)
version = "Forge " + version;
protocolversion = 0;
}
ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + ").");
return true;
}
}
}
}
return false;
}
}
}

View file

@ -0,0 +1,688 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Implementation for Minecraft 1.8.X Protocol
/// </summary>
class Protocol18Handler : IMinecraftCom
{
IMinecraftComHandler handler;
private int compression_treshold = 0;
private bool autocomplete_received = false;
private string autocomplete_result = "";
private bool login_phase = true;
private bool encrypted = false;
private int protocolversion;
private Thread netRead;
Crypto.IAesStream s;
TcpClient c;
public Protocol18Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
{
ConsoleIO.SetAutoCompleteEngine(this);
ChatParser.InitTranslations();
this.c = Client;
this.protocolversion = ProtocolVersion;
this.handler = Handler;
}
private Protocol18Handler(TcpClient Client)
{
this.c = Client;
}
/// <summary>
/// Separate thread. Network reading loop.
/// </summary>
private void Updater()
{
int keep_alive_interval = 100;
int keep_alive_timer = 100;
try
{
do
{
Thread.Sleep(100);
keep_alive_timer--;
if (keep_alive_timer <= 0)
{
SendRAW(getPaddingPacket());
keep_alive_timer = keep_alive_interval;
}
}
while (Update());
}
catch (System.IO.IOException) { }
catch (SocketException) { }
catch (ObjectDisposedException) { }
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
}
/// <summary>
/// Read data from the network. Should be called on a separate thread.
/// </summary>
/// <returns></returns>
private bool Update()
{
handler.OnUpdate();
if (c.Client == null || !c.Connected) { return false; }
try
{
while (c.Client.Available > 0)
{
int packetID = 0;
byte[] packetData = new byte[] { };
readNextPacket(ref packetID, ref packetData);
handlePacket(packetID, packetData);
}
}
catch (SocketException) { return false; }
return true;
}
/// <summary>
/// Read the next packet from the network
/// </summary>
/// <param name="packetID">will contain packet ID</param>
/// <param name="packetData">will contain raw packet Data</param>
private void readNextPacket(ref int packetID, ref byte[] packetData)
{
int size = readNextVarIntRAW(); //Packet size
packetData = readDataRAW(size); //Packet contents
if (compression_treshold > 0) //Handle packet decompression
{
int size_uncompressed = readNextVarInt(ref packetData);
if (size_uncompressed != 0) // != 0 means compressed, let's decompress
packetData = ZlibUtils.Decompress(packetData, size_uncompressed);
}
packetID = readNextVarInt(ref packetData); //Packet ID
}
/// <summary>
/// Handle the given packet
/// </summary>
/// <param name="packetID">Packet ID</param>
/// <param name="packetData">Packet contents</param>
/// <returns>TRUE if the packet was processed, FALSE if ignored or unknown</returns>
private bool handlePacket(int packetID, byte[] packetData)
{
if (login_phase)
{
switch (packetID) //Packet IDs are different while logging in
{
case 0x03:
compression_treshold = readNextVarInt(ref packetData);
break;
default:
return false; //Ignored packet
}
}
else //Regular in-game packets
{
switch (packetID)
{
case 0x00:
SendPacket(0x00, getVarInt(readNextVarInt(ref packetData)));
break;
case 0x02:
handler.OnTextReceived(ChatParser.ParseText(readNextString(ref packetData)));
break;
case 0x3A:
int autocomplete_count = readNextVarInt(ref packetData);
string tab_list = "";
for (int i = 0; i < autocomplete_count; i++)
{
autocomplete_result = readNextString(ref packetData);
if (autocomplete_result != "")
tab_list = tab_list + autocomplete_result + " ";
}
autocomplete_received = true;
tab_list = tab_list.Trim();
if (tab_list.Length > 0)
ConsoleIO.WriteLineFormatted("§8" + tab_list, false);
break;
case 0x40:
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData)));
return false;
case 0x46:
compression_treshold = readNextVarInt(ref packetData);
break;
default:
return false; //Ignored packet
}
}
return true; //Packet processed
}
/// <summary>
/// Start the updating thread. Should be called after login success.
/// </summary>
private void StartUpdating()
{
netRead = new Thread(new ThreadStart(Updater));
netRead.Name = "ProtocolPacketHandler";
netRead.Start();
}
/// <summary>
/// Disconnect from the server, cancel network reading.
/// </summary>
public void Dispose()
{
try
{
if (netRead != null)
{
netRead.Abort();
c.Close();
}
}
catch { }
}
/// <summary>
/// Read some data directly from the network
/// </summary>
/// <param name="offset">Amount of bytes to read</param>
/// <returns>The data read from the network as an array</returns>
private byte[] readDataRAW(int offset)
{
if (offset > 0)
{
try
{
byte[] cache = new byte[offset];
Receive(cache, 0, offset, SocketFlags.None);
return cache;
}
catch (OutOfMemoryException) { }
}
return new byte[] { };
}
/// <summary>
/// Read some data from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="offset">Amount of bytes to read</param>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The data read from the cache as an array</returns>
private byte[] readData(int offset, ref byte[] cache)
{
List<byte> read = new List<byte>();
List<byte> list = new List<byte>(cache);
while (offset > 0 && list.Count > 0)
{
read.Add(list[0]);
list.RemoveAt(0);
offset--;
}
cache = list.ToArray();
return read.ToArray();
}
/// <summary>
/// Read a string from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The string</returns>
private string readNextString(ref byte[] cache)
{
int length = readNextVarInt(ref cache);
if (length > 0)
{
return Encoding.UTF8.GetString(readData(length, ref cache));
}
else return "";
}
/// <summary>
/// Read a byte array from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The byte array</returns>
private byte[] readNextByteArray(ref byte[] cache)
{
int len = readNextVarInt(ref cache);
return readData(len, ref cache);
}
/// <summary>
/// Read an integer from the network
/// </summary>
/// <returns>The integer</returns>
private int readNextVarIntRAW()
{
int i = 0;
int j = 0;
int k = 0;
byte[] tmp = new byte[1];
while (true)
{
Receive(tmp, 0, 1, SocketFlags.None);
k = tmp[0];
i |= (k & 0x7F) << j++ * 7;
if (j > 5) throw new OverflowException("VarInt too big");
if ((k & 0x80) != 128) break;
}
return i;
}
/// <summary>
/// Read an integer from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The integer</returns>
private int readNextVarInt(ref byte[] cache)
{
int i = 0;
int j = 0;
int k = 0;
byte[] tmp = new byte[1];
while (true)
{
tmp = readData(1, ref cache);
k = tmp[0];
i |= (k & 0x7F) << j++ * 7;
if (j > 5) throw new OverflowException("VarInt too big");
if ((k & 0x80) != 128) break;
}
return i;
}
/// <summary>
/// Build an integer for sending over the network
/// </summary>
/// <param name="paramInt">Integer to encode</param>
/// <returns>Byte array for this integer</returns>
private static byte[] getVarInt(int paramInt)
{
List<byte> bytes = new List<byte>();
while ((paramInt & -128) != 0)
{
bytes.Add((byte)(paramInt & 127 | 128));
paramInt = (int)(((uint)paramInt) >> 7);
}
bytes.Add((byte)paramInt);
return bytes.ToArray();
}
/// <summary>
/// Easily append several byte arrays
/// </summary>
/// <param name="bytes">Bytes to append</param>
/// <returns>Array containing all the data</returns>
private static byte[] concatBytes(params byte[][] bytes)
{
List<byte> result = new List<byte>();
foreach (byte[] array in bytes)
result.AddRange(array);
return result.ToArray();
}
/// <summary>
/// C-like atoi function for parsing an int from string
/// </summary>
/// <param name="str">String to parse</param>
/// <returns>Int parsed</returns>
private static int atoi(string str)
{
return int.Parse(new string(str.Trim().TakeWhile(char.IsDigit).ToArray()));
}
/// <summary>
/// Network reading method. Read bytes from the socket or encrypted socket.
/// </summary>
private void Receive(byte[] buffer, int start, int offset, SocketFlags f)
{
int read = 0;
while (read < offset)
{
if (encrypted)
{
read += s.Read(buffer, start + read, offset - read);
}
else read += c.Client.Receive(buffer, start + read, offset - read, f);
}
}
/// <summary>
/// Send a packet to the server, compression and encryption will be handled automatically
/// </summary>
/// <param name="packetID">packet ID</param>
/// <param name="packetData">packet Data</param>
private void SendPacket(int packetID, byte[] packetData)
{
//The inner packet
byte[] the_packet = concatBytes(getVarInt(packetID), packetData);
if (compression_treshold > 0) //Compression enabled?
{
if (the_packet.Length > compression_treshold) //Packet long enough for compressing?
{
byte[] uncompressed_length = getVarInt(the_packet.Length);
byte[] compressed_packet = ZlibUtils.Compress(the_packet);
byte[] compressed_packet_length = getVarInt(compressed_packet.Length);
the_packet = concatBytes(compressed_packet_length, compressed_packet);
}
else
{
byte[] uncompressed_length = getVarInt(0); //Not compressed (short packet)
the_packet = concatBytes(uncompressed_length, the_packet);
}
}
SendRAW(concatBytes(getVarInt(the_packet.Length), the_packet));
}
/// <summary>
/// Send raw data to the server. Encryption will be handled automatically.
/// </summary>
/// <param name="buffer">data to send</param>
private void SendRAW(byte[] buffer)
{
if (encrypted)
{
s.Write(buffer, 0, buffer.Length);
}
else c.Client.Send(buffer);
}
/// <summary>
/// Do the Minecraft login.
/// </summary>
/// <returns>True if login successful</returns>
public bool Login()
{
byte[] protocol_version = getVarInt(protocolversion);
byte[] server_adress_val = Encoding.UTF8.GetBytes(handler.getServerHost());
byte[] server_adress_len = getVarInt(server_adress_val.Length);
byte[] server_port = BitConverter.GetBytes((ushort)handler.getServerPort()); Array.Reverse(server_port);
byte[] next_state = getVarInt(2);
byte[] handshake_packet = concatBytes( protocol_version, server_adress_len, server_adress_val, server_port, next_state);
SendPacket(0x00, handshake_packet);
byte[] username_val = Encoding.UTF8.GetBytes(handler.getUsername());
byte[] username_len = getVarInt(username_val.Length);
byte[] login_packet = concatBytes(username_len, username_val);
SendPacket(0x00, login_packet);
int packetID = -1;
byte[] packetData = new byte[] { };
while (true)
{
readNextPacket(ref packetID, ref packetData);
if (packetID == 0x00) //Login rejected
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData)));
return false;
}
else if (packetID == 0x01) //Encryption request
{
string serverID = readNextString(ref packetData);
byte[] Serverkey = readNextByteArray(ref packetData);
byte[] token = readNextByteArray(ref packetData);
return StartEncryption(handler.getUserUUID(), handler.getSessionID(), token, serverID, Serverkey);
}
else if (packetID == 0x02) //Login successful
{
ConsoleIO.WriteLineFormatted("§8Server is in offline mode.");
login_phase = false;
StartUpdating();
return true; //No need to check session or start encryption
}
else handlePacket(packetID, packetData);
}
}
/// <summary>
/// Start network encryption. Automatically called by Login() if the server requests encryption.
/// </summary>
/// <returns>True if encryption was successful</returns>
private bool StartEncryption(string uuid, string sessionID, byte[] token, string serverIDhash, byte[] serverKey)
{
System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey);
byte[] secretKey = CryptoHandler.GenerateAESPrivateKey();
ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated.");
if (serverIDhash != "-")
{
Console.WriteLine("Checking Session...");
if (!ProtocolHandler.SessionCheck(uuid, sessionID, CryptoHandler.getServerHash(serverIDhash, serverKey, secretKey)))
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Failed to check session.");
return false;
}
}
//Encrypt the data
byte[] key_enc = RSAService.Encrypt(secretKey, false);
byte[] token_enc = RSAService.Encrypt(token, false);
byte[] key_len = getVarInt(key_enc.Length);
byte[] token_len = getVarInt(token_enc.Length);
//Encryption Response packet
SendPacket(0x01, concatBytes(key_len, key_enc, token_len, token_enc));
//Start client-side encryption
s = CryptoHandler.getAesStream(c.GetStream(), secretKey, this);
encrypted = true;
//Process the next packet
int packetID = -1;
byte[] packetData = new byte[] { };
while (true)
{
readNextPacket(ref packetID, ref packetData);
if (packetID == 0x00) //Login rejected
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData)));
return false;
}
else if (packetID == 0x02) //Login successful
{
login_phase = false;
StartUpdating();
return true;
}
else handlePacket(packetID, packetData);
}
}
/// <summary>
/// Useless padding packet for solving Mono issue.
/// </summary>
/// <returns>The padding packet</returns>
public byte[] getPaddingPacket()
{
//Will generate a 15-bytes long padding packet
byte[] compression = compression_treshold >= 0 ? getVarInt(0) : new byte[] { };
byte[] id = getVarInt(0x17); //Plugin Message
byte[] channel_name = Encoding.UTF8.GetBytes("MCC|Pad");
byte[] channel_name_len = getVarInt(channel_name.Length);
byte[] data = compression_treshold >= 0 ? new byte[] { 0x00, 0x00, 0x00 } : new byte[] { 0x00, 0x00, 0x00, 0x00 };
byte[] data_len = getVarInt(data.Length);
byte[] packet_data = concatBytes(compression, id, channel_name_len, channel_name, data_len, data);
byte[] packet_length = getVarInt(packet_data.Length);
return concatBytes(packet_length, packet_data);
}
/// <summary>
/// Send a chat message to the server
/// </summary>
/// <param name="message">Message</param>
/// <returns>True if properly sent</returns>
public bool SendChatMessage(string message)
{
if (String.IsNullOrEmpty(message))
return true;
try
{
byte[] message_val = Encoding.UTF8.GetBytes(message);
byte[] message_len = getVarInt(message_val.Length);
byte[] message_packet = concatBytes(message_len, message_val);
SendPacket(0x01, message_packet);
return true;
}
catch (SocketException) { return false; }
catch (System.IO.IOException) { return false; }
}
/// <summary>
/// Send a respawn packet to the server
/// </summary>
/// <param name="message">Message</param>
/// <returns>True if properly sent</returns>
public bool SendRespawnPacket()
{
try
{
SendPacket(0x16, new byte[] { 0 });
return true;
}
catch (SocketException) { return false; }
}
/// <summary>
/// Disconnect from the server
/// </summary>
/// <param name="message">Optional disconnect reason</param>
public void Disconnect()
{
try
{
byte[] message_val = Encoding.UTF8.GetBytes("\"disconnect.quitting\"");
byte[] message_len = getVarInt(message_val.Length);
byte[] disconnect_packet = concatBytes(message_len, message_val);
SendPacket(0x40, disconnect_packet);
}
catch (SocketException) { }
catch (System.IO.IOException) { }
catch (NullReferenceException) { }
catch (ObjectDisposedException) { }
}
/// <summary>
/// Autocomplete text while typing username or command
/// </summary>
/// <param name="BehindCursor">Text behind cursor</param>
/// <returns>Completed text</returns>
public string AutoComplete(string BehindCursor)
{
if (String.IsNullOrEmpty(BehindCursor))
return "";
byte[] tocomplete_val = Encoding.UTF8.GetBytes(BehindCursor);
byte[] tocomplete_len = getVarInt(tocomplete_val.Length);
byte[] has_position = new byte[] { 0x00 }; //false, no position sent
byte[] tabcomplete_packet = concatBytes(tocomplete_len, tocomplete_val, has_position);
autocomplete_received = false;
autocomplete_result = BehindCursor;
SendPacket(0x14, tabcomplete_packet);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms)
while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }
return autocomplete_result;
}
/// <summary>
/// Ping a Minecraft server to get information about the server
/// </summary>
/// <returns>True if ping was successful</returns>
public static bool doPing(string host, int port, ref int protocolversion)
{
string version = "";
TcpClient tcp = ProxyHandler.newTcpClient(host, port);
tcp.ReceiveBufferSize = 1024 * 1024;
byte[] packet_id = getVarInt(0);
byte[] protocol_version = getVarInt(4);
byte[] server_adress_val = Encoding.UTF8.GetBytes(host);
byte[] server_adress_len = getVarInt(server_adress_val.Length);
byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port);
byte[] next_state = getVarInt(1);
byte[] packet = concatBytes(packet_id, protocol_version, server_adress_len, server_adress_val, server_port, next_state);
byte[] tosend = concatBytes(getVarInt(packet.Length), packet);
tcp.Client.Send(tosend, SocketFlags.None);
byte[] status_request = getVarInt(0);
byte[] request_packet = concatBytes(getVarInt(status_request.Length), status_request);
tcp.Client.Send(request_packet, SocketFlags.None);
int packetID = -1;
byte[] packetData = new byte[] { };
Protocol18Handler ComTmp = new Protocol18Handler(tcp);
ComTmp.readNextPacket(ref packetID, ref packetData);
if (packetData.Length > 0) //Verify Response length
{
if (packetID == 0x00) //Read Packet ID
{
string result = ComTmp.readNextString(ref packetData); //Get the Json data
if (result[0] == '{' && result.Contains("protocol\":") && result.Contains("name\":\""))
{
string[] tmp_ver = result.Split(new string[] { "protocol\":" }, StringSplitOptions.None);
string[] tmp_name = result.Split(new string[] { "name\":\"" }, StringSplitOptions.None);
if (tmp_ver.Length >= 2 && tmp_name.Length >= 2)
{
protocolversion = atoi(tmp_ver[1]);
version = tmp_name[1].Split('"')[0];
if (result.Contains("modinfo\":"))
{
//Server is running Forge (which is not supported)
version = "Forge " + version;
protocolversion = 0;
}
ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + ").");
return true;
}
}
}
}
return false;
}
}
}

View file

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ionic.Zlib;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Quick Zlib compression handling for network packet compression.
/// Note: Underlying compression handling is taken from the DotNetZip Library.
/// This library is open source and provided under the Microsoft Public License.
/// More info about DotNetZip at dotnetzip.codeplex.com.
/// </summary>
public static class ZlibUtils
{
/// <summary>
/// Compress a byte array into another bytes array using Zlib compression
/// </summary>
/// <param name="to_compress">Data to compress</param>
/// <returns>Compressed data as a byte array</returns>
public static byte[] Compress(byte[] to_compress)
{
ZlibStream stream = new ZlibStream(new System.IO.MemoryStream(to_compress, false), CompressionMode.Compress);
List<byte> temp_compression_list = new List<byte>();
byte[] b = new byte[1];
while (true)
{
int read = stream.Read(b, 0, 1);
if (read > 0) { temp_compression_list.Add(b[0]); }
else break;
}
stream.Close();
return temp_compression_list.ToArray();
}
/// <summary>
/// Decompress a byte array into another byte array of the specified size
/// </summary>
/// <param name="to_decompress">Data to decompress</param>
/// <param name="size_uncompressed">Size of the data once decompressed</param>
/// <returns>Decompressed data as a byte array</returns>
public static byte[] Decompress(byte[] to_decompress, int size_uncompressed)
{
ZlibStream stream = new ZlibStream(new System.IO.MemoryStream(to_decompress, false), CompressionMode.Decompress);
byte[] packetData_decompressed = new byte[size_uncompressed];
stream.Read(packetData_decompressed, 0, size_uncompressed);
stream.Close();
return packetData_decompressed;
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Crypto;
namespace MinecraftClient.Protocol
{
/// <summary>
/// Interface for the Minecraft protocol handler.
/// A protocol handler is used to communicate with the Minecraft server.
/// This interface allows to abstract from the underlying minecraft version in other parts of the program.
/// The protocol handler will take care of parsing and building the appropriate network packets.
/// </summary>
public interface IMinecraftCom : IDisposable, IAutoComplete, IPaddingProvider
{
/// <summary>
/// Start the login procedure once connected to the server
/// </summary>
/// <returns>True if login was successful</returns>
bool Login();
/// <summary>
/// Disconnect from the server
/// </summary>
/// <param name="message">Reason</param>
void Disconnect();
/// <summary>
/// Send a chat message or command to the server
/// </summary>
/// <param name="message">Text to send</param>
/// <returns>True if successfully sent</returns>
bool SendChatMessage(string message);
/// <summary>
/// Allow to respawn after death
/// </summary>
/// <returns>True if packet successfully sent</returns>
bool SendRespawnPacket();
}
}

View file

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MinecraftClient.Protocol
{
/// <summary>
/// Interface for the MinecraftCom Handler.
/// It defines some callbacks that the MinecraftCom handler must have.
/// It allows the protocol handler to abstract from the other parts of the program.
/// </summary>
public interface IMinecraftComHandler
{
/* The MinecraftCom Hanler must
* provide these getters */
int getServerPort();
string getServerHost();
string getUsername();
string getUserUUID();
string getSessionID();
/// <summary>
/// This method is called when the protocol handler receives a chat message
/// </summary>
void OnTextReceived(string text);
/// <summary>
/// This method is called when the connection has been lost
/// </summary>
void OnConnectionLost(ChatBot.DisconnectReason reason, string message);
/// <summary>
/// Called ~10 times per second (10 ticks per second)
/// Useful for updating bots in other parts of the program
/// </summary>
void OnUpdate();
}
}

View file

@ -0,0 +1,275 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MinecraftClient.Protocol.Handlers;
using MinecraftClient.Proxy;
using System.Net.Sockets;
using System.Net.Security;
namespace MinecraftClient.Protocol
{
/// <summary>
/// Handle login, session, server ping and provide a protocol handler for interacting with a minecraft server.
/// </summary>
public static class ProtocolHandler
{
/// <summary>
/// Retrieve information about a Minecraft server
/// </summary>
/// <param name="serverIP">Server IP to ping</param>
/// <param name="serverPort">Server Port to ping</param>
/// <param name="protocolversion">Will contain protocol version, if ping successful</param>
/// <returns>TRUE if ping was successful</returns>
public static bool GetServerInfo(string serverIP, short serverPort, ref int protocolversion)
{
try
{
if (Protocol16Handler.doPing(serverIP, serverPort, ref protocolversion))
{
return true;
}
else if (Protocol17Handler.doPing(serverIP, serverPort, ref protocolversion))
{
return true;
}
else
{
ConsoleIO.WriteLineFormatted("§8Unexpected answer from the server (is that a Minecraft server ?)");
return false;
}
}
catch
{
ConsoleIO.WriteLineFormatted("§8An error occured while attempting to connect to this IP.");
return false;
}
}
/// <summary>
/// Get a protocol handler for the specified Minecraft version
/// </summary>
/// <param name="Client">Tcp Client connected to the server</param>
/// <param name="ProtocolVersion">Protocol version to handle</param>
/// <param name="Handler">Handler with the appropriate callbacks</param>
/// <returns></returns>
public static IMinecraftCom getProtocolHandler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler)
{
int[] supportedVersions_Protocol16 = { 51, 60, 61, 72, 73, 74, 78 };
if (Array.IndexOf(supportedVersions_Protocol16, ProtocolVersion) > -1)
return new Protocol16Handler(Client, ProtocolVersion, Handler);
int[] supportedVersions_Protocol17 = { 4, 5 };
if (Array.IndexOf(supportedVersions_Protocol17, ProtocolVersion) > -1)
return new Protocol17Handler(Client, ProtocolVersion, Handler);
int[] supportedVersions_Protocol18 = { 47 };
if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1)
return new Protocol18Handler(Client, ProtocolVersion, Handler);
throw new NotSupportedException("The protocol version no." + ProtocolVersion + " is not supported.");
}
/// <summary>
/// Convert a human-readable Minecraft version number to network protocol version number
/// </summary>
/// <param name="MCVersion">The Minecraft version number</param>
/// <returns>The protocol version number or 0 if could not determine protocol version: error, unknown, not supported</returns>
public static int MCVer2ProtocolVersion(string MCVersion)
{
if (MCVersion.Contains('.'))
{
switch (MCVersion.Split(' ')[0].Trim())
{
case "1.4.6":
case "1.4.7":
return 51;
case "1.5.1":
return 60;
case "1.5.2":
return 61;
case "1.6.0":
return 72;
case "1.6.1":
case "1.6.2":
case "1.6.3":
case "1.6.4":
return 73;
case "1.7.2":
case "1.7.3":
case "1.7.4":
case "1.7.5":
return 4;
case "1.7.6":
case "1.7.7":
case "1.7.8":
case "1.7.9":
case "1.7.10":
return 5;
case "1.8.0":
return 47;
default:
return 0;
}
}
else
{
try
{
return Int32.Parse(MCVersion);
}
catch
{
return -1;
}
}
}
public enum LoginResult { OtherError, ServiceUnavailable, SSLError, Success, WrongPassword, AccountMigrated, NotPremium };
/// <summary>
/// Allows to login to a premium Minecraft account using the Yggdrasil authentication scheme.
/// </summary>
/// <param name="user">Login</param>
/// <param name="pass">Password</param>
/// <param name="accesstoken">Will contain the access token returned by Minecraft.net, if the login is successful</param>
/// <param name="uuid">Will contain the player's UUID, needed for multiplayer</param>
/// <returns>Returns the status of the login (Success, Failure, etc.)</returns>
public static LoginResult GetLogin(ref string user, string pass, ref string accesstoken, ref string uuid)
{
try
{
string result = "";
string json_request = "{\"agent\": { \"name\": \"Minecraft\", \"version\": 1 }, \"username\": \"" + jsonEncode(user) + "\", \"password\": \"" + jsonEncode(pass) + "\" }";
int code = doHTTPSPost("authserver.mojang.com", "/authenticate", json_request, ref result);
if (code == 200)
{
if (result.Contains("availableProfiles\":[]}"))
{
return LoginResult.NotPremium;
}
else
{
string[] temp = result.Split(new string[] { "accessToken\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { accesstoken = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "name\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { user = temp[1].Split('"')[0]; }
temp = result.Split(new string[] { "availableProfiles\":[{\"id\":\"" }, StringSplitOptions.RemoveEmptyEntries);
if (temp.Length >= 2) { uuid = temp[1].Split('"')[0]; }
return LoginResult.Success;
}
}
else if (code == 403)
{
if (result.Contains("UserMigratedException"))
{
return LoginResult.AccountMigrated;
}
else return LoginResult.WrongPassword;
}
else if (code == 503)
{
return LoginResult.ServiceUnavailable;
}
else
{
ConsoleIO.WriteLineFormatted("§8Got error code from server: " + code);
return LoginResult.OtherError;
}
}
catch (System.Security.Authentication.AuthenticationException)
{
return LoginResult.SSLError;
}
catch
{
return LoginResult.OtherError;
}
}
/// <summary>
/// Check session using Mojang's Yggdrasil authentication scheme. Allows to join an online-mode server
/// </summary>
/// <param name="user">Username</param>
/// <param name="accesstoken">Session ID</param>
/// <param name="serverhash">Server ID</param>
/// <returns>TRUE if session was successfully checked</returns>
public static bool SessionCheck(string uuid, string accesstoken, string serverhash)
{
try
{
string result = "";
string json_request = "{\"accessToken\":\"" + accesstoken + "\",\"selectedProfile\":\"" + uuid + "\",\"serverId\":\"" + serverhash + "\"}";
int code = doHTTPSPost("sessionserver.mojang.com", "/session/minecraft/join", json_request, ref result);
return (result == "");
}
catch { return false; }
}
/// <summary>
/// Manual HTTPS request since we must directly use a TcpClient because of the proxy.
/// This method connects to the server, enables SSL, do the request and read the response.
/// </summary>
/// <param name="host">Host to connect to</param>
/// <param name="endpoint">Endpoint for making the request</param>
/// <param name="request">Request payload</param>
/// <param name="result">Request result</param>
/// <returns>HTTP Status code</returns>
private static int doHTTPSPost(string host, string endpoint, string request, ref string result)
{
TcpClient client = ProxyHandler.newTcpClient(host, 443);
SslStream stream = new SslStream(client.GetStream());
stream.AuthenticateAsClient(host);
List<String> http_request = new List<string>();
http_request.Add("POST " + endpoint + " HTTP/1.1");
http_request.Add("Host: " + host);
http_request.Add("User-Agent: MCC/" + Program.Version);
http_request.Add("Content-Type: application/json");
http_request.Add("Content-Length: " + Encoding.ASCII.GetBytes(request).Length);
http_request.Add("Connection: close");
http_request.Add("");
http_request.Add(request);
stream.Write(Encoding.ASCII.GetBytes(String.Join("\r\n", http_request.ToArray())));
System.IO.StreamReader sr = new System.IO.StreamReader(stream);
string raw_result = sr.ReadToEnd();
if (raw_result.StartsWith("HTTP/1.1"))
{
result = raw_result.Substring(raw_result.IndexOf("\r\n\r\n") + 4);
return Settings.str2int(raw_result.Split(' ')[1]);
}
else return 520; //Web server is returning an unknown error
}
/// <summary>
/// Encode a string to a json string.
/// Will convert special chars to \u0000 unicode escape sequences.
/// </summary>
/// <param name="text">Source text</param>
/// <returns>Encoded text</returns>
private static string jsonEncode(string text)
{
StringBuilder result = new StringBuilder();
foreach (char c in text)
{
if (char.IsLetterOrDigit(c))
{
result.Append(c);
}
else
{
result.Append("\\u");
result.Append(((int)c).ToString("x4"));
}
}
return result.ToString();
}
}
}

View file

@ -0,0 +1,60 @@
/*
* Authors: Benton Stark
*
* Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
using System;
using System.Net.Sockets;
using System.ComponentModel;
namespace Starksoft.Net.Proxy
{
/// <summary>
/// Event arguments class for the EncryptAsyncCompleted event.
/// </summary>
public class CreateConnectionAsyncCompletedEventArgs : AsyncCompletedEventArgs
{
private TcpClient _proxyConnection;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="error">Exception information generated by the event.</param>
/// <param name="cancelled">Cancelled event flag. This flag is set to true if the event was cancelled.</param>
/// <param name="proxyConnection">Proxy Connection. The initialized and open TcpClient proxy connection.</param>
public CreateConnectionAsyncCompletedEventArgs(Exception error, bool cancelled, TcpClient proxyConnection)
: base(error, cancelled, null)
{
_proxyConnection = proxyConnection;
}
/// <summary>
/// The proxy connection.
/// </summary>
public TcpClient ProxyConnection
{
get { return _proxyConnection; }
}
}
}

View file

@ -0,0 +1,77 @@
/*
* Authors: Benton Stark
*
* Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
using System;
using System.Runtime.Serialization;
namespace Starksoft.Net.Proxy
{
/// <summary>
/// This exception is thrown when a general, unexpected proxy error.
/// </summary>
[Serializable()]
public class ProxyException : Exception
{
/// <summary>
/// Constructor.
/// </summary>
public ProxyException()
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Exception message text.</param>
public ProxyException(string message)
: base(message)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="message">Exception message text.</param>
/// <param name="innerException">The inner exception object.</param>
public ProxyException(string message, Exception innerException)
:
base(message, innerException)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="info">Serialization information.</param>
/// <param name="context">Stream context information.</param>
protected ProxyException(SerializationInfo info,
StreamingContext context)
: base(info, context)
{
}
}
}

View file

@ -0,0 +1,527 @@
/*
* Authors: Benton Stark
*
* Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
using System;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Globalization;
using System.ComponentModel;
namespace Starksoft.Net.Proxy
{
/// <summary>
/// HTTP connection proxy class. This class implements the HTTP standard proxy protocol.
/// <para>
/// You can use this class to set up a connection to an HTTP proxy server. Calling the
/// CreateConnection() method initiates the proxy connection and returns a standard
/// System.Net.Socks.TcpClient object that can be used as normal. The proxy plumbing
/// is all handled for you.
/// </para>
/// <code>
///
/// </code>
/// </summary>
public class HttpProxyClient : IProxyClient
{
private string _proxyHost;
private int _proxyPort;
private string _proxyUsername;
private string _proxyPassword;
private HttpResponseCodes _respCode;
private string _respText;
private TcpClient _tcpClient;
private TcpClient _tcpClientCached;
private const int HTTP_PROXY_DEFAULT_PORT = 8080;
private const string HTTP_PROXY_CONNECT_CMD = "CONNECT {0}:{1} HTTP/1.0\r\nHOST {0}:{1}\r\n\r\n";
private const string HTTP_PROXY_AUTHENTICATE_CMD = "CONNECT {0}:{1} HTTP/1.0\r\nHOST {0}:{1}\r\nProxy-Authorization: Basic {2}\r\n\r\n";
private const int WAIT_FOR_DATA_INTERVAL = 50; // 50 ms
private const int WAIT_FOR_DATA_TIMEOUT = 15000; // 15 seconds
private const string PROXY_NAME = "HTTP";
private enum HttpResponseCodes
{
None = 0,
Continue = 100,
SwitchingProtocols = 101,
OK = 200,
Created = 201,
Accepted = 202,
NonAuthoritiveInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
MultipleChoices = 300,
MovedPermanetly = 301,
Found = 302,
SeeOther = 303,
NotModified = 304,
UserProxy = 305,
TemporaryRedirect = 307,
BadRequest = 400,
Unauthorized = 401,
PaymentRequired = 402,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
NotAcceptable = 406,
ProxyAuthenticantionRequired = 407,
RequestTimeout = 408,
Conflict = 409,
Gone = 410,
PreconditionFailed = 411,
RequestEntityTooLarge = 413,
RequestURITooLong = 414,
UnsupportedMediaType = 415,
RequestedRangeNotSatisfied = 416,
ExpectationFailed = 417,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HTTPVersionNotSupported = 505
}
/// <summary>
/// Constructor.
/// </summary>
public HttpProxyClient() { }
/// <summary>
/// Creates a HTTP proxy client object using the supplied TcpClient object connection.
/// </summary>
/// <param name="tcpClient">A TcpClient connection object.</param>
public HttpProxyClient(TcpClient tcpClient)
{
if (tcpClient == null)
throw new ArgumentNullException("tcpClient");
_tcpClientCached = tcpClient;
}
/// <summary>
/// Constructor. The default HTTP proxy port 8080 is used.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy.</param>
public HttpProxyClient(string proxyHost)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
_proxyHost = proxyHost;
_proxyPort = HTTP_PROXY_DEFAULT_PORT;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyPort">Port number to connect to the proxy server.</param>
/// <param name="proxyUsername">Username for the proxy server.</param>
/// <param name="proxyPassword">Password for the proxy server.</param>
public HttpProxyClient(string proxyHost, int proxyPort, string proxyUsername, string proxyPassword)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
if (String.IsNullOrEmpty(proxyUsername))
throw new ArgumentNullException("proxyUsername");
if (proxyPassword == null)
throw new ArgumentNullException("proxyPassword");
if (proxyPort <= 0 || proxyPort > 65535)
throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
_proxyHost = proxyHost;
_proxyPort = proxyPort;
_proxyUsername = proxyUsername;
_proxyPassword = proxyPassword;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyPort">Port number for the proxy server.</param>
public HttpProxyClient(string proxyHost, int proxyPort)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
if (proxyPort <= 0 || proxyPort > 65535)
throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
_proxyHost = proxyHost;
_proxyPort = proxyPort;
}
/// <summary>
/// Gets or sets host name or IP address of the proxy server.
/// </summary>
public string ProxyHost
{
get { return _proxyHost; }
set { _proxyHost = value; }
}
/// <summary>
/// Gets or sets port number for the proxy server.
/// </summary>
public int ProxyPort
{
get { return _proxyPort; }
set { _proxyPort = value; }
}
/// <summary>
/// Gets String representing the name of the proxy.
/// </summary>
/// <remarks>This property will always return the value 'HTTP'</remarks>
public string ProxyName
{
get { return PROXY_NAME; }
}
/// <summary>
/// Gets or sets the TcpClient object.
/// This property can be set prior to executing CreateConnection to use an existing TcpClient connection.
/// </summary>
public TcpClient TcpClient
{
get { return _tcpClientCached; }
set { _tcpClientCached = value; }
}
/// <summary>
/// Creates a remote TCP connection through a proxy server to the destination host on the destination port.
/// </summary>
/// <param name="destinationHost">Destination host name or IP address.</param>
/// <param name="destinationPort">Port number to connect to on the destination host.</param>
/// <returns>
/// Returns an open TcpClient object that can be used normally to communicate
/// with the destination server
/// </returns>
/// <remarks>
/// This method creates a connection to the proxy server and instructs the proxy server
/// to make a pass through connection to the specified destination host on the specified
/// port.
/// </remarks>
public TcpClient CreateConnection(string destinationHost, int destinationPort)
{
try
{
// if we have no cached tcpip connection then create one
if (_tcpClientCached == null)
{
if (String.IsNullOrEmpty(_proxyHost))
throw new ProxyException("ProxyHost property must contain a value.");
if (_proxyPort <= 0 || _proxyPort > 65535)
throw new ProxyException("ProxyPort value must be greater than zero and less than 65535");
// create new tcp client object to the proxy server
_tcpClient = new TcpClient();
// attempt to open the connection
_tcpClient.Connect(_proxyHost, _proxyPort);
}
else
{
_tcpClient = _tcpClientCached;
}
// send connection command to proxy host for the specified destination host and port
SendConnectionCommand(destinationHost, destinationPort);
// remove the private reference to the tcp client so the proxy object does not keep it
// return the open proxied tcp client object to the caller for normal use
TcpClient rtn = _tcpClient;
_tcpClient = null;
return rtn;
}
catch (SocketException ex)
{
throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "Connection to proxy host {0} on port {1} failed.", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient)), ex);
}
}
private void SendConnectionCommand(string host, int port)
{
NetworkStream stream = _tcpClient.GetStream();
string connectCmd = CreateCommandString(host, port);
byte[] request = ASCIIEncoding.ASCII.GetBytes(connectCmd);
// send the connect request
stream.Write(request, 0, request.Length);
// wait for the proxy server to respond
WaitForData(stream);
// PROXY SERVER RESPONSE
// =======================================================================
//HTTP/1.0 200 Connection Established<CR><LF>
//[.... other HTTP header lines ending with <CR><LF>..
//ignore all of them]
//<CR><LF> // Last Empty Line
// create an byte response array
byte[] response = new byte[_tcpClient.ReceiveBufferSize];
StringBuilder sbuilder = new StringBuilder();
int bytes = 0;
long total = 0;
do
{
bytes = stream.Read(response, 0, _tcpClient.ReceiveBufferSize);
total += bytes;
sbuilder.Append(System.Text.ASCIIEncoding.UTF8.GetString(response, 0, bytes));
} while (stream.DataAvailable);
ParseResponse(sbuilder.ToString());
// evaluate the reply code for an error condition
if (_respCode != HttpResponseCodes.OK)
HandleProxyCommandError(host, port);
}
private string CreateCommandString(string host, int port)
{
string connectCmd;
if (!string.IsNullOrEmpty(_proxyUsername))
{
// gets the user/pass into base64 encoded string in the form of [username]:[password]
string auth = Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", _proxyUsername, _proxyPassword)));
// PROXY SERVER REQUEST
// =======================================================================
//CONNECT starksoft.com:443 HTTP/1.0<CR><LF>
//HOST starksoft.com:443<CR><LF>
//Proxy-Authorization: username:password<CR><LF>
// NOTE: username:password string will be base64 encoded as one
// concatenated string
//[... other HTTP header lines ending with <CR><LF> if required]>
//<CR><LF> // Last Empty Line
connectCmd = String.Format(CultureInfo.InvariantCulture, HTTP_PROXY_AUTHENTICATE_CMD, host, port.ToString(CultureInfo.InvariantCulture), auth);
}
else
{
// PROXY SERVER REQUEST
// =======================================================================
//CONNECT starksoft.com:443 HTTP/1.0 <CR><LF>
//HOST starksoft.com:443<CR><LF>
//[... other HTTP header lines ending with <CR><LF> if required]>
//<CR><LF> // Last Empty Line
connectCmd = String.Format(CultureInfo.InvariantCulture, HTTP_PROXY_CONNECT_CMD + "\r\n", host, port.ToString(CultureInfo.InvariantCulture));
}
return connectCmd;
}
private void HandleProxyCommandError(string host, int port)
{
string msg;
switch (_respCode)
{
case HttpResponseCodes.None:
msg = String.Format(CultureInfo.InvariantCulture, "Proxy destination {0} on port {1} failed to return a recognized HTTP response code. Server response: {2}", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient), _respText);
break;
case HttpResponseCodes.BadGateway:
//HTTP/1.1 502 Proxy Error (The specified Secure Sockets Layer (SSL) port is not allowed. ISA Server is not configured to allow SSL requests from this port. Most Web browsers use port 443 for SSL requests.)
msg = String.Format(CultureInfo.InvariantCulture, "Proxy destination {0} on port {1} responded with a 502 code - Bad Gateway. If you are connecting to a Microsoft ISA destination please refer to knowledge based article Q283284 for more information. Server response: {2}", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient), _respText);
break;
default:
msg = String.Format(CultureInfo.InvariantCulture, "Proxy destination {0} on port {1} responded with a {2} code - {3}", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient), ((int)_respCode).ToString(CultureInfo.InvariantCulture), _respText);
break;
}
// throw a new application exception
throw new ProxyException(msg);
}
private void WaitForData(NetworkStream stream)
{
int sleepTime = 0;
while (!stream.DataAvailable)
{
Thread.Sleep(WAIT_FOR_DATA_INTERVAL);
sleepTime += WAIT_FOR_DATA_INTERVAL;
if (sleepTime > WAIT_FOR_DATA_TIMEOUT)
throw new ProxyException(String.Format("A timeout while waiting for the proxy server at {0} on port {1} to respond.", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient) ));
}
}
private void ParseResponse(string response)
{
string[] data = null;
// get rid of the LF character if it exists and then split the string on all CR
data = response.Replace('\n', ' ').Split('\r');
ParseCodeAndText(data[0]);
}
private void ParseCodeAndText(string line)
{
int begin = 0;
int end = 0;
string val = null;
if (line.IndexOf("HTTP") == -1)
throw new ProxyException(String.Format("No HTTP response received from proxy destination. Server response: {0}.", line));
begin = line.IndexOf(" ") + 1;
end = line.IndexOf(" ", begin);
val = line.Substring(begin, end - begin);
Int32 code = 0;
if (!Int32.TryParse(val, out code))
throw new ProxyException(String.Format("An invalid response code was received from proxy destination. Server response: {0}.", line));
_respCode = (HttpResponseCodes)code;
_respText = line.Substring(end + 1).Trim();
}
#region "Async Methods"
private BackgroundWorker _asyncWorker;
private Exception _asyncException;
bool _asyncCancelled;
/// <summary>
/// Gets a value indicating whether an asynchronous operation is running.
/// </summary>
/// <remarks>Returns true if an asynchronous operation is running; otherwise, false.
/// </remarks>
public bool IsBusy
{
get { return _asyncWorker == null ? false : _asyncWorker.IsBusy; }
}
/// <summary>
/// Gets a value indicating whether an asynchronous operation is cancelled.
/// </summary>
/// <remarks>Returns true if an asynchronous operation is cancelled; otherwise, false.
/// </remarks>
public bool IsAsyncCancelled
{
get { return _asyncCancelled; }
}
/// <summary>
/// Cancels any asychronous operation that is currently active.
/// </summary>
public void CancelAsync()
{
if (_asyncWorker != null && !_asyncWorker.CancellationPending && _asyncWorker.IsBusy)
{
_asyncCancelled = true;
_asyncWorker.CancelAsync();
}
}
private void CreateAsyncWorker()
{
if (_asyncWorker != null)
_asyncWorker.Dispose();
_asyncException = null;
_asyncWorker = null;
_asyncCancelled = false;
_asyncWorker = new BackgroundWorker();
}
/// <summary>
/// Event handler for CreateConnectionAsync method completed.
/// </summary>
public event EventHandler<CreateConnectionAsyncCompletedEventArgs> CreateConnectionAsyncCompleted;
/// <summary>
/// Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port.
/// </summary>
/// <param name="destinationHost">Destination host name or IP address.</param>
/// <param name="destinationPort">Port number to connect to on the destination host.</param>
/// <returns>
/// Returns an open TcpClient object that can be used normally to communicate
/// with the destination server
/// </returns>
/// <remarks>
/// This method creates a connection to the proxy server and instructs the proxy server
/// to make a pass through connection to the specified destination host on the specified
/// port.
/// </remarks>
public void CreateConnectionAsync(string destinationHost, int destinationPort)
{
if (_asyncWorker != null && _asyncWorker.IsBusy)
throw new InvalidOperationException("The HttpProxy object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time.");
CreateAsyncWorker();
_asyncWorker.WorkerSupportsCancellation = true;
_asyncWorker.DoWork += new DoWorkEventHandler(CreateConnectionAsync_DoWork);
_asyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CreateConnectionAsync_RunWorkerCompleted);
Object[] args = new Object[2];
args[0] = destinationHost;
args[1] = destinationPort;
_asyncWorker.RunWorkerAsync(args);
}
private void CreateConnectionAsync_DoWork(object sender, DoWorkEventArgs e)
{
try
{
Object[] args = (Object[])e.Argument;
e.Result = CreateConnection((string)args[0], (int)args[1]);
}
catch (Exception ex)
{
_asyncException = ex;
}
}
private void CreateConnectionAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (CreateConnectionAsyncCompleted != null)
CreateConnectionAsyncCompleted(this, new CreateConnectionAsyncCompletedEventArgs(_asyncException, _asyncCancelled, (TcpClient)e.Result));
}
#endregion
}
}

View file

@ -0,0 +1,97 @@
/*
* Authors: Benton Stark
*
* Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
namespace Starksoft.Net.Proxy
{
/// <summary>
/// Proxy client interface. This is the interface that all proxy clients must implement.
/// </summary>
public interface IProxyClient
{
/// <summary>
/// Event handler for CreateConnectionAsync method completed.
/// </summary>
event EventHandler<CreateConnectionAsyncCompletedEventArgs> CreateConnectionAsyncCompleted;
/// <summary>
/// Gets or sets proxy host name or IP address.
/// </summary>
string ProxyHost { get; set; }
/// <summary>
/// Gets or sets proxy port number.
/// </summary>
int ProxyPort { get; set; }
/// <summary>
/// Gets String representing the name of the proxy.
/// </summary>
string ProxyName { get; }
/// <summary>
/// Gets or set the TcpClient object if one was specified in the constructor.
/// </summary>
TcpClient TcpClient { get; set; }
/// <summary>
/// Creates a remote TCP connection through a proxy server to the destination host on the destination port.
/// </summary>
/// <param name="destinationHost">Destination host name or IP address.</param>
/// <param name="destinationPort">Port number to connect to on the destination host.</param>
/// <returns>
/// Returns an open TcpClient object that can be used normally to communicate
/// with the destination server
/// </returns>
/// <remarks>
/// This method creates a connection to the proxy server and instructs the proxy server
/// to make a pass through connection to the specified destination host on the specified
/// port.
/// </remarks>
TcpClient CreateConnection(string destinationHost, int destinationPort);
/// <summary>
/// Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port.
/// </summary>
/// <param name="destinationHost">Destination host name or IP address.</param>
/// <param name="destinationPort">Port number to connect to on the destination host.</param>
/// <returns>
/// Returns an open TcpClient object that can be used normally to communicate
/// with the destination server
/// </returns>
/// <remarks>
/// This method creates a connection to the proxy server and instructs the proxy server
/// to make a pass through connection to the specified destination host on the specified
/// port.
/// </remarks>
void CreateConnectionAsync(string destinationHost, int destinationPort);
}
}

View file

@ -0,0 +1,208 @@
/*
* Authors: Benton Stark
*
* Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
namespace Starksoft.Net.Proxy
{
/// <summary>
/// The type of proxy.
/// </summary>
public enum ProxyType
{
/// <summary>
/// No Proxy specified. Note this option will cause an exception to be thrown if used to create a proxy object by the factory.
/// </summary>
None,
/// <summary>
/// HTTP Proxy
/// </summary>
Http,
/// <summary>
/// SOCKS v4 Proxy
/// </summary>
Socks4,
/// <summary>
/// SOCKS v4a Proxy
/// </summary>
Socks4a,
/// <summary>
/// SOCKS v5 Proxy
/// </summary>
Socks5
}
/// <summary>
/// Factory class for creating new proxy client objects.
/// </summary>
/// <remarks>
/// <code>
/// // create an instance of the client proxy factory
/// ProxyClientFactory factory = new ProxyClientFactory();
///
/// // use the proxy client factory to generically specify the type of proxy to create
/// // the proxy factory method CreateProxyClient returns an IProxyClient object
/// IProxyClient proxy = factory.CreateProxyClient(ProxyType.Http, "localhost", 6588);
///
/// // create a connection through the proxy to www.starksoft.com over port 80
/// System.Net.Sockets.TcpClient tcpClient = proxy.CreateConnection("www.starksoft.com", 80);
/// </code>
/// </remarks>
public class ProxyClientFactory
{
/// <summary>
/// Factory method for creating new proxy client objects.
/// </summary>
/// <param name="type">The type of proxy client to create.</param>
/// <returns>Proxy client object.</returns>
public IProxyClient CreateProxyClient(ProxyType type)
{
if (type == ProxyType.None)
throw new ArgumentOutOfRangeException("type");
switch (type)
{
case ProxyType.Http:
return new HttpProxyClient();
case ProxyType.Socks4:
return new Socks4ProxyClient();
case ProxyType.Socks4a:
return new Socks4aProxyClient();
case ProxyType.Socks5:
return new Socks5ProxyClient();
default:
throw new ProxyException(String.Format("Unknown proxy type {0}.", type.ToString()));
}
}
/// <summary>
/// Factory method for creating new proxy client objects using an existing TcpClient connection object.
/// </summary>
/// <param name="type">The type of proxy client to create.</param>
/// <param name="tcpClient">Open TcpClient object.</param>
/// <returns>Proxy client object.</returns>
public IProxyClient CreateProxyClient(ProxyType type, TcpClient tcpClient)
{
if (type == ProxyType.None)
throw new ArgumentOutOfRangeException("type");
switch (type)
{
case ProxyType.Http:
return new HttpProxyClient(tcpClient);
case ProxyType.Socks4:
return new Socks4ProxyClient(tcpClient);
case ProxyType.Socks4a:
return new Socks4aProxyClient(tcpClient);
case ProxyType.Socks5:
return new Socks5ProxyClient(tcpClient);
default:
throw new ProxyException(String.Format("Unknown proxy type {0}.", type.ToString()));
}
}
/// <summary>
/// Factory method for creating new proxy client objects.
/// </summary>
/// <param name="type">The type of proxy client to create.</param>
/// <param name="proxyHost">The proxy host or IP address.</param>
/// <param name="proxyPort">The proxy port number.</param>
/// <returns>Proxy client object.</returns>
public IProxyClient CreateProxyClient(ProxyType type, string proxyHost, int proxyPort)
{
if (type == ProxyType.None)
throw new ArgumentOutOfRangeException("type");
switch (type)
{
case ProxyType.Http:
return new HttpProxyClient(proxyHost, proxyPort);
case ProxyType.Socks4:
return new Socks4ProxyClient(proxyHost, proxyPort);
case ProxyType.Socks4a:
return new Socks4aProxyClient(proxyHost, proxyPort);
case ProxyType.Socks5:
return new Socks5ProxyClient(proxyHost, proxyPort);
default:
throw new ProxyException(String.Format("Unknown proxy type {0}.", type.ToString()));
}
}
/// <summary>
/// Factory method for creating new proxy client objects.
/// </summary>
/// <param name="type">The type of proxy client to create.</param>
/// <param name="proxyHost">The proxy host or IP address.</param>
/// <param name="proxyPort">The proxy port number.</param>
/// <param name="proxyUsername">The proxy username. This parameter is only used by Http, Socks4 and Socks5 proxy objects.</param>
/// <param name="proxyPassword">The proxy user password. This parameter is only used Http, Socks5 proxy objects.</param>
/// <returns>Proxy client object.</returns>
public IProxyClient CreateProxyClient(ProxyType type, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword)
{
if (type == ProxyType.None)
throw new ArgumentOutOfRangeException("type");
switch (type)
{
case ProxyType.Http:
return new HttpProxyClient(proxyHost, proxyPort, proxyUsername, proxyPassword);
case ProxyType.Socks4:
return new Socks4ProxyClient(proxyHost, proxyPort, proxyUsername);
case ProxyType.Socks4a:
return new Socks4aProxyClient(proxyHost, proxyPort, proxyUsername);
case ProxyType.Socks5:
return new Socks5ProxyClient(proxyHost, proxyPort, proxyUsername, proxyPassword);
default:
throw new ProxyException(String.Format("Unknown proxy type {0}.", type.ToString()));
}
}
/// <summary>
/// Factory method for creating new proxy client objects.
/// </summary>
/// <param name="type">The type of proxy client to create.</param>
/// <param name="tcpClient">Open TcpClient object.</param>
/// <param name="proxyHost">The proxy host or IP address.</param>
/// <param name="proxyPort">The proxy port number.</param>
/// <param name="proxyUsername">The proxy username. This parameter is only used by Http, Socks4 and Socks5 proxy objects.</param>
/// <param name="proxyPassword">The proxy user password. This parameter is only used Http, Socks5 proxy objects.</param>
/// <returns>Proxy client object.</returns>
public IProxyClient CreateProxyClient(ProxyType type, TcpClient tcpClient, string proxyHost, int proxyPort, string proxyUsername, string proxyPassword)
{
IProxyClient c = CreateProxyClient(type, proxyHost, proxyPort, proxyUsername, proxyPassword);
c.TcpClient = tcpClient;
return c;
}
}
}

View file

@ -0,0 +1,585 @@
/*
* Authors: Benton Stark
*
* Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Globalization;
using System.IO;
using System.Threading;
using System.ComponentModel;
namespace Starksoft.Net.Proxy
{
/// <summary>
/// Socks4 connection proxy class. This class implements the Socks4 standard proxy protocol.
/// </summary>
/// <remarks>
/// This class implements the Socks4 proxy protocol standard for TCP communciations.
/// </remarks>
public class Socks4ProxyClient : IProxyClient
{
private const int WAIT_FOR_DATA_INTERVAL = 50; // 50 ms
private const int WAIT_FOR_DATA_TIMEOUT = 15000; // 15 seconds
private const string PROXY_NAME = "SOCKS4";
private TcpClient _tcpClient;
private TcpClient _tcpClientCached;
private string _proxyHost;
private int _proxyPort;
private string _proxyUserId;
/// <summary>
/// Default Socks4 proxy port.
/// </summary>
internal const int SOCKS_PROXY_DEFAULT_PORT = 1080;
/// <summary>
/// Socks4 version number.
/// </summary>
internal const byte SOCKS4_VERSION_NUMBER = 4;
/// <summary>
/// Socks4 connection command value.
/// </summary>
internal const byte SOCKS4_CMD_CONNECT = 0x01;
/// <summary>
/// Socks4 bind command value.
/// </summary>
internal const byte SOCKS4_CMD_BIND = 0x02;
/// <summary>
/// Socks4 reply request grant response value.
/// </summary>
internal const byte SOCKS4_CMD_REPLY_REQUEST_GRANTED = 90;
/// <summary>
/// Socks4 reply request rejected or failed response value.
/// </summary>
internal const byte SOCKS4_CMD_REPLY_REQUEST_REJECTED_OR_FAILED = 91;
/// <summary>
/// Socks4 reply request rejected becauase the proxy server can not connect to the IDENTD server value.
/// </summary>
internal const byte SOCKS4_CMD_REPLY_REQUEST_REJECTED_CANNOT_CONNECT_TO_IDENTD = 92;
/// <summary>
/// Socks4 reply request rejected because of a different IDENTD server.
/// </summary>
internal const byte SOCKS4_CMD_REPLY_REQUEST_REJECTED_DIFFERENT_IDENTD = 93;
/// <summary>
/// Create a Socks4 proxy client object. The default proxy port 1080 is used.
/// </summary>
public Socks4ProxyClient() { }
/// <summary>
/// Creates a Socks4 proxy client object using the supplied TcpClient object connection.
/// </summary>
/// <param name="tcpClient">A TcpClient connection object.</param>
public Socks4ProxyClient(TcpClient tcpClient)
{
if (tcpClient == null)
throw new ArgumentNullException("tcpClient");
_tcpClientCached = tcpClient;
}
/// <summary>
/// Create a Socks4 proxy client object. The default proxy port 1080 is used.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyUserId">Proxy user identification information.</param>
public Socks4ProxyClient(string proxyHost, string proxyUserId)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
if (proxyUserId == null)
throw new ArgumentNullException("proxyUserId");
_proxyHost = proxyHost;
_proxyPort = SOCKS_PROXY_DEFAULT_PORT;
_proxyUserId = proxyUserId;
}
/// <summary>
/// Create a Socks4 proxy client object.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyPort">Port used to connect to proxy server.</param>
/// <param name="proxyUserId">Proxy user identification information.</param>
public Socks4ProxyClient(string proxyHost, int proxyPort, string proxyUserId)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
if (proxyPort <= 0 || proxyPort > 65535)
throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
if (proxyUserId == null)
throw new ArgumentNullException("proxyUserId");
_proxyHost = proxyHost;
_proxyPort = proxyPort;
_proxyUserId = proxyUserId;
}
/// <summary>
/// Create a Socks4 proxy client object. The default proxy port 1080 is used.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
public Socks4ProxyClient(string proxyHost)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
_proxyHost = proxyHost;
_proxyPort = SOCKS_PROXY_DEFAULT_PORT;
}
/// <summary>
/// Create a Socks4 proxy client object.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyPort">Port used to connect to proxy server.</param>
public Socks4ProxyClient(string proxyHost, int proxyPort)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
if (proxyPort <= 0 || proxyPort > 65535)
throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
_proxyHost = proxyHost;
_proxyPort = proxyPort;
}
/// <summary>
/// Gets or sets host name or IP address of the proxy server.
/// </summary>
public string ProxyHost
{
get { return _proxyHost; }
set { _proxyHost = value; }
}
/// <summary>
/// Gets or sets port used to connect to proxy server.
/// </summary>
public int ProxyPort
{
get { return _proxyPort; }
set { _proxyPort = value; }
}
/// <summary>
/// Gets String representing the name of the proxy.
/// </summary>
/// <remarks>This property will always return the value 'SOCKS4'</remarks>
virtual public string ProxyName
{
get { return PROXY_NAME; }
}
/// <summary>
/// Gets or sets proxy user identification information.
/// </summary>
public string ProxyUserId
{
get { return _proxyUserId; }
set { _proxyUserId = value; }
}
/// <summary>
/// Gets or sets the TcpClient object.
/// This property can be set prior to executing CreateConnection to use an existing TcpClient connection.
/// </summary>
public TcpClient TcpClient
{
get { return _tcpClientCached; }
set { _tcpClientCached = value; }
}
/// <summary>
/// Creates a TCP connection to the destination host through the proxy server
/// host.
/// </summary>
/// <param name="destinationHost">Destination host name or IP address of the destination server.</param>
/// <param name="destinationPort">Port number to connect to on the destination server.</param>
/// <returns>
/// Returns an open TcpClient object that can be used normally to communicate
/// with the destination server
/// </returns>
/// <remarks>
/// This method creates a connection to the proxy server and instructs the proxy server
/// to make a pass through connection to the specified destination host on the specified
/// port.
/// </remarks>
public TcpClient CreateConnection(string destinationHost, int destinationPort)
{
if (String.IsNullOrEmpty(destinationHost))
throw new ArgumentNullException("destinationHost");
if (destinationPort <= 0 || destinationPort > 65535)
throw new ArgumentOutOfRangeException("destinationPort", "port must be greater than zero and less than 65535");
try
{
// if we have no cached tcpip connection then create one
if (_tcpClientCached == null)
{
if (String.IsNullOrEmpty(_proxyHost))
throw new ProxyException("ProxyHost property must contain a value.");
if (_proxyPort <= 0 || _proxyPort > 65535)
throw new ProxyException("ProxyPort value must be greater than zero and less than 65535");
// create new tcp client object to the proxy server
_tcpClient = new TcpClient();
// attempt to open the connection
_tcpClient.Connect(_proxyHost, _proxyPort);
}
else
{
_tcpClient = _tcpClientCached;
}
// send connection command to proxy host for the specified destination host and port
SendCommand(_tcpClient.GetStream(), SOCKS4_CMD_CONNECT, destinationHost, destinationPort, _proxyUserId);
// remove the private reference to the tcp client so the proxy object does not keep it
// return the open proxied tcp client object to the caller for normal use
TcpClient rtn = _tcpClient;
_tcpClient = null;
return rtn;
}
catch (Exception ex)
{
throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "Connection to proxy host {0} on port {1} failed.", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient)), ex);
}
}
/// <summary>
/// Sends a command to the proxy server.
/// </summary>
/// <param name="proxy">Proxy server data stream.</param>
/// <param name="command">Proxy byte command to execute.</param>
/// <param name="destinationHost">Destination host name or IP address.</param>
/// <param name="destinationPort">Destination port number</param>
/// <param name="userId">IDENTD user ID value.</param>
internal virtual void SendCommand(NetworkStream proxy, byte command, string destinationHost, int destinationPort, string userId)
{
// PROXY SERVER REQUEST
// The client connects to the SOCKS server and sends a CONNECT request when
// it wants to establish a connection to an application server. The client
// includes in the request packet the IP address and the port number of the
// destination host, and userid, in the following format.
//
// +----+----+----+----+----+----+----+----+----+----+....+----+
// | VN | CD | DSTPORT | DSTIP | USERID |NULL|
// +----+----+----+----+----+----+----+----+----+----+....+----+
// # of bytes: 1 1 2 4 variable 1
//
// VN is the SOCKS protocol version number and should be 4. CD is the
// SOCKS command code and should be 1 for CONNECT request. NULL is a byte
// of all zero bits.
// userId needs to be a zero length string so that the GetBytes method
// works properly
if (userId == null)
userId = "";
byte[] destIp = GetIPAddressBytes(destinationHost);
byte[] destPort = GetDestinationPortBytes(destinationPort);
byte[] userIdBytes = ASCIIEncoding.ASCII.GetBytes(userId);
byte[] request = new byte[9 + userIdBytes.Length];
// set the bits on the request byte array
request[0] = SOCKS4_VERSION_NUMBER;
request[1] = command;
destPort.CopyTo(request, 2);
destIp.CopyTo(request, 4);
userIdBytes.CopyTo(request, 8);
request[8 + userIdBytes.Length] = 0x00; // null (byte with all zeros) terminator for userId
// send the connect request
proxy.Write(request, 0, request.Length);
// wait for the proxy server to respond
WaitForData(proxy);
// PROXY SERVER RESPONSE
// The SOCKS server checks to see whether such a request should be granted
// based on any combination of source IP address, destination IP address,
// destination port number, the userid, and information it may obtain by
// consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS
// server makes a connection to the specified port of the destination host.
// A reply packet is sent to the client when this connection is established,
// or when the request is rejected or the operation fails.
//
// +----+----+----+----+----+----+----+----+
// | VN | CD | DSTPORT | DSTIP |
// +----+----+----+----+----+----+----+----+
// # of bytes: 1 1 2 4
//
// VN is the version of the reply code and should be 0. CD is the result
// code with one of the following values:
//
// 90: request granted
// 91: request rejected or failed
// 92: request rejected becuase SOCKS server cannot connect to
// identd on the client
// 93: request rejected because the client program and identd
// report different user-ids
//
// The remaining fields are ignored.
//
// The SOCKS server closes its connection immediately after notifying
// the client of a failed or rejected request. For a successful request,
// the SOCKS server gets ready to relay traffic on both directions. This
// enables the client to do I/O on its connection as if it were directly
// connected to the application server.
// create an 8 byte response array
byte[] response = new byte[8];
// read the resonse from the network stream
proxy.Read(response, 0, 8);
// evaluate the reply code for an error condition
if (response[1] != SOCKS4_CMD_REPLY_REQUEST_GRANTED)
HandleProxyCommandError(response, destinationHost, destinationPort);
}
/// <summary>
/// Translate the host name or IP address to a byte array.
/// </summary>
/// <param name="destinationHost">Host name or IP address.</param>
/// <returns>Byte array representing IP address in bytes.</returns>
internal byte[] GetIPAddressBytes(string destinationHost)
{
IPAddress ipAddr = null;
// if the address doesn't parse then try to resolve with dns
if (!IPAddress.TryParse(destinationHost, out ipAddr))
{
try
{
ipAddr = Dns.GetHostEntry(destinationHost).AddressList[0];
}
catch (Exception ex)
{
throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "A error occurred while attempting to DNS resolve the host name {0}.", destinationHost), ex);
}
}
// return address bytes
return ipAddr.GetAddressBytes();
}
/// <summary>
/// Translate the destination port value to a byte array.
/// </summary>
/// <param name="value">Destination port.</param>
/// <returns>Byte array representing an 16 bit port number as two bytes.</returns>
internal byte[] GetDestinationPortBytes(int value)
{
byte[] array = new byte[2];
array[0] = Convert.ToByte(value / 256);
array[1] = Convert.ToByte(value % 256);
return array;
}
/// <summary>
/// Receive a byte array from the proxy server and determine and handle and errors that may have occurred.
/// </summary>
/// <param name="response">Proxy server command response as a byte array.</param>
/// <param name="destinationHost">Destination host.</param>
/// <param name="destinationPort">Destination port number.</param>
internal void HandleProxyCommandError(byte[] response, string destinationHost, int destinationPort)
{
if (response == null)
throw new ArgumentNullException("response");
// extract the reply code
byte replyCode = response[1];
// extract the ip v4 address (4 bytes)
byte[] ipBytes = new byte[4];
for (int i = 0; i < 4; i++)
ipBytes[i] = response[i + 4];
// convert the ip address to an IPAddress object
IPAddress ipAddr = new IPAddress(ipBytes);
// extract the port number big endian (2 bytes)
byte[] portBytes = new byte[2];
portBytes[0] = response[3];
portBytes[1] = response[2];
Int16 port = BitConverter.ToInt16(portBytes, 0);
// translate the reply code error number to human readable text
string proxyErrorText;
switch (replyCode)
{
case SOCKS4_CMD_REPLY_REQUEST_REJECTED_OR_FAILED:
proxyErrorText = "connection request was rejected or failed";
break;
case SOCKS4_CMD_REPLY_REQUEST_REJECTED_CANNOT_CONNECT_TO_IDENTD:
proxyErrorText = "connection request was rejected because SOCKS destination cannot connect to identd on the client";
break;
case SOCKS4_CMD_REPLY_REQUEST_REJECTED_DIFFERENT_IDENTD:
proxyErrorText = "connection request rejected because the client program and identd report different user-ids";
break;
default:
proxyErrorText = String.Format(CultureInfo.InvariantCulture, "proxy client received an unknown reply with the code value '{0}' from the proxy destination", replyCode.ToString(CultureInfo.InvariantCulture));
break;
}
// build the exeception message string
string exceptionMsg = String.Format(CultureInfo.InvariantCulture, "The {0} concerning destination host {1} port number {2}. The destination reported the host as {3} port {4}.", proxyErrorText, destinationHost, destinationPort, ipAddr.ToString(), port.ToString(CultureInfo.InvariantCulture));
// throw a new application exception
throw new ProxyException(exceptionMsg);
}
internal void WaitForData(NetworkStream stream)
{
int sleepTime = 0;
while (!stream.DataAvailable)
{
Thread.Sleep(WAIT_FOR_DATA_INTERVAL);
sleepTime += WAIT_FOR_DATA_INTERVAL;
if (sleepTime > WAIT_FOR_DATA_TIMEOUT)
throw new ProxyException("A timeout while waiting for the proxy destination to respond.");
}
}
#region "Async Methods"
private BackgroundWorker _asyncWorker;
private Exception _asyncException;
bool _asyncCancelled;
/// <summary>
/// Gets a value indicating whether an asynchronous operation is running.
/// </summary>
/// <remarks>Returns true if an asynchronous operation is running; otherwise, false.
/// </remarks>
public bool IsBusy
{
get { return _asyncWorker == null ? false : _asyncWorker.IsBusy; }
}
/// <summary>
/// Gets a value indicating whether an asynchronous operation is cancelled.
/// </summary>
/// <remarks>Returns true if an asynchronous operation is cancelled; otherwise, false.
/// </remarks>
public bool IsAsyncCancelled
{
get { return _asyncCancelled; }
}
/// <summary>
/// Cancels any asychronous operation that is currently active.
/// </summary>
public void CancelAsync()
{
if (_asyncWorker != null && !_asyncWorker.CancellationPending && _asyncWorker.IsBusy)
{
_asyncCancelled = true;
_asyncWorker.CancelAsync();
}
}
private void CreateAsyncWorker()
{
if (_asyncWorker != null)
_asyncWorker.Dispose();
_asyncException = null;
_asyncWorker = null;
_asyncCancelled = false;
_asyncWorker = new BackgroundWorker();
}
/// <summary>
/// Event handler for CreateConnectionAsync method completed.
/// </summary>
public event EventHandler<CreateConnectionAsyncCompletedEventArgs> CreateConnectionAsyncCompleted;
/// <summary>
/// Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port
/// using the supplied open TcpClient object with an open connection to proxy server.
/// </summary>
/// <param name="destinationHost">Destination host name or IP address.</param>
/// <param name="destinationPort">Port number to connect to on the destination host.</param>
/// <returns>
/// Returns TcpClient object that can be used normally to communicate
/// with the destination server.
/// </returns>
/// <remarks>
/// This instructs the proxy server to make a pass through connection to the specified destination host on the specified
/// port.
/// </remarks>
public void CreateConnectionAsync(string destinationHost, int destinationPort)
{
if (_asyncWorker != null && _asyncWorker.IsBusy)
throw new InvalidOperationException("The Socks4/4a object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time.");
CreateAsyncWorker();
_asyncWorker.WorkerSupportsCancellation = true;
_asyncWorker.DoWork += new DoWorkEventHandler(CreateConnectionAsync_DoWork);
_asyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CreateConnectionAsync_RunWorkerCompleted);
Object[] args = new Object[2];
args[0] = destinationHost;
args[1] = destinationPort;
_asyncWorker.RunWorkerAsync(args);
}
private void CreateConnectionAsync_DoWork(object sender, DoWorkEventArgs e)
{
try
{
Object[] args = (Object[])e.Argument;
e.Result = CreateConnection((string)args[0], (int)args[1]);
}
catch (Exception ex)
{
_asyncException = ex;
}
}
private void CreateConnectionAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (CreateConnectionAsyncCompleted != null)
CreateConnectionAsyncCompleted(this, new CreateConnectionAsyncCompletedEventArgs(_asyncException, _asyncCancelled, (TcpClient)e.Result));
}
#endregion
}
}

View file

@ -0,0 +1,230 @@
/*
* Authors: Benton Stark
*
* Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace Starksoft.Net.Proxy
{
/// <summary>
/// Socks4a connection proxy class. This class implements the Socks4a standard proxy protocol
/// which is an extension of the Socks4 protocol
/// </summary>
/// <remarks>
/// In Socks version 4A if the client cannot resolve the destination host's domain name
/// to find its IP address the server will attempt to resolve it.
/// </remarks>
public class Socks4aProxyClient : Socks4ProxyClient
{
private const string PROXY_NAME = "SOCKS4a";
/// <summary>
/// Default constructor.
/// </summary>
public Socks4aProxyClient()
: base()
{ }
/// <summary>
/// Creates a Socks4 proxy client object using the supplied TcpClient object connection.
/// </summary>
/// <param name="tcpClient">An open TcpClient object with an established connection.</param>
public Socks4aProxyClient(TcpClient tcpClient)
: base(tcpClient)
{ }
/// <summary>
/// Create a Socks4a proxy client object. The default proxy port 1080 is used.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyUserId">Proxy user identification information for an IDENTD server.</param>
public Socks4aProxyClient(string proxyHost, string proxyUserId)
: base(proxyHost, proxyUserId)
{ }
/// <summary>
/// Create a Socks4a proxy client object.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyPort">Port used to connect to proxy server.</param>
/// <param name="proxyUserId">Proxy user identification information.</param>
public Socks4aProxyClient(string proxyHost, int proxyPort, string proxyUserId)
: base(proxyHost, proxyPort, proxyUserId)
{ }
/// <summary>
/// Create a Socks4 proxy client object. The default proxy port 1080 is used.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
public Socks4aProxyClient(string proxyHost) : base(proxyHost)
{ }
/// <summary>
/// Create a Socks4a proxy client object.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyPort">Port used to connect to proxy server.</param>
public Socks4aProxyClient(string proxyHost, int proxyPort)
: base(proxyHost, proxyPort)
{ }
/// <summary>
/// Gets String representing the name of the proxy.
/// </summary>
/// <remarks>This property will always return the value 'SOCKS4a'</remarks>
public override string ProxyName
{
get { return PROXY_NAME; }
}
/// <summary>
/// Sends a command to the proxy server.
/// </summary>
/// <param name="proxy">Proxy server data stream.</param>
/// <param name="command">Proxy byte command to execute.</param>
/// <param name="destinationHost">Destination host name or IP address.</param>
/// <param name="destinationPort">Destination port number</param>
/// <param name="userId">IDENTD user ID value.</param>
/// <remarks>
/// This method override the SendCommand message in the Sock4ProxyClient object. The override adds support for the
/// Socks4a extensions which allow the proxy client to optionally command the proxy server to resolve the
/// destination host IP address.
/// </remarks>
internal override void SendCommand(NetworkStream proxy, byte command, string destinationHost, int destinationPort, string userId)
{
// PROXY SERVER REQUEST
//Please read SOCKS4.protocol first for an description of the version 4
//protocol. This extension is intended to allow the use of SOCKS on hosts
//which are not capable of resolving all domain names.
//
//In version 4, the client sends the following packet to the SOCKS server
//to request a CONNECT or a BIND operation:
//
// +----+----+----+----+----+----+----+----+----+----+....+----+
// | VN | CD | DSTPORT | DSTIP | USERID |NULL|
// +----+----+----+----+----+----+----+----+----+----+....+----+
// # of bytes: 1 1 2 4 variable 1
//
//VN is the SOCKS protocol version number and should be 4. CD is the
//SOCKS command code and should be 1 for CONNECT or 2 for BIND. NULL
//is a byte of all zero bits.
//
//For version 4A, if the client cannot resolve the destination host's
//domain name to find its IP address, it should set the first three bytes
//of DSTIP to NULL and the last byte to a non-zero value. (This corresponds
//to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The
//Internet Assigned Numbers Authority -- such an address is inadmissible
//as a destination IP address and thus should never occur if the client
//can resolve the domain name.) Following the NULL byte terminating
//USERID, the client must sends the destination domain name and termiantes
//it with another NULL byte. This is used for both CONNECT and BIND requests.
//
//A server using protocol 4A must check the DSTIP in the request packet.
//If it represent address 0.0.0.x with nonzero x, the server must read
//in the domain name that the client sends in the packet. The server
//should resolve the domain name and make connection to the destination
//host if it can.
//
//SOCKSified sockd may pass domain names that it cannot resolve to
//the next-hop SOCKS server.
// userId needs to be a zero length string so that the GetBytes method
// works properly
if (userId == null)
userId = "";
byte[] destIp = {0,0,0,1}; // build the invalid ip address as specified in the 4a protocol
byte[] destPort = GetDestinationPortBytes(destinationPort);
byte[] userIdBytes = ASCIIEncoding.ASCII.GetBytes(userId);
byte[] hostBytes = ASCIIEncoding.ASCII.GetBytes(destinationHost);
byte[] request = new byte[10 + userIdBytes.Length + hostBytes.Length];
// set the bits on the request byte array
request[0] = SOCKS4_VERSION_NUMBER;
request[1] = command;
destPort.CopyTo(request, 2);
destIp.CopyTo(request, 4);
userIdBytes.CopyTo(request, 8); // copy the userid to the request byte array
request[8 + userIdBytes.Length] = 0x00; // null (byte with all zeros) terminator for userId
hostBytes.CopyTo(request, 9 + userIdBytes.Length); // copy the host name to the request byte array
request[9 + userIdBytes.Length + hostBytes.Length] = 0x00; // null (byte with all zeros) terminator for userId
// send the connect request
proxy.Write(request, 0, request.Length);
// wait for the proxy server to send a response
base.WaitForData(proxy);
// PROXY SERVER RESPONSE
// The SOCKS server checks to see whether such a request should be granted
// based on any combination of source IP address, destination IP address,
// destination port number, the userid, and information it may obtain by
// consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS
// server makes a connection to the specified port of the destination host.
// A reply packet is sent to the client when this connection is established,
// or when the request is rejected or the operation fails.
//
// +----+----+----+----+----+----+----+----+
// | VN | CD | DSTPORT | DSTIP |
// +----+----+----+----+----+----+----+----+
// # of bytes: 1 1 2 4
//
// VN is the version of the reply code and should be 0. CD is the result
// code with one of the following values:
//
// 90: request granted
// 91: request rejected or failed
// 92: request rejected becuase SOCKS server cannot connect to
// identd on the client
// 93: request rejected because the client program and identd
// report different user-ids
//
// The remaining fields are ignored.
//
// The SOCKS server closes its connection immediately after notifying
// the client of a failed or rejected request. For a successful request,
// the SOCKS server gets ready to relay traffic on both directions. This
// enables the client to do I/O on its connection as if it were directly
// connected to the application server.
// create an 8 byte response array
byte[] response = new byte[8];
// read the resonse from the network stream
proxy.Read(response, 0, 8);
// evaluate the reply code for an error condition
if (response[1] != SOCKS4_CMD_REPLY_REQUEST_GRANTED)
HandleProxyCommandError(response, destinationHost, destinationPort);
}
}
}

View file

@ -0,0 +1,765 @@
/*
* Authors: Benton Stark
*
* Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Globalization;
using System.ComponentModel;
namespace Starksoft.Net.Proxy
{
/// <summary>
/// Socks5 connection proxy class. This class implements the Socks5 standard proxy protocol.
/// </summary>
/// <remarks>
/// This implementation supports TCP proxy connections with a Socks v5 server.
/// </remarks>
public class Socks5ProxyClient : IProxyClient
{
private string _proxyHost;
private int _proxyPort;
private string _proxyUserName;
private string _proxyPassword;
private SocksAuthentication _proxyAuthMethod;
private TcpClient _tcpClient;
private TcpClient _tcpClientCached;
private const string PROXY_NAME = "SOCKS5";
private const int SOCKS5_DEFAULT_PORT = 1080;
private const byte SOCKS5_VERSION_NUMBER = 5;
private const byte SOCKS5_RESERVED = 0x00;
private const byte SOCKS5_AUTH_NUMBER_OF_AUTH_METHODS_SUPPORTED = 2;
private const byte SOCKS5_AUTH_METHOD_NO_AUTHENTICATION_REQUIRED = 0x00;
private const byte SOCKS5_AUTH_METHOD_GSSAPI = 0x01;
private const byte SOCKS5_AUTH_METHOD_USERNAME_PASSWORD = 0x02;
private const byte SOCKS5_AUTH_METHOD_IANA_ASSIGNED_RANGE_BEGIN = 0x03;
private const byte SOCKS5_AUTH_METHOD_IANA_ASSIGNED_RANGE_END = 0x7f;
private const byte SOCKS5_AUTH_METHOD_RESERVED_RANGE_BEGIN = 0x80;
private const byte SOCKS5_AUTH_METHOD_RESERVED_RANGE_END = 0xfe;
private const byte SOCKS5_AUTH_METHOD_REPLY_NO_ACCEPTABLE_METHODS = 0xff;
private const byte SOCKS5_CMD_CONNECT = 0x01;
private const byte SOCKS5_CMD_BIND = 0x02;
private const byte SOCKS5_CMD_UDP_ASSOCIATE = 0x03;
private const byte SOCKS5_CMD_REPLY_SUCCEEDED = 0x00;
private const byte SOCKS5_CMD_REPLY_GENERAL_SOCKS_SERVER_FAILURE = 0x01;
private const byte SOCKS5_CMD_REPLY_CONNECTION_NOT_ALLOWED_BY_RULESET = 0x02;
private const byte SOCKS5_CMD_REPLY_NETWORK_UNREACHABLE = 0x03;
private const byte SOCKS5_CMD_REPLY_HOST_UNREACHABLE = 0x04;
private const byte SOCKS5_CMD_REPLY_CONNECTION_REFUSED = 0x05;
private const byte SOCKS5_CMD_REPLY_TTL_EXPIRED = 0x06;
private const byte SOCKS5_CMD_REPLY_COMMAND_NOT_SUPPORTED = 0x07;
private const byte SOCKS5_CMD_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 0x08;
private const byte SOCKS5_ADDRTYPE_IPV4 = 0x01;
private const byte SOCKS5_ADDRTYPE_DOMAIN_NAME = 0x03;
private const byte SOCKS5_ADDRTYPE_IPV6 = 0x04;
/// <summary>
/// Authentication itemType.
/// </summary>
private enum SocksAuthentication
{
/// <summary>
/// No authentication used.
/// </summary>
None,
/// <summary>
/// Username and password authentication.
/// </summary>
UsernamePassword
}
/// <summary>
/// Create a Socks5 proxy client object.
/// </summary>
public Socks5ProxyClient() { }
/// <summary>
/// Creates a Socks5 proxy client object using the supplied TcpClient object connection.
/// </summary>
/// <param name="tcpClient">A TcpClient connection object.</param>
public Socks5ProxyClient(TcpClient tcpClient)
{
if (tcpClient == null)
throw new ArgumentNullException("tcpClient");
_tcpClientCached = tcpClient;
}
/// <summary>
/// Create a Socks5 proxy client object. The default proxy port 1080 is used.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
public Socks5ProxyClient(string proxyHost)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
_proxyHost = proxyHost;
_proxyPort = SOCKS5_DEFAULT_PORT;
}
/// <summary>
/// Create a Socks5 proxy client object.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyPort">Port used to connect to proxy server.</param>
public Socks5ProxyClient(string proxyHost, int proxyPort)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
if (proxyPort <= 0 || proxyPort > 65535)
throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
_proxyHost = proxyHost;
_proxyPort = proxyPort;
}
/// <summary>
/// Create a Socks5 proxy client object. The default proxy port 1080 is used.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyUserName">Proxy authentication user name.</param>
/// <param name="proxyPassword">Proxy authentication password.</param>
public Socks5ProxyClient(string proxyHost, string proxyUserName, string proxyPassword)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
if (proxyUserName == null)
throw new ArgumentNullException("proxyUserName");
if (proxyPassword == null)
throw new ArgumentNullException("proxyPassword");
_proxyHost = proxyHost;
_proxyPort = SOCKS5_DEFAULT_PORT;
_proxyUserName = proxyUserName;
_proxyPassword = proxyPassword;
}
/// <summary>
/// Create a Socks5 proxy client object.
/// </summary>
/// <param name="proxyHost">Host name or IP address of the proxy server.</param>
/// <param name="proxyPort">Port used to connect to proxy server.</param>
/// <param name="proxyUserName">Proxy authentication user name.</param>
/// <param name="proxyPassword">Proxy authentication password.</param>
public Socks5ProxyClient(string proxyHost, int proxyPort, string proxyUserName, string proxyPassword)
{
if (String.IsNullOrEmpty(proxyHost))
throw new ArgumentNullException("proxyHost");
if (proxyPort <= 0 || proxyPort > 65535)
throw new ArgumentOutOfRangeException("proxyPort", "port must be greater than zero and less than 65535");
if (proxyUserName == null)
throw new ArgumentNullException("proxyUserName");
if (proxyPassword == null)
throw new ArgumentNullException("proxyPassword");
_proxyHost = proxyHost;
_proxyPort = proxyPort;
_proxyUserName = proxyUserName;
_proxyPassword = proxyPassword;
}
/// <summary>
/// Gets or sets host name or IP address of the proxy server.
/// </summary>
public string ProxyHost
{
get { return _proxyHost; }
set { _proxyHost = value; }
}
/// <summary>
/// Gets or sets port used to connect to proxy server.
/// </summary>
public int ProxyPort
{
get { return _proxyPort; }
set { _proxyPort = value; }
}
/// <summary>
/// Gets String representing the name of the proxy.
/// </summary>
/// <remarks>This property will always return the value 'SOCKS5'</remarks>
public string ProxyName
{
get { return PROXY_NAME; }
}
/// <summary>
/// Gets or sets proxy authentication user name.
/// </summary>
public string ProxyUserName
{
get { return _proxyUserName; }
set { _proxyUserName = value; }
}
/// <summary>
/// Gets or sets proxy authentication password.
/// </summary>
public string ProxyPassword
{
get { return _proxyPassword; }
set { _proxyPassword = value; }
}
/// <summary>
/// Gets or sets the TcpClient object.
/// This property can be set prior to executing CreateConnection to use an existing TcpClient connection.
/// </summary>
public TcpClient TcpClient
{
get { return _tcpClientCached; }
set { _tcpClientCached = value; }
}
/// <summary>
/// Creates a remote TCP connection through a proxy server to the destination host on the destination port.
/// </summary>
/// <param name="destinationHost">Destination host name or IP address of the destination server.</param>
/// <param name="destinationPort">Port number to connect to on the destination host.</param>
/// <returns>
/// Returns an open TcpClient object that can be used normally to communicate
/// with the destination server
/// </returns>
/// <remarks>
/// This method creates a connection to the proxy server and instructs the proxy server
/// to make a pass through connection to the specified destination host on the specified
/// port.
/// </remarks>
public TcpClient CreateConnection(string destinationHost, int destinationPort)
{
if (String.IsNullOrEmpty(destinationHost))
throw new ArgumentNullException("destinationHost");
if (destinationPort <= 0 || destinationPort > 65535)
throw new ArgumentOutOfRangeException("destinationPort", "port must be greater than zero and less than 65535");
try
{
// if we have no cached tcpip connection then create one
if (_tcpClientCached == null)
{
if (String.IsNullOrEmpty(_proxyHost))
throw new ProxyException("ProxyHost property must contain a value.");
if (_proxyPort <= 0 || _proxyPort > 65535)
throw new ProxyException("ProxyPort value must be greater than zero and less than 65535");
// create new tcp client object to the proxy server
_tcpClient = new TcpClient();
// attempt to open the connection
_tcpClient.Connect(_proxyHost, _proxyPort);
}
else
{
_tcpClient = _tcpClientCached;
}
// determine which authentication method the client would like to use
DetermineClientAuthMethod();
// negotiate which authentication methods are supported / accepted by the server
NegotiateServerAuthMethod();
// send a connect command to the proxy server for destination host and port
SendCommand(SOCKS5_CMD_CONNECT, destinationHost, destinationPort);
// remove the private reference to the tcp client so the proxy object does not keep it
// return the open proxied tcp client object to the caller for normal use
TcpClient rtn = _tcpClient;
_tcpClient = null;
return rtn;
}
catch (Exception ex)
{
throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "Connection to proxy host {0} on port {1} failed.", Utils.GetHost(_tcpClient), Utils.GetPort(_tcpClient)), ex);
}
}
private void DetermineClientAuthMethod()
{
// set the authentication itemType used based on values inputed by the user
if (_proxyUserName != null && _proxyPassword != null)
_proxyAuthMethod = SocksAuthentication.UsernamePassword;
else
_proxyAuthMethod = SocksAuthentication.None;
}
private void NegotiateServerAuthMethod()
{
// get a reference to the network stream
NetworkStream stream = _tcpClient.GetStream();
// SERVER AUTHENTICATION REQUEST
// The client connects to the server, and sends a version
// identifier/method selection message:
//
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
byte[] authRequest = new byte[4];
authRequest[0] = SOCKS5_VERSION_NUMBER;
authRequest[1] = SOCKS5_AUTH_NUMBER_OF_AUTH_METHODS_SUPPORTED;
authRequest[2] = SOCKS5_AUTH_METHOD_NO_AUTHENTICATION_REQUIRED;
authRequest[3] = SOCKS5_AUTH_METHOD_USERNAME_PASSWORD;
// send the request to the server specifying authentication types supported by the client.
stream.Write(authRequest, 0, authRequest.Length);
// SERVER AUTHENTICATION RESPONSE
// The server selects from one of the methods given in METHODS, and
// sends a METHOD selection message:
//
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
//
// If the selected METHOD is X'FF', none of the methods listed by the
// client are acceptable, and the client MUST close the connection.
//
// The values currently defined for METHOD are:
// * X'00' NO AUTHENTICATION REQUIRED
// * X'01' GSSAPI
// * X'02' USERNAME/PASSWORD
// * X'03' to X'7F' IANA ASSIGNED
// * X'80' to X'FE' RESERVED FOR PRIVATE METHODS
// * X'FF' NO ACCEPTABLE METHODS
// receive the server response
byte[] response = new byte[2];
stream.Read(response, 0, response.Length);
// the first byte contains the socks version number (e.g. 5)
// the second byte contains the auth method acceptable to the proxy server
byte acceptedAuthMethod = response[1];
// if the server does not accept any of our supported authenication methods then throw an error
if (acceptedAuthMethod == SOCKS5_AUTH_METHOD_REPLY_NO_ACCEPTABLE_METHODS)
{
_tcpClient.Close();
throw new ProxyException("The proxy destination does not accept the supported proxy client authentication methods.");
}
// if the server accepts a username and password authentication and none is provided by the user then throw an error
if (acceptedAuthMethod == SOCKS5_AUTH_METHOD_USERNAME_PASSWORD && _proxyAuthMethod == SocksAuthentication.None)
{
_tcpClient.Close();
throw new ProxyException("The proxy destination requires a username and password for authentication.");
}
if (acceptedAuthMethod == SOCKS5_AUTH_METHOD_USERNAME_PASSWORD)
{
// USERNAME / PASSWORD SERVER REQUEST
// Once the SOCKS V5 server has started, and the client has selected the
// Username/Password Authentication protocol, the Username/Password
// subnegotiation begins. This begins with the client producing a
// Username/Password request:
//
// +----+------+----------+------+----------+
// |VER | ULEN | UNAME | PLEN | PASSWD |
// +----+------+----------+------+----------+
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
// +----+------+----------+------+----------+
// create a data structure (binary array) containing credentials
// to send to the proxy server which consists of clear username and password data
byte[] credentials = new byte[_proxyUserName.Length + _proxyPassword.Length + 3];
// for SOCKS5 username/password authentication the VER field must be set to 0x01
// http://en.wikipedia.org/wiki/SOCKS
// field 1: version number, 1 byte (must be 0x01)"
credentials[0] = 0x01;
credentials[1] = (byte)_proxyUserName.Length;
Array.Copy(ASCIIEncoding.ASCII.GetBytes(_proxyUserName), 0, credentials, 2, _proxyUserName.Length);
credentials[_proxyUserName.Length + 2] = (byte)_proxyPassword.Length;
Array.Copy(ASCIIEncoding.ASCII.GetBytes(_proxyPassword), 0, credentials, _proxyUserName.Length + 3, _proxyPassword.Length);
// USERNAME / PASSWORD SERVER RESPONSE
// The server verifies the supplied UNAME and PASSWD, and sends the
// following response:
//
// +----+--------+
// |VER | STATUS |
// +----+--------+
// | 1 | 1 |
// +----+--------+
//
// A STATUS field of X'00' indicates success. If the server returns a
// `failure' (STATUS value other than X'00') status, it MUST close the
// connection.
// transmit credentials to the proxy server
stream.Write(credentials, 0, credentials.Length);
// read the response from the proxy server
byte[] crResponse = new byte[2];
stream.Read(crResponse, 0, crResponse.Length);
// check to see if the proxy server accepted the credentials
if (crResponse[1] != 0)
{
_tcpClient.Close();
throw new ProxyException("Proxy authentification failure! The proxy server has reported that the userid and/or password is not valid.");
}
}
}
private byte GetDestAddressType(string host)
{
IPAddress ipAddr = null;
bool result = IPAddress.TryParse(host, out ipAddr);
if (!result)
return SOCKS5_ADDRTYPE_DOMAIN_NAME;
switch (ipAddr.AddressFamily)
{
case AddressFamily.InterNetwork:
return SOCKS5_ADDRTYPE_IPV4;
case AddressFamily.InterNetworkV6:
return SOCKS5_ADDRTYPE_IPV6;
default:
throw new ProxyException(String.Format(CultureInfo.InvariantCulture, "The host addess {0} of type '{1}' is not a supported address type. The supported types are InterNetwork and InterNetworkV6.", host, Enum.GetName(typeof(AddressFamily), ipAddr.AddressFamily)));
}
}
private byte[] GetDestAddressBytes(byte addressType, string host)
{
switch (addressType)
{
case SOCKS5_ADDRTYPE_IPV4:
case SOCKS5_ADDRTYPE_IPV6:
return IPAddress.Parse(host).GetAddressBytes();
case SOCKS5_ADDRTYPE_DOMAIN_NAME:
// create a byte array to hold the host name bytes plus one byte to store the length
byte[] bytes = new byte[host.Length + 1];
// if the address field contains a fully-qualified domain name. The first
// octet of the address field contains the number of octets of name that
// follow, there is no terminating NUL octet.
bytes[0] = Convert.ToByte(host.Length);
Encoding.ASCII.GetBytes(host).CopyTo(bytes, 1);
return bytes;
default:
return null;
}
}
private byte[] GetDestPortBytes(int value)
{
byte[] array = new byte[2];
array[0] = Convert.ToByte(value / 256);
array[1] = Convert.ToByte(value % 256);
return array;
}
private void SendCommand(byte command, string destinationHost, int destinationPort)
{
NetworkStream stream = _tcpClient.GetStream();
byte addressType = GetDestAddressType(destinationHost);
byte[] destAddr = GetDestAddressBytes(addressType, destinationHost);
byte[] destPort = GetDestPortBytes(destinationPort);
// The connection request is made up of 6 bytes plus the
// length of the variable address byte array
//
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// * VER protocol version: X'05'
// * CMD
// * CONNECT X'01'
// * BIND X'02'
// * UDP ASSOCIATE X'03'
// * RSV RESERVED
// * ATYP address itemType of following address
// * IP V4 address: X'01'
// * DOMAINNAME: X'03'
// * IP V6 address: X'04'
// * DST.ADDR desired destination address
// * DST.PORT desired destination port in network octet order
byte[] request = new byte[4 + destAddr.Length + 2];
request[0] = SOCKS5_VERSION_NUMBER;
request[1] = command;
request[2] = SOCKS5_RESERVED;
request[3] = addressType;
destAddr.CopyTo(request, 4);
destPort.CopyTo(request, 4 + destAddr.Length);
// send connect request.
stream.Write(request, 0, request.Length);
// PROXY SERVER RESPONSE
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// * VER protocol version: X'05'
// * REP Reply field:
// * X'00' succeeded
// * X'01' general SOCKS server failure
// * X'02' connection not allowed by ruleset
// * X'03' Network unreachable
// * X'04' Host unreachable
// * X'05' Connection refused
// * X'06' TTL expired
// * X'07' Command not supported
// * X'08' Address itemType not supported
// * X'09' to X'FF' unassigned
//* RSV RESERVED
//* ATYP address itemType of following address
byte[] response = new byte[255];
// read proxy server response
stream.Read(response, 0, response.Length);
byte replyCode = response[1];
// evaluate the reply code for an error condition
if (replyCode != SOCKS5_CMD_REPLY_SUCCEEDED)
HandleProxyCommandError(response, destinationHost, destinationPort );
}
private void HandleProxyCommandError(byte[] response, string destinationHost, int destinationPort)
{
string proxyErrorText;
byte replyCode = response[1];
byte addrType = response[3];
string addr = "";
Int16 port = 0;
switch (addrType)
{
case SOCKS5_ADDRTYPE_DOMAIN_NAME:
int addrLen = Convert.ToInt32(response[4]);
byte[] addrBytes = new byte[addrLen];
for (int i = 0; i < addrLen; i++)
addrBytes[i] = response[i + 5];
addr = System.Text.ASCIIEncoding.ASCII.GetString(addrBytes);
byte[] portBytesDomain = new byte[2];
portBytesDomain[0] = response[6 + addrLen];
portBytesDomain[1] = response[5 + addrLen];
port = BitConverter.ToInt16(portBytesDomain, 0);
break;
case SOCKS5_ADDRTYPE_IPV4:
byte[] ipv4Bytes = new byte[4];
for (int i = 0; i < 4; i++)
ipv4Bytes[i] = response[i + 4];
IPAddress ipv4 = new IPAddress(ipv4Bytes);
addr = ipv4.ToString();
byte[] portBytesIpv4 = new byte[2];
portBytesIpv4[0] = response[9];
portBytesIpv4[1] = response[8];
port = BitConverter.ToInt16(portBytesIpv4, 0);
break;
case SOCKS5_ADDRTYPE_IPV6:
byte[] ipv6Bytes = new byte[16];
for (int i = 0; i < 16; i++)
ipv6Bytes[i] = response[i + 4];
IPAddress ipv6 = new IPAddress(ipv6Bytes);
addr = ipv6.ToString();
byte[] portBytesIpv6 = new byte[2];
portBytesIpv6[0] = response[21];
portBytesIpv6[1] = response[20];
port = BitConverter.ToInt16(portBytesIpv6, 0);
break;
}
switch (replyCode)
{
case SOCKS5_CMD_REPLY_GENERAL_SOCKS_SERVER_FAILURE:
proxyErrorText = "a general socks destination failure occurred";
break;
case SOCKS5_CMD_REPLY_CONNECTION_NOT_ALLOWED_BY_RULESET:
proxyErrorText = "the connection is not allowed by proxy destination rule set";
break;
case SOCKS5_CMD_REPLY_NETWORK_UNREACHABLE:
proxyErrorText = "the network was unreachable";
break;
case SOCKS5_CMD_REPLY_HOST_UNREACHABLE:
proxyErrorText = "the host was unreachable";
break;
case SOCKS5_CMD_REPLY_CONNECTION_REFUSED:
proxyErrorText = "the connection was refused by the remote network";
break;
case SOCKS5_CMD_REPLY_TTL_EXPIRED:
proxyErrorText = "the time to live (TTL) has expired";
break;
case SOCKS5_CMD_REPLY_COMMAND_NOT_SUPPORTED:
proxyErrorText = "the command issued by the proxy client is not supported by the proxy destination";
break;
case SOCKS5_CMD_REPLY_ADDRESS_TYPE_NOT_SUPPORTED:
proxyErrorText = "the address type specified is not supported";
break;
default:
proxyErrorText = String.Format(CultureInfo.InvariantCulture, "that an unknown reply with the code value '{0}' was received by the destination", replyCode.ToString(CultureInfo.InvariantCulture));
break;
}
string exceptionMsg = String.Format(CultureInfo.InvariantCulture, "The {0} concerning destination host {1} port number {2}. The destination reported the host as {3} port {4}.", proxyErrorText, destinationHost, destinationPort, addr, port.ToString(CultureInfo.InvariantCulture));
throw new ProxyException(exceptionMsg);
}
#region "Async Methods"
private BackgroundWorker _asyncWorker;
private Exception _asyncException;
bool _asyncCancelled;
/// <summary>
/// Gets a value indicating whether an asynchronous operation is running.
/// </summary>
/// <remarks>Returns true if an asynchronous operation is running; otherwise, false.
/// </remarks>
public bool IsBusy
{
get { return _asyncWorker == null ? false : _asyncWorker.IsBusy; }
}
/// <summary>
/// Gets a value indicating whether an asynchronous operation is cancelled.
/// </summary>
/// <remarks>Returns true if an asynchronous operation is cancelled; otherwise, false.
/// </remarks>
public bool IsAsyncCancelled
{
get { return _asyncCancelled; }
}
/// <summary>
/// Cancels any asychronous operation that is currently active.
/// </summary>
public void CancelAsync()
{
if (_asyncWorker != null && !_asyncWorker.CancellationPending && _asyncWorker.IsBusy)
{
_asyncCancelled = true;
_asyncWorker.CancelAsync();
}
}
private void CreateAsyncWorker()
{
if (_asyncWorker != null)
_asyncWorker.Dispose();
_asyncException = null;
_asyncWorker = null;
_asyncCancelled = false;
_asyncWorker = new BackgroundWorker();
}
/// <summary>
/// Event handler for CreateConnectionAsync method completed.
/// </summary>
public event EventHandler<CreateConnectionAsyncCompletedEventArgs> CreateConnectionAsyncCompleted;
/// <summary>
/// Asynchronously creates a remote TCP connection through a proxy server to the destination host on the destination port.
/// </summary>
/// <param name="destinationHost">Destination host name or IP address.</param>
/// <param name="destinationPort">Port number to connect to on the destination host.</param>
/// <returns>
/// Returns TcpClient object that can be used normally to communicate
/// with the destination server.
/// </returns>
/// <remarks>
/// This method instructs the proxy server
/// to make a pass through connection to the specified destination host on the specified
/// port.
/// </remarks>
public void CreateConnectionAsync(string destinationHost, int destinationPort)
{
if (_asyncWorker != null && _asyncWorker.IsBusy)
throw new InvalidOperationException("The Socks4 object is already busy executing another asynchronous operation. You can only execute one asychronous method at a time.");
CreateAsyncWorker();
_asyncWorker.WorkerSupportsCancellation = true;
_asyncWorker.DoWork += new DoWorkEventHandler(CreateConnectionAsync_DoWork);
_asyncWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CreateConnectionAsync_RunWorkerCompleted);
Object[] args = new Object[2];
args[0] = destinationHost;
args[1] = destinationPort;
_asyncWorker.RunWorkerAsync(args);
}
private void CreateConnectionAsync_DoWork(object sender, DoWorkEventArgs e)
{
try
{
Object[] args = (Object[])e.Argument;
e.Result = CreateConnection((string)args[0], (int)args[1]);
}
catch (Exception ex)
{
_asyncException = ex;
}
}
private void CreateConnectionAsync_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (CreateConnectionAsyncCompleted != null)
CreateConnectionAsyncCompleted(this, new CreateConnectionAsyncCompletedEventArgs(_asyncException, _asyncCancelled, (TcpClient)e.Result));
}
#endregion
}
}

View file

@ -0,0 +1,43 @@
using System;
using System.Text;
using System.Globalization;
using System.Net.Sockets;
namespace Starksoft.Net.Proxy
{
internal static class Utils
{
internal static string GetHost(TcpClient client)
{
if (client == null)
throw new ArgumentNullException("client");
string host = "";
try
{
host = ((System.Net.IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
}
catch
{ };
return host;
}
internal static string GetPort(TcpClient client)
{
if (client == null)
throw new ArgumentNullException("client");
string port = "";
try
{
port = ((System.Net.IPEndPoint)client.Client.RemoteEndPoint).Port.ToString(CultureInfo.InvariantCulture);
}
catch
{ };
return port;
}
}
}

View file

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using Starksoft.Net.Proxy;
namespace MinecraftClient.Proxy
{
/// <summary>
/// Automatically handle proxies according to the app Settings.
/// Note: Underlying proxy handling is taken from Starksoft, LLC's Biko Library.
/// This library is open source and provided under the MIT license. More info at biko.codeplex.com.
/// </summary>
public static class ProxyHandler
{
public enum Type { HTTP, SOCKS4, SOCKS4a, SOCKS5 };
private static ProxyClientFactory factory = new ProxyClientFactory();
private static IProxyClient proxy;
private static bool proxy_ok = false;
/// <summary>
/// Create a regular TcpClient or a proxied TcpClient according to the app Settings.
/// </summary>
public static TcpClient newTcpClient(string host, int port)
{
try
{
if (Settings.ProxyEnabled)
{
ProxyType innerProxytype = ProxyType.Http;
switch (Settings.proxyType)
{
case Type.HTTP: innerProxytype = ProxyType.Http; break;
case Type.SOCKS4: innerProxytype = ProxyType.Socks4; break;
case Type.SOCKS4a: innerProxytype = ProxyType.Socks4a; break;
case Type.SOCKS5: innerProxytype = ProxyType.Socks5; break;
}
if (Settings.ProxyUsername != "" && Settings.ProxyPassword != "")
{
proxy = factory.CreateProxyClient(innerProxytype, Settings.ProxyHost, Settings.ProxyPort, Settings.ProxyUsername, Settings.ProxyPassword);
}
else proxy = factory.CreateProxyClient(innerProxytype, Settings.ProxyHost, Settings.ProxyPort);
if (!proxy_ok)
{
ConsoleIO.WriteLineFormatted("§8Connected to proxy " + Settings.ProxyHost + ':' + Settings.ProxyPort);
proxy_ok = true;
}
return proxy.CreateConnection(host, port);
}
else return new TcpClient(host, port);
}
catch (ProxyException e)
{
ConsoleIO.WriteLineFormatted("§8" + e.Message);
proxy = null;
return null;
}
}
}
}

View file

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

View file

@ -20,9 +20,19 @@ namespace MinecraftClient
public static string Username = "";
public static string Password = "";
public static string ServerIP = "";
public static short ServerPort = 25565;
public static string ServerVersion = "";
public static string SingleCommand = "";
public static string ConsoleTitle = "";
//Proxy Settings
public static bool ProxyEnabled = false;
public static string ProxyHost = "";
public static int ProxyPort = 0;
public static Proxy.ProxyHandler.Type proxyType = Proxy.ProxyHandler.Type.HTTP;
public static string ProxyUsername = "";
public static string ProxyPassword = "";
//Other Settings
public static string TranslationsFile_FromMCDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\assets\objects\9e\9e2fdc43fc1c7024ff5922b998fadb2971a64ee0"; //MC 1.7.4 en_GB.lang
public static string TranslationsFile_Website_Index = "https://s3.amazonaws.com/Minecraft.Download/indexes/1.7.4.json";
@ -30,6 +40,10 @@ namespace MinecraftClient
public static List<string> Bots_Owners = new List<string>();
public static string Language = "en_GB";
public static bool chatTimeStamps = false;
public static bool exitOnFailure = false;
public static char internalCmdChar = '/';
public static bool playerHeadAsIcon = false;
public static string chatbotLogFile = "";
//AntiAFK Settings
public static bool AntiAFK_Enabled = false;
@ -52,7 +66,7 @@ namespace MinecraftClient
public static bool ChatLog_Enabled = false;
public static bool ChatLog_DateTime = true;
public static string ChatLog_File = "chatlog.txt";
public static Bots.ChatLog.MessageFilter ChatLog_Filter = Bots.ChatLog.MessageFilter.AllMessages;
public static ChatBots.ChatLog.MessageFilter ChatLog_Filter = ChatBots.ChatLog.MessageFilter.AllMessages;
//PlayerListLog Settings
public static bool PlayerLog_Enabled = false;
@ -71,8 +85,14 @@ namespace MinecraftClient
//Remote Control
public static bool RemoteCtrl_Enabled = false;
public static bool RemoteCtrl_AutoTpaccept = true;
private enum ParseMode { Default, Main, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl };
//Custom app variables and Minecraft accounts
private static Dictionary<string, string> AppVars = new Dictionary<string, string>();
private static Dictionary<string, KeyValuePair<string, string>> Accounts = new Dictionary<string, KeyValuePair<string, string>>();
private static Dictionary<string, KeyValuePair<string, short>> Servers = new Dictionary<string, KeyValuePair<string, short>>();
private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl };
/// <summary>
/// Load settings from the give INI file
@ -104,6 +124,8 @@ namespace MinecraftClient
case "main": pMode = ParseMode.Main; break;
case "scriptscheduler": pMode = ParseMode.ScriptScheduler; break;
case "remotecontrol": pMode = ParseMode.RemoteControl; break;
case "proxy": pMode = ParseMode.Proxy; break;
case "appvars": pMode = ParseMode.AppVars; break;
default: pMode = ParseMode.Default; break;
}
}
@ -120,16 +142,70 @@ namespace MinecraftClient
{
case "login": Login = argValue; break;
case "password": Password = argValue; break;
case "serverip": ServerIP = argValue; break;
case "serverip": setServerIP(argValue); break;
case "singlecommand": SingleCommand = argValue; break;
case "language": Language = argValue; break;
case "consoletitle": ConsoleTitle = argValue; break;
case "timestamps": chatTimeStamps = str2bool(argValue); break;
case "exitonfailure": exitOnFailure = str2bool(argValue); break;
case "playerheadicon": playerHeadAsIcon = str2bool(argValue); break;
case "chatbotlogfile": chatbotLogFile = argValue; break;
case "mcversion": ServerVersion = argValue; break;
case "botowners":
Bots_Owners.Clear();
foreach (string name in argValue.ToLower().Replace(" ", "").Split(','))
Bots_Owners.Add(name);
break;
case "internalcmdchar":
switch (argValue.ToLower())
{
case "none": internalCmdChar = ' '; break;
case "slash": internalCmdChar = '/'; break;
case "backslash": internalCmdChar = '\\'; break;
}
break;
case "accountlist":
if (File.Exists(argValue))
{
foreach (string account_line in File.ReadAllLines(argValue))
{
//Each line contains account data: 'Alias,Login,Password'
string[] account_data = account_line.Split('#')[0].Trim().Split(',');
if (account_data.Length == 3)
Accounts[account_data[0].ToLower()]
= new KeyValuePair<string, string>(account_data[1], account_data[2]);
}
}
break;
case "serverlist":
if (File.Exists(argValue))
{
//Backup current server info
string server_host_temp = ServerIP;
short server_port_temp = ServerPort;
foreach (string server_line in File.ReadAllLines(argValue))
{
//Each line contains server data: 'Alias,Host:Port'
string[] server_data = server_line.Split('#')[0].Trim().Split(',');
server_data[0] = server_data[0].ToLower();
if (server_data.Length == 2
&& server_data[0] != "localhost"
&& !server_data[0].Contains('.')
&& setServerIP(server_data[1]))
Servers[server_data[0]]
= new KeyValuePair<string, short>(ServerIP, ServerPort);
}
//Restore current server info
ServerIP = server_host_temp;
ServerPort = server_port_temp;
}
break;
}
break;
@ -167,7 +243,7 @@ namespace MinecraftClient
{
case "enabled": ChatLog_Enabled = str2bool(argValue); break;
case "timestamps": ChatLog_DateTime = str2bool(argValue); break;
case "filter": ChatLog_Filter = Bots.ChatLog.str2filter(argValue); break;
case "filter": ChatLog_Filter = ChatBots.ChatLog.str2filter(argValue); break;
case "logfile": ChatLog_File = argValue; break;
}
break;
@ -194,8 +270,42 @@ namespace MinecraftClient
switch (argName.ToLower())
{
case "enabled": RemoteCtrl_Enabled = str2bool(argValue); break;
case "autotpaccept": RemoteCtrl_AutoTpaccept = str2bool(argValue); break;
}
break;
case ParseMode.Proxy:
switch (argName.ToLower())
{
case "enabled": ProxyEnabled = str2bool(argValue); break;
case "type":
argValue = argValue.ToLower();
if (argValue == "http") { proxyType = Proxy.ProxyHandler.Type.HTTP; }
else if (argValue == "socks4") { proxyType = Proxy.ProxyHandler.Type.SOCKS4; }
else if (argValue == "socks4a"){ proxyType = Proxy.ProxyHandler.Type.SOCKS4a;}
else if (argValue == "socks5") { proxyType = Proxy.ProxyHandler.Type.SOCKS5; }
break;
case "server":
string[] host_splitted = argValue.Split(':');
if (host_splitted.Length == 1)
{
ProxyHost = host_splitted[0];
ProxyPort = 80;
}
else if (host_splitted.Length == 2)
{
ProxyHost = host_splitted[0];
ProxyPort = str2int(host_splitted[1]);
}
break;
case "username": ProxyUsername = argValue; break;
case "password": ProxyPassword = argValue; break;
}
break;
case ParseMode.AppVars:
setVar(argName, argValue);
break;
}
}
}
@ -222,15 +332,36 @@ namespace MinecraftClient
+ "#leave blank to prompt user on startup\r\n"
+ "#Use \"-\" as password for offline mode\r\n"
+ "\r\n"
+ "login=\r\npassword=\r\nserverip=\r\n"
+ "login=\r\n"
+ "password=\r\n"
+ "serverip=\r\n"
+ "\r\n"
+ "#Advanced settings\r\n"
+ "\r\n"
+ "language=en_GB\r\n"
+ "botowners=Player1,Player2,Player3\r\n"
+ "consoletitle=%username% - Minecraft Console Client\r\n"
+ "internalcmdchar=slash #use 'none', 'slash' or 'backslash'\r\n"
+ "mcversion=auto #use 'auto' or '1.X.X' values\r\n"
+ "chatbotlogfile= #leave empty for no logfile\r\n"
+ "accountlist=accounts.txt\r\n"
+ "serverlist=servers.txt\r\n"
+ "playerheadicon=true\r\n"
+ "exitonfailure=false\r\n"
+ "timestamps=false\r\n"
+ "\r\n"
+ "[AppVars]\r\n"
+ "#yourvar=yourvalue\r\n"
+ "#can be used in some other fields as %yourvar%\r\n"
+ "#%username% and %serverip% are reserved variables.\r\n"
+ "\r\n"
+ "[Proxy]\r\n"
+ "enabled=false\r\n"
+ "type=HTTP #Supported types: HTTP, SOCKS4, SOCKS4a, SOCKS5\r\n"
+ "server=0.0.0.0:0000\r\n"
+ "username=\r\n"
+ "password=\r\n"
+ "\r\n"
+ "#Bot Settings\r\n"
+ "\r\n"
+ "[Alerts]\r\n"
@ -254,7 +385,7 @@ namespace MinecraftClient
+ "enabled=false\r\n"
+ "timestamps=true\r\n"
+ "filter=messages\r\n"
+ "logfile=chatlog.txt\r\n"
+ "logfile=chatlog-%username%-%serverip%.txt\r\n"
+ "\r\n"
+ "[Hangman]\r\n"
+ "enabled=false\r\n"
@ -267,11 +398,136 @@ namespace MinecraftClient
+ "tasksfile=tasks.ini\r\n"
+ "\r\n"
+ "[RemoteControl]\r\n"
+ "enabled=false\r\n", Encoding.UTF8);
+ "enabled=false\r\n"
+ "autotpaccept=true\r\n", Encoding.UTF8);
}
public static int str2int(string str) { try { return Convert.ToInt32(str); } catch { return 0; } }
public static bool str2bool(string str) { return str == "true" || str == "1"; }
/// <summary>
/// Load login/password using an account alias
/// </summary>
/// <returns>True if the account was found and loaded</returns>
public static bool setAccount(string accountAlias)
{
accountAlias = accountAlias.ToLower();
if (Accounts.ContainsKey(accountAlias))
{
Settings.Login = Accounts[accountAlias].Key;
Settings.Password = Accounts[accountAlias].Value;
return true;
}
else return false;
}
/// <summary>
/// Load server information in ServerIP and ServerPort variables from a "serverip:port" couple or server alias
/// </summary>
/// <returns>True if the server IP was valid and loaded, false otherwise</returns>
public static bool setServerIP(string server)
{
string[] sip = server.Split(':');
string host = sip[0];
short port = 25565;
if (sip.Length > 1)
{
try
{
port = Convert.ToInt16(sip[1]);
}
catch (FormatException) { return false; }
}
if (host == "localhost" || host.Contains('.'))
{
ServerIP = host;
ServerPort = port;
return true;
}
else if (Servers.ContainsKey(server))
{
ServerIP = Servers[server].Key;
ServerPort = Servers[server].Value;
return true;
}
return false;
}
/// <summary>
/// Set a custom %variable% which will be available through expandVars()
/// </summary>
/// <param name="varName">Name of the variable</param>
/// <param name="varData">Value of the variable</param>
/// <returns>True if the parameters were valid</returns>
public static bool setVar(string varName, string varData)
{
varName = new string(varName.TakeWhile(char.IsLetterOrDigit).ToArray()).ToLower();
if (varName.Length > 0)
{
AppVars[varName] = varData;
return true;
}
else return false;
}
/// <summary>
/// Replace %variables% with their value
/// </summary>
/// <param name="str">String to parse</param>
/// <returns>Modifier string</returns>
public static string expandVars(string str)
{
StringBuilder result = new StringBuilder();
for (int i = 0; i < str.Length; i++)
{
if (str[i] == '%')
{
bool varname_ok = false;
StringBuilder var_name = new StringBuilder();
for (int j = i + 1; j < str.Length; j++)
{
if (!char.IsLetterOrDigit(str[j]))
{
if (str[j] == '%')
varname_ok = var_name.Length > 0;
break;
}
else var_name.Append(str[j]);
}
if (varname_ok)
{
string varname = var_name.ToString();
string varname_lower = varname.ToLower();
i = i + varname.Length + 1;
switch (varname_lower)
{
case "username": result.Append(Username); break;
case "serverip": result.Append(ServerIP); break;
case "serverport": result.Append(ServerPort); break;
default:
if (AppVars.ContainsKey(varname_lower))
{
result.Append(AppVars[varname_lower]);
}
else result.Append("%" + varname + '%');
break;
}
}
else result.Append(str[i]);
}
else result.Append(str[i]);
}
return result.ToString();
}
}
}

View file

@ -0,0 +1,182 @@
==================================================================
Minecraft Client v1.8.0 for Minecraft 1.4.6 to 1.8.0 - By ORelio
==================================================================
Thanks for dowloading Minecraft Console Client!
Minecraft Console Client is a lightweight app able to connect to any minecraft server,
both offline and online mode. It enables you to send commands and receive text messages
in a fast and easy way without having to open the main Minecraft game.
============
How to use
============
First, extract the archive if not already extracted.
On Windows, simply open MinecraftClient.exe by double-clicking on it.
On Mac or Linux, open a terminal in this folder and run "mono MinecraftClient.exe".
===========================================
Using Configuration files & Enabling bots
===========================================
Simply open the INI file with a text editor and change the values.
To enable a bot change the "enabled" value in the INI file from "false" to "true".
You will still be able to send and receive chat messages when a bot is loaded.
You can remove or comment some lines from the INI file to use the default values instead.
You can have several INI files and drag & drop one of them over MinecraftClient.exe
====================
Command-line usage
====================
> MinecraftClient.exe username password server
This will automatically connect you to the chosen server.
> MinecraftClient.exe username password server "/mycommand"
This will automatically send "/mycommand" to the server and close.
To send several commands and maybe stay connected, use the Scripting bot instead.
> MinecraftClient.exe myconfig.ini
This will load the specified configuration file
If the file contains login / password / server ip, it will automatically connect.
> MinecraftClient.exe myconfig.ini othername otherpassword otherIP
Load the specified configuration file and override some settings from the file.
===================
Internal commands
===================
These commands can be performed from the chat prompt, scripts or remote control.
From chat prompt, commands must by default be prepended with a slash, eg. /quit
In scripts and remote control, no slash is needed to perform the command.
- quit or exit: disconnect from the server and close the application
- reco [account] : disconnect and reconnect to the server
- connect <server> [account] : go to the given server and resume the script
- script <script name> : run a script containing a list of commands
- send <text> : send a message or a command to the server
- respawn : Use this to respawn if you are dead (like clicking "respawn" ingame)
- log <text> : display some text in the console (useful for scripts)
- set varname=value : set a value which can be used as %varname% in further commands
- wait <time> : wait <time> : wait X ticks (10 ticks = ~1 second. Only for scripts)
- help : show command help. Tip: Use "/send /help" for server help
[account] is an account alias defined in accounts file, read more below.
<server> is either a server IP or a server alias defined in servers file
===========================
Servers and Accounts file
===========================
These two files can be used to store info about accounts and server, and give them aliases.
The purpose of this is to give them an easy-to-remember alias and to avoid typing account passwords.
As what you are typing can be read by the server admin if using the remote control feature,
using aliases is really important for privacy and for safely switching between accounts.
To use these files, simply take a look at sample-accounts.txt and sample-servers.txt.
Once you have created your files, fill the 'accountlist' and 'serverlist' fields in INI file.
============================
How to write a script file
============================
A script can be launched by using /script <filename> in the client
The client will automatically look for your script in the current directory or "scripts" subfolder.
If the file extension is .txt, you may omit it and the client will still find the script
Regarding the script file, it is a text file with one instruction per line.
Any line beginning with "#" is ignored and treated as a comment.
Allowed instructions are given in "Internal commands" section.
Application variables defined using the 'set' command or [AppVars] INI section can be used.
The following read-only variables can also be used: %username%, %serverip%, %serverport%
==========================
Using HTTP/Socks proxies
==========================
If you are on a restricted network you might want to use some HTTP or SOCKS proxies.
To do so, find a proxy, enable proxying in INI file and fill in the relevant settings.
Proxy with username/password authentication are supported but have not been tested.
=============================================
Connecting to servers when ping is disabled
=============================================
On some server, the server list ping feature has been disabled, which prevents Minecraft Console Client
from pinging the server to determine the Minecraft version to use. To connect to this kind of servers,
find out which Minecraft version is running on the server, and fill in the 'mcversion' field in INI file.
This will disable the ping step while connecting, but requires you to manually provide the version to use.
=========================
About translation files
=========================
When connecting to 1.6+ servers, you will need a translation file to display properly some chat messages.
These files describe how some messages should be printed depending on your preferred language.
The client will automatically load en_GB.lang from your Minecraft folder if Minecraft is installed on your
computer, or download it from Mojang's servers. You may choose another language in the config file.
======================
Using the Alerts bot
======================
Write in alerts.txt the words you want the console to beep/alert you on.
Write in alerts-exclude.txt the words you want NOT to be alerted on.
For example write Yourname in alerts and <Yourname> in alerts-exclude.txt
=========================
Using the AutoRelog bot
=========================
Write in kickmessages.txt some words, such as "Restarting" for example.
If the kick message contains one of them, you will be automatically re-connected
A kick message "Connection has been lost." is generated by the console itself when connection is lost.
A kick message "Login failed." is generated the same way when it failed to login to the server.
A kick message "Failed to ping this IP." is generated when it failed to ping the server.
You can use them for reconnecting when connection is lost or the login failed.
============================
Using the Script Scheduler
============================
The script scheduler allows you to perform scripts on various events.
Simply enable the ScriptScheduler bot and specify a tasks file in your INI file.
Please read sample-tasks.ini for learning how to make your own task file.
========================
Using the hangman game
========================
Use "/tell <bot username> start" to start the game.
Don't forget to add your username in botowners INI setting if you want it to obey.
Edit the provided configuration files to customize the words and the owners.
==========================
Using the Remote Control
==========================
When the remote control bot is enabled, you can send commands to your bot using whispers.
Don't forget to add your username in botowners INI setting if you want it to obey.
To perform a command simply do the following: /tell <yourbot> <thecommand>
Where <thecommand> is an internal command as described in "Internal commands" section.
If enabled, remote control will auto-accept /tpa and /tpahere requests from the bot owners.
=========================
Disclaimer & Last words
=========================
Even if everything should work, I am not responsible of any damage this app could cause to your computer or your server.
This app does not steal your password. If you don't trust it, don't use it or check & compile the source code.
Also, remember that when you connect to a server with this program, you will appear where you left the last time.
This means that you can die if you log in in an unsafe place on a survival server!
Use the script scheduler bot to send a teleport command after logging in.
You can find more info at:
http://www.minecraftforum.net/topic/1314800-/
+--------------------+
| © 2012-2014 ORelio |
+--------------------+

View file

@ -0,0 +1,14 @@
# Minecraft Console Client
# Account list file
# Put account data as comma separated values
# Values are: Alias,Login,Password
# It allows a fast account switching
# without directly using the credentials
# Usage examples:
# /tell <mybot> reco Player2
# /connect <serverip> Player1
Player1,playerone@email.com,thepassword
Player2,TestBot,-

View file

@ -1,13 +1,8 @@
# This is a sample script for Minecraft Console Client
# Any line beginning with "#" is ignored and treated as a comment.
# Allowed instructions: send, wait, disconnect, exit
# send <text> : send a message or a command to the server
# wait <time> : wait X ticks (10 ticks = 1 second)
# connect <serverip> : go to the given server and resume the script
# disconnect : disconnect from the server and exit the client
# exit : exit this script but stay connected to the server
# Allowed instructions: see README file for an updated list of instructions.
send Hello World! I'm a bot scripted using Minecraft Console Client.
wait 60
send Now quitting. Bye :)
disconnect
exit

View file

@ -0,0 +1,18 @@
# Minecraft Console Client
# Server list file
# Put server data as comma separated values
# Values are: Alias,ServerIP:Port
# Aliases cannot contains dots or spaces
# The name "localhost" cannot be used as an alias
# It allows an easier and faster server switching
# with short aliases instead of full server IP
# It also adds a bit of privacy for remote control
# Usage examples:
# /tell <mybot> connect Server1
# /connect Server2
Server1,localhost
Server2,mc.awesomeserver.com:25567
Server3,192.168.1.27:1348 # Example of LAN server

View file

@ -10,8 +10,10 @@
triggerOnFirstLogin=false
triggerOnLogin=false
triggerOnTime=true
triggerOnInterval=false
timeValue=19:30
timeValue=08:10
timeInterval=0
script=event.txt
# Another minimal example: some properties may be omitted
@ -30,5 +32,13 @@ triggerOnTime=true
timeValue=00:00
script=midnight.txt
# Example of task occuring every 30 seconds
# Could be used for making a custom antiAFK procedure
[Task]
triggerOnInterval=true
timeInterval=30
script=advanced-anti-afk.txt
# Enjoy!
# - ORelio

View file

@ -2,8 +2,20 @@ Minecraft Console Client
========================
Minecraft Console Client is a lightweight app allowing to connect to any minecraft server,
send commands and receive text messages in a fast and easy way without having to open the main Minecraft game.
send commands and receive text messages in a fast and easy way without having to open the main Minecraft game. It also provides various automation for administration and other purposes.
This source code is published under the CDDL-1.0 license.
##How to use
More info at http://www.minecraftforum.net/topic/1314800-/
Downloads, help and more info at http://www.minecraftforum.net/topic/1314800-/
##License
Unless specifically stated, the code is from me or contributors, and available under CDDL-1.0.<br/>
Else, the license and original author are mentioned in source file headers.<br/>
The main terms of the CDDL-1.0 license are basically the following:
- You may use the licensed code in whole or in part in any program you desire, regardless of the license of the program as a whole (or rather, as excluding the code you are borrowing). The program itself may be open or closed source, free or commercial.
- However, in all cases, any modifications, improvements, or additions to the CDDL code (any code that is referenced in direct modifications to the CDDL code is considered an addition to the CDDL code, and so is bound by this requirement; e.g. a modification of a math function to use a fast lookup table makes that table itself an addition to the CDDL code, regardless of whether it's in a source code file of its own) must be made publicly and freely available in source, under the CDDL license itself.
- In any program (source or binary) that uses CDDL code, recognition must be given to the source (either project or author) of the CDDL code. As well, modifications to the CDDL code (which must be distributed as source) may not remove notices indicating the ancestry of the code.<br/><br/>
More info at http://qstuff.blogspot.fr/2007/04/why-cddl.html<br/>
Full license at http://opensource.org/licenses/CDDL-1.0