diff --git a/HttpBins/HttpServer.dll b/HttpBins/HttpServer.dll index bf543068..b87662e7 100644 Binary files a/HttpBins/HttpServer.dll and b/HttpBins/HttpServer.dll differ diff --git a/HttpBins/HttpServer.pdb b/HttpBins/HttpServer.pdb index f1cfd83f..b1eaaeeb 100644 Binary files a/HttpBins/HttpServer.pdb and b/HttpBins/HttpServer.pdb differ diff --git a/HttpBins/HttpServer.xml b/HttpBins/HttpServer.xml index fff737b5..c5e51443 100644 --- a/HttpBins/HttpServer.xml +++ b/HttpBins/HttpServer.xml @@ -4,697 +4,6 @@ HttpServer - - - Used to read from a string object. - - - - - Base interface to read string tokens from different sources. - - - - - Assign a new buffer - - Buffer to process. - Where to start process buffer - Buffer length - - - - Assign a new buffer - - Buffer to process - - - - Consume current character. - - - - - Consume specified characters - - One or more characters. - - - - Consumes horizontal white spaces (space and tab). - - - - - Consume horizontal white spaces and the specified character. - - Extra character to consume - - - - Checks if one of the remaining bytes are a specified character. - - Character to find. - true if found; otherwise false. - - - - Read a character. - - Character if not EOF; otherwise null. - - - - Get a text line. - - - Will merge multiline headers. - - - - Read quoted string - - string if current character (in buffer) is a quote; otherwise null. - - - - Read until end of string, or to one of the delimiters are found. - - characters to stop at - A string (can be ). - - Will not consume the delimiter. - - - - - Read until end of string, or to one of the delimiters are found. - - A string (can be ). - - Will not consume the delimiter. - - - - - Read to end of buffer, or until specified delimiter is found. - - Delimiter to find. - A string (can be ). - - Will not consume the delimiter. - - - - - Will read until specified delimiter is found. - - Character to stop at. - A string if the delimiter was found; otherwise null. - - Will trim away spaces and tabs from the end. - Will not consume the delimiter. - - - - - Read until one of the delimiters are found. - - characters to stop at - A string if one of the delimiters was found; otherwise null. - - Will trim away spaces and tabs from the end. - Will not consume the delimiter. - - - - - Read until a horizontal white space occurs. - - A string if a white space was found; otherwise null. - - - - Gets current character - - if end of buffer. - - - - Gets if end of buffer have been reached - - - - - Gets if more bytes can be processed. - - - - - Gets or sets current position in buffer. - - - THINK before you manually change the position since it can blow up - the whole parsing in your face. - - - - - Gets total length of buffer. - - - - - Gets or sets line number. - - - - - Gets next character - - if end of buffer. - - - - Gets number of bytes left. - - - - - Initializes a new instance of the class. - - Buffer to process. - - - - Initializes a new instance of the class. - - - - - Assign a new buffer - - Buffer to process. - Where to start process buffer - Buffer length - MUST be of type . - buffer needs to be of type string - - - - Assign a new buffer - - Buffer to process - MUST be of type . - buffer needs to be of type string - - - - Consume current character. - - - - - Get a text line. - - - Will merge multiline headers. - - - - Read quoted string - - string if current character (in buffer) is a quote; otherwise null. - - - - Read until end of string, or to one of the delimiters are found. - - characters to stop at - A string (can be ). - InvalidOperationException. - - - - Read until end of string, or to one of the delimiters are found. - - A string (can be ). - - Will not consume the delimiter. - - - - - Read to end of buffer, or until specified delimiter is found. - - Delimiter to find. - A string (can be ). - InvalidOperationException. - - - - Consume specified characters - - One or more characters. - - - - Consumes horizontal white spaces (space and tab). - - - - - Read a character. - - - Character if not EOF; otherwise null. - - - - - Will read until specified delimiter is found. - - Character to stop at. - - A string if the delimiter was found; otherwise null. - - - Will trim away spaces and tabs from the end. - Will not consume the delimiter. - - InvalidOperationException. - - - - Read until one of the delimiters are found. - - characters to stop at - - A string if one of the delimiters was found; otherwise null. - - - Will not consume the delimiter. - - InvalidOperationException. - - - - Read until a horizontal white space occurs (or end, or end of line). - - - A string if a white space was found; otherwise null. - - - - - Consume horizontal white spaces and the specified character. - - Extra character to consume - - - - Checks if one of the remaining bytes are a specified character. - - Character to find. - - true if found; otherwise false. - - - - - Gets or sets line number. - - - - - Gets if end of buffer have been reached - - - - - - Gets if more bytes can be processed. - - - - - - Gets next character - - if end of buffer. - - - - Gets current character - - if end of buffer. - - - - Gets or sets current position in buffer. - - - THINK before you manually change the position since it can blow up - the whole parsing in your face. - - - - - Gets total length of buffer. - - - - - - Gets number of bytes left. - - - - - Reads strings from a byte array. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - Encoding to use when converting byte array to strings. - - - - Initializes a new instance of the class. - - Buffer to read from. - Encoding to use when converting byte array to strings. - - - - Assign a new buffer - - Buffer to process. - Where to start process buffer - Buffer length - Buffer needs to be a byte array - - - - Assign a new buffer - - Buffer to process - Buffer needs to be a byte array - - - - Consume current character. - - - - - Get a text line. - - - Will merge multi line headers. - - - - Read quoted string - - string if current character (in buffer) is a quote; otherwise null. - - - - Read until end of string, or to one of the delimiters are found. - - characters to stop at - - A string (can be ). - - - Will not consume the delimiter. - - InvalidOperationException. - - - - Read until end of string, or to one of the delimiters are found. - - A string (can be ). - - Will not consume the delimiter. - - - - - Read to end of buffer, or until specified delimiter is found. - - Delimiter to find. - - A string (can be ). - - - Will not consume the delimiter. - - InvalidOperationException. - - - - Consume specified characters - - One or more characters. - - - - Consumes horizontal white spaces (space and tab). - - - - - Consume horizontal white spaces and the specified character. - - Extra character to consume - - - - Read a character. - - - Character if not EOF; otherwise null. - - - - - Will read until specified delimiter is found. - - Character to stop at. - - A string if the delimiter was found; otherwise null. - - - Will trim away spaces and tabs from the end. - InvalidOperationException. - - - - Read until one of the delimiters are found. - - characters to stop at - - A string if one of the delimiters was found; otherwise null. - - - Will not consume the delimiter. - - InvalidOperationException. - - - - Read until a horizontal white space occurs. - - A string if a white space was found; otherwise null. - - - - Checks if one of the remaining bytes are a specified character. - - Character to find. - - true if found; otherwise false. - - - - - Gets or sets line number. - - - - - Gets if end of buffer have been reached - - - - - - Gets if more bytes can be processed. - - - - - - Gets next character - - if end of buffer. - - - - Gets current character - - if end of buffer. - - - - Gets or sets current position in buffer. - - - THINK before you manually change the position since it can blow up - the whole parsing in your face. - - - - - Gets total length of buffer. - - - - - - Gets number of bytes left. - - - - - Event arguments used when a new header have been parsed. - - - - - Initializes a new instance of the class. - - Name of header. - Header value. - Name cannot be empty - value is null. - - - - Initializes a new instance of the class. - - - - - Gets or sets header name. - - - - - Gets or sets header value. - - - - - Resource information. - - - Used by content providers to be able to get information - on resources (views, files etc). - - - - - Gets or sets date when resource was modified. - - - if not used. - - - Should always be universal time. - - - - - Gets or sets resource stream. - - - - - Loads resources from a specific location (such as assembly, hard drive etc). - - - - - Checks if a resource exists in the specified directory - - Uri path to resource - true if resource was found; otherwise false. - - - if (resources.Exists("/files/user/user.png")) - Debug.WriteLine("Resource exists."); - - - - - - Find all views in a folder/path. - - Absolute Uri path to files that should be found, can end with wild card. - Collection to add all view names to. - - - - Gets a resource. - - Uri path to resource. - Resource - Uri contains forbidden characters. - - - Resource resource = resources.Get("/files/user/user.png"); - - - - - - Parses Cookie header. - - - - - Used to parse header values - - - - - Parse a header - - Name of header. - Reader containing value. - HTTP Header - Header value is not of the expected format. - - - - Parse a header - - Name of header. - Reader containing value. - HTTP Header - Header value is not of the expected format. - Used by to filter out unwanted connections. @@ -716,6 +25,248 @@ Gets socket. + + + A request have been received. + + + + + + + Initializes a new instance of the class. + + context that received the request. + Received request. + Response to send. + + + + Gets context that received the request. + + + Do not forget to set to true if you are sending + back a response manually through . + + + + + Gets or sets if the request have been handled. + + + The library will not attempt to send the response object + back to the client if this property is set to true. + + + + + Gets request object. + + + + + Gets response object. + + + + + Cookies that should be set. + + + + + Adds a cookie in the collection. + + cookie to add + cookie is null + Name and Content must be specified. + + + + Copy a request cookie + + + When the cookie should expire + + + + Remove all cookies + + + + + Gets a collection enumerator on the cookie list. + + collection enumerator + + + + Returns an enumerator that iterates through the collection. + + + + A that can be used to iterate through the collection. + + 1 + + + + Gets the count of cookies in the collection. + + + + + Gets the cookie of a given identifier. + + Cookie if found; otherwise null. + + + + An exception that can't be handled by the library have been thrown. + + + + + Initializes a new instance of the class. + + The exception. + + + + Gets caught exception. + + + + + Data decoded from a POST body. + + + + + Initializes a new instance of the class. + + + + + Gets or sets decoded files. + + + + + Gets or sets decoded parameters. + + + + + Stores sessions in files. + + + All session parameters must be serializable. + + + + + Stores sessions in your favorite store + + + + + + + + Saves the specified session. + + The session. + + + + Touches the specified session + + Session id. + + Used to prevent sessions from expiring. + + + + + Loads a session + + Session id. + Session if found; otherwise null. + + + + Delete a session + + Id of session + + + + Saves the specified session. + + The session. + + + + Touches the specified session + + Session id. + + Used to prevent sessions from expiring. + + + + + Loads a session + + Session id. + Session if found; otherwise null. + + + + HTTP methods. + + + + + Unknown method + + + + + Posting data + + + + + Get data + + + + + Update data + + + + + Remove data + + + + + Get only HTTP headers. + + + + + Options HTTP 1.1 header. + + + + + Http listener. + + Http listener @@ -790,2098 +341,6 @@ Fill the body with a user friendly error page, or redirect to somewhere else. - - - Collection of files. - - - - - Checks if a file exists. - - Name of the file (form item name) - - - - - Add a new file. - - File to add. - - - - Remove all files from disk. - - - - - Get a file - - Name in form - File if found; otherwise null. - - - - Gets number of files - - - - - Custom network stream to mark sockets as reusable when disposing the stream. - - - - - Creates a new instance of the class for the specified . - - - The that the will use to send and receive data. - - - The parameter is null. - - - The parameter is not connected. - -or- - The property of the parameter is not . - -or- - The parameter is in a nonblocking state. - - - - - Initializes a new instance of the class for the specified with the specified ownership. - - - The that the will use to send and receive data. - - - Set to true to indicate that the will take ownership of the ; otherwise, false. - - - The parameter is null. - - - The parameter is not connected. - -or- - the value of the property of the parameter is not . - -or- - the parameter is in a nonblocking state. - - - - - Creates a new instance of the class for the specified with the specified access rights. - - - The that the will use to send and receive data. - - - A bitwise combination of the values that specify the type of access given to the over the provided . - - - The parameter is null. - - - The parameter is not connected. - -or- - the property of the parameter is not . - -or- - the parameter is in a nonblocking state. - - - - - Creates a new instance of the class for the specified with the specified access rights and the specified ownership. - - - The that the will use to send and receive data. - - - A bitwise combination of the values that specifies the type of access given to the over the provided . - - - Set to true to indicate that the will take ownership of the ; otherwise, false. - - - The parameter is null. - - - The parameter is not connected. - -or- - The property of the parameter is not . - -or- - The parameter is in a nonblocking state. - - - - - Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. - - - - - Releases the unmanaged resources used by the and optionally releases the managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Request sent to a HTTP server. - - - - - - Base interface for request and response. - - - - - Add a new header. - - - - - - - Add a new header. - - Header to add. - - - - Gets body stream. - - - - - Size of the body. MUST be specified before sending the header, - unless property Chunked is set to true. - - - - - Kind of content in the body - - Default is text/html - - - - Gets or sets encoding - - - - - Gets headers. - - - - - Get a header - - Type that it should be cast to - Name of header - Header if found and casted properly; otherwise null. - - - - Gets or sets connection header. - - - - - Gets cookies. - - - - - Gets all uploaded files. - - - - - Gets form parameters. - - - - - Gets or sets HTTP version. - - - - - Gets if request is an Ajax request. - - - - - Gets or sets HTTP method. - - - - - Gets query string and form parameters - - - - - Gets query string. - - - - - Gets requested URI. - - - - - Serves files in the web server. - - - - FileModule fileModule = new FileModule(); - fileModule.Resources.Add(new FileResources("/", "C:\\inetpub\\myweb")); - - - - - - HTTP Module - - - - - Process a request. - - Request information - What to do next. - - - - Initializes a new instance of the class. - - baseUri or basePath is null. - - - - Mime types that this class can handle per default - - - Contains the following mime types: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Will send a file to client. - - HTTP context containing outbound stream. - Response containing headers. - File stream - - - - Process a request. - - Request information - What to do next. - Failed to find file extension - Forbidden file type. - - - - Gets a list with all allowed content types. - - All other mime types will result in . - - - - Gets provider used to add files to the file manager, - - - - - An exception that can't be handled by the library have been thrown. - - - - - Initializes a new instance of the class. - - The exception. - - - - Gets caught exception. - - - - - Helper for content types. - - - - - Decodes forms that have multiple sections. - - - http://www.faqs.org/rfcs/rfc1867.html - - - - - Decodes body stream. - - - - - Decode body stream - - Stream containing the content - Content type header - Stream encoding - Decoded data. - Body format is invalid for the specified content type. - Something unexpected failed. - - - - All content types that the decoder can parse. - - A collection of all content types that the decoder can handle. - - - - form-data - - - - - multipart/form-data - - - - - Decode body stream - - Stream containing the content - Content type header - Stream encoding - Decoded data. - Body format is invalid for the specified content type. - Something unexpected failed. - stream is null. - - - - All content types that the decoder can parse. - - A collection of all content types that the decoder can handle. - - - - A HTTP parser using delegates to which parsing methods. - - - - - Initializes a new instance of the class. - - - - - Parser method to copy all body bytes. - - - Needed since a TCP packet can contain multiple messages - after each other, or partial messages. - - - - Try to find a header name. - - - - - - Get header values. - - - Will also look for multi header values and automatically merge them to one line. - Content length is not a number. - - - - Toggle body bytes event. - - - - - - - - Raise the event, since we have successfully parsed a message and it's body. - - - - - First message line. - - Will always contain three elements. - Used to raise the or event - depending on the words in the array. - BadRequestException. - - - - Continue parsing a message - - Byte buffer containing bytes - Where to start the parsing - Number of bytes to parse - index where the parsing stopped. - Parsing failed. - - - - Parses the first line in a request/response. - - true if first line is well formatted; otherwise false. - Invalid request/response line. - - - - Reset parser to initial state. - - - - - Gets or sets current line number. - - - - - The request line has been parsed. - - - - - Response line has been parsed. - - - - - Parsed a header. - - - - - Received body bytes. - - - - - A message have been successfully parsed. - - - - - Used to be able to quickly swap parser method. - - - - - - Default log filter implementation. - - - - - Determines which classes can log - - - - - Checks if the specified type can send - log entries at the specified level. - - Log level - Type that want to write a log entry. - true if logging is allowed; otherwise false. - - - - Add a name space filter. - - Name space to add filter for. - Minimum log level required. - - - // Parsing can only add error and fatal messages - AddNamespace("SipSharp.Messages.Headers.Parsers", LogLevel.Error); - AddType(typeof(SipParser), LogLevel.Error); - - // Transport layer can only log warnings, errors and fatal messages - AddNamespace("SipSharp.Transports.*", LogLevel.Warning); - - - - - - Used to specify standard filter rules - - - Parser can only display errors. Transports only warnings. - - - - - Add filter for a type - - Type to add filter for. - Minimum log level required. - - - // Parsing can only add error and fatal messages - AddNamespace("SipSharp.Messages.Headers.Parsers", LogLevel.Error); - AddType(typeof(SipParser), LogLevel.Error); - - // Transport layer can only log warnings, errors and fatal messages - AddNamespace("SipSharp.Transports.*", LogLevel.Warning); - - - - - - Add filter for a type - - Type to add filter for. - Minimum log level required. - - - // Parsing can only add error and fatal messages - AddNamespace("SipSharp.Messages.Headers.Parsers", LogLevel.Error); - AddType("SipSharp.Messages.MessageFactory", LogLevel.Error); - - // Transport layer can only log warnings, errors and fatal messages - AddNamespace("SipSharp.Transports.*", LogLevel.Warning); - - - Type could not be identified. - - - - Checks if the specified type can send - log entries at the specified level. - - Log level - Type that want to write a log entry. - true if logging is allowed; otherwise false. - - - No filters = everything logged. = no logs. Don't use a rule with '*' or '.*' - - - - User have specified a wild card filter. - - - Wild card filters are used to log a name space and - all it's children name spaces. - - - - - Factory implementation used to create logs. - - - - - Create a new logger. - - Type that requested a logger. - Logger for the specified type; - - MUST ALWAYS return a logger. Return if no logging - should be used. - - - - - Response to a request. - - - - - Redirect user. - - Where to redirect to. - - Any modifications after a redirect will be ignored. - - - - - Gets connection type. - - - - - Gets cookies. - - - - - Gets HTTP version. - - - Default is HTTP/1.1 - - - - - Information about why a specific status code was used. - - - - - Status code that is sent to the client. - - Default is - - - - Gets or sets content type - - - - - Rules are used to perform operations before a request is being handled. - Rules can be used to create routing etc. - - - - - Process the incoming request. - - Request context information. - Processing result. - If any parameter is null. - - - - Used to access resources. - - - - - Add a new resource loader. - - Provider to add. - Manager have been started. - - - - Check if a resource exists. - - Uri to check - true if found; otherwise false. - - - if (manager.Exists("/views/user/view.haml")) - return true - - - - - - Get a resource. - - Uri path to resource. - Resource if found; otherwise null. - - - Resource resource = manager.Get("/views/user/view.haml"); - - - - - - Start manager. - - - - - Gets number of resource providers - - - - - Parses "Date" header. - - - - - Parse a header - - Name of header. - Reader containing value. - HTTP Header - Header value is not of the expected format. - - - - Content-type - - - - - Header in a message - - - Important! Each header should override ToString() - and return it's data correctly formatted as a HTTP header value. - - - - - Gets header name - - - - - Gets value as it would be sent back to client. - - - - - Header name. - - - - - Initializes a new instance of the class. - - Type of the content. - Value parameters. - - - - Initializes a new instance of the class. - - Type of the content. - - - - Returns data formatted as a HTTP header value. - - - A that represents the current . - - - - - Gets all parameters. - - - - - Gets content type. - - - - - Gets header name - - - - - Used to get or set properties on objects. - - - This class should be a bit faster than the standard reflection. - - - - - Get cached type. - - Type to get/set properties in - Type to use - - - - Flyweight design pattern implementation. - - Type of object. - - - - Initializes a new instance of the class. - - How large buffers to allocate. - - - - Get an object. - - Created object. - Will create one if queue is empty. - - - - Enqueues the specified buffer. - - Object to enqueue. - Buffer is is less than the minimum requirement. - - - - Used to create new objects. - - Type of objects to create. - Newly created object. - . - - - - Used to load/store sessions in the server. - - - - - Initializes a new instance of the class. - - Web server that the provider is for.. - Store to use. - - - - Initializes a new instance of the class. - - The server. - - Uses a file store. - - - - - Loads a session for all requests that got the session cookie. - - The sender. - The instance containing the event data. - - - - Gets current session - - Session if set, otherwise null. - - - - Gets or sets the session life time in minutes. - - The session life time. - - - - A session have been loaded. Use to access it. - - - - - Used to build headers. - - - - - Add a parser - - Header that the parser is for. - Parser implementation - - Will replace any existing parser for the specified header. - - - - - Add all default (built-in) parsers. - - - Will not replace previously added parsers. - - - - - Create a header parser - - implementation. - - - Uses attribute to find which headers - the parser is for. - - Will not replace previously added parsers. - - - - - Parse a header. - - Name of header - Header value - Header. - Value is not a well formatted header value. - - - - Arguments used when more body bytes have come. - - - - - Initializes a new instance of the class. - - buffer that contains the received bytes. - offset in buffer where to start processing. - number of bytes from that should be parsed. - buffer is null. - - - - Initializes a new instance of the class. - - - - - Gets or sets buffer that contains the received bytes. - - - - - Gets or sets number of bytes from that should be parsed. - - - - - Gets or sets offset in buffer where to start processing. - - - - - redirects from one URL to another. - - - - - Initializes a new instance of the class. - - Absolute path (no server name) - Absolute path (no server name) - - server.Add(new RedirectRule("/", "/user/index")); - - - - - Initializes a new instance of the class. - - Absolute path (no server name) - Absolute path (no server name) - true if request should be redirected, false if the request URI should be replaced. - - server.Add(new RedirectRule("/", "/user/index")); - - - - - Process the incoming request. - - Request context. - Processing result. - If any parameter is null. - - - - Gets string to match request URI with. - - Is compared to request.Uri.AbsolutePath - - - - Gets whether the server should redirect the client instead of simply modifying the URI. - - - false means that the rule will replace - the current request URI with the new one from this class. - true means that a redirect response is sent to the client. - - - - - Gets where to redirect. - - - - - cookie sent by the client/browser - - - - - - Constructor. - - cookie identifier - cookie content - id or content is null - id is empty - - - - Gets the cookie HTML representation. - - cookie string - - - - Gets the cookie identifier. - - - - - Gets value. - - - Set to null to remove cookie. - - - - - A request have been parsed successfully by the server. - - - - - Initializes a new instance of the class. - - Received request. - - - - Gets received request. - - - - - Context that received a HTTP request. - - - - - Disconnect context. - - - - - Gets if current context is using a secure connection. - - - - - Gets logger. - - - - - Gets remote end point - - - - - Gets stream used to send/receive data to/from remote end point. - - - - The stream can be any type of stream, do not assume that it's a network - stream. For instance, it can be a or a ZipStream. - - - - - - Gets the currently handled request - - The request. - - - - Gets the response that is going to be sent back - - The response. - - - - File sent from remote end. - - - - - Gets or sets content type. - - - - - Gets or sets name in form. - - - - - Gets or sets name original file name - - - - - Gets or sets filename for locally stored file. - - - - - Client X.509 certificate, X.509 chain, and any SSL policy errors encountered - during the SSL stream creation - - - - - Initializes a new instance of the class. - - The certificate. - Client security certificate chain. - Any SSL policy errors encountered during the SSL stream creation. - - - - Client security certificate - - - - - Client security certificate chain - - - - - Any SSL policy errors encountered during the SSL stream creation - - - - - Provides sessions. - - Type of session object - - Will always use files for sessions (utilizing the binary formatter), but can - also cache them in memory. - - If caching is enabled, it will only write sessions to disk every 20 seconds if they have - been accessed the last minute (to not keep writing dead sessions to disk). - - - - - - Initializes a new instance of the class. - - Session type must use [Serializable] attribute. - - - - Create a new session. - - - - - - Load session - - Id of session. - Session if found; otherwise null. - sessionId is null. - - - - Load session when a new request comes in. - - - - - - - Save a session to disk. - - Session to write to disk. - - You are responsible for writing sessions to disk if you are not using caching. - - - - - Start the session system and hook - - - - - - Stop session handling - - - - - Gets or sets session cookie name - - - - - Gets or sets cache - - - - - Gets current session. - - - - - Gets or sets number of seconds before a session expired. - - - A session have expired if nothing have accessed it for X seconds. This - class modifies the write time each time it's accessed. - - - - - Determines if cookie should be set in the response. - - - - - Invoked when a session have been changed and should be written to disc. - - - - - Base class for sessions. - - - Your class must be tagged with attribute to be able to use sessions. - - - - - The session have been changed and should be written to disk. - - - - - Session have been changed. - - - - - Gets or sets when session was accessed last - - - - - Gets current session. - - - - - Gets or sets session id. - - - - - Gets or sets when the session was last written to disk. - - - - - Used when the request line have been successfully parsed. - - - - - Initializes a new instance of the class. - - The HTTP method. - The URI path. - The HTTP version. - - - - Initializes a new instance of the class. - - - - - Gets or sets HTTP method. - - - Should be one of the methods declared in . - - - - - Gets or sets requested URI path. - - - - - Gets or sets the version of the SIP protocol that the client want to use. - - - - - cookie being sent back to the browser. - - - - - - Constructor. - - cookie identifier - cookie content - cookie expiration date. Use for session cookie. - id or content is null - id is empty - - - - Create a new cookie - - name identifying the cookie - cookie value - when the cookie expires. Setting will delete the cookie when the session is closed. - Path to where the cookie is valid - Domain that the cookie is valid for. - - - - Create a new cookie - - Name and value will be used - when the cookie expires. - - - - Gets the cookie HTML representation. - - cookie string - - - - Gets when the cookie expires. - - means that the cookie expires when the session do so. - - - - Gets path that the cookie is valid under. - - - - - Used to define which headers a parse is for. - - - - - Initializes a new instance of the class. - - Name of the header. - - - - Gets name of header that this parser is for. - - - - - Collection of headers. - - - - - Gets a header - - header name. - header if found; otherwise null. - - - - Something failed during parsing. - - - - - Request couldn't be parsed successfully. - - - - - Exception thrown from HTTP server. - - - - - Initializes a new instance of the class. - - HTTP status code. - Exception description. - - - - Initializes a new instance of the class. - - HTTP status code. - Exception description. - Inner exception. - - - - Gets HTTP status code. - - - - - Initializes a new instance of the class. - - Exception description. - - - - Initializes a new instance of the class. - - Exception description. - Exception description. - - - - Initializes a new instance of the class. - - Exception description. - - - - Initializes a new instance of the class. - - Exception description. - Inner exception. - - - - Provides resources. - - - - - Get all view names from a folder. - - Path to find views in. - A collection of view names (without path). - - - - Add a new resource loader. - - Provider to add. - Manager have been started. - - - - Start manager. - - - - - Check if a resource exists. - - Uri to check - true if found; otherwise false. - - - if (manager.Exists("/views/user/view.haml")) - return true - - - - - - Get a resource. - - Uri path to resource. - Resource if found; otherwise null. - - - Resource resource = manager.Get("/views/user/view.haml"); - - - - - - Gets number of resource providers - - - - - Collection of parameters. - - - or is not used since each parameter can - have multiple values. - - - - - Collection of parameters - - - - - Get a parameter. - - - - - - - Add a query string parameter. - - Parameter name - Value - - - - Checks if the specified parameter exists - - Parameter name. - true if found; otherwise false; - - - - Gets number of parameters. - - - - - Gets last value of an parameter. - - Parameter name - String if found; otherwise null. - - - - Initializes a new instance of the class. - - Collections to merge. - - Later collections will overwrite parameters from earlier collections. - - - - - Initializes a new instance of the class. - - - - - Get a list of string arrays. - - - - - - Get parameters - - Sub array (text array) - - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - 1 - - - - Returns an enumerator that iterates through a collection. - - - An object that can be used to iterate through the collection. - - 2 - - - - Get a parameter. - - - - - - - Add a query string parameter. - - Parameter name - Value - - - - Checks if the specified parameter exists - - Parameter name. - true if found; otherwise false; - - - - Gets number of parameters. - - - - - Gets last value of an parameter. - - Parameter name - String if found; otherwise null. - - - - Result of processing. - - - - - Continue with the next handler - - - - - No more handlers can process the request. - - - The server will process the response object and - generate a HTTP response from it. - - - - - Response have been sent back by the handler. - - - This option should only be used if you are streaming - something or sending back a custom result. The server will - not process the response object or send anything back - to the client. - - - - - A HTTP context - - - - - - - - Initializes a new instance of the class. - - Socket received from HTTP listener. - Context used to parse incoming messages. - - - - Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - - 2 - - - - Disconnect context. - - - - - Close and release socket. - - - - - Create stream used to send and receive bytes from the socket. - - Socket to wrap - Stream - Stream could not be created. - - - - Interpret incoming data. - - - - - - A request was received from the parser. - - - - - - - Parse all complete requests in buffer. - - - offset in buffer where parsing stopped. - Parsing failed. - - - - Start content. - - A socket operation failed. - Reading from stream failed. - - - - Gets currently executing HTTP context. - - - - - Gets or sets description - - - - - gets factory used to build request objects - - - - - Gets socket - - - - - Gets remove end point - - - - - Gets network stream. - - - - - Gets the currently handled request - - The request. - - - - Gets the response that is going to be sent back - - The response. - - - - Gets logger. - - - - - Gets if current context is using a secure connection. - - - - - Triggered for all requests in the server (after the response have been sent) - - - - - Triggered for current request (after the response have been sent) - - - - - A new request have been received. - - - - - A new request have been received (invoked for ALL requests) - - - - - Client have been disconnected. - - - - - Client asks if he may continue. - - - If the body is too large or anything like that you should respond . - - - - - Used to store all headers that that aren't recognized. - - - - - Initializes a new instance of the class. - - The name. - The value. - - - - Gets or sets value - - - - - Gets header name - - - - - The Connection general-header field allows the sender to specify options - that are desired for that particular connection and MUST NOT be - communicated by proxies over further connections. - - - - HTTP/1.1 proxies MUST parse the Connection header field before a - message is forwarded and, for each connection-token in this field, - remove any header field(s) from the message with the same name as the - connection-token. Connection options are signaled by the presence of - a connection-token in the Connection header field, not by any - corresponding additional header field(s), since the additional header - field may not be sent if there are no parameters associated with that - connection option. - - Message headers listed in the Connection header MUST NOT include - end-to-end headers, such as Cache-Control. - - HTTP/1.1 defines the "close" connection option for the sender to - signal that the connection will be closed after completion of the - response. For example, - - Connection: close - - in either the request or the response header fields indicates that - the connection SHOULD NOT be considered `persistent' (section 8.1) - after the current request/response is complete. - - HTTP/1.1 applications that do not support persistent connections MUST - include the "close" connection option in every message. - - A system receiving an HTTP/1.0 (or lower-version) message that - includes a Connection header MUST, for each connection-token in this - field, remove and ignore any header field(s) from the message with - the same name as the connection-token. This protects against mistaken - forwarding of such header fields by pre-HTTP/1.1 proxies. See section - 19.6.2 in RFC2616. - - - - - - Header name - - - - - Default connection header for HTTP/1.0 - - - - - Default connection header for HTTP/1.1 - - - - - Initializes a new instance of the class. - - Connection type. - The parameters. - - - - Initializes a new instance of the class. - - The type. - - - - Returns data formatted as a HTTP header value. - - - A that represents the current . - - - - - Gets connection parameters. - - - - - Gets or sets connection type - - - - - Gets header name - - - - - Type of HTTP connection - - - - - Connection is closed after each request-response - - - - - Connection is kept alive for X seconds (unless another request have been made) - - - - - Parameter in - - - - - Gets *last* value. - - - Parameters can have multiple values. This property will always get the last value in the list. - - String if any value exist; otherwise null. - - - - Gets or sets name. - - - - - Gets a list of all values. - - - - - A parameter in . - - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - 1 - - - - Returns an enumerator that iterates through a collection. - - - An object that can be used to iterate through the collection. - - 2 - - - - Gets last value. - - - Parameters can have multiple values. This property will always get the last value in the list. - - String if any value exist; otherwise null. - - - - Gets or sets name. - - - - - Gets a list of all values. - - - - - Secure version of the HTTP listener. - - - - - Http listener. - - Initializes a new instance of the class. @@ -3020,271 +479,120 @@ If the body is too large or anything like that you should respond . - + - Initializes a new instance of the class. + Contents of a cookie header. - Address to accept new connections on. - Port to accept connections on. - Certificate securing the connection. - + - Create a new context + Header in a message - Accepted socket - A new context. - Factory is assigned by the on each incoming request. + Important! Each header should override ToString() + and return it's data correctly formatted as a HTTP header value. - + - Gets if listener is secure. + Gets header name + + + + + Gets value as it would be sent back to client. + + + + + Initializes a new instance of the class. + + The collection. + collection is null. + + + + Gets cookie collection + + + + + Gets header name + + + + + Gets value as it would be sent back to client. - + - Gets or sets SSL protocol. + Request couldn't be parsed successfully. - + - Gets or sets if client certificate should be used. + Exception thrown from HTTP server. - + - A response have been received. + Initializes a new instance of the class. + + HTTP status code. + Exception description. + + + + Initializes a new instance of the class. + + HTTP status code. + Exception description. + Inner exception. + + + + Gets HTTP status code. - + - Initializes a new instance of the class. + Initializes a new instance of the class. - The response. + Exception description. - + - Gets or sets response. + Initializes a new instance of the class. + + Exception description. + Exception description. + + + + Something failed during parsing. - + - Implements HTTP Digest authentication. It's more secure than Basic auth since password is - encrypted with a "key" from the server. + Initializes a new instance of the class. - - Keep in mind that the password is encrypted with MD5. Use a combination of SSL and digest auth to be secure. - + Exception description. - + - Authenticates requests + Initializes a new instance of the class. + + Exception description. + Inner exception. + + + + Used to parse header values - - - Authenticate request - - Authorization header send by web client - Realm to authenticate in, typically a domain name. - HTTP Verb used in the request. - User if authentication was successful; otherwise null. - - - - Create a authentication challenge. - - Realm that the user should authenticate in - A WWW-Authenticate header. - If realm is empty or null. - - - - Gets authenticator scheme - - - digest - - - - - Initializes a new instance of the class. - - Supplies users during authentication process. - - - - Used by test classes to be able to use hardcoded values - - - - - An authentication response have been received from the web browser. - Check if it's correct - - Contents from the Authorization header - Realm that should be authenticated - GET/POST/PUT/DELETE etc. - - Authentication object that is stored for the request. A user class or something like that. - - if authenticationHeader is invalid - If any of the parameters is empty or null. - - - - Encrypts parameters into a Digest string - - Realm that the user want to log into. - User logging in - Users password. - HTTP method. - Uri/domain that generated the login prompt. - Quality of Protection. - "Number used ONCE" - Hexadecimal request counter. - "Client Number used ONCE" - Digest encrypted string - - - - - - Md5 hex encoded "userName:realm:password", without the quotes. - Md5 hex encoded "method:uri", without the quotes - Quality of Protection - "Number used ONCE" - Hexadecimal request counter. - Client number used once - - - - - Create a authentication challenge. - - Realm that the user should authenticate in - A correct auth request. - If realm is empty or null. - - - - Gets the current nonce. - - - - - - Gets the Md5 hash bin hex2. - - To be hashed. - - - - - determines if the nonce is valid or has expired. - - nonce value (check wikipedia for info) - true if the nonce has not expired. - - - - Gets authentication scheme name - - - - - Gets authenticator scheme - - - - digest - - - - - Session in the system - - - - - Gets or sets session id. - - - - - Factory creating null logger. - - - - - Initializes a new instance of the class. - - - - - Create a new logger. - - Type that requested a logger. - Logger for the specified type; - - MUST ALWAYS return a logger. Return if no logging - should be used. - - - - - Logger instance. - - - - - HTTP methods. - - - - - Unknown method - - - - - Posting data - - - - - Get data - - - - - Update data - - - - - Remove data - - - - - Get only HTTP headers. - - - - - Options HTTP 1.1 header. - - - - - Parses . - - - + Parse a header @@ -3293,54 +601,175 @@ HTTP Header Header value is not of the expected format. - + - Request couldn't be parsed successfully. + Collection of body decoders. + + + Body decoders are used to parse request body and convert it + into a and a . + + + + + Add another body decoder. + + + + + + Decode body stream + + Stream containing the content + Content type header + Stream encoding + Decoded data. + Body format is invalid for the specified content type. + Something unexpected failed. + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + 1 + + + + Returns an enumerator that iterates through a collection. + + + An object that can be used to iterate through the collection. + + 2 + + + + Gets number of decoders. - + - Initializes a new instance of the class. - - Exception description. - - - - Initializes a new instance of the class. - - Exception description. - Exception description. - - - - Arguments for . + Custom network stream to mark sockets as reusable when disposing the stream. - + - Initializes a new instance of the class. + Creates a new instance of the class for the specified . - The context. + + The that the will use to send and receive data. + + + The parameter is null. + + + The parameter is not connected. + -or- + The property of the parameter is not . + -or- + The parameter is in a nonblocking state. + - + - Gets or sets thrown exception + Initializes a new instance of the class for the specified with the specified ownership. + + + The that the will use to send and receive data. + + + Set to true to indicate that the will take ownership of the ; otherwise, false. + + + The parameter is null. + + + The parameter is not connected. + -or- + the value of the property of the parameter is not . + -or- + the parameter is in a nonblocking state. + + + + + Creates a new instance of the class for the specified with the specified access rights. + + + The that the will use to send and receive data. + + + A bitwise combination of the values that specify the type of access given to the over the provided . + + + The parameter is null. + + + The parameter is not connected. + -or- + the property of the parameter is not . + -or- + the parameter is in a nonblocking state. + + + + + Creates a new instance of the class for the specified with the specified access rights and the specified ownership. + + + The that the will use to send and receive data. + + + A bitwise combination of the values that specifies the type of access given to the over the provided . + + + Set to true to indicate that the will take ownership of the ; otherwise, false. + + + The parameter is null. + + + The parameter is not connected. + -or- + The property of the parameter is not . + -or- + The parameter is in a nonblocking state. + + + + + Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. - + - Gets or sets if error page was provided. + Releases the unmanaged resources used by the and optionally releases the managed resources. + + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + + Parses query string - + - Gets requested resource. + Parse a query string + string to parse + A collection + reader is null. - + - Gets response to send + Parse a query string + string to parse + A collection + queryString is null. @@ -3570,74 +999,203 @@ Initializes a new instance of the class. - + - Class to make dynamic binding of redirects. Instead of having to specify a number of similar redirect rules - a regular expression can be used to identify redirect URLs and their targets. + Used to build headers. - - [a-z0-9]+)", "/users/${target}/?find=true", RegexOptions.IgnoreCase) - ]]> - - + - Initializes a new instance of the class. + Add a parser - Expression to match URL - Expression to generate URL - - [a-zA-Z0-9]+)", "/user/${first}")); - Result of ie. /employee1 will then be /user/employee1 - ]]> - + Header that the parser is for. + Parser implementation + + Will replace any existing parser for the specified header. + - + - Initializes a new instance of the class. + Add all default (built-in) parsers. - Expression to match URL - Expression to generate URL - Regular expression options to use, can be null - - [a-zA-Z0-9]+)", "/user/{first}", RegexOptions.IgnoreCase)); - Result of ie. /employee1 will then be /user/employee1 - ]]> - + + Will not replace previously added parsers. + - + - Initializes a new instance of the class. + Create a header parser - Expression to match URL - Expression to generate URL - Regular expression options to apply - true if request should be redirected, false if the request URI should be replaced. - - [a-zA-Z0-9]+)", "/user/${first}", RegexOptions.None)); - Result of ie. /employee1 will then be /user/employee1 - ]]> - - Argument is null. - + implementation. + + + Uses attribute to find which headers + the parser is for. + + Will not replace previously added parsers. + - + - Process the incoming request. + Parse a header. + + Name of header + Header value + Header. + Value is not a well formatted header value. + + + + Request sent to a HTTP server. + + + + + + Base interface for request and response. + + + + + Add a new header. + + + + + + + Add a new header. + + Header to add. + + + + Gets body stream. + + + + + Size of the body. MUST be specified before sending the header, + unless property Chunked is set to true. + + + + + Kind of content in the body + + Default is text/html + + + + Gets or sets encoding + + + + + Gets headers. + + + + + Get a header + + Type that it should be cast to + Name of header + Header if found and casted properly; otherwise null. + + + + Gets or sets connection header. + + + + + Gets cookies. + + + + + Gets all uploaded files. + + + + + Gets form parameters. + + + + + Gets or sets HTTP version. + + + + + Gets if request is an Ajax request. + + + + + Gets or sets HTTP method. + + + + + Gets query string and form parameters + + + + + Gets query string. + + + + + Gets requested URI. - Request context. - Processing result. - If any parameter is null. Load resources from disk. + + + Loads resources from a specific location (such as assembly, hard drive etc). + + + + + Checks if a resource exists in the specified directory + + Uri path to resource + true if resource was found; otherwise false. + + + if (resources.Exists("/files/user/user.png")) + Debug.WriteLine("Resource exists."); + + + + + + Find all views in a folder/path. + + Absolute Uri path to files that should be found, can end with wild card. + Collection to add all view names to. + + + + Gets a resource. + + Uri path to resource. + Resource + Uri contains forbidden characters. + + + Resource resource = resources.Get("/files/user/user.png"); + + + Default forbidden characters. @@ -3803,6 +1361,1749 @@ Response containing call headers. Content used to send headers. + + + Decodes body stream. + + + + + Decode body stream + + Stream containing the content + Content type header + Stream encoding + Decoded data. + Body format is invalid for the specified content type. + Something unexpected failed. + + + + All content types that the decoder can parse. + + A collection of all content types that the decoder can handle. + + + + Decodes forms that have multiple sections. + + + http://www.faqs.org/rfcs/rfc1867.html + + + + + form-data + + + + + multipart/form-data + + + + + Decode body stream + + Stream containing the content + Content type header + Stream encoding + Decoded data. + Body format is invalid for the specified content type. + Something unexpected failed. + stream is null. + + + + All content types that the decoder can parse. + + A collection of all content types that the decoder can handle. + + + + Provides sessions. + + Type of session object + + Will always use files for sessions (utilizing the binary formatter), but can + also cache them in memory. + + If caching is enabled, it will only write sessions to disk every 20 seconds if they have + been accessed the last minute (to not keep writing dead sessions to disk). + + + + + + Initializes a new instance of the class. + + Session type must use [Serializable] attribute. + + + + Create a new session. + + + + + + Load session + + Id of session. + Session if found; otherwise null. + sessionId is null. + + + + Load session when a new request comes in. + + + + + + + Save a session to disk. + + Session to write to disk. + + You are responsible for writing sessions to disk if you are not using caching. + + + + + Start the session system and hook + + + + + + Stop session handling + + + + + Gets or sets session cookie name + + + + + Gets or sets cache + + + + + Gets current session. + + + + + Gets or sets number of seconds before a session expired. + + + A session have expired if nothing have accessed it for X seconds. This + class modifies the write time each time it's accessed. + + + + + Determines if cookie should be set in the response. + + + + + Invoked when a session have been changed and should be written to disc. + + + + + First line in a response have been received + + + + + Gets or sets motivation to why the status code was used. + + + + + Gets or sets message status code + + + + + Gets or sets sip protocol version used. + + + + + A request have been parsed successfully by the server. + + + + + Initializes a new instance of the class. + + Received request. + + + + Gets received request. + + + + + Provider returning user to be authenticated. + + + + + Lookups the specified user + + User name. + Typically web server domain name. + User if found; otherwise null. + + User name can basically be anything. For instance name entered by user when using + basic or digest authentication, or SID when using Windows authentication. + + + + + Gets the principal to use. + + Successfully authenticated user. + + + Invoked when a user have successfully been authenticated. + + + + + + + User information used during authentication process. + + + + + Gets or sets user name used during authentication. + + + + + Gets or sets unencrypted password. + + + Password as clear text. You could use instead if your passwords + are encrypted in the database. + + + + + Gets or sets HA1 hash. + + + + Digest authentication requires clear text passwords to work. If you + do not have that, you can store a HA1 hash in your database (which is part of + the Digest authentication process). + + + A HA1 hash is simply a Md5 encoded string: "UserName:Realm:Password". The quotes should + not be included. Realm is the currently requested Host (as in Request.Headers["host"]). + + + Leave the string as null if you are not using HA1 hashes. + + + + + + Used to get or set properties on objects. + + + This class should be a bit faster than the standard reflection. + + + + + Get cached type. + + Type to get/set properties in + Type to use + + + + Used to load/store sessions in the server. + + + + + Initializes a new instance of the class. + + Web server that the provider is for.. + Store to use. + + + + Initializes a new instance of the class. + + The server. + + Uses a file store. + + + + + Loads a session for all requests that got the session cookie. + + The sender. + The instance containing the event data. + + + + Gets current session + + Session if set, otherwise null. + + + + Gets or sets the session life time in minutes. + + The session life time. + + + + A session have been loaded. Use to access it. + + + + + Secure version of the HTTP listener. + + + + + Initializes a new instance of the class. + + Address to accept new connections on. + Port to accept connections on. + Certificate securing the connection. + + + + Create a new context + + Accepted socket + A new context. + + Factory is assigned by the on each incoming request. + + + + + Gets if listener is secure. + + + + + + Gets or sets SSL protocol. + + + + + Gets or sets if client certificate should be used. + + + + + Arguments used when more body bytes have come. + + + + + Initializes a new instance of the class. + + buffer that contains the received bytes. + offset in buffer where to start processing. + number of bytes from that should be parsed. + buffer is null. + + + + Initializes a new instance of the class. + + + + + Gets or sets buffer that contains the received bytes. + + + + + Gets or sets number of bytes from that should be parsed. + + + + + Gets or sets offset in buffer where to start processing. + + + + + HTTP Module + + + + + Process a request. + + Request information + What to do next. + + + + Something unexpected went wrong. + + + + + Initializes a new instance of the class. + + Exception description. + + + + Initializes a new instance of the class. + + Exception description. + Inner exception. + + + + File sent from remote end. + + + + + Gets or sets content type. + + + + + Gets or sets name in form. + + + + + Gets or sets name original file name + + + + + Gets or sets filename for locally stored file. + + + + + Parses Cookie header. + + + + + Parse a header + + Name of header. + Reader containing value. + HTTP Header + Header value is not of the expected format. + + + + Decodes URL encoded values. + + + + + + + Stream containing the content + Content type header + Stream encoding + Collection with all parameters. + Body format is invalid for the specified content type. + Failed to read all bytes from body stream. + + + + All content types that the decoder can parse. + + A collection of all content types that the decoder can handle. + + + + Base class for sessions. + + + Your class must be tagged with attribute to be able to use sessions. + + + + + The session have been changed and should be written to disk. + + + + + Session have been changed. + + + + + Gets or sets when session was accessed last + + + + + Gets current session. + + + + + Gets or sets session id. + + + + + Gets or sets when the session was last written to disk. + + + + + Parses and builds messages + + + The message factory takes care of building messages + from all end points. + + Since both message and packet protocols are used, the factory + hands out contexts to all end points. The context keeps a state + to be able to parse partial messages properly. + + + Each end point need to hand the context back to the message factory + when the client disconnects (or a message have been parsed). + + + + + + Initializes a new instance of the class. + + Factory used to create headers. + + + + Create a new message factory context. + + A new context. + + A context is used to parse messages from a specific endpoint. + + + + + Release a used factory context. + + + + + + A request have been received from one of the end points. + + + + + A response have been received from one of the end points. + + + + + Interface used to write to log files. + + + If you want to use the built in filtering mechanism, create a constructor + which takes one parameter, a . + + + + + Write an entry that helps when debugging code. + + Log message + + + + Write an entry that helps when debugging code. + + Log message + Thrown exception to log. + + + + Something went wrong, but the application do not need to die. The current thread/request + cannot continue as expected. + + Log message + + + + Something went wrong, but the application do not need to die. The current thread/request + cannot continue as expected. + + Log message + Thrown exception to log. + + + + Something went very wrong, application might not recover. + + Log message + + + + Something went very wrong, application might not recover. + + Log message + Thrown exception to log. + + + + Informational message, needed when helping customer to find a problem. + + Log message + + + + Informational message, needed when helping customer to find a problem. + + Log message + Thrown exception to log. + + + + Write a entry that helps when trying to find hard to find bugs. + + Log message + + + + Write a entry that helps when trying to find hard to find bugs. + + Log message + Thrown exception to log. + + + + Something is not as we expect, but the code can continue to run without any changes. + + Log message + + + + Something is not as we expect, but the code can continue to run without any changes. + + Log message + Thrown exception to log. + + + + cookie sent by the client/browser + + + + + + Constructor. + + cookie identifier + cookie content + id or content is null + id is empty + + + + Gets the cookie HTML representation. + + cookie string + + + + Gets the cookie identifier. + + + + + Gets value. + + + Set to null to remove cookie. + + + + + Arguments for . + + + + + Initializes a new instance of the class. + + The context. + + + + Gets or sets thrown exception + + + + + Gets or sets if error page was provided. + + + + + Gets requested resource. + + + + + Gets response to send + + + + + Implements HTTP Digest authentication. It's more secure than Basic auth since password is + encrypted with a "key" from the server. + + + Keep in mind that the password is encrypted with MD5. Use a combination of SSL and digest auth to be secure. + + + + + Authenticates requests + + + + + Authenticate request + + Authorization header send by web client + Realm to authenticate in, typically a domain name. + HTTP Verb used in the request. + User if authentication was successful; otherwise null. + + + + Create a authentication challenge. + + Realm that the user should authenticate in + A WWW-Authenticate header. + If realm is empty or null. + + + + Gets authenticator scheme + + + digest + + + + + Initializes a new instance of the class. + + Supplies users during authentication process. + + + + Used by test classes to be able to use hardcoded values + + + + + An authentication response have been received from the web browser. + Check if it's correct + + Contents from the Authorization header + Realm that should be authenticated + GET/POST/PUT/DELETE etc. + + Authentication object that is stored for the request. A user class or something like that. + + if authenticationHeader is invalid + If any of the parameters is empty or null. + + + + Encrypts parameters into a Digest string + + Realm that the user want to log into. + User logging in + Users password. + HTTP method. + Uri/domain that generated the login prompt. + Quality of Protection. + "Number used ONCE" + Hexadecimal request counter. + "Client Number used ONCE" + Digest encrypted string + + + + + + Md5 hex encoded "userName:realm:password", without the quotes. + Md5 hex encoded "method:uri", without the quotes + Quality of Protection + "Number used ONCE" + Hexadecimal request counter. + Client number used once + + + + + Create a authentication challenge. + + Realm that the user should authenticate in + A correct auth request. + If realm is empty or null. + + + + Gets the current nonce. + + + + + + Gets the Md5 hash bin hex2. + + To be hashed. + + + + + determines if the nonce is valid or has expired. + + nonce value (check wikipedia for info) + true if the nonce has not expired. + + + + Gets authentication scheme name + + + + + Gets authenticator scheme + + + + digest + + + + + Provides authentication in the web server. + + + To initiate authentication you just need to throw a Una + + + + + Add a authenticator. + + + + + + Authenticate request. + + + + + Requires that a AuthorizationHeader have been sent by the client. If not, + request one by sending a WWW-Authentication header (can be generated by the Challenge method). + + Authorization header was not found in the request. + Requested authentication scheme is not supported. + + + + Create a challenge header (WWW-authenticate) + + Response that the authentication header should be added to + Realm that the user should authenticate in + WWW-Authenticate header. + + + Scheme can currently be basic or digest. Basic is not very safe, but easier to use. + Digest is quite safe. + + + + Requested scheme is not supported. + + + + Creates a console logger. + + + + + Factory implementation used to create logs. + + + + + Create a new logger. + + Type that requested a logger. + Logger for the specified type; + + MUST ALWAYS return a logger. Return if no logging + should be used. + + + + + Initializes a new instance of the class. + + The filter. + + + + Create a new logger. + + Type that requested a logger. + Logger for the specified type; + + MUST ALWAYS return a logger. Return if no logging + should be used. + + + + + redirects from one URL to another. + + + + + Rules are used to perform operations before a request is being handled. + Rules can be used to create routing etc. + + + + + Process the incoming request. + + Request context information. + Processing result. + If any parameter is null. + + + + Initializes a new instance of the class. + + Absolute path (no server name) + Absolute path (no server name) + + server.Add(new RedirectRule("/", "/user/index")); + + + + + Initializes a new instance of the class. + + Absolute path (no server name) + Absolute path (no server name) + true if request should be redirected, false if the request URI should be replaced. + + server.Add(new RedirectRule("/", "/user/index")); + + + + + Process the incoming request. + + Request context. + Processing result. + If any parameter is null. + + + + Gets string to match request URI with. + + Is compared to request.Uri.AbsolutePath + + + + Gets whether the server should redirect the client instead of simply modifying the URI. + + + false means that the rule will replace + the current request URI with the new one from this class. + true means that a redirect response is sent to the client. + + + + + Gets where to redirect. + + + + + The Connection general-header field allows the sender to specify options + that are desired for that particular connection and MUST NOT be + communicated by proxies over further connections. + + + + HTTP/1.1 proxies MUST parse the Connection header field before a + message is forwarded and, for each connection-token in this field, + remove any header field(s) from the message with the same name as the + connection-token. Connection options are signaled by the presence of + a connection-token in the Connection header field, not by any + corresponding additional header field(s), since the additional header + field may not be sent if there are no parameters associated with that + connection option. + + Message headers listed in the Connection header MUST NOT include + end-to-end headers, such as Cache-Control. + + HTTP/1.1 defines the "close" connection option for the sender to + signal that the connection will be closed after completion of the + response. For example, + + Connection: close + + in either the request or the response header fields indicates that + the connection SHOULD NOT be considered `persistent' (section 8.1) + after the current request/response is complete. + + HTTP/1.1 applications that do not support persistent connections MUST + include the "close" connection option in every message. + + A system receiving an HTTP/1.0 (or lower-version) message that + includes a Connection header MUST, for each connection-token in this + field, remove and ignore any header field(s) from the message with + the same name as the connection-token. This protects against mistaken + forwarding of such header fields by pre-HTTP/1.1 proxies. See section + 19.6.2 in RFC2616. + + + + + + Header name + + + + + Default connection header for HTTP/1.0 + + + + + Default connection header for HTTP/1.1 + + + + + Initializes a new instance of the class. + + Connection type. + The parameters. + + + + Initializes a new instance of the class. + + The type. + + + + Returns data formatted as a HTTP header value. + + + A that represents the current . + + + + + Gets connection parameters. + + + + + Gets or sets connection type + + + + + Gets header name + + + + + Type of HTTP connection + + + + + Connection is closed after each request-response + + + + + Connection is kept alive for X seconds (unless another request have been made) + + + + + Requests message-body be sent with an encoding to be specified in the "Transfer-Encoding" header. + + + + + Helper for content types. + + + + + Flyweight design pattern implementation. + + Type of object. + + + + Initializes a new instance of the class. + + How large buffers to allocate. + + + + Get an object. + + Created object. + Will create one if queue is empty. + + + + Enqueues the specified buffer. + + Object to enqueue. + Buffer is is less than the minimum requirement. + + + + Used to create new objects. + + Type of objects to create. + Newly created object. + . + + + + A HTTP parser using delegates to which parsing methods. + + + + + Initializes a new instance of the class. + + + + + Parser method to copy all body bytes. + + + Needed since a TCP packet can contain multiple messages + after each other, or partial messages. + + + + Try to find a header name. + + + + + + Get header values. + + + Will also look for multi header values and automatically merge them to one line. + Content length is not a number. + + + + Toggle body bytes event. + + + + + + + + Raise the event, since we have successfully parsed a message and it's body. + + + + + First message line. + + Will always contain three elements. + Used to raise the or event + depending on the words in the array. + BadRequestException. + + + + Continue parsing a message + + Byte buffer containing bytes + Where to start the parsing + Number of bytes to parse + index where the parsing stopped. + Parsing failed. + + + + Parses the first line in a request/response. + + true if first line is well formatted; otherwise false. + Invalid request/response line. + + + + Reset parser to initial state. + + + + + Gets or sets current line number. + + + + + The request line has been parsed. + + + + + Response line has been parsed. + + + + + Parsed a header. + + + + + Received body bytes. + + + + + A message have been successfully parsed. + + + + + Used to be able to quickly swap parser method. + + + + + + Get or create components used in the web server framework + + + + + + + + Get or create a type. + + Type to create + Created type. + + Gets or creates types in the framework. + Check for more information on which + types the factory should contain. + + + + + Used to create all key types in the HTTP server. + + + Should have factory methods at least for the following types: + , , + , , + , , + , , + . + + Check the default implementations to see which constructor + parameters you will get. + + + HttpFactory.Add(typeof(IRequest), (type, args) => new MyRequest((string)args[0])); + + + + + + Initializes a new instance of the class. + + + + + Add a factory method for a type. + + Type to create + Method creating the type. + + + + Used to + + + + + + + Setup our singleton. + + + + + + We want to use a singleton, but we also want to be able + to let the developer to setup his own header factory. + Therefore we use this method to create our own factory only if the user + have not specified one. + + + + + Small method to create a message factory singleton and replace then default delegate method. + + + + + + + + Create a type. + + Type to create + Created type. + + + + Gets http factory for the current listener. + + + + + Delegate used to create a certain type + + Created type. + + Method must never fail. + + + + + Used to define which headers a parse is for. + + + + + Initializes a new instance of the class. + + Name of the header. + + + + Gets name of header that this parser is for. + + + + + Parses "Date" header. + + + + + Parse a header + + Name of header. + Reader containing value. + HTTP Header + Header value is not of the expected format. + + + + Authorization response + + + + A user agent that wishes to authenticate itself with a server-- + usually, but not necessarily, after receiving a 401 response--does + so by including an Authorization request-header field with the + request. The Authorization field value consists of credentials + containing the authentication information of the user agent for + the realm of the resource being requested. + + + Authorization = "Authorization" ":" credentials + + + HTTP access authentication is described in "HTTP Authentication: + Basic and Digest Access Authentication" [43]. If a request is + authenticated and a realm specified, the same credentials SHOULD + be valid for all other requests within this realm (assuming that + the authentication scheme itself does not require otherwise, such + as credentials that vary according to a challenge value or using + synchronized clocks). + When a shared cache (see section 13.7) receives a request + containing an Authorization field, it MUST NOT return the + corresponding response as a reply to any other request, unless one + of the following specific exceptions holds: + + + + If the response includes the "s-maxage" cache-control + directive, the cache MAY use that response in replying to a + subsequent request. But (if the specified maximum age has + passed) a proxy cache MUST first revalidate it with the origin + server, using the request-headers from the new request to allow + the origin server to authenticate the new request. (This is the + defined behavior for s-maxage.) If the response includes "s- + maxage=0", the proxy MUST always revalidate it before re-using + it. + + If the response includes the "must-revalidate" cache-control + directive, the cache MAY use that response in replying to a + subsequent request. But if the response is stale, all caches + MUST first revalidate it with the origin server, using the + request-headers from the new request to allow the origin server + to authenticate the new request. + + If the response includes the "public" cache-control directive, + it MAY be returned in reply to any subsequent request. + + + + + + + Name constant + + + + + Gets or sets authentication data. + + + + + Gets or sets authentication protocol. + + + + + Gets name of header. + + + + + User needs to authenticate. + + + + + + + Initializes a new instance of the class. + + Exception description. + + + + Initializes a new instance of the class. + + Exception description. + Inner exception. + + + + A response have been received. + + + + + Initializes a new instance of the class. + + The response. + + + + Gets or sets response. + + + + + Creates a single message for one of the end points. + + + The factory is + + + + + Initializes a new instance of the class. + + The MSG factory. + The factory. + The parser. + + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + 2 + + + + Received a header from parser + + + + + + + Will continue the parsing until nothing more can be parsed. + + buffer to parse + where to start in the buffer + number of bytes to process. + Position where parser stopped parsing. + Parsing failed. + + + + Reset parser. + + + Something failed, reset parser so it can start on a new request. + + + + + A request have been successfully parsed. + + + + + A response have been successfully parsed. + + + + + Client asks if he may continue. + + + If the body is too large or anything like that you should respond . + + + + + Used to notify about 100-continue header. + + + + + Initializes a new instance of the class. + + request that want to continue. + + + + Gets request that want to continue + + + + + Default log filter implementation. + + + + + Determines which classes can log + + + + + Checks if the specified type can send + log entries at the specified level. + + Log level + Type that want to write a log entry. + true if logging is allowed; otherwise false. + + + + Add a name space filter. + + Name space to add filter for. + Minimum log level required. + + + // Parsing can only add error and fatal messages + AddNamespace("SipSharp.Messages.Headers.Parsers", LogLevel.Error); + AddType(typeof(SipParser), LogLevel.Error); + + // Transport layer can only log warnings, errors and fatal messages + AddNamespace("SipSharp.Transports.*", LogLevel.Warning); + + + + + + Used to specify standard filter rules + + + Parser can only display errors. Transports only warnings. + + + + + Add filter for a type + + Type to add filter for. + Minimum log level required. + + + // Parsing can only add error and fatal messages + AddNamespace("SipSharp.Messages.Headers.Parsers", LogLevel.Error); + AddType(typeof(SipParser), LogLevel.Error); + + // Transport layer can only log warnings, errors and fatal messages + AddNamespace("SipSharp.Transports.*", LogLevel.Warning); + + + + + + Add filter for a type + + Type to add filter for. + Minimum log level required. + + + // Parsing can only add error and fatal messages + AddNamespace("SipSharp.Messages.Headers.Parsers", LogLevel.Error); + AddType("SipSharp.Messages.MessageFactory", LogLevel.Error); + + // Transport layer can only log warnings, errors and fatal messages + AddNamespace("SipSharp.Transports.*", LogLevel.Warning); + + + Type could not be identified. + + + + Checks if the specified type can send + log entries at the specified level. + + Log level + Type that want to write a log entry. + true if logging is allowed; otherwise false. + + + No filters = everything logged. = no logs. Don't use a rule with '*' or '.*' + + + + User have specified a wild card filter. + + + Wild card filters are used to log a name space and + all it's children name spaces. + + + + + Result of processing. + + + + + Continue with the next handler + + + + + No more handlers can process the request. + + + The server will process the response object and + generate a HTTP response from it. + + + + + Response have been sent back by the handler. + + + This option should only be used if you are streaming + something or sending back a custom result. The server will + not process the response object or send anything back + to the client. + + + + + Used to store all headers that that aren't recognized. + + + + + Initializes a new instance of the class. + + The name. + The value. + + + + Gets or sets value + + + + + Gets header name + + Parses . @@ -3817,577 +3118,399 @@ HTTP Header Header value is not of the expected format. - + - Collection of headers. + Collection of parameters - + - Initializes a new instance of the class. - - Factory used to created headers. - - - - Adds a header - - - Will replace any existing header with the same name. - - header to add - header is null. - Header name cannot be null. - - - - Add a header. - - Header name - Header value - - Will try to parse the header and create a object. - - Header value is not correctly formatted. - name or value is null. - - - - Add a header. - - Header name - Header value - - Will try to parse the header and create a object. - - name or value is null. - - - - Get a header - - Type that it should be cast to - Name of header - Header if found and casted properly; otherwise null. - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - 1 - - - - Returns an enumerator that iterates through a collection. - - - An object that can be used to iterate through the collection. - - 2 - - - - Gets a header - - header name. - header if found; otherwise null. - - - - Used to authenticate users - - - Authentication is requested by throwing - - - - - Implements basic authentication scheme. - - - - - Create a response that can be sent in the WWW-Authenticate header. - - Realm that the user should authenticate in - Not used by basic authentication - A WWW-Authenticate header. - Argument is null. - - - - An authentication response have been received from the web browser. - Check if it's correct - - Authorization header - Realm that should be authenticated - GET/POST/PUT/DELETE etc. - Authentication object that is stored for the request. A user class or something like that. - if authenticationHeader is invalid - If any of the paramters is empty or null. - - - - Gets authenticator scheme - - - - digest - - - - - Request implementation. - - - - - Initializes a new instance of the class. - - The method. - The path. - The version. - - - - Get a header - - Type that it should be cast to - Name of header - Header if found and casted properly; otherwise null. - - - - Add a new header. - - - - - - - Add a new header. - - Header to add. - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - 1 - - - - Returns an enumerator that iterates through a collection. - - - An object that can be used to iterate through the collection. - - 2 - - - - Gets a header. + Get a parameter. - + - Gets request URI. + Add a query string parameter. + + Parameter name + Value + + + + Checks if the specified parameter exists + + Parameter name. + true if found; otherwise false; + + + + Gets number of parameters. - + - Gets cookies. + Gets last value of an parameter. + + Parameter name + String if found; otherwise null. + + + + Parameter in - + - Gets all uploaded files. + Gets *last* value. + + + Parameters can have multiple values. This property will always get the last value in the list. + + String if any value exist; otherwise null. + + + + Gets or sets name. - + - Gets query string and form parameters + Gets a list of all values. - + - Gets form parameters. + A parameter in . - + - Gets query string. + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + 1 + + + + Returns an enumerator that iterates through a collection. + + + An object that can be used to iterate through the collection. + + 2 + + + + Gets last value. + + + Parameters can have multiple values. This property will always get the last value in the list. + + String if any value exist; otherwise null. + + + + Gets or sets name. - + - Gets if request is an Ajax request. + Gets a list of all values. - + - Gets or sets connection header. + Used when the request line have been successfully parsed. - + - Gets or sets HTTP version. + Initializes a new instance of the class. + + The HTTP method. + The URI path. + The HTTP version. + + + + Initializes a new instance of the class. - + Gets or sets HTTP method. + + Should be one of the methods declared in . + - + - Gets requested URI. + Gets or sets requested URI path. - + - Kind of content in the body - - Default is text/html - - - - Gets or sets encoding + Gets or sets the version of the SIP protocol that the client want to use. - + - Gets headers. + Content-type - + - Gets body stream. + Header name. - + - Size of the body. MUST be specified before sending the header, - unless property Chunked is set to true. + Initializes a new instance of the class. - - Any specifically assigned value or Body stream length. - + Type of the content. + Value parameters. - + - Initializes a new instance of the class. + Initializes a new instance of the class. - SSL protocol to use. - The socket. - The context. - Server certificate to use. + Type of the content. - + - Create stream used to send and receive bytes from the socket. + Returns data formatted as a HTTP header value. - Socket to wrap - Stream - Stream could not be created. - - - - Gets or sets client certificate. - - - - - Gets used protocol. - - - - - Gets or sets if client certificate should be used instead of server certificate. - - - - - A list of request cookies. - - - - - Let's copy all the cookies. - - value from cookie header. - - - - Initializes a new instance of the class. - - - - - Adds a cookie in the collection. - - cookie to add - cookie is null - Name must be specified. - - - - Remove all cookies. - - - - - Remove a cookie from the collection. - - Name of cookie. - - - - Gets a collection enumerator on the cookie list. - - collection enumerator - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. + A that represents the current . - 1 - + - Gets the count of cookies in the collection. + Gets all parameters. - + - Gets the cookie of a given identifier (null if not existing). + Gets content type. - + - Parses query string + Gets header name - + - Parse a query string + Requested resource may not be accessed. - string to parse - A collection - reader is null. + + Normally thrown after an authentication attempt have failed too many times. + + - + - Parse a query string + Initializes a new instance of the class. - string to parse - A collection - queryString is null. + Exception description. - + - Stores sessions in files. + Initializes a new instance of the class. + Exception description. + Inner exception. + + + + Stream-based multipart handling. + + In this incarnation deals with an HttpInputStream as we are now using + IntPtr-based streams instead of byte []. In the future, we will also + send uploads above a certain threshold into the disk (to implement + limit-less HttpInputFiles). + - All session parameters must be serializable. + Taken from HttpRequest in mono (http://www.mono-project.com) - + - Stores sessions in your favorite store + Client X.509 certificate, X.509 chain, and any SSL policy errors encountered + during the SSL stream creation + + + + + Initializes a new instance of the class. + + The certificate. + Client security certificate chain. + Any SSL policy errors encountered during the SSL stream creation. + + + + Client security certificate + + + + + Client security certificate chain + + + + + Any SSL policy errors encountered during the SSL stream creation + + + + + Credits and description: http://theinstructionlimit.com/?p=76 - + Converted to .Net 2.0 - + - Saves the specified session. + Session in the system - The session. - + - Touches the specified session + Gets or sets session id. - Session id. + + + + Factory creating null logger. + + + + + Initializes a new instance of the class. + + + + + Create a new logger. + + Type that requested a logger. + Logger for the specified type; - Used to prevent sessions from expiring. + MUST ALWAYS return a logger. Return if no logging + should be used. - + - Loads a session - - Session id. - Session if found; otherwise null. - - - - Delete a session - - Id of session - - - - Saves the specified session. - - The session. - - - - Touches the specified session - - Session id. - - Used to prevent sessions from expiring. - - - - - Loads a session - - Session id. - Session if found; otherwise null. - - - - Create a HTTP response object. + Logger instance. - + - Initializes a new instance of the class. - - HTTP Version. - HTTP status code. - Why the status code was selected. - Version must start with 'HTTP/' - - - - Initializes a new instance of the class. - - Context that the response will be sent through. - Request that the response is for. - Version must start with 'HTTP/' - - - - Redirect user. - - Where to redirect to. - - Any modifications after a redirect will be ignored. - - - - - Add a new header. - - - - - - - Add a new header. - - Header to add. - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - 1 - - - - Returns an enumerator that iterates through a collection. - - - An object that can be used to iterate through the collection. - - 2 - - - - Gets a header. - - - - - - - Gets connection type. + Provides resources. - + - Status code that is sent to the client. - - Default is - - - - Gets HTTP version. - - - Default is HTTP/1.1 - - - - - Information about why a specific status code was used. + Used to access resources. - + - Size of the body. MUST be specified before sending the header, - unless property Chunked is set to true. + Add a new resource loader. - - Any specifically assigned value or Body stream length. - + Provider to add. + Manager have been started. - + - Kind of content in the body + Check if a resource exists. - Default is text/html + Uri to check + true if found; otherwise false. + + + if (manager.Exists("/views/user/view.haml")) + return true + + - + - Gets or sets encoding + Get a resource. + + Uri path to resource. + Resource if found; otherwise null. + + + Resource resource = manager.Get("/views/user/view.haml"); + + + + + + Start manager. - + - Gets cookies. + Gets number of resource providers - + - Gets body stream. + Get all view names from a folder. + + Path to find views in. + A collection of view names (without path). + + + + Add a new resource loader. + + Provider to add. + Manager have been started. + + + + Start manager. - + - Gets headers. + Check if a resource exists. + + Uri to check + true if found; otherwise false. + + + if (manager.Exists("/views/user/view.haml")) + return true + + + + + + Get a resource. + + Uri path to resource. + Resource if found; otherwise null. + + + Resource resource = manager.Get("/views/user/view.haml"); + + + + + + Gets number of resource providers @@ -4551,131 +3674,819 @@ Gets or sets uri path. - + - Something unexpected went wrong. + A list of request cookies. - + - Initializes a new instance of the class. + Let's copy all the cookies. - Exception description. + value from cookie header. - + - Initializes a new instance of the class. - - Exception description. - Inner exception. - - - - Decodes URL encoded values. + Initializes a new instance of the class. - + + Adds a cookie in the collection. + + cookie to add + cookie is null + Name must be specified. + + + + Remove all cookies. + + + + + Remove a cookie from the collection. + + Name of cookie. + + + + Gets a collection enumerator on the cookie list. + + collection enumerator + + + + Returns an enumerator that iterates through the collection. + - - Stream containing the content - Content type header - Stream encoding - Collection with all parameters. - Body format is invalid for the specified content type. - Failed to read all bytes from body stream. + + A that can be used to iterate through the collection. + + 1 - + - All content types that the decoder can parse. + Gets the count of cookies in the collection. - A collection of all content types that the decoder can handle. - + - Parses and builds messages + Gets the cookie of a given identifier (null if not existing). + + + + + Collection of headers. + + + + + Collection of headers. + + + + + Gets a header + + header name. + header if found; otherwise null. + + + + Initializes a new instance of the class. + + Factory used to created headers. + + + + Adds a header - The message factory takes care of building messages - from all end points. - - Since both message and packet protocols are used, the factory - hands out contexts to all end points. The context keeps a state - to be able to parse partial messages properly. - - - Each end point need to hand the context back to the message factory - when the client disconnects (or a message have been parsed). - + Will replace any existing header with the same name. + header to add + header is null. + Header name cannot be null. - + - Initializes a new instance of the class. + Add a header. - Factory used to create headers. - - - - Create a new message factory context. - - A new context. + Header name + Header value - A context is used to parse messages from a specific endpoint. + Will try to parse the header and create a object. + + Header value is not correctly formatted. + name or value is null. + + + + Add a header. + + Header name + Header value + + Will try to parse the header and create a object. + + name or value is null. + + + + Get a header + + Type that it should be cast to + Name of header + Header if found and casted properly; otherwise null. + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + 1 + + + + Returns an enumerator that iterates through a collection. + + + An object that can be used to iterate through the collection. + + 2 + + + + Gets a header + + header name. + header if found; otherwise null. + + + + Header for "Date" and "If-Modified-Since" + + + + The field value is an HTTP-date, as described in section 3.3.1 in RFC2616; + it MUST be sent in RFC 1123 [8]-date format. An example is + + Date: Tue, 15 Nov 1994 08:12:31 GMT + + Origin servers MUST include a Date header field in all + responses, except in these cases: + + If the response status code is 100 (Continue) or 101 (Switching + Protocols), the response MAY include a Date header field, at the server's + option. + If the response status code conveys a server error, e.g. 500 + (Internal Server Error) or 503 (Service Unavailable), and it is inconvenient + or impossible to generate a valid Date. + If the server does not have a clock that can provide a + reasonable approximation of the current time, its responses MUST NOT include + a Date header field. In this case, the rules in section 14.18.1 in RFC2616 + MUST be followed. + + + A received message that does not have a Date header field MUST + be assigned one by the recipient if the message will be cached by that + recipient or gatewayed via a protocol which requires a Date. An HTTP + implementation without a clock MUST NOT cache responses without revalidating + them on every use. An HTTP cache, especially a shared cache, SHOULD use a + mechanism, such as NTP [28], to synchronize its clock with a reliable + external standard. + Clients SHOULD only send a Date header field in messages that + include an entity-body, as in the case of the PUT and POST requests, and + even then it is optional. A client without a clock MUST NOT send a Date + header field in a request. + The HTTP-date sent in a Date header SHOULD NOT represent a date + and time subsequent to the generation of the message. It SHOULD represent + the best available approximation of the date and time of message generation, + unless the implementation has no means of generating a reasonably accurate + date and time. In theory, the date ought to represent the moment just before + the entity is generated. In practice, the date can be generated at any time + during the message origination without affecting its semantic value. + + + + + + Header name + + + + + Initializes a new instance of the class. + + Header name. + Name must not be empty. + + + + Initializes a new instance of the class. + + Header name. + Universal time. + + + + Returns data formatted as a HTTP header value. + + + A that represents the current . + + + + + Gets or sets date time. + + Should be in UTC. + + + + Gets header name + + + + + Type cached for fast property value modifications. + + + + + Get a property value. + + Instance to get value from. + Name of property. + Property value. + + + + Assign a value, try to convert it if it's not the same type as the property type. + + Object containing the property + Name of property + Value to convert and assign + Failed to find property. + Could not convert value type to property type. + + + + Assign value to a property + + Object containing the property + Name of property + Value to assign, must be of the same type as the property. + Failed to find property. + + + + Used to cache property information + + + + + Gets the property. + + The name. + + Failed to find property. + + + InvalidCastException. + + + + Get a property value. + + Instance to get value from. + Name of property. + Property value. + + + + Assign a value, try to convert it if it's not the same type as the property type. + + Object containing the property + Name of property + Value to convert and assign + Failed to find property. + Could not convert value type to property type. + + + + Assign value to a property + + Object containing the property + Name of property + Value to assign, must be of the same type as the property. + Failed to find property. + + + + Gets or sets member info + + + + + Gets or sets member type + + + + + Base interface to read string tokens from different sources. + + + + + Assign a new buffer + + Buffer to process. + Where to start process buffer + Buffer length + + + + Assign a new buffer + + Buffer to process + + + + Consume current character. + + + + + Consume specified characters + + One or more characters. + + + + Consumes horizontal white spaces (space and tab). + + + + + Consume horizontal white spaces and the specified character. + + Extra character to consume + + + + Checks if one of the remaining bytes are a specified character. + + Character to find. + true if found; otherwise false. + + + + Read a character. + + Character if not EOF; otherwise null. + + + + Get a text line. + + + Will merge multiline headers. + + + + Read quoted string + + string if current character (in buffer) is a quote; otherwise null. + + + + Read until end of string, or to one of the delimiters are found. + + characters to stop at + A string (can be ). + + Will not consume the delimiter. - + - Release a used factory context. + Read until end of string, or to one of the delimiters are found. - + A string (can be ). + + Will not consume the delimiter. + - + - A request have been received from one of the end points. + Read to end of buffer, or until specified delimiter is found. + + Delimiter to find. + A string (can be ). + + Will not consume the delimiter. + + + + + Will read until specified delimiter is found. + + Character to stop at. + A string if the delimiter was found; otherwise null. + + Will trim away spaces and tabs from the end. + Will not consume the delimiter. + + + + + Read until one of the delimiters are found. + + characters to stop at + A string if one of the delimiters was found; otherwise null. + + Will trim away spaces and tabs from the end. + Will not consume the delimiter. + + + + + Read until a horizontal white space occurs. + + A string if a white space was found; otherwise null. + + + + Gets current character + + if end of buffer. + + + + Gets if end of buffer have been reached - + - A response have been received from one of the end points. + Gets if more bytes can be processed. - + - Priority for log entries + Gets or sets current position in buffer. - + + THINK before you manually change the position since it can blow up + the whole parsing in your face. + - + - Very detailed logs to be able to follow the flow of the program. + Gets total length of buffer. - + - Logs to help debug errors in the application + Gets or sets line number. - + - Information to be able to keep track of state changes etc. + Gets next character + + if end of buffer. + + + + Gets number of bytes left. - + - Something did not go as we expected, but it's no problem. + Factory is used to create new logs in the system. - + - Something that should not fail failed, but we can still keep - on going. + Assigns log factory being used. + + The log factory. + A factory have already been assigned. + + + + Create a new logger. + + Type that requested a logger. + Logger for the specified type; + + + + Component that should be registered in the container. + + + Register using all interfaces that is specified in this assembly. + + + + + Used to authenticate users + + + Authentication is requested by throwing + + + + + Request implementation. - + - Something failed, and we cannot handle it properly. + Initializes a new instance of the class. + The method. + The path. + The version. + + + + Get a header + + Type that it should be cast to + Name of header + Header if found and casted properly; otherwise null. + + + + Add a new header. + + + + + + + Add a new header. + + Header to add. + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + 1 + + + + Returns an enumerator that iterates through a collection. + + + An object that can be used to iterate through the collection. + + 2 + + + + Gets a header. + + + + + + + Gets request URI. + + + + + Gets cookies. + + + + + Gets all uploaded files. + + + + + Gets query string and form parameters + + + + + Gets form parameters. + + + + + Gets query string. + + + + + Gets if request is an Ajax request. + + + + + Gets or sets connection header. + + + + + Gets or sets HTTP version. + + + + + Gets or sets HTTP method. + + + + + Gets requested URI. + + + + + Kind of content in the body + + Default is text/html + + + + Gets or sets encoding + + + + + Gets headers. + + + + + Gets body stream. + + + + + Size of the body. MUST be specified before sending the header, + unless property Chunked is set to true. + + + Any specifically assigned value or Body stream length. + + + + + Event arguments used when a new header have been parsed. + + + + + Initializes a new instance of the class. + + Name of header. + Header value. + Name cannot be empty + value is null. + + + + Initializes a new instance of the class. + + + + + Gets or sets header name. + + + + + Gets or sets header value. + + + + + Collection of parameters. + + + or is not used since each parameter can + have multiple values. + + + + + Initializes a new instance of the class. + + Collections to merge. + + Later collections will overwrite parameters from earlier collections. + + + + + Initializes a new instance of the class. + + + + + Get a list of string arrays. + + + + + + Get parameters + + Sub array (text array) + + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + 1 + + + + Returns an enumerator that iterates through a collection. + + + An object that can be used to iterate through the collection. + + 2 + + + + Get a parameter. + + + + + + + Add a query string parameter. + + Parameter name + Value + + + + Checks if the specified parameter exists + + Parameter name. + true if found; otherwise false; + + + + Gets number of parameters. + + + + + Gets last value of an parameter. + + Parameter name + String if found; otherwise null. + + + + Parses . + + + + + Parse a header + + Name of header. + Reader containing value. + HTTP Header + Header value is not of the expected format. + + + + Parses numerical values + + + + + Parse a header + + Name of header. + Reader containing value. + HTTP Header + Header value is not of the expected format. @@ -4691,6 +4502,366 @@ HTTP Header Header value is not of the expected format. + + + Request couldn't be parsed successfully. + + + + + Initializes a new instance of the class. + + Exception description. + + + + Initializes a new instance of the class. + + Exception description. + Exception description. + + + + Implements basic authentication scheme. + + + + + Create a response that can be sent in the WWW-Authenticate header. + + Realm that the user should authenticate in + Not used by basic authentication + A WWW-Authenticate header. + Argument is null. + + + + An authentication response have been received from the web browser. + Check if it's correct + + Authorization header + Realm that should be authenticated + GET/POST/PUT/DELETE etc. + Authentication object that is stored for the request. A user class or something like that. + if authenticationHeader is invalid + If any of the paramters is empty or null. + + + + Gets authenticator scheme + + + + digest + + + + + Reads strings from a byte array. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class. + + Encoding to use when converting byte array to strings. + + + + Initializes a new instance of the class. + + Buffer to read from. + Encoding to use when converting byte array to strings. + + + + Assign a new buffer + + Buffer to process. + Where to start process buffer + Buffer length + Buffer needs to be a byte array + + + + Assign a new buffer + + Buffer to process + Buffer needs to be a byte array + + + + Consume current character. + + + + + Get a text line. + + + Will merge multi line headers. + + + + Read quoted string + + string if current character (in buffer) is a quote; otherwise null. + + + + Read until end of string, or to one of the delimiters are found. + + characters to stop at + + A string (can be ). + + + Will not consume the delimiter. + + InvalidOperationException. + + + + Read until end of string, or to one of the delimiters are found. + + A string (can be ). + + Will not consume the delimiter. + + + + + Read to end of buffer, or until specified delimiter is found. + + Delimiter to find. + + A string (can be ). + + + Will not consume the delimiter. + + InvalidOperationException. + + + + Consume specified characters + + One or more characters. + + + + Consumes horizontal white spaces (space and tab). + + + + + Consume horizontal white spaces and the specified character. + + Extra character to consume + + + + Read a character. + + + Character if not EOF; otherwise null. + + + + + Will read until specified delimiter is found. + + Character to stop at. + + A string if the delimiter was found; otherwise null. + + + Will trim away spaces and tabs from the end. + InvalidOperationException. + + + + Read until one of the delimiters are found. + + characters to stop at + + A string if one of the delimiters was found; otherwise null. + + + Will not consume the delimiter. + + InvalidOperationException. + + + + Read until a horizontal white space occurs. + + A string if a white space was found; otherwise null. + + + + Checks if one of the remaining bytes are a specified character. + + Character to find. + + true if found; otherwise false. + + + + + Gets or sets line number. + + + + + Gets if end of buffer have been reached + + + + + + Gets if more bytes can be processed. + + + + + + Gets next character + + if end of buffer. + + + + Gets current character + + if end of buffer. + + + + Gets or sets current position in buffer. + + + THINK before you manually change the position since it can blow up + the whole parsing in your face. + + + + + Gets total length of buffer. + + + + + + Gets number of bytes left. + + + + + Response to a request. + + + + + Redirect user. + + Where to redirect to. + + Any modifications after a redirect will be ignored. + + + + + Gets connection type. + + + + + Gets cookies. + + + + + Gets HTTP version. + + + Default is HTTP/1.1 + + + + + Information about why a specific status code was used. + + + + + Status code that is sent to the client. + + Default is + + + + Gets or sets content type + + + + + Request context + + + Contains information about a HTTP request and where it came from. + + + + + Gets or sets http context. + + + + + Gets or sets http request. + + + + + Gets or sets http response. + + + + + Contains numerical value. + + + + + Initializes a new instance of the class. + + The name. + The value. + + + + Returns data formatted as a HTTP header value. + + + A that represents the current . + + + + + Gets value + + + + + Gets header name + + Form parameters where form string arrays have been converted to real arrays. @@ -4822,970 +4993,199 @@ Dictionary key is property name. - + - Requested resource may not be accessed. + Used to read from a string object. + + + + Initializes a new instance of the class. + + Buffer to process. + + + + Initializes a new instance of the class. + + + + + Assign a new buffer + + Buffer to process. + Where to start process buffer + Buffer length + MUST be of type . + buffer needs to be of type string + + + + Assign a new buffer + + Buffer to process + MUST be of type . + buffer needs to be of type string + + + + Consume current character. + + + + + Get a text line. + + + Will merge multiline headers. + + + + Read quoted string + + string if current character (in buffer) is a quote; otherwise null. + + + + Read until end of string, or to one of the delimiters are found. + + characters to stop at + A string (can be ). + InvalidOperationException. + + + + Read until end of string, or to one of the delimiters are found. + + A string (can be ). - Normally thrown after an authentication attempt have failed too many times. - - - - - - Initializes a new instance of the class. - - Exception description. - - - - Initializes a new instance of the class. - - Exception description. - Inner exception. - - - - User needs to authenticate. - - - - - - - Initializes a new instance of the class. - - Exception description. - - - - Initializes a new instance of the class. - - Exception description. - Inner exception. - - - - First line in a response have been received - - - - - Gets or sets motivation to why the status code was used. - - - - - Gets or sets message status code - - - - - Gets or sets sip protocol version used. - - - - - Request context - - - Contains information about a HTTP request and where it came from. + Will not consume the delimiter. - + - Gets or sets http context. + Read to end of buffer, or until specified delimiter is found. + + Delimiter to find. + A string (can be ). + InvalidOperationException. + + + + Consume specified characters + + One or more characters. + + + + Consumes horizontal white spaces (space and tab). - + - Gets or sets http request. - - - - - Gets or sets http response. - - - - - Header for "Date" and "If-Modified-Since" - - - - The field value is an HTTP-date, as described in section 3.3.1 in RFC2616; - it MUST be sent in RFC 1123 [8]-date format. An example is - - Date: Tue, 15 Nov 1994 08:12:31 GMT - - Origin servers MUST include a Date header field in all - responses, except in these cases: - - If the response status code is 100 (Continue) or 101 (Switching - Protocols), the response MAY include a Date header field, at the server's - option. - If the response status code conveys a server error, e.g. 500 - (Internal Server Error) or 503 (Service Unavailable), and it is inconvenient - or impossible to generate a valid Date. - If the server does not have a clock that can provide a - reasonable approximation of the current time, its responses MUST NOT include - a Date header field. In this case, the rules in section 14.18.1 in RFC2616 - MUST be followed. - - - A received message that does not have a Date header field MUST - be assigned one by the recipient if the message will be cached by that - recipient or gatewayed via a protocol which requires a Date. An HTTP - implementation without a clock MUST NOT cache responses without revalidating - them on every use. An HTTP cache, especially a shared cache, SHOULD use a - mechanism, such as NTP [28], to synchronize its clock with a reliable - external standard. - Clients SHOULD only send a Date header field in messages that - include an entity-body, as in the case of the PUT and POST requests, and - even then it is optional. A client without a clock MUST NOT send a Date - header field in a request. - The HTTP-date sent in a Date header SHOULD NOT represent a date - and time subsequent to the generation of the message. It SHOULD represent - the best available approximation of the date and time of message generation, - unless the implementation has no means of generating a reasonably accurate - date and time. In theory, the date ought to represent the moment just before - the entity is generated. In practice, the date can be generated at any time - during the message origination without affecting its semantic value. - - - - - - Header name - - - - - Initializes a new instance of the class. - - Header name. - Name must not be empty. - - - - Initializes a new instance of the class. - - Header name. - Universal time. - - - - Returns data formatted as a HTTP header value. + Read a character. - A that represents the current . + Character if not EOF; otherwise null. - + - Gets or sets date time. - - Should be in UTC. - - - - Gets header name - - - - - The Cache-Control general-header field is used to specify directives that - MUST be obeyed by all caching mechanisms along the request/response - chain. . - - - - The directives specify behavior intended to prevent caches from adversely - interfering with the request or response. These directives typically - override the default caching algorithms. Cache directives are - unidirectional in that the presence of a directive in a request does not - imply that the same directive is to be given in the response. - Note that HTTP/1.0 caches might not implement Cache-Control and - might only implement Pragma: no-cache (see section 14.32 in RFC2616). - Cache directives MUST be passed through by a proxy or gateway - application, regardless of their significance to that application, since the - directives might be applicable to all recipients along the request/response - chain. It is not possible to specify a cache- directive for a specific cache - - - When a directive appears without any 1#field-name parameter, the - directive applies to the entire request or response. When such a - directive appears with a 1#field-name parameter, it applies only to - the named field or fields, and not to the rest of the request or - response. This mechanism supports extensibility; implementations of - future versions of the HTTP protocol might apply these directives to - header fields not defined in HTTP/1.1. - - - The cache-control directives can be broken down into these general - categories: - - - Restrictions on what are cacheable; these may only be imposed by - the origin server. - - Restrictions on what may be stored by a cache; these may be - imposed by either the origin server or the user agent. - - Modifications of the basic expiration mechanism; these may be - imposed by either the origin server or the user agent. - - Controls over cache revalidation and reload; these may only be - imposed by a user agent. - - Control over transformation of entities. - - Extensions to the caching system. - - - - - - - - Header name - - - - - Gets header name - - - - - Authorization response + Will read until specified delimiter is found. + Character to stop at. + + A string if the delimiter was found; otherwise null. + - - A user agent that wishes to authenticate itself with a server-- - usually, but not necessarily, after receiving a 401 response--does - so by including an Authorization request-header field with the - request. The Authorization field value consists of credentials - containing the authentication information of the user agent for - the realm of the resource being requested. - - - Authorization = "Authorization" ":" credentials - - - HTTP access authentication is described in "HTTP Authentication: - Basic and Digest Access Authentication" [43]. If a request is - authenticated and a realm specified, the same credentials SHOULD - be valid for all other requests within this realm (assuming that - the authentication scheme itself does not require otherwise, such - as credentials that vary according to a challenge value or using - synchronized clocks). - When a shared cache (see section 13.7) receives a request - containing an Authorization field, it MUST NOT return the - corresponding response as a reply to any other request, unless one - of the following specific exceptions holds: - - - - If the response includes the "s-maxage" cache-control - directive, the cache MAY use that response in replying to a - subsequent request. But (if the specified maximum age has - passed) a proxy cache MUST first revalidate it with the origin - server, using the request-headers from the new request to allow - the origin server to authenticate the new request. (This is the - defined behavior for s-maxage.) If the response includes "s- - maxage=0", the proxy MUST always revalidate it before re-using - it. - - If the response includes the "must-revalidate" cache-control - directive, the cache MAY use that response in replying to a - subsequent request. But if the response is stale, all caches - MUST first revalidate it with the origin server, using the - request-headers from the new request to allow the origin server - to authenticate the new request. - - If the response includes the "public" cache-control directive, - it MAY be returned in reply to any subsequent request. - - + Will trim away spaces and tabs from the end. + Will not consume the delimiter. + InvalidOperationException. - + - Name constant - - - - - Gets or sets authentication data. - - - - - Gets or sets authentication protocol. - - - - - Gets name of header. - - - - - Factory is used to create new logs in the system. - - - - - Assigns log factory being used. - - The log factory. - A factory have already been assigned. - - - - Create a new logger. - - Type that requested a logger. - Logger for the specified type; - - - - A request have been received. + Read until one of the delimiters are found. + characters to stop at + + A string if one of the delimiters was found; otherwise null. + + Will not consume the delimiter. + InvalidOperationException. - + - Initializes a new instance of the class. - - context that received the request. - Received request. - Response to send. - - - - Gets context that received the request. - - - Do not forget to set to true if you are sending - back a response manually through . - - - - - Gets or sets if the request have been handled. - - - The library will not attempt to send the response object - back to the client if this property is set to true. - - - - - Gets request object. - - - - - Gets response object. - - - - - Get or create components used in the web server framework - - - - - - - - Get or create a type. - - Type to create - Created type. - - Gets or creates types in the framework. - Check for more information on which - types the factory should contain. - - - - - Parses numerical values - - - - - Parse a header - - Name of header. - Reader containing value. - HTTP Header - Header value is not of the expected format. - - - - Contains parameters for HTTP headers. - - - - - Add a parameter - - name - value - - Existing parameter with the same name will be replaced. - - - - - Parse parameters. - - Parser containing buffer to parse. - A collection with all parameters (or just a empty collection). - Expected a value after equal sign. - - - - Parse parameters. - - Parser containing buffer to parse. - Parameter delimiter - A collection with all parameters (or just a empty collection). - Expected a value after equal sign. - - - - Returns a that represents the current . + Read until a horizontal white space occurs (or end, or end of line). - A that represents the current . + A string if a white space was found; otherwise null. - + - Gets or sets a value + Consume horizontal white spaces and the specified character. - parameter name - value if found; otherwise null. + Extra character to consume - + - Component that should be registered in the container. - - - Register using all interfaces that is specified in this assembly. - - - - - Stream-based multipart handling. - - In this incarnation deals with an HttpInputStream as we are now using - IntPtr-based streams instead of byte []. In the future, we will also - send uploads above a certain threshold into the disk (to implement - limit-less HttpInputFiles). - - - Taken from HttpRequest in mono (http://www.mono-project.com) - - - - - Interface used to write to log files. - - - If you want to use the built in filtering mechanism, create a constructor - which takes one parameter, a . - - - - - Write an entry that helps when debugging code. - - Log message - - - - Write an entry that helps when debugging code. - - Log message - Thrown exception to log. - - - - Something went wrong, but the application do not need to die. The current thread/request - cannot continue as expected. - - Log message - - - - Something went wrong, but the application do not need to die. The current thread/request - cannot continue as expected. - - Log message - Thrown exception to log. - - - - Something went very wrong, application might not recover. - - Log message - - - - Something went very wrong, application might not recover. - - Log message - Thrown exception to log. - - - - Informational message, needed when helping customer to find a problem. - - Log message - - - - Informational message, needed when helping customer to find a problem. - - Log message - Thrown exception to log. - - - - Write a entry that helps when trying to find hard to find bugs. - - Log message - - - - Write a entry that helps when trying to find hard to find bugs. - - Log message - Thrown exception to log. - - - - Something is not as we expect, but the code can continue to run without any changes. - - Log message - - - - Something is not as we expect, but the code can continue to run without any changes. - - Log message - Thrown exception to log. - - - - Used to create all key types in the HTTP server. - - - Should have factory methods at least for the following types: - , , - , , - , , - , , - . - - Check the default implementations to see which constructor - parameters you will get. - - - HttpFactory.Add(typeof(IRequest), (type, args) => new MyRequest((string)args[0])); - - - - - - Initializes a new instance of the class. - - - - - Add a factory method for a type. - - Type to create - Method creating the type. - - - - Used to - - - - - - - Setup our singleton. - - - - - - We want to use a singleton, but we also want to be able - to let the developer to setup his own header factory. - Therefore we use this method to create our own factory only if the user - have not specified one. - - - - - Small method to create a message factory singleton and replace then default delegate method. - - - - - - - - Create a type. - - Type to create - Created type. - - - - Gets http factory for the current listener. - - - - - Delegate used to create a certain type - - Created type. - - Method must never fail. - - - - - Contains numerical value. - - - - - Initializes a new instance of the class. - - The name. - The value. - - - - Returns data formatted as a HTTP header value. + Checks if one of the remaining bytes are a specified character. + Character to find. - A that represents the current . + true if found; otherwise false. - + - Gets value + Gets or sets line number. - + - Gets header name - - - - - Contents of a cookie header. - - - - - Initializes a new instance of the class. - - The collection. - collection is null. - - - - Gets cookie collection - - - - - Gets header name - - - - - Gets value as it would be sent back to client. + Gets if end of buffer have been reached - + - Data decoded from a POST body. + Gets if more bytes can be processed. + - + - Initializes a new instance of the class. + Gets next character + if end of buffer. - + - Gets or sets decoded files. + Gets current character + if end of buffer. - + - Gets or sets decoded parameters. - - - - - Collection of body decoders. + Gets or sets current position in buffer. - Body decoders are used to parse request body and convert it - into a and a . + THINK before you manually change the position since it can blow up + the whole parsing in your face. - + - Add another body decoder. + Gets total length of buffer. - + - + - Decode body stream - - Stream containing the content - Content type header - Stream encoding - Decoded data. - Body format is invalid for the specified content type. - Something unexpected failed. - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - 1 - - - - Returns an enumerator that iterates through a collection. - - - An object that can be used to iterate through the collection. - - 2 - - - - Gets number of decoders. - - - - - Credits and description: http://theinstructionlimit.com/?p=76 - - - Converted to .Net 2.0 - - - - - Type cached for fast property value modifications. - - - - - Get a property value. - - Instance to get value from. - Name of property. - Property value. - - - - Assign a value, try to convert it if it's not the same type as the property type. - - Object containing the property - Name of property - Value to convert and assign - Failed to find property. - Could not convert value type to property type. - - - - Assign value to a property - - Object containing the property - Name of property - Value to assign, must be of the same type as the property. - Failed to find property. - - - - Used to cache property information - - - - - Gets the property. - - The name. - - Failed to find property. - - - InvalidCastException. - - - - Get a property value. - - Instance to get value from. - Name of property. - Property value. - - - - Assign a value, try to convert it if it's not the same type as the property type. - - Object containing the property - Name of property - Value to convert and assign - Failed to find property. - Could not convert value type to property type. - - - - Assign value to a property - - Object containing the property - Name of property - Value to assign, must be of the same type as the property. - Failed to find property. - - - - Gets or sets member info - - - - - Gets or sets member type - - - - - A request have been received. - - - - - Initializes a new instance of the class. - - The request. - End point that the request was received from. - - - - End point that the message was received from. - - - - - Received request. - - - - - Creates a single message for one of the end points. - - - The factory is - - - - - Initializes a new instance of the class. - - The MSG factory. - The factory. - The parser. - - - - Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - - 2 - - - - Received a header from parser - - - - - - - Will continue the parsing until nothing more can be parsed. - - buffer to parse - where to start in the buffer - number of bytes to process. - Position where parser stopped parsing. - Parsing failed. - - - - Reset parser. - - - Something failed, reset parser so it can start on a new request. - - - - - A request have been successfully parsed. - - - - - A response have been successfully parsed. - - - - - Client asks if he may continue. - - - If the body is too large or anything like that you should respond . - - - - - Used to notify about 100-continue header. - - - - - Initializes a new instance of the class. - - request that want to continue. - - - - Gets request that want to continue + Gets number of bytes left. @@ -5879,6 +5279,43 @@ Log message Thrown exception to log. + + + Priority for log entries + + + + + + Very detailed logs to be able to follow the flow of the program. + + + + + Logs to help debug errors in the application + + + + + Information to be able to keep track of state changes etc. + + + + + Something did not go as we expected, but it's no problem. + + + + + Something that should not fail failed, but we can still keep + on going. + + + + + Something failed, and we cannot handle it properly. + + This class writes to the console. @@ -5995,189 +5432,757 @@ Gets or sets type that the logger is for - + - Creates a console logger. + A HTTP context - - - - Initializes a new instance of the class. - - The filter. - - - - Create a new logger. - - Type that requested a logger. - Logger for the specified type; - MUST ALWAYS return a logger. Return if no logging - should be used. + - + - Cookies that should be set. + Context that received a HTTP request. - + - Adds a cookie in the collection. - - cookie to add - cookie is null - Name and Content must be specified. - - - - Copy a request cookie - - - When the cookie should expire - - - - Remove all cookies + Disconnect context. - + - Gets a collection enumerator on the cookie list. + Gets if current context is using a secure connection. - collection enumerator - + + + Gets logger. + + + + + Gets remote end point + + + + + Gets stream used to send/receive data to/from remote end point. + + + + The stream can be any type of stream, do not assume that it's a network + stream. For instance, it can be a or a ZipStream. + + + + + + Gets the currently handled request + + The request. + + + + Gets the response that is going to be sent back + + The response. + + + + Initializes a new instance of the class. + + Socket received from HTTP listener. + Context used to parse incoming messages. + + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + 2 + + + + Disconnect context. + + + + + Close and release socket. + + + + + Create stream used to send and receive bytes from the socket. + + Socket to wrap + Stream + Stream could not be created. + + + + Interpret incoming data. + + + + + + A request was received from the parser. + + + + + + + Parse all complete requests in buffer. + + + offset in buffer where parsing stopped. + Parsing failed. + + + + Start content. + + A socket operation failed. + Reading from stream failed. + + + + Gets currently executing HTTP context. + + + + + Gets or sets description + + + + + gets factory used to build request objects + + + + + Gets socket + + + + + Gets remove end point + + + + + Gets network stream. + + + + + Gets the currently handled request + + The request. + + + + Gets the response that is going to be sent back + + The response. + + + + Gets logger. + + + + + Gets if current context is using a secure connection. + + + + + Triggered for all requests in the server (after the response have been sent) + + + + + Triggered for current request (after the response have been sent) + + + + + A new request have been received. + + + + + A new request have been received (invoked for ALL requests) + + + + + Client have been disconnected. + + + + + Client asks if he may continue. + + + If the body is too large or anything like that you should respond . + + + + + Initializes a new instance of the class. + + SSL protocol to use. + The socket. + The context. + Server certificate to use. + + + + Create stream used to send and receive bytes from the socket. + + Socket to wrap + Stream + Stream could not be created. + + + + Gets or sets client certificate. + + + + + Gets used protocol. + + + + + Gets or sets if client certificate should be used instead of server certificate. + + + + + Resource information. + + + Used by content providers to be able to get information + on resources (views, files etc). + + + + + Gets or sets date when resource was modified. + + + if not used. + + + Should always be universal time. + + + + + Gets or sets resource stream. + + + + + Contains parameters for HTTP headers. + + + + + Add a parameter + + name + value + + Existing parameter with the same name will be replaced. + + + + + Parse parameters. + + Parser containing buffer to parse. + A collection with all parameters (or just a empty collection). + Expected a value after equal sign. + + + + Parse parameters. + + Parser containing buffer to parse. + Parameter delimiter + A collection with all parameters (or just a empty collection). + Expected a value after equal sign. + + + + Returns a that represents the current . + + + A that represents the current . + + + + + Gets or sets a value + + parameter name + value if found; otherwise null. + + + + Create a HTTP response object. + + + + + Initializes a new instance of the class. + + HTTP Version. + HTTP status code. + Why the status code was selected. + Version must start with 'HTTP/' + + + + Initializes a new instance of the class. + + Context that the response will be sent through. + Request that the response is for. + Version must start with 'HTTP/' + + + + Redirect user. + + Where to redirect to. + + Any modifications after a redirect will be ignored. + + + + + Add a new header. + + + + + + + Add a new header. + + Header to add. + + Returns an enumerator that iterates through the collection. - - A that can be used to iterate through the collection. + A that can be used to iterate through the collection. 1 - + - Gets the count of cookies in the collection. + Returns an enumerator that iterates through a collection. + + An object that can be used to iterate through the collection. + + 2 - + - Gets the cookie of a given identifier. + Gets a header. - Cookie if found; otherwise null. - - - - Provider returning user to be authenticated. - - - - - Lookups the specified user - - User name. - Typically web server domain name. - User if found; otherwise null. - - User name can basically be anything. For instance name entered by user when using - basic or digest authentication, or SID when using Windows authentication. - - - - - Gets the principal to use. - - Successfully authenticated user. + - - Invoked when a user have successfully been authenticated. - - - - + - User information used during authentication process. + Gets connection type. - + - Gets or sets user name used during authentication. + Status code that is sent to the client. + Default is - + - Gets or sets unencrypted password. + Gets HTTP version. - Password as clear text. You could use instead if your passwords - are encrypted in the database. + Default is HTTP/1.1 - + - Gets or sets HA1 hash. + Information about why a specific status code was used. + + + + + Size of the body. MUST be specified before sending the header, + unless property Chunked is set to true. + + + Any specifically assigned value or Body stream length. + + + + + Kind of content in the body + + Default is text/html + + + + Gets or sets encoding + + + + + Gets cookies. + + + + + Gets body stream. + + + + + Gets headers. + + + + + A request have been received. + + + + + Initializes a new instance of the class. + + The request. + End point that the request was received from. + + + + End point that the message was received from. + + + + + Received request. + + + + + Class to make dynamic binding of redirects. Instead of having to specify a number of similar redirect rules + a regular expression can be used to identify redirect URLs and their targets. + + + [a-z0-9]+)", "/users/${target}/?find=true", RegexOptions.IgnoreCase) + ]]> + + + + + Initializes a new instance of the class. + + Expression to match URL + Expression to generate URL + + [a-zA-Z0-9]+)", "/user/${first}")); + Result of ie. /employee1 will then be /user/employee1 + ]]> + + + + + Initializes a new instance of the class. + + Expression to match URL + Expression to generate URL + Regular expression options to use, can be null + + [a-zA-Z0-9]+)", "/user/{first}", RegexOptions.IgnoreCase)); + Result of ie. /employee1 will then be /user/employee1 + ]]> + + + + + Initializes a new instance of the class. + + Expression to match URL + Expression to generate URL + Regular expression options to apply + true if request should be redirected, false if the request URI should be replaced. + + [a-zA-Z0-9]+)", "/user/${first}", RegexOptions.None)); + Result of ie. /employee1 will then be /user/employee1 + ]]> + + Argument is null. + + + + + Process the incoming request. + + Request context. + Processing result. + If any parameter is null. + + + + cookie being sent back to the browser. + + + + + + Constructor. + + cookie identifier + cookie content + cookie expiration date. Use for session cookie. + id or content is null + id is empty + + + + Create a new cookie + + name identifying the cookie + cookie value + when the cookie expires. Setting will delete the cookie when the session is closed. + Path to where the cookie is valid + Domain that the cookie is valid for. + + + + Create a new cookie + + Name and value will be used + when the cookie expires. + + + + Gets the cookie HTML representation. + + cookie string + + + + Gets when the cookie expires. + + means that the cookie expires when the session do so. + + + + Gets path that the cookie is valid under. + + + + + The Cache-Control general-header field is used to specify directives that + MUST be obeyed by all caching mechanisms along the request/response + chain. . + + + + The directives specify behavior intended to prevent caches from adversely + interfering with the request or response. These directives typically + override the default caching algorithms. Cache directives are + unidirectional in that the presence of a directive in a request does not + imply that the same directive is to be given in the response. + Note that HTTP/1.0 caches might not implement Cache-Control and + might only implement Pragma: no-cache (see section 14.32 in RFC2616). + Cache directives MUST be passed through by a proxy or gateway + application, regardless of their significance to that application, since the + directives might be applicable to all recipients along the request/response + chain. It is not possible to specify a cache- directive for a specific cache + + + When a directive appears without any 1#field-name parameter, the + directive applies to the entire request or response. When such a + directive appears with a 1#field-name parameter, it applies only to + the named field or fields, and not to the rest of the request or + response. This mechanism supports extensibility; implementations of + future versions of the HTTP protocol might apply these directives to + header fields not defined in HTTP/1.1. + + + The cache-control directives can be broken down into these general + categories: + + + Restrictions on what are cacheable; these may only be imposed by + the origin server. + + Restrictions on what may be stored by a cache; these may be + imposed by either the origin server or the user agent. + + Modifications of the basic expiration mechanism; these may be + imposed by either the origin server or the user agent. + + Controls over cache revalidation and reload; these may only be + imposed by a user agent. + + Control over transformation of entities. + + Extensions to the caching system. + + + + + + + + Header name + + + + + Gets header name + + + + + Serves files in the web server. + + + + FileModule fileModule = new FileModule(); + fileModule.Resources.Add(new FileResources("/", "C:\\inetpub\\myweb")); + + + + + + Initializes a new instance of the class. + + baseUri or basePath is null. + + + + Mime types that this class can handle per default - - Digest authentication requires clear text passwords to work. If you - do not have that, you can store a HA1 hash in your database (which is part of - the Digest authentication process). - - - A HA1 hash is simply a Md5 encoded string: "UserName:Realm:Password". The quotes should - not be included. Realm is the currently requested Host (as in Request.Headers["host"]). - - - Leave the string as null if you are not using HA1 hashes. - + Contains the following mime types: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - Provides authentication in the web server. + Will send a file to client. - - To initiate authentication you just need to throw a Una - + HTTP context containing outbound stream. + Response containing headers. + File stream - + - Add a authenticator. + Process a request. - + Request information + What to do next. + Failed to find file extension + Forbidden file type. - + - Authenticate request. + Gets a list with all allowed content types. - + All other mime types will result in . + + + + Gets provider used to add files to the file manager, + + + + + Collection of files. + + + + + Checks if a file exists. + + Name of the file (form item name) - - Requires that a AuthorizationHeader have been sent by the client. If not, - request one by sending a WWW-Authentication header (can be generated by the Challenge method). - - Authorization header was not found in the request. - Requested authentication scheme is not supported. - + - Create a challenge header (WWW-authenticate) + Add a new file. + + File to add. + + + + Remove all files from disk. + + + + + Get a file + + Name in form + File if found; otherwise null. + + + + Gets number of files - Response that the authentication header should be added to - Realm that the user should authenticate in - WWW-Authenticate header. - - - Scheme can currently be basic or digest. Basic is not very safe, but easier to use. - Digest is quite safe. - - - - Requested scheme is not supported. diff --git a/TShock.sln b/TShock.sln index 85d6a0f9..db301cae 100644 --- a/TShock.sln +++ b/TShock.sln @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Terraria.vsmdi = Terraria.vsmdi EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TShockRestTestPlugin", "TShockRestTestPlugin\TShockRestTestPlugin.csproj", "{F2FEDAFB-58DE-4611-9168-A86112C346C7}" +EndProject Global GlobalSection(TestCaseManagementSettings) = postSolution CategoryFile = Terraria.vsmdi @@ -52,6 +54,16 @@ Global {F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|Any CPU.ActiveCfg = Release|Any CPU {F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {F3742F51-D7BF-4754-A68A-CD944D2A21FF}.Release|x86.ActiveCfg = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Debug|x86.ActiveCfg = Debug|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Any CPU.Build.0 = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F2FEDAFB-58DE-4611-9168-A86112C346C7}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TShockAPI/BackupManager.cs b/TShockAPI/BackupManager.cs index 8aa8c2f5..b871c6c1 100644 --- a/TShockAPI/BackupManager.cs +++ b/TShockAPI/BackupManager.cs @@ -1,100 +1,96 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011 The TShock Team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -using System; -using System.IO; -using System.Threading; -using Terraria; - -namespace TShockAPI -{ - public class BackupManager - { - public string BackupPath { get; set; } - public int Interval { get; set; } - public int KeepFor { get; set; } - - private DateTime lastbackup = DateTime.UtcNow; - - public BackupManager(string path) - { - BackupPath = path; - } - - public bool IsBackupTime - { - get { return (Interval > 0) && ((DateTime.UtcNow - lastbackup).TotalMinutes >= Interval); } - } - - public void Backup() - { - lastbackup = DateTime.UtcNow; - ThreadPool.QueueUserWorkItem(DoBackup); - ThreadPool.QueueUserWorkItem(DeleteOld); - } - - private void DoBackup(object o) - { - try - { - string worldname = Main.worldPathName; - string name = Path.GetFileName(worldname); - - Main.worldPathName = Path.Combine(BackupPath, string.Format("{0}.{1:dd.MM.yy-HH.mm.ss}.bak", name, DateTime.UtcNow)); - - string worldpath = Path.GetDirectoryName(Main.worldPathName); - if (worldpath != null && !Directory.Exists(worldpath)) - Directory.CreateDirectory(worldpath); - - TShock.Utils.Broadcast("Server map saving, potential lag spike"); - Console.WriteLine("Backing up world..."); - - Thread SaveWorld = new Thread(TShock.Utils.SaveWorld); - SaveWorld.Start(); - - while (SaveWorld.ThreadState == ThreadState.Running) - Thread.Sleep(50); - Console.WriteLine("World backed up"); - Console.ForegroundColor = ConsoleColor.Gray; - Log.Info(string.Format("World backed up ({0})", Main.worldPathName)); - - Main.worldPathName = worldname; - } - catch (Exception ex) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("Backup failed"); - Console.ForegroundColor = ConsoleColor.Gray; - Log.Error("Backup failed"); - Log.Error(ex.ToString()); - } - } - - private void DeleteOld(object o) - { - if (KeepFor <= 0) - return; - foreach (var fi in new DirectoryInfo(BackupPath).GetFiles("*.bak")) - { - if ((DateTime.UtcNow - fi.LastWriteTimeUtc).TotalMinutes > KeepFor) - { - fi.Delete(); - } - } - } - } +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.IO; +using System.Threading; +using Terraria; + +namespace TShockAPI +{ + public class BackupManager + { + public string BackupPath { get; set; } + public int Interval { get; set; } + public int KeepFor { get; set; } + + private DateTime lastbackup = DateTime.UtcNow; + + public BackupManager(string path) + { + BackupPath = path; + } + + public bool IsBackupTime + { + get { return (Interval > 0) && ((DateTime.UtcNow - lastbackup).TotalMinutes >= Interval); } + } + + public void Backup() + { + lastbackup = DateTime.UtcNow; + ThreadPool.QueueUserWorkItem(DoBackup); + ThreadPool.QueueUserWorkItem(DeleteOld); + } + + private void DoBackup(object o) + { + try + { + string worldname = Main.worldPathName; + string name = Path.GetFileName(worldname); + + Main.worldPathName = Path.Combine(BackupPath, string.Format("{0}.{1:dd.MM.yy-HH.mm.ss}.bak", name, DateTime.UtcNow)); + + string worldpath = Path.GetDirectoryName(Main.worldPathName); + if (worldpath != null && !Directory.Exists(worldpath)) + Directory.CreateDirectory(worldpath); + + TShock.Utils.Broadcast("Server map saving, potential lag spike"); + Console.WriteLine("Backing up world..."); + + SaveManager.Instance.SaveWorld(); + Console.WriteLine("World backed up"); + Console.ForegroundColor = ConsoleColor.Gray; + Log.Info(string.Format("World backed up ({0})", Main.worldPathName)); + + Main.worldPathName = worldname; + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Backup failed"); + Console.ForegroundColor = ConsoleColor.Gray; + Log.Error("Backup failed"); + Log.Error(ex.ToString()); + } + } + + private void DeleteOld(object o) + { + if (KeepFor <= 0) + return; + foreach (var fi in new DirectoryInfo(BackupPath).GetFiles("*.bak")) + { + if ((DateTime.UtcNow - fi.LastWriteTimeUtc).TotalMinutes > KeepFor) + { + fi.Delete(); + } + } + } + } } \ No newline at end of file diff --git a/TShockAPI/Commands.cs b/TShockAPI/Commands.cs old mode 100644 new mode 100755 index 3feb0311..b0c6e68e --- a/TShockAPI/Commands.cs +++ b/TShockAPI/Commands.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011 The TShock Team @@ -407,8 +407,18 @@ namespace TShockAPI TShock.InventoryDB.InsertPlayerData(args.Player); } args.Player.SendMessage("Authenticated as " + user.Name + " successfully.", Color.LimeGreen); + Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + user.Name); - } + if ((args.Player.LoginHarassed) && (TShock.Config.RememberLeavePos)){ + if (TShock.RememberedPos.GetLeavePos(args.Player.Name, args.Player.IP) != Vector2.Zero) + { + Vector2 pos = TShock.RememberedPos.GetLeavePos(args.Player.Name, args.Player.IP); + + args.Player.Teleport((int) pos.X, (int) pos.Y + 3); + } + args.Player.LoginHarassed = false; + + }} else { args.Player.SendMessage("Incorrect password", Color.LimeGreen); @@ -574,7 +584,10 @@ namespace TShockAPI else if (subcmd == "del" && args.Parameters.Count == 2) { var user = new User(); - if (args.Parameters[1].Contains(".")) + if (args.Parameters[1].Split('.').Count() ==4) + + // changed to support dot character in usernames + // if (args.Parameters[1].Contains(".")) user.Address = args.Parameters[1]; else user.Name = args.Parameters[1]; @@ -620,7 +633,11 @@ namespace TShockAPI else if (subcmd == "group") { var user = new User(); - if (args.Parameters[1].Contains(".")) + if (args.Parameters[1].Split('.').Count()==4) + + //changed to support dot character in usernames + //if (args.Parameters[1].Contains(".")) + user.Address = args.Parameters[1]; else user.Name = args.Parameters[1]; @@ -769,7 +786,7 @@ namespace TShockAPI string reason = args.Parameters.Count > 1 ? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)) : "Misbehaviour."; - if (!TShock.Utils.Kick(players[0], reason)) + if (!TShock.Utils.Kick(players[0], reason, !args.Player.RealPlayer, false, args.Player.Name)) { args.Player.SendMessage("You can't kick another admin!", Color.Red); } @@ -816,7 +833,7 @@ namespace TShockAPI string reason = args.Parameters.Count > 1 ? String.Join(" ", args.Parameters.GetRange(1, args.Parameters.Count - 1)) : "Misbehaviour."; - if (!TShock.Utils.Ban(players[0], reason)) + if (!TShock.Utils.Ban(players[0], reason, !args.Player.RealPlayer, args.Player.Name)) { args.Player.SendMessage("You can't ban another admin!", Color.Red); } @@ -860,25 +877,14 @@ namespace TShockAPI var ban = TShock.Bans.GetBanByName(plStr); if (ban != null) { - if (TShock.Bans.RemoveBan(ban.IP)) - args.Player.SendMessage(string.Format("Unbanned {0} ({1})!", ban.Name, ban.IP), Color.Red); - else - args.Player.SendMessage(string.Format("Failed to unban {0} ({1})!", ban.Name, ban.IP), Color.Red); - } - else if (!TShock.Config.EnableBanOnUsernames) - { - ban = TShock.Bans.GetBanByIp(plStr); - - if (ban == null) - args.Player.SendMessage(string.Format("Failed to unban {0}, not found.", args.Parameters[0]), Color.Red); - else if (TShock.Bans.RemoveBan(ban.IP)) + if (TShock.Bans.RemoveBan(ban.IP, true)) args.Player.SendMessage(string.Format("Unbanned {0} ({1})!", ban.Name, ban.IP), Color.Red); else args.Player.SendMessage(string.Format("Failed to unban {0} ({1})!", ban.Name, ban.IP), Color.Red); } else { - args.Player.SendMessage("Invalid player!", Color.Red); + args.Player.SendMessage(string.Format("No bans for player {0} exist", plStr), Color.Red); } } @@ -937,8 +943,8 @@ namespace TShockAPI return; } - string plStr = args.Parameters[0]; - var ban = TShock.Bans.GetBanByIp(plStr); + var ip = args.Parameters[0]; + var ban = TShock.Bans.GetBanByIp(ip); if (ban != null) { if (TShock.Bans.RemoveBan(ban.IP)) @@ -948,7 +954,7 @@ namespace TShockAPI } else { - args.Player.SendMessage("Invalid player!", Color.Red); + args.Player.SendMessage(string.Format("No bans for ip {0} exist", ip), Color.Red); } } @@ -1001,38 +1007,37 @@ namespace TShockAPI } } - TShock.Utils.ForceKickAll("Server shutting down!"); - WorldGen.saveWorld(); - Netplay.disconnect = true; + TShock.Utils.StopServer(); } - //Added restart command - private static void Restart(CommandArgs args) - { - if (Main.runningMono){ - Log.ConsoleInfo("Sorry, this command has not yet been implemented in Mono"); - }else{ - if (TShock.Config.ServerSideInventory) - { - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) - { - TShock.InventoryDB.InsertPlayerData(player); - } - } - } + //Added restart command + private static void Restart(CommandArgs args) + { + if (Main.runningMono) + { + Log.ConsoleInfo("Sorry, this command has not yet been implemented in Mono"); + } + else + { + if (TShock.Config.ServerSideInventory) + { + foreach (TSPlayer player in TShock.Players) + { + if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) + { + TShock.InventoryDB.InsertPlayerData(player); + } + } + } - TShock.Utils.ForceKickAll("Server restarting!"); - WorldGen.saveWorld(); - Netplay.disconnect = true; - System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); - Environment.Exit(0); - }} + TShock.Utils.StopServer(); + System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase); + Environment.Exit(0); + } + } private static void OffNoSave(CommandArgs args) { - TShock.Utils.ForceKickAll("Server shutting down!"); - Netplay.disconnect = true; + TShock.Utils.StopServer(false); } private static void CheckUpdates(CommandArgs args) @@ -1085,7 +1090,7 @@ namespace TShockAPI if (args.Parameters.Count < 1) { - ply.SendMessage("Picking a random ore!", Color.Green); + ply.SendMessage("Picking a random ore!", Color.Green); //should this be a help message instead? num = WorldGen.genRand.Next(6); } else if (args.Parameters[0] == "cobalt") @@ -1112,7 +1117,34 @@ namespace TShockAPI { num = 5; } - + else if (args.Parameters[0] == "demonite") + { + num = 7; + } + else if (args.Parameters[0] == "sapphire") + { + num = 8; + } + else if (args.Parameters[0] == "ruby") + { + num = 9; + } + else if (args.Parameters[0] == "emerald") + { + num = 10; + } + else if (args.Parameters[0] == "topaz") + { + num = 11; + } + else if (args.Parameters[0] == "amethyst") + { + num = 12; + } + else if (args.Parameters[0] == "diamond") + { + num = 13; + } else { num = 2; @@ -1147,7 +1179,41 @@ namespace TShockAPI num = 9; num3 *= 1.1f; } - + else if (num == 7) + { + num = 22; + num3 *= 1; + } + else if (num == 8) + { + num = 63; + num3 *= .80f; + } + else if (num == 9) + { + num = 64; + num3 *=1; + } + else if (num == 10) + { + num = 65; + num3 *= 1; + } + else if (num == 11) + { + num = 66; + num3 *= 1; + } + else if (num == 12) + { + num = 67; + num3 *= 1; + } + else if (num == 13) + { + num = 68; + num3 *= 1; + } else { num = 111; @@ -1165,11 +1231,11 @@ namespace TShockAPI { int i2 = WorldGen.genRand.Next(100, Main.maxTilesX - 100); double num6 = Main.worldSurface; - if ((num == 108) || (num == 6) || (num == 7) || (num == 8) || (num == 9)) + if ((num == 108) || (num == 6) || (num == 7) || (num == 8) || (num == 9) ||((num > 62) && (num < 69))) { num6 = Main.rockLayer; } - if (num == 111) + if ((num == 111) || (num == 22) || (num == 68)) { num6 = (Main.rockLayer + Main.rockLayer + (double)Main.maxTilesY) / 3.0; } @@ -1544,6 +1610,8 @@ namespace TShockAPI Main.tile[x, y].type = 2; break; case 32: + case 113: + case 110: Main.tile[x, y].type = 0; Main.tile[x, y].active = false; break; @@ -1552,11 +1620,14 @@ namespace TShockAPI break; case 112: case 116: - Main.tile[x, y].type = 169; + Main.tile[x, y].type = 53; break; - case 113: + case 118: Main.tile[x, y].type = 38; break; + case 115: + Main.tile[x, y].type = 52; + break; default: continue; } @@ -2186,15 +2257,13 @@ namespace TShockAPI { Main.spawnTileX = args.Player.TileX + 1; Main.spawnTileY = args.Player.TileY + 3; - - TShock.Utils.Broadcast("Server map saving, potential lag spike"); - Thread SaveWorld = new Thread(TShock.Utils.SaveWorld); - SaveWorld.Start(); + SaveManager.Instance.SaveWorld(false); } private static void Reload(CommandArgs args) { FileTools.SetupConfig(); + TShock.HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs()); TShock.Groups.LoadPermisions(); TShock.Regions.ReloadAllRegions(); args.Player.SendMessage( @@ -2215,9 +2284,7 @@ namespace TShockAPI private static void Save(CommandArgs args) { - TShock.Utils.Broadcast("Server map saving, potential lag spike"); - Thread SaveWorld = new Thread(TShock.Utils.SaveWorld); - SaveWorld.Start(); + SaveManager.Instance.SaveWorld(false); } private static void Settle(CommandArgs args) diff --git a/TShockAPI/ConfigFile.cs b/TShockAPI/ConfigFile.cs index 4bb88a62..d03ff4f7 100644 --- a/TShockAPI/ConfigFile.cs +++ b/TShockAPI/ConfigFile.cs @@ -1,287 +1,287 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011 The TShock Team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -using System; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Text; -using Newtonsoft.Json; - -namespace TShockAPI -{ - public class ConfigFile - { - [Description( - "The equation for calculating invasion size is 100 + (multiplier * (number of active players with greater than 200 health))" - )] public int InvasionMultiplier = 1; - - [Description("The default maximum mobs that will spawn per wave. Higher means more mobs in that wave.")] public int - DefaultMaximumSpawns = 5; - - [Description("The delay between waves. Shorter values lead to less mobs.")] public int DefaultSpawnRate = 600; - [Description("The port the server runs on.")] public int ServerPort = 7777; - [Description("Enable or disable the whitelist based on IP addresses in whitelist.txt")] public bool EnableWhitelist; - - [Description( - "Enable the ability for invaison size to never decrease. Make sure to run /invade, and note that this adds 2 million+ goblins to the spawn que for the map." - )] public bool InfiniteInvasion; - - [Description("Set the server pvp mode. Vaild types are, \"normal\", \"always\", \"disabled\"")] public string PvPMode - = "normal"; - - [Description("Prevents tiles from being placed within SpawnProtectionRadius of the default spawn.")] public bool - SpawnProtection = true; - - [Description("Radius from spawn tile for SpawnProtection.")] public int SpawnProtectionRadius = 10; - - [Description( - "Max slots for the server. If you want people to be kicked with \"Server is full\" set this to how many players you want max and then set Terraria max players to 2 higher." - )] public int MaxSlots = 8; - - [Description("Global protection agent for any block distance based anti-grief check.")] public bool RangeChecks = true; - [Description("Disables any building; placing of blocks")] public bool DisableBuild; - - [Description("#.#.#. = Red/Blue/Green - RGB Colors for the Admin Chat Color. Max value: 255")] public float[] - SuperAdminChatRGB = {255, 0, 0}; - - [Description("Super admin group chat prefix")] public string SuperAdminChatPrefix = "(Admin) "; - [Description("Super admin group chat suffix")] public string SuperAdminChatSuffix = ""; - - [Description( - "Backup frequency in minutes. So, a value of 60 = 60 minutes. Backups are stored in the \\tshock\\backups folder.")] public int BackupInterval; - - [Description("How long backups are kept in minutes. 2880 = 2 days.")] public int BackupKeepFor = 60; - - [Description( - "Remembers where a player left off. It works by remembering the IP, NOT the character. \neg. When you try to disconnect, and reconnect to be automatically placed at spawn, you'll be at your last location. Note: Won't save after server restarts." - )] public bool RememberLeavePos; - - [Description("Hardcore players ONLY. This means softcore players cannot join.")] public bool HardcoreOnly; - [Description("Mediumcore players ONLY. This means softcore players cannot join.")] public bool MediumcoreOnly; - [Description("Kicks a Hardcore player on death.")] public bool KickOnMediumcoreDeath; - [Description("Bans a Hardcore player on death.")] public bool BanOnMediumcoreDeath; - - [Description("Enable/Disable Terrarias built in auto save")] public bool AutoSave = true; - - [Description("Number of failed login attempts before kicking the player.")] public int MaximumLoginAttempts = 3; - - [Description("Not implemented")] public string RconPassword = ""; - [Description("Not implemented")] public int RconPort = 7777; - - [Description("Used when replying to a rest /status request.")] public string ServerName = ""; - [Description("Not implemented")] public string MasterServer = "127.0.0.1"; - - [Description("Valid types are \"sqlite\" and \"mysql\"")] public string StorageType = "sqlite"; - - [Description("The MySQL Hostname and port to direct connections to")] public string MySqlHost = "localhost:3306"; - [Description("Database name to connect to")] public string MySqlDbName = ""; - [Description("Database username to connect with")] public string MySqlUsername = ""; - [Description("Database password to connect with")] public string MySqlPassword = ""; - - [Description("Bans a Mediumcore player on death.")] public string MediumcoreBanReason = "Death results in a ban"; - [Description("Kicks a Mediumcore player on death.")] public string MediumcoreKickReason = "Death results in a kick"; - - [Description("Enables DNS resolution of incoming connections with GetGroupForIPExpensive.")] public bool - EnableDNSHostResolution; - - [Description("Enables kicking of banned users by matching their IP Address")] public bool EnableIPBans = true; - - [Description("Enables kicking of banned users by matching their Character Name")] public bool EnableBanOnUsernames; - - [Description("Selects the default group name to place new registrants under")] public string - DefaultRegistrationGroupName = "default"; - - [Description("Selects the default group name to place non registered users under")] public string - DefaultGuestGroupName = "guest"; - - [Description("Force-Disable printing logs to players with the log permission")] public bool DisableSpewLogs = true; - - [Description("Valid types are \"sha512\", \"sha256\", \"md5\", append with \"-xp\" for the xp supported algorithms")] public string HashAlgorithm = "sha512"; - - [Description("Buffers up the packets and sends them out at the end of each frame")] public bool BufferPackets = true; - - [Description("String that is used when kicking people when the server is full.")] public string ServerFullReason = - "Server is full"; - - [Description("String that is used when kicking people when the server is full with no reserved slots.")] public string - ServerFullNoReservedReason = "Server is full. No reserved slots open."; - - [Description("This will save the world if Terraria crashes from an unhandled exception.")] public bool - SaveWorldOnCrash = true; - - [Description("This will announce a player's location on join")] public bool EnableGeoIP; - - [Description("This will turn on a token requirement for the /status API endpoint.")] public bool - EnableTokenEndpointAuthentication; - - [Description("This is used when the API endpoint /status is queried.")] public string ServerNickname = "TShock Server"; - - [Description("Enable/Disable the rest api.")] public bool RestApiEnabled; - - [Description("This is the port which the rest api will listen on.")] public int RestApiPort = 7878; - - [Description("Disable tombstones for all players.")] public bool DisableTombstones = true; - - [Description("Displays a player's IP on join to everyone who has the log permission")] public bool DisplayIPToAdmins; - - [Description( - "Some tiles are 'fixed' by not letting TShock handle them. Disabling this may break certain asthetic tiles.")] public - bool EnableInsecureTileFixes = true; - - [Description("Kicks users using a proxy as identified with the GeoIP database")] public bool KickProxyUsers = true; - - [Description("Disables hardmode, can't never be activated. Overrides /starthardmode")] public bool DisableHardmode; - - [Description("Disables Dungeon Guardian from being spawned by player packets, this will instead force a respawn")] public bool DisableDungeonGuardian; - - [Description("Enable Server Side Inventory checks, EXPERIMENTAL")] public bool ServerSideInventory; - - [Description("How often SSI should save, in minutes")] public int ServerSideInventorySave = 15; - - [Description("Time, in milliseconds, to disallow discarding items after logging in when ServerSideInventory is ON")] public int LogonDiscardThreshold=250; - - [Description("Disables reporting of playercount to the stat system.")] public bool DisablePlayerCountReporting; - - [Description("Disables clown bomb projectiles from spawning")] public bool DisableClownBombs; - - [Description("Disables snow ball projectiles from spawning")] public bool DisableSnowBalls; - - [Description( - "Change ingame chat format, {0} = Group Name, {1} = Group Prefix, {2} = Player Name, {3} = Group Suffix, {4} = Chat Message" - )] public string ChatFormat = "{1}{2}{3}: {4}"; - - [Description("Force the world time to be normal, day, or night")] public string ForceTime = "normal"; - - [Description("Disable/Revert a player if they exceed this number of tile kills within 1 second.")] public int - TileKillThreshold = 60; - - [Description("Disable/Revert a player if they exceed this number of tile places within 1 second.")] public int - TilePlaceThreshold = 20; - - [Description("Disable a player if they exceed this number of liquid sets within 1 second.")] public int - TileLiquidThreshold = 15; - - [Description("Disable a player if they exceed this number of projectile new within 1 second.")] public int - ProjectileThreshold = 50; - - [Description("Ignore shrapnel from crystal bullets for Projectile Threshold.")] public bool - ProjIgnoreShrapnel = true; - - [Description("Require all players to register or login before being allowed to play.")] public bool RequireLogin; - - [Description( - "Disables Invisibility potions from being used in PvP (Note, they can use them on the client, but the effect isn't sent to the rest of the server)" - )] public bool DisableInvisPvP; - - [Description("The maximum distance players disabled for various reasons can move from")] public int - MaxRangeForDisabled = 10; - - [Description("Server password required to join server")] public string ServerPassword = ""; - - [Description("Protect chests with region and build permissions")] public bool RegionProtectChests; - - [Description("Disable users from being able to login with account password when joining")] public bool - DisableLoginBeforeJoin; - - [Description("Allows users to register any username with /register")] public bool AllowRegisterAnyUsername; - - [Description("Allows users to login with any username with /login")] public bool AllowLoginAnyUsername = true; - - [Description("The maximum damage a player/npc can inflict")] public int MaxDamage = 175; - - [Description("The maximum damage a projectile can inflict")] public int MaxProjDamage = 175; - - [Description("Ignores checking to see if player 'can' update a projectile")] public bool IgnoreProjUpdate = false; - - [Description("Ignores checking to see if player 'can' kill a projectile")] public bool IgnoreProjKill = false; - - [Description("Ignores all no clip checks for players")] public bool IgnoreNoClip = false; - - [Description("Allow Ice placement even when user does not have canbuild")] public bool AllowIce = false; - - public static ConfigFile Read(string path) - { - if (!File.Exists(path)) - return new ConfigFile(); - using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - return Read(fs); - } - } - - public static ConfigFile Read(Stream stream) - { - using (var sr = new StreamReader(stream)) - { - var cf = JsonConvert.DeserializeObject(sr.ReadToEnd()); - if (ConfigRead != null) - ConfigRead(cf); - return cf; - } - } - - public void Write(string path) - { - using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write)) - { - Write(fs); - } - } - - public void Write(Stream stream) - { - var str = JsonConvert.SerializeObject(this, Formatting.Indented); - using (var sw = new StreamWriter(stream)) - { - sw.Write(str); - } - } - - public static Action ConfigRead; - - - public static void DumpDescriptions() - { - var sb = new StringBuilder(); - var defaults = new ConfigFile(); - - foreach (var field in defaults.GetType().GetFields().OrderBy(f => f.Name)) - { - if (field.IsStatic) - continue; - - var name = field.Name; - var type = field.FieldType.Name; - - var descattr = - field.GetCustomAttributes(false).FirstOrDefault(o => o is DescriptionAttribute) as DescriptionAttribute; - var desc = descattr != null && !string.IsNullOrWhiteSpace(descattr.Description) ? descattr.Description : "None"; - - var def = field.GetValue(defaults); - - sb.AppendLine("## {0} ".SFormat(name)); - sb.AppendLine("**Type:** {0} ".SFormat(type)); - sb.AppendLine("**Description:** {0} ".SFormat(desc)); - sb.AppendLine("**Default:** \"{0}\" ".SFormat(def)); - sb.AppendLine(); - } - - File.WriteAllText("ConfigDescriptions.txt", sb.ToString()); - } - } +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using Newtonsoft.Json; + +namespace TShockAPI +{ + public class ConfigFile + { + [Description( + "The equation for calculating invasion size is 100 + (multiplier * (number of active players with greater than 200 health))" + )] public int InvasionMultiplier = 1; + + [Description("The default maximum mobs that will spawn per wave. Higher means more mobs in that wave.")] public int + DefaultMaximumSpawns = 5; + + [Description("The delay between waves. Shorter values lead to less mobs.")] public int DefaultSpawnRate = 600; + [Description("The port the server runs on.")] public int ServerPort = 7777; + [Description("Enable or disable the whitelist based on IP addresses in whitelist.txt")] public bool EnableWhitelist; + + [Description( + "Enable the ability for invaison size to never decrease. Make sure to run /invade, and note that this adds 2 million+ goblins to the spawn que for the map." + )] public bool InfiniteInvasion; + + [Description("Set the server pvp mode. Vaild types are, \"normal\", \"always\", \"disabled\"")] public string PvPMode + = "normal"; + + [Description("Prevents tiles from being placed within SpawnProtectionRadius of the default spawn.")] public bool + SpawnProtection = true; + + [Description("Radius from spawn tile for SpawnProtection.")] public int SpawnProtectionRadius = 10; + + [Description( + "Max slots for the server. If you want people to be kicked with \"Server is full\" set this to how many players you want max and then set Terraria max players to 2 higher." + )] public int MaxSlots = 8; + + [Description("Global protection agent for any block distance based anti-grief check.")] public bool RangeChecks = true; + [Description("Disables any building; placing of blocks")] public bool DisableBuild; + + [Description("#.#.#. = Red/Blue/Green - RGB Colors for the Admin Chat Color. Max value: 255")] public float[] + SuperAdminChatRGB = {255, 0, 0}; + + [Description("Super admin group chat prefix")] public string SuperAdminChatPrefix = "(Admin) "; + [Description("Super admin group chat suffix")] public string SuperAdminChatSuffix = ""; + + [Description( + "Backup frequency in minutes. So, a value of 60 = 60 minutes. Backups are stored in the \\tshock\\backups folder.")] public int BackupInterval; + + [Description("How long backups are kept in minutes. 2880 = 2 days.")] public int BackupKeepFor = 60; + + [Description( + "Remembers where a player left off. It works by remembering the IP, NOT the character. \neg. When you try to disconnect, and reconnect to be automatically placed at spawn, you'll be at your last location. Note: Won't save after server restarts." + )] public bool RememberLeavePos; + + [Description("Hardcore players ONLY. This means softcore players cannot join.")] public bool HardcoreOnly; + [Description("Mediumcore players ONLY. This means softcore players cannot join.")] public bool MediumcoreOnly; + [Description("Kicks a Hardcore player on death.")] public bool KickOnMediumcoreDeath; + [Description("Bans a Hardcore player on death.")] public bool BanOnMediumcoreDeath; + + [Description("Enable/Disable Terrarias built in auto save")] public bool AutoSave = true; + + [Description("Number of failed login attempts before kicking the player.")] public int MaximumLoginAttempts = 3; + + [Description("Not implemented")] public string RconPassword = ""; + [Description("Not implemented")] public int RconPort = 7777; + + [Description("Used when replying to a rest /status request.")] public string ServerName = ""; + [Description("Not implemented")] public string MasterServer = "127.0.0.1"; + + [Description("Valid types are \"sqlite\" and \"mysql\"")] public string StorageType = "sqlite"; + + [Description("The MySQL Hostname and port to direct connections to")] public string MySqlHost = "localhost:3306"; + [Description("Database name to connect to")] public string MySqlDbName = ""; + [Description("Database username to connect with")] public string MySqlUsername = ""; + [Description("Database password to connect with")] public string MySqlPassword = ""; + + [Description("Bans a Mediumcore player on death.")] public string MediumcoreBanReason = "Death results in a ban"; + [Description("Kicks a Mediumcore player on death.")] public string MediumcoreKickReason = "Death results in a kick"; + + [Description("Enables DNS resolution of incoming connections with GetGroupForIPExpensive.")] public bool + EnableDNSHostResolution; + + [Description("Enables kicking of banned users by matching their IP Address")] public bool EnableIPBans = true; + + [Description("Enables kicking of banned users by matching their Character Name")] public bool EnableBanOnUsernames; + + [Description("Selects the default group name to place new registrants under")] public string + DefaultRegistrationGroupName = "default"; + + [Description("Selects the default group name to place non registered users under")] public string + DefaultGuestGroupName = "guest"; + + [Description("Force-Disable printing logs to players with the log permission")] public bool DisableSpewLogs = true; + + [Description("Valid types are \"sha512\", \"sha256\", \"md5\", append with \"-xp\" for the xp supported algorithms")] public string HashAlgorithm = "sha512"; + + [Description("Buffers up the packets and sends them out at the end of each frame")] public bool BufferPackets = true; + + [Description("String that is used when kicking people when the server is full.")] public string ServerFullReason = + "Server is full"; + + [Description("String that is used when kicking people when the server is full with no reserved slots.")] public string + ServerFullNoReservedReason = "Server is full. No reserved slots open."; + + [Description("This will save the world if Terraria crashes from an unhandled exception.")] public bool + SaveWorldOnCrash = true; + + [Description("This will announce a player's location on join")] public bool EnableGeoIP; + + [Description("This will turn on a token requirement for the /status API endpoint.")] public bool + EnableTokenEndpointAuthentication; + + [Description("This is used when the API endpoint /status is queried.")] public string ServerNickname = "TShock Server"; + + [Description("Enable/Disable the rest api.")] public bool RestApiEnabled; + + [Description("This is the port which the rest api will listen on.")] public int RestApiPort = 7878; + + [Description("Disable tombstones for all players.")] public bool DisableTombstones = true; + + [Description("Displays a player's IP on join to everyone who has the log permission")] public bool DisplayIPToAdmins; + + [Description( + "Some tiles are 'fixed' by not letting TShock handle them. Disabling this may break certain asthetic tiles.")] public + bool EnableInsecureTileFixes = true; + + [Description("Kicks users using a proxy as identified with the GeoIP database")] public bool KickProxyUsers = true; + + [Description("Disables hardmode, can't never be activated. Overrides /starthardmode")] public bool DisableHardmode; + + [Description("Disables Dungeon Guardian from being spawned by player packets, this will instead force a respawn")] public bool DisableDungeonGuardian; + + [Description("Enable Server Side Inventory checks, EXPERIMENTAL")] public bool ServerSideInventory; + + [Description("How often SSI should save, in minutes")] public int ServerSideInventorySave = 15; + + [Description("Time, in milliseconds, to disallow discarding items after logging in when ServerSideInventory is ON")] public int LogonDiscardThreshold=250; + + [Description("Disables reporting of playercount to the stat system.")] public bool DisablePlayerCountReporting; + + [Description("Disables clown bomb projectiles from spawning")] public bool DisableClownBombs; + + [Description("Disables snow ball projectiles from spawning")] public bool DisableSnowBalls; + + [Description( + "Change ingame chat format, {0} = Group Name, {1} = Group Prefix, {2} = Player Name, {3} = Group Suffix, {4} = Chat Message" + )] public string ChatFormat = "{1}{2}{3}: {4}"; + + [Description("Force the world time to be normal, day, or night")] public string ForceTime = "normal"; + + [Description("Disable/Revert a player if they exceed this number of tile kills within 1 second.")] public int + TileKillThreshold = 60; + + [Description("Disable/Revert a player if they exceed this number of tile places within 1 second.")] public int + TilePlaceThreshold = 20; + + [Description("Disable a player if they exceed this number of liquid sets within 1 second.")] public int + TileLiquidThreshold = 15; + + [Description("Disable a player if they exceed this number of projectile new within 1 second.")] public int + ProjectileThreshold = 50; + + [Description("Ignore shrapnel from crystal bullets for Projectile Threshold.")] public bool + ProjIgnoreShrapnel = true; + + [Description("Require all players to register or login before being allowed to play.")] public bool RequireLogin; + + [Description( + "Disables Invisibility potions from being used in PvP (Note, they can use them on the client, but the effect isn't sent to the rest of the server)" + )] public bool DisableInvisPvP; + + [Description("The maximum distance players disabled for various reasons can move from")] public int + MaxRangeForDisabled = 10; + + [Description("Server password required to join server")] public string ServerPassword = ""; + + [Description("Protect chests with region and build permissions")] public bool RegionProtectChests; + + [Description("Disable users from being able to login with account password when joining")] public bool + DisableLoginBeforeJoin; + + [Description("Allows users to register any username with /register")] public bool AllowRegisterAnyUsername; + + [Description("Allows users to login with any username with /login")] public bool AllowLoginAnyUsername = true; + + [Description("The maximum damage a player/npc can inflict")] public int MaxDamage = 175; + + [Description("The maximum damage a projectile can inflict")] public int MaxProjDamage = 175; + + [Description("Ignores checking to see if player 'can' update a projectile")] public bool IgnoreProjUpdate = false; + + [Description("Ignores checking to see if player 'can' kill a projectile")] public bool IgnoreProjKill = false; + + [Description("Ignores all no clip checks for players")] public bool IgnoreNoClip = false; + + [Description("Allow Ice placement even when user does not have canbuild")] public bool AllowIce = false; + + public static ConfigFile Read(string path) + { + if (!File.Exists(path)) + return new ConfigFile(); + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + return Read(fs); + } + } + + public static ConfigFile Read(Stream stream) + { + using (var sr = new StreamReader(stream)) + { + var cf = JsonConvert.DeserializeObject(sr.ReadToEnd()); + if (ConfigRead != null) + ConfigRead(cf); + return cf; + } + } + + public void Write(string path) + { + using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write)) + { + Write(fs); + } + } + + public void Write(Stream stream) + { + var str = JsonConvert.SerializeObject(this, Formatting.Indented); + using (var sw = new StreamWriter(stream)) + { + sw.Write(str); + } + } + + public static Action ConfigRead; + + + public static void DumpDescriptions() + { + var sb = new StringBuilder(); + var defaults = new ConfigFile(); + + foreach (var field in defaults.GetType().GetFields().OrderBy(f => f.Name)) + { + if (field.IsStatic) + continue; + + var name = field.Name; + var type = field.FieldType.Name; + + var descattr = + field.GetCustomAttributes(false).FirstOrDefault(o => o is DescriptionAttribute) as DescriptionAttribute; + var desc = descattr != null && !string.IsNullOrWhiteSpace(descattr.Description) ? descattr.Description : "None"; + + var def = field.GetValue(defaults); + + sb.AppendLine("## {0} ".SFormat(name)); + sb.AppendLine("**Type:** {0} ".SFormat(type)); + sb.AppendLine("**Description:** {0} ".SFormat(desc)); + sb.AppendLine("**Default:** \"{0}\" ".SFormat(def)); + sb.AppendLine(); + } + + File.WriteAllText("ConfigDescriptions.txt", sb.ToString()); + } + } } \ No newline at end of file diff --git a/TShockAPI/DB/BanManager.cs b/TShockAPI/DB/BanManager.cs index 5afa69f9..d00e8264 100644 --- a/TShockAPI/DB/BanManager.cs +++ b/TShockAPI/DB/BanManager.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011 The TShock Team @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; +using System.Collections.Generic; using System.Data; using System.IO; using MySql.Data.MySqlClient; @@ -39,15 +40,15 @@ namespace TShockAPI.DB db.GetSqlType() == SqlType.Sqlite ? (IQueryBuilder) new SqliteQueryCreator() : new MysqlQueryCreator()); - try{ - creator.EnsureExists(table); - } - catch (DllNotFoundException ex) -{ -System.Console.WriteLine("Possible problem with your database - is Sqlite3.dll present?"); -throw new Exception("Could not find a database library (probably Sqlite3.dll)"); -} - + try + { + creator.EnsureExists(table); + } + catch (DllNotFoundException) + { + System.Console.WriteLine("Possible problem with your database - is Sqlite3.dll present?"); + throw new Exception("Could not find a database library (probably Sqlite3.dll)"); + } } public Ban GetBanByIp(string ip) @@ -67,12 +68,30 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)"); return null; } + public List GetBans() + { + List banlist = new List(); + try + { + using (var reader = database.QueryReader("SELECT * FROM Bans")) + { + while (reader.Read()) + { + banlist.Add(new Ban(reader.Get("IP"), reader.Get("Name"), reader.Get("Reason"))); + } + return banlist; + } + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + Console.WriteLine(ex.StackTrace); + } + return null; + } + public Ban GetBanByName(string name, bool casesensitive = true) { - if (!TShock.Config.EnableBanOnUsernames) - { - return null; - } try { var namecol = casesensitive ? "Name" : "UPPER(Name)"; @@ -91,7 +110,14 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)"); return null; } - public bool AddBan(string ip, string name = "", string reason = "") +#if COMPAT_SIGS + [Obsolete("This method is for signature compatibility for external code only")] + public bool AddBan(string ip, string name, string reason) + { + return AddBan(ip, name, reason, false); + } +#endif + public bool AddBan(string ip, string name = "", string reason = "", bool exceptions = false) { try { @@ -99,19 +125,34 @@ throw new Exception("Could not find a database library (probably Sqlite3.dll)"); } catch (Exception ex) { + if (exceptions) + throw ex; Log.Error(ex.ToString()); } return false; } +#if COMPAT_SIGS + [Obsolete("This method is for signature compatibility for external code only")] public bool RemoveBan(string ip) + { + return RemoveBan(ip, false, true, false); + } +#endif + public bool RemoveBan(string match, bool byName = false, bool casesensitive = true, bool exceptions = false) { try { - return database.Query("DELETE FROM Bans WHERE IP=@0", ip) != 0; + if (!byName) + return database.Query("DELETE FROM Bans WHERE IP=@0", match) != 0; + + var namecol = casesensitive ? "Name" : "UPPER(Name)"; + return database.Query("DELETE FROM Bans WHERE " + namecol + "=@0", casesensitive ? match : match.ToUpper()) != 0; } catch (Exception ex) { + if (exceptions) + throw ex; Log.Error(ex.ToString()); } return false; diff --git a/TShockAPI/DB/GroupManager.cs b/TShockAPI/DB/GroupManager.cs index da3ce79f..573f7ac7 100644 --- a/TShockAPI/DB/GroupManager.cs +++ b/TShockAPI/DB/GroupManager.cs @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; +using System.Collections; using System.Collections.Generic; using System.Data; using System.IO; @@ -24,11 +25,10 @@ using MySql.Data.MySqlClient; namespace TShockAPI.DB { - public class GroupManager + public class GroupManager : IEnumerable { private IDbConnection database; - - public List groups = new List(); + public readonly List groups = new List(); public GroupManager(IDbConnection db) { @@ -48,14 +48,23 @@ namespace TShockAPI.DB : new MysqlQueryCreator()); creator.EnsureExists(table); - //Add default groups - AddGroup("guest", "canbuild,canregister,canlogin,canpartychat,cantalkinthird"); - AddGroup("default", "guest", "warp,canchangepassword"); - AddGroup("newadmin", "default", "kick,editspawn,reservedslot"); - AddGroup("admin", "newadmin", + // Load Permissions from the DB + LoadPermisions(); + + // Add default groups if they don't exist + AddDefaultGroup("guest", "", "canbuild,canregister,canlogin,canpartychat,cantalkinthird"); + AddDefaultGroup("default", "guest", "warp,canchangepassword"); + AddDefaultGroup("newadmin", "default", "kick,editspawn,reservedslot"); + AddDefaultGroup("admin", "newadmin", "ban,unban,whitelist,causeevents,spawnboss,spawnmob,managewarp,time,tp,pvpfun,kill,logs,immunetokick,tphere"); - AddGroup("trustedadmin", "admin", "maintenance,cfg,butcher,item,heal,immunetoban,usebanneditem,manageusers"); - AddGroup("vip", "default", "reservedslot"); + AddDefaultGroup("trustedadmin", "admin", "maintenance,cfg,butcher,item,heal,immunetoban,usebanneditem,manageusers"); + AddDefaultGroup("vip", "default", "reservedslot"); + } + + private void AddDefaultGroup(string name, string parent, string permissions) + { + if (!GroupExists(name)) + AddGroup(name, parent, permissions); } @@ -64,30 +73,52 @@ namespace TShockAPI.DB if (group == "superadmin") return true; - return groups.Any(g => g.Name.Equals(group)); } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return groups.GetEnumerator(); + } + + public Group GetGroupByName(string name) + { + var ret = groups.Where(g => g.Name == name); + return 1 == ret.Count() ? ret.ElementAt(0) : null; + } + /// /// Adds group with name and permissions if it does not exist. /// /// name of group /// parent of group /// permissions - public String AddGroup(String name, string parentname, String permissions, String chatcolor) + /// chatcolor + /// exceptions true indicates use exceptions for errors false otherwise + public String AddGroup(String name, string parentname, String permissions, String chatcolor = Group.defaultChatColor, bool exceptions = false) { - String message = ""; if (GroupExists(name)) + { + if (exceptions) + throw new GroupExistsException(name); return "Error: Group already exists. Use /modGroup to change permissions."; + } var group = new Group(name, null, chatcolor); - group.permissions.Add(permissions); + group.Permissions = permissions; if (!string.IsNullOrWhiteSpace(parentname)) { var parent = groups.FirstOrDefault(gp => gp.Name == parentname); if (parent == null) { - var error = "Invalid parent {0} for group {1}".SFormat(group.Name, parentname); + var error = "Invalid parent {0} for group {1}".SFormat(parentname, group.Name); + if (exceptions) + throw new GroupManagerException(error); Log.ConsoleError(error); return error; } @@ -98,81 +129,136 @@ namespace TShockAPI.DB ? "INSERT OR IGNORE INTO GroupList (GroupName, Parent, Commands, ChatColor) VALUES (@0, @1, @2, @3);" : "INSERT IGNORE INTO GroupList SET GroupName=@0, Parent=@1, Commands=@2, ChatColor=@3"; if (database.Query(query, name, parentname, permissions, chatcolor) == 1) - message = "Group " + name + " has been created successfully."; + { + groups.Add(group); + return "Group " + name + " has been created successfully."; + } + else if (exceptions) + throw new GroupManagerException("Failed to add group '" + name + "'"); - groups.Add(group); - - return message; + return ""; } public String AddGroup(String name, String permissions) { - return AddGroup(name, "", permissions, "255,255,255"); + return AddGroup(name, null, permissions, Group.defaultChatColor, false); } - public String AddGroup(String name, string parent, String permissions) +#if COMPAT_SIGS + [Obsolete("This method is for signature compatibility for external code only")] + public String AddGroup(String name, string parentname, String permissions) { - return AddGroup(name, parent, permissions, "255,255,255"); + return AddGroup(name, parentname, permissions, Group.defaultChatColor, false); } + [Obsolete("This method is for signature compatibility for external code only")] + public String AddGroup(String name, string parentname, String permissions, String chatcolor) + { + return AddGroup(name, parentname, permissions, chatcolor, false); + } +#endif + /// + /// Updates a group including permissions + /// + /// name of the group to update + /// parent of group + /// permissions + /// chatcolor + public void UpdateGroup(string name, string parentname, string permissions, string chatcolor) + { + if (!GroupExists(name)) + throw new GroupNotExistException(name); + + Group parent = null; + if (!string.IsNullOrWhiteSpace(parentname)) + { + parent = groups.FirstOrDefault(gp => gp.Name == parentname); + if (null == parent) + throw new GroupManagerException("Invalid parent {0} for group {1}".SFormat(parentname, name)); + } + + // NOTE: we use newgroup.XYZ to ensure any validation is also persisted to the DB + var newgroup = new Group(name, parent, chatcolor, permissions); + string query = "UPDATE GroupList SET Parent=@0, Commands=@1, ChatColor=@2 WHERE GroupName=@3"; + if (database.Query(query, parentname, newgroup.Permissions, newgroup.ChatColor, name) != 1) + throw new GroupManagerException("Failed to update group '" + name + "'"); + + groups.Remove(TShock.Utils.GetGroup(name)); + groups.Add(newgroup); + } + +#if COMPAT_SIGS + [Obsolete("This method is for signature compatibility for external code only")] public String DeleteGroup(String name) { - String message = ""; + return DeleteGroup(name, false); + } +#endif + public String DeleteGroup(String name, bool exceptions = false) + { if (!GroupExists(name)) + { + if (exceptions) + throw new GroupNotExistException(name); return "Error: Group doesn't exists."; + } if (database.Query("DELETE FROM GroupList WHERE GroupName=@0", name) == 1) - message = "Group " + name + " has been deleted successfully."; - groups.Remove(TShock.Utils.GetGroup(name)); + { + groups.Remove(TShock.Utils.GetGroup(name)); + return "Group " + name + " has been deleted successfully."; + } + else if (exceptions) + throw new GroupManagerException("Failed to delete group '" + name + "'"); - return message; + return ""; } public String AddPermissions(String name, List permissions) { - String message = ""; if (!GroupExists(name)) return "Error: Group doesn't exists."; var group = TShock.Utils.GetGroup(name); - //Add existing permissions (without duplicating) - permissions.AddRange(group.permissions.Where(s => !permissions.Contains(s))); + var oldperms = group.Permissions; // Store old permissions in case of error + permissions.ForEach(p => group.AddPermission(p)); + + if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", group.Permissions, name) == 1) + return "Group " + name + " has been modified successfully."; - if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", String.Join(",", permissions), name) != 0) - { - message = "Group " + name + " has been modified successfully."; - group.SetPermission(permissions); - } - return message; + // Restore old permissions so DB and internal object are in a consistent state + group.Permissions = oldperms; + return ""; } public String DeletePermissions(String name, List permissions) { - String message = ""; if (!GroupExists(name)) return "Error: Group doesn't exists."; var group = TShock.Utils.GetGroup(name); + var oldperms = group.Permissions; // Store old permissions in case of error + permissions.ForEach(p => group.RemovePermission(p)); - //Only get permissions that exist in the group. - var newperms = group.permissions.Except(permissions); - - if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", String.Join(",", newperms), name) != 0) - { - message = "Group " + name + " has been modified successfully."; - group.SetPermission(newperms.ToList()); - } - return message; + if (database.Query("UPDATE GroupList SET Commands=@0 WHERE GroupName=@1", group.Permissions, name) == 1) + return "Group " + name + " has been modified successfully."; + + // Restore old permissions so DB and internal object are in a consistent state + group.Permissions = oldperms; + return ""; } public void LoadPermisions() { - //Create a temporary list so if there is an error it doesn't override the currently loaded groups with broken groups. + // Create a temporary list so if there is an error it doesn't override the currently loaded groups with broken groups. var tempgroups = new List(); tempgroups.Add(new SuperAdminGroup()); if (groups == null || groups.Count < 2) - groups = tempgroups; + { + groups.Clear(); + groups.AddRange(tempgroups); + } try { @@ -181,34 +267,9 @@ namespace TShockAPI.DB { while (reader.Read()) { - string groupname = reader.Get("GroupName"); - var group = new Group(groupname); - + var group = new Group(reader.Get("GroupName"), null, reader.Get("ChatColor"), reader.Get("Commands")); group.Prefix = reader.Get("Prefix"); group.Suffix = reader.Get("Suffix"); - - //Inherit Given commands - String[] commands = reader.Get("Commands").Split(','); - foreach (var t in commands) - { - var str = t.Trim(); - if (str.StartsWith("!")) - { - group.NegatePermission(str.Substring(1)); - } - else - { - group.AddPermission(str); - } - } - String[] chatcolour = (reader.Get("ChatColor") ?? "").Split(','); - if (chatcolour.Length == 3) - { - byte.TryParse(chatcolour[0], out group.R); - byte.TryParse(chatcolour[1], out group.G); - byte.TryParse(chatcolour[2], out group.B); - } - groupsparents.Add(Tuple.Create(group, reader.Get("Parent"))); } } @@ -222,7 +283,7 @@ namespace TShockAPI.DB var parent = groupsparents.FirstOrDefault(gp => gp.Item1.Name == parentname); if (parent == null) { - Log.ConsoleError("Invalid parent {0} for group {1}".SFormat(group.Name, parentname)); + Log.ConsoleError("Invalid parent {0} for group {1}".SFormat(parentname, group.Name)); return; } group.Parent = parent.Item1; @@ -230,8 +291,8 @@ namespace TShockAPI.DB tempgroups.Add(group); } - - groups = tempgroups; + groups.Clear(); + groups.AddRange(tempgroups); } catch (Exception ex) { @@ -239,4 +300,36 @@ namespace TShockAPI.DB } } } -} \ No newline at end of file + + [Serializable] + public class GroupManagerException : Exception + { + public GroupManagerException(string message) + : base(message) + { + } + + public GroupManagerException(string message, Exception inner) + : base(message, inner) + { + } + } + + [Serializable] + public class GroupExistsException : GroupManagerException + { + public GroupExistsException(string name) + : base("Group '" + name + "' already exists") + { + } + } + + [Serializable] + public class GroupNotExistException : GroupManagerException + { + public GroupNotExistException(string name) + : base("Group '" + name + "' does not exist") + { + } + } +} diff --git a/TShockAPI/DB/IQueryBuilder.cs b/TShockAPI/DB/IQueryBuilder.cs index 107375da..b5f84105 100644 --- a/TShockAPI/DB/IQueryBuilder.cs +++ b/TShockAPI/DB/IQueryBuilder.cs @@ -33,141 +33,25 @@ namespace TShockAPI.DB string InsertValues(string table, List values); string ReadColumn(string table, List wheres); string DeleteRow(string table, List wheres); + string RenameTable(string from, string to); } - public class SqliteQueryCreator : IQueryBuilder + public class SqliteQueryCreator : GenericQueryCreator, IQueryBuilder { - public string CreateTable(SqlTable table) + public override string CreateTable(SqlTable table) { var columns = table.Columns.Select( c => - "'{0}' {1} {2} {3} {4}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", + "'{0}' {1} {2} {3} {4} {5}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", c.AutoIncrement ? "AUTOINCREMENT" : "", c.NotNull ? "NOT NULL" : "", c.Unique ? "UNIQUE" : "")); - return "CREATE TABLE '{0}' ({1})".SFormat(table.Name, string.Join(", ", columns)); + return "CREATE TABLE {0} ({1})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns)); } - private static Random rand = new Random(); - - /// - /// Alter a table from source to destination - /// - /// Must have name and column names. Column types are not required - /// Must have column names and column types. - /// - public string AlterTable(SqlTable from, SqlTable to) + public override string RenameTable(string from, string to) { - var rstr = rand.NextString(20); - var alter = "ALTER TABLE '{0}' RENAME TO '{1}_{0}'".SFormat(from.Name, rstr); - var create = CreateTable(to); - //combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable. - //exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data? - var insert = "INSERT INTO '{0}' ({1}) SELECT {1} FROM {2}_{0}".SFormat(from.Name, - string.Join(", ", - from.Columns.Where( - c => - to.Columns.Any( - c2 => c2.Name == c.Name)).Select - (c => c.Name)), rstr); - var drop = "DROP TABLE '{0}_{1}'".SFormat(rstr, from.Name); - return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop); - /* - ALTER TABLE "main"."Bans" RENAME TO "oXHFcGcd04oXHFcGcd04_Bans" - CREATE TABLE "main"."Bans" ("IP" TEXT PRIMARY KEY ,"Name" TEXT) - INSERT INTO "main"."Bans" SELECT "IP","Name" FROM "main"."oXHFcGcd04oXHFcGcd04_Bans" - DROP TABLE "main"."oXHFcGcd04oXHFcGcd04_Bans" - * - * Twitchy - Oh. I get it! - */ - } - - public string DeleteRow(string table, List wheres) - { - var sbwheres = new StringBuilder(); - int count = 0; - foreach (SqlValue where in wheres) - { - sbwheres.Append(where.Name + "=" + where.Value); - if (count != wheres.Count - 1) - sbwheres.Append(" AND "); - count++; - } - if (wheres.Count > 0) - return "DELETE FROM '{0}' WHERE {1} ".SFormat(table, sbwheres.ToString()); - else - return "DELETE FROM '{0}'".SFormat(table, sbwheres.ToString()); - } - - public string UpdateValue(string table, List values, List wheres) - { - var sbvalues = new StringBuilder(); - var sbwheres = new StringBuilder(); - int count = 0; - foreach (SqlValue value in values) - { - sbvalues.Append(value.Name + "=" + value.Value); - if (count != values.Count - 1) - sbvalues.Append(","); - count++; - } - count = 0; - foreach (SqlValue where in wheres) - { - sbwheres.Append(where.Name + "=" + where.Value); - if (count != wheres.Count - 1) - sbwheres.Append(" AND "); - count++; - } - - if (wheres.Count > 0) - return "UPDATE '{0}' SET {1} WHERE {2}".SFormat(table, sbvalues.ToString(), sbwheres.ToString()); - else - return "UPDATE '{0}' SET {1}".SFormat(table, sbvalues.ToString()); - } - - public string InsertValues(string table, List values) - { - var sbnames = new StringBuilder(); - var sbvalues = new StringBuilder(); - int count = 0; - - foreach (SqlValue name in values) - { - sbnames.Append(name.Name); - - if (count != values.Count - 1) - sbnames.Append(", "); - count++; - } - count = 0; - foreach (SqlValue value in values) - { - sbvalues.Append(value.Value.ToString()); - if (count != values.Count - 1) - sbvalues.Append(", "); - count++; - } - return "INSERT INTO '{0}' ({1}) VALUES ({2})".SFormat(table, sbnames.ToString(), sbvalues.ToString()); - } - - public string ReadColumn(string table, List wheres) - { - var sbwheres = new StringBuilder(); - int count = 0; - - foreach (SqlValue where in wheres) - { - sbwheres.Append(where.Name + "=" + where.Value); - if (count != wheres.Count - 1) - sbwheres.Append(" AND "); - count++; - } - - if (wheres.Count > 0) - return "SELECT * FROM {0} WHERE {1}".SFormat(table, sbwheres.ToString()); - else - return "SELECT * FROM {0}".SFormat(table); + return "ALTER TABLE {0} RENAME TO {1}".SFormat(from, to); } private static readonly Dictionary TypesAsStrings = new Dictionary @@ -189,137 +73,32 @@ namespace TShockAPI.DB return ret; throw new NotImplementedException(Enum.GetName(typeof (MySqlDbType), type)); } + + protected override string EscapeTableName(string table) + { + return table.SFormat("'{0}'", table); + } } - public class MysqlQueryCreator : IQueryBuilder + public class MysqlQueryCreator : GenericQueryCreator, IQueryBuilder { - public string CreateTable(SqlTable table) + public override string CreateTable(SqlTable table) { var columns = table.Columns.Select( c => - "{0} {1} {2} {3}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", + "{0} {1} {2} {3} {4}".SFormat(c.Name, DbTypeToString(c.Type, c.Length), c.Primary ? "PRIMARY KEY" : "", c.AutoIncrement ? "AUTO_INCREMENT" : "", c.NotNull ? "NOT NULL" : "")); var uniques = table.Columns.Where(c => c.Unique).Select(c => c.Name); - return "CREATE TABLE {0} ({1} {2})".SFormat(table.Name, string.Join(", ", columns), + return "CREATE TABLE {0} ({1} {2})".SFormat(EscapeTableName(table.Name), string.Join(", ", columns), uniques.Count() > 0 ? ", UNIQUE({0})".SFormat(string.Join(", ", uniques)) : ""); } - private static Random rand = new Random(); - - /// - /// Alter a table from source to destination - /// - /// Must have name and column names. Column types are not required - /// Must have column names and column types. - /// - public string AlterTable(SqlTable from, SqlTable to) + public override string RenameTable(string from, string to) { - var rstr = rand.NextString(20); - var alter = "RENAME TABLE {0} TO {1}_{0}".SFormat(from.Name, rstr); - var create = CreateTable(to); - //combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable. - //exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data? - var insert = "INSERT INTO {0} ({1}) SELECT {1} FROM {2}_{0}".SFormat(from.Name, - string.Join(", ", - from.Columns.Where( - c => - to.Columns.Any( - c2 => c2.Name == c.Name)).Select( - c => c.Name)), rstr); - var drop = "DROP TABLE {0}_{1}".SFormat(rstr, from.Name); - return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop); - } - - public string DeleteRow(string table, List wheres) - { - var sbwheres = new StringBuilder(); - int count = 0; - foreach (SqlValue where in wheres) - { - sbwheres.Append(where.Name + "=" + where.Value); - if (count != wheres.Count - 1) - sbwheres.Append(" AND "); - count++; - } - if (wheres.Count > 0) - return "DELETE FROM {0} WHERE {1} ".SFormat(table, sbwheres.ToString()); - else - return "DELETE FROM {0}".SFormat(table, sbwheres.ToString()); - } - - public string UpdateValue(string table, List values, List wheres) - { - var sbvalues = new StringBuilder(); - var sbwheres = new StringBuilder(); - int count = 0; - foreach (SqlValue value in values) - { - sbvalues.Append(value.Name + "=" + value.Value); - if (count != values.Count - 1) - sbvalues.Append("AND"); - count++; - } - count = 0; - foreach (SqlValue where in wheres) - { - sbwheres.Append(where.Name + "=" + where.Value); - if (count != wheres.Count - 1) - sbwheres.Append(" AND "); - count++; - } - - if (wheres.Count > 0) - return "UPDATE {0} SET {1} WHERE {2}".SFormat(table, sbvalues.ToString(), sbwheres.ToString()); - else - return "UPDATE {0} SET {1}".SFormat(table, sbvalues.ToString()); - } - - public string InsertValues(string table, List values) - { - var sbnames = new StringBuilder(); - var sbvalues = new StringBuilder(); - int count = 0; - - foreach (SqlValue name in values) - { - sbnames.Append(name.Name); - - if (count != values.Count - 1) - sbnames.Append(", "); - count++; - } - count = 0; - foreach (SqlValue value in values) - { - sbvalues.Append(value.Value.ToString()); - if (count != values.Count - 1) - sbvalues.Append(", "); - count++; - } - - return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(table, sbnames.ToString(), sbvalues.ToString()); - } - - public string ReadColumn(string table, List wheres) - { - var sbwheres = new StringBuilder(); - int count = 0; - - foreach (SqlValue where in wheres) - { - sbwheres.Append(where.Name + "=" + where.Value); - if (count != wheres.Count - 1) - sbwheres.Append(" AND "); - count++; - } - - if (wheres.Count > 0) - return "SELECT * FROM {0} WHERE {1}".SFormat(table, sbwheres.ToString()); - else - return "SELECT * FROM {0}".SFormat(table); + return "RENAME TABLE {0} TO {1}".SFormat(from, to); } private static readonly Dictionary TypesAsStrings = new Dictionary @@ -340,5 +119,95 @@ namespace TShockAPI.DB return ret + (length != null ? "({0})".SFormat((int) length) : ""); throw new NotImplementedException(Enum.GetName(typeof (MySqlDbType), type)); } + + protected override string EscapeTableName(string table) + { + return table.SFormat("`{0}`", table); + } } -} \ No newline at end of file + + public abstract class GenericQueryCreator + { + protected static Random rand = new Random(); + protected abstract string EscapeTableName(string table); + public abstract string CreateTable(SqlTable table); + public abstract string RenameTable(string from, string to); + + /// + /// Alter a table from source to destination + /// + /// Must have name and column names. Column types are not required + /// Must have column names and column types. + /// + public string AlterTable(SqlTable from, SqlTable to) + { + /* + * Any example outpuf from this looks like:- + ALTER TABLE "main"."Bans" RENAME TO "oXHFcGcd04oXHFcGcd04_Bans" + CREATE TABLE "main"."Bans" ("IP" TEXT PRIMARY KEY ,"Name" TEXT) + INSERT INTO "main"."Bans" SELECT "IP","Name" FROM "main"."oXHFcGcd04oXHFcGcd04_Bans" + DROP TABLE "main"."oXHFcGcd04oXHFcGcd04_Bans" + * + * Twitchy - Oh. I get it! + */ + var rstr = rand.NextString(20); + var escapedTable = EscapeTableName(from.Name); + var tmpTable = EscapeTableName("{0}_{1}".SFormat(rstr, from.Name)); + var alter = RenameTable(escapedTable, tmpTable); + var create = CreateTable(to); + // combine all columns in the 'from' variable excluding ones that aren't in the 'to' variable. + // exclude the ones that aren't in 'to' variable because if the column is deleted, why try to import the data? + var columns = string.Join(", ", from.Columns.Where(c => to.Columns.Any(c2 => c2.Name == c.Name)).Select(c => c.Name)); + var insert = "INSERT INTO {0} ({1}) SELECT {1} FROM {2}".SFormat(escapedTable, columns, tmpTable); + var drop = "DROP TABLE {0}".SFormat(tmpTable); + return "{0}; {1}; {2}; {3};".SFormat(alter, create, insert, drop); + } + + public string DeleteRow(string table, List wheres) + { + return "DELETE FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres)); + } + + public string UpdateValue(string table, List values, List wheres) + { + if (0 == values.Count) + throw new ArgumentException("No values supplied"); + + return "UPDATE {0} SET {1} {2}".SFormat(EscapeTableName(table), string.Join(", ", values.Select(v => v.Name + " = " + v.Value)), BuildWhere(wheres)); + } + + public string ReadColumn(string table, List wheres) + { + return "SELECT * FROM {0} {1}".SFormat(EscapeTableName(table), BuildWhere(wheres)); + } + + public string InsertValues(string table, List values) + { + var sbnames = new StringBuilder(); + var sbvalues = new StringBuilder(); + int count = 0; + foreach (SqlValue value in values) + { + sbnames.Append(value.Name); + sbvalues.Append(value.Value.ToString()); + + if (count != values.Count - 1) + { + sbnames.Append(", "); + sbvalues.Append(", "); + } + count++; + } + + return "INSERT INTO {0} ({1}) VALUES ({2})".SFormat(EscapeTableName(table), sbnames, sbvalues); + } + + protected static string BuildWhere(List wheres) + { + if (0 == wheres.Count) + return string.Empty; + + return "WHERE {0}".SFormat(string.Join(", ", wheres.Select(v => v.Name + " = " + v.Value))); + } + } +} diff --git a/TShockAPI/DB/RegionManager.cs b/TShockAPI/DB/RegionManager.cs index 25391bf2..c384cf66 100644 --- a/TShockAPI/DB/RegionManager.cs +++ b/TShockAPI/DB/RegionManager.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011 The TShock Team diff --git a/TShockAPI/DB/UserManager.cs b/TShockAPI/DB/UserManager.cs index 70423dfb..68ade122 100644 --- a/TShockAPI/DB/UserManager.cs +++ b/TShockAPI/DB/UserManager.cs @@ -18,7 +18,10 @@ along with this program. If not, see . using System; using System.Data; using System.IO; +using System.Collections.Generic; +using System.Linq; using MySql.Data.MySqlClient; +using System.Text.RegularExpressions; namespace TShockAPI.DB { @@ -50,20 +53,25 @@ namespace TShockAPI.DB /// User user public void AddUser(User user) { + if (!TShock.Groups.GroupExists(user.Group)) + throw new GroupNotExistsException(user.Group); + + int ret; try { - if (!TShock.Groups.GroupExists(user.Group)) - throw new GroupNotExistsException(user.Group); - - if ( - database.Query("INSERT INTO Users (Username, Password, UserGroup, IP) VALUES (@0, @1, @2, @3);", user.Name, - TShock.Utils.HashPassword(user.Password), user.Group, user.Address) < 1) - throw new UserExistsException(user.Name); + ret = database.Query("INSERT INTO Users (Username, Password, UserGroup, IP) VALUES (@0, @1, @2, @3);", user.Name, + TShock.Utils.HashPassword(user.Password), user.Group, user.Address); } catch (Exception ex) { - throw new UserManagerException("AddUser SQL returned an error", ex); + // Detect duplicate user using a regexp as Sqlite doesn't have well structured exceptions + if (Regex.IsMatch(ex.Message, "Username.*not unique")) + throw new UserExistsException(user.Name); + throw new UserManagerException("AddUser SQL returned an error (" + ex.Message + ")", ex); } + + if (1 > ret) + throw new UserExistsException(user.Name); } /// @@ -123,11 +131,18 @@ namespace TShockAPI.DB { try { - if (!TShock.Groups.GroupExists(group)) + Group grp = TShock.Groups.GetGroupByName(group); + if (null == grp) throw new GroupNotExistsException(group); if (database.Query("UPDATE Users SET UserGroup = @0 WHERE Username = @1;", group, user.Name) == 0) throw new UserNotExistException(user.Name); + + // Update player group reference for any logged in player + foreach (var player in TShock.Players.Where(p => null != p && p.UserAccountName == user.Name)) + { + player.Group = grp; + } } catch (Exception ex) { @@ -239,37 +254,83 @@ namespace TShockAPI.DB public User GetUser(User user) { + bool multiple = false; + string query; + string type; + object arg; + if (0 != user.ID) + { + query = "SELECT * FROM Users WHERE ID=@0"; + arg = user.ID; + type = "id"; + } + else if (string.IsNullOrEmpty(user.Address)) + { + query = "SELECT * FROM Users WHERE Username=@0"; + arg = user.Name; + type = "name"; + } + else + { + query = "SELECT * FROM Users WHERE IP=@0"; + arg = user.Address; + type = "ip"; + } + try { - QueryResult result; - if (string.IsNullOrEmpty(user.Address)) + using (var result = database.QueryReader(query, arg)) { - result = database.QueryReader("SELECT * FROM Users WHERE Username=@0", user.Name); - } - else - { - result = database.QueryReader("SELECT * FROM Users WHERE IP=@0", user.Address); - } - - using (var reader = result) - { - if (reader.Read()) + if (result.Read()) { - user.ID = reader.Get("ID"); - user.Group = reader.Get("Usergroup"); - user.Password = reader.Get("Password"); - user.Name = reader.Get("Username"); - user.Address = reader.Get("IP"); - return user; + user = LoadUserFromResult(user, result); + // Check for multiple matches + if (!result.Read()) + return user; + multiple = true; } } } catch (Exception ex) { - throw new UserManagerException("GetUserID SQL returned an error", ex); + throw new UserManagerException("GetUser SQL returned an error (" + ex.Message + ")", ex); } + if (multiple) + throw new UserManagerException(String.Format("Multiple users found for {0} '{1}'", type, arg)); + throw new UserNotExistException(string.IsNullOrEmpty(user.Address) ? user.Name : user.Address); } + + public List GetUsers() + { + try + { + List users = new List(); + using (var reader = database.QueryReader("SELECT * FROM Users")) + { + while (reader.Read()) + { + users.Add(LoadUserFromResult(new User(), reader)); + } + return users; + } + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + return null; + } + + private User LoadUserFromResult(User user, QueryResult result) + { + user.ID = result.Get("ID"); + user.Group = result.Get("Usergroup"); + user.Password = result.Get("Password"); + user.Name = result.Get("Username"); + user.Address = result.Get("IP"); + return user; + } } public class User diff --git a/TShockAPI/FileTools.cs b/TShockAPI/FileTools.cs index 8f3c93d4..ac4dfd31 100644 --- a/TShockAPI/FileTools.cs +++ b/TShockAPI/FileTools.cs @@ -1,119 +1,119 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011 The TShock Team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -using System; -using System.IO; - -namespace TShockAPI -{ - public class FileTools - { - internal static string RulesPath - { - get { return Path.Combine(TShock.SavePath, "rules.txt"); } - } - - internal static string MotdPath - { - get { return Path.Combine(TShock.SavePath, "motd.txt"); } - } - - internal static string WhitelistPath - { - get { return Path.Combine(TShock.SavePath, "whitelist.txt"); } - } - - internal static string RememberedPosPath - { - get { return Path.Combine(TShock.SavePath, "oldpos.xml"); } - } - - internal static string ConfigPath - { - get { return Path.Combine(TShock.SavePath, "config.json"); } - } - - public static void CreateFile(string file) - { - File.Create(file).Close(); - } - - public static void CreateIfNot(string file, string data = "") - { - if (!File.Exists(file)) - { - File.WriteAllText(file, data); - } - } - - /// - /// Sets up the configuration file for all variables, and creates any missing files. - /// - public static void SetupConfig() - { - if (!Directory.Exists(TShock.SavePath)) - { - Directory.CreateDirectory(TShock.SavePath); - } - - CreateIfNot(RulesPath, "Respect the admins!\nDon't use TNT!"); - CreateIfNot(MotdPath, - "This server is running TShock for Terraria.\n Type /help for a list of commands.\n%255,000,000%Current map: %map%\nCurrent players: %players%"); - CreateIfNot(WhitelistPath); - if (File.Exists(ConfigPath)) - { - TShock.Config = ConfigFile.Read(ConfigPath); - // Add all the missing config properties in the json file - } - TShock.Config.Write(ConfigPath); - - } - - /// - /// Tells if a user is on the whitelist - /// - /// string ip of the user - /// true/false - public static bool OnWhitelist(string ip) - { - if (!TShock.Config.EnableWhitelist) - { - return true; - } - CreateIfNot(WhitelistPath, "127.0.0.1"); - using (var tr = new StreamReader(WhitelistPath)) - { - string whitelist = tr.ReadToEnd(); - ip = TShock.Utils.GetRealIP(ip); - bool contains = whitelist.Contains(ip); - if (!contains) - { - foreach (var line in whitelist.Split(Environment.NewLine.ToCharArray())) - { - if (string.IsNullOrWhiteSpace(line)) - continue; - contains = TShock.Utils.GetIPv4Address(line).Equals(ip); - if (contains) - return true; - } - return false; - } - return true; - } - } - } +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.IO; + +namespace TShockAPI +{ + public class FileTools + { + internal static string RulesPath + { + get { return Path.Combine(TShock.SavePath, "rules.txt"); } + } + + internal static string MotdPath + { + get { return Path.Combine(TShock.SavePath, "motd.txt"); } + } + + internal static string WhitelistPath + { + get { return Path.Combine(TShock.SavePath, "whitelist.txt"); } + } + + internal static string RememberedPosPath + { + get { return Path.Combine(TShock.SavePath, "oldpos.xml"); } + } + + internal static string ConfigPath + { + get { return Path.Combine(TShock.SavePath, "config.json"); } + } + + public static void CreateFile(string file) + { + File.Create(file).Close(); + } + + public static void CreateIfNot(string file, string data = "") + { + if (!File.Exists(file)) + { + File.WriteAllText(file, data); + } + } + + /// + /// Sets up the configuration file for all variables, and creates any missing files. + /// + public static void SetupConfig() + { + if (!Directory.Exists(TShock.SavePath)) + { + Directory.CreateDirectory(TShock.SavePath); + } + + CreateIfNot(RulesPath, "Respect the admins!\nDon't use TNT!"); + CreateIfNot(MotdPath, + "This server is running TShock for Terraria.\n Type /help for a list of commands.\n%255,000,000%Current map: %map%\nCurrent players: %players%"); + CreateIfNot(WhitelistPath); + if (File.Exists(ConfigPath)) + { + TShock.Config = ConfigFile.Read(ConfigPath); + // Add all the missing config properties in the json file + } + TShock.Config.Write(ConfigPath); + + } + + /// + /// Tells if a user is on the whitelist + /// + /// string ip of the user + /// true/false + public static bool OnWhitelist(string ip) + { + if (!TShock.Config.EnableWhitelist) + { + return true; + } + CreateIfNot(WhitelistPath, "127.0.0.1"); + using (var tr = new StreamReader(WhitelistPath)) + { + string whitelist = tr.ReadToEnd(); + ip = TShock.Utils.GetRealIP(ip); + bool contains = whitelist.Contains(ip); + if (!contains) + { + foreach (var line in whitelist.Split(Environment.NewLine.ToCharArray())) + { + if (string.IsNullOrWhiteSpace(line)) + continue; + contains = TShock.Utils.GetIPv4Address(line).Equals(ip); + if (contains) + return true; + } + return false; + } + return true; + } + } + } } \ No newline at end of file diff --git a/TShockAPI/GetDataHandlers.cs b/TShockAPI/GetDataHandlers.cs index 96b9b19d..bb595a5c 100644 --- a/TShockAPI/GetDataHandlers.cs +++ b/TShockAPI/GetDataHandlers.cs @@ -1,2704 +1,2756 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011 The TShock Team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.IO.Streams; -using System.Linq; -using System.Text; -using Terraria; -using TShockAPI.Net; - -namespace TShockAPI -{ - public delegate bool GetDataHandlerDelegate(GetDataHandlerArgs args); - - public class GetDataHandlerArgs : EventArgs - { - public TSPlayer Player { get; private set; } - public MemoryStream Data { get; private set; } - - public Player TPlayer - { - get { return Player.TPlayer; } - } - - public GetDataHandlerArgs(TSPlayer player, MemoryStream data) - { - Player = player; - Data = data; - } - } - - public static class GetDataHandlers - { - private static Dictionary GetDataHandlerDelegates; - public static int[] WhitelistBuffMaxTime; - #region Events - - /// - /// Used when a TileEdit event is called. - /// - public class TileEditEventArgs : HandledEventArgs - { - /// - /// The TSPlayer who made the tile edit - /// - public TSPlayer Player { get; set; } - - /// - /// The tile coordinate on the X plane - /// - public int X { get; set; } - - /// - /// The tile coordinate on the Y plane - /// - public int Y { get; set; } - - /// - /// The Tile ID being edited. - /// - public byte Type { get; set; } - /// - /// The EditType. - /// (KillTile = 0, PlaceTile = 1, KillWall = 2, PlaceWall = 3, KillTileNoItem = 4, PlaceWire = 5, KillWire = 6) - /// - public byte EditType { get; set; } - } - - /// - /// TileEdit - called when a tile is placed or destroyed - /// - public static HandlerList TileEdit; - private static bool OnTileEdit(TSPlayer ply, int x, int y, byte type, byte editType) - { - if (TileEdit == null) - return false; - - var args = new TileEditEventArgs - { - Player = ply, - X = x, - Y = y, - Type = type, - EditType = editType - }; - TileEdit.Invoke(null, args); - return args.Handled; - } - /// - /// For use in a TogglePvp event - /// - public class TogglePvpEventArgs : HandledEventArgs - { - /// - /// The Terraria player ID of the player - /// - public byte PlayerId { get; set; } - /// - /// Enable/disable pvp? - /// - public bool Pvp { get; set; } - } - /// - /// TogglePvp - called when a player toggles pvp - /// - public static HandlerList TogglePvp; - private static bool OnPvpToggled(byte _id, bool _pvp) - { - if (TogglePvp == null) - return false; - - var args = new TogglePvpEventArgs - { - PlayerId = _id, - Pvp = _pvp, - }; - TogglePvp.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlayerSlot event - /// - public class PlayerSlotEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID - /// - public byte PlayerId { get; set; } - /// - /// The slot edited - /// - public byte Slot { get; set; } - /// - /// The stack edited - /// - public byte Stack { get; set; } - /// - /// The item prefix - /// - public byte Prefix { get; set; } - /// - /// Item type - /// - public short Type { get; set; } - } - /// - /// PlayerSlot - called at a PlayerSlot event - /// - public static HandlerList PlayerSlot; - private static bool OnPlayerSlot(byte _plr, byte _slot, byte _stack, byte _prefix, short _type) - { - if (PlayerSlot == null) - return false; - - var args = new PlayerSlotEventArgs - { - PlayerId = _plr, - Slot = _slot, - Stack = _stack, - Prefix = _prefix, - Type = _type - }; - PlayerSlot.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlayerHP event - /// - public class PlayerHPEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte PlayerId { get; set; } - /// - /// Current HP - /// - public short Current { get; set; } - /// - /// Maximum HP - /// - public short Max { get; set; } - } - /// - /// PlayerHP - called at a PlayerHP event - /// - public static HandlerList PlayerHP; - - private static bool OnPlayerHP(byte _plr, short _cur, short _max) - { - if (PlayerHP == null) - return false; - - var args = new PlayerHPEventArgs - { - PlayerId = _plr, - Current = _cur, - Max = _max, - }; - PlayerHP.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlayerMana event - /// - public class PlayerManaEventArgs : HandledEventArgs - { - public byte PlayerId { get; set; } - public short Current { get; set; } - public short Max { get; set; } - } - /// - /// PlayerMana - called at a PlayerMana event - /// - public static HandlerList PlayerMana; - - private static bool OnPlayerMana(byte _plr, short _cur, short _max) - { - if (PlayerMana == null) - return false; - - var args = new PlayerManaEventArgs - { - PlayerId = _plr, - Current = _cur, - Max = _max, - }; - PlayerMana.Invoke(null, args); - return args.Handled; - } - - public class PlayerInfoEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte PlayerId { get; set; } - /// - /// Hair color - /// - public byte Hair { get; set; } - /// - /// Gender (male = true) - /// - public bool Male { get; set; } - /// - /// Character difficulty - /// - public byte Difficulty { get; set; } - /// - /// Player/character name - /// - public string Name { get; set; } - } - /// - /// PlayerInfo - called at a PlayerInfo event - /// If this is cancelled, the server will ForceKick the player. If this should be changed in the future, let someone know. - /// - public static HandlerList PlayerInfo; - - private static bool OnPlayerInfo(byte _plrid, byte _hair, bool _male, byte _difficulty, string _name) - { - if (PlayerInfo == null) - return false; - - var args = new PlayerInfoEventArgs - { - PlayerId = _plrid, - Hair = _hair, - Male = _male, - Difficulty = _difficulty, - Name = _name, - }; - PlayerInfo.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a TileKill event - /// - public class TileKillEventArgs : HandledEventArgs - { - /// - /// The X coordinate that is being killed - /// - public int TileX { get; set; } - /// - /// The Y coordinate that is being killed - /// - public int TileY { get; set; } - } - /// - /// TileKill - When a tile is removed fromt he world - /// - public static HandlerList TileKill; - - private static bool OnTileKill(int tilex, int tiley) - { - if (TileKill == null) - return false; - - var args = new TileKillEventArgs - { - TileX = tilex, - TileY = tiley, - }; - TileKill.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a KillMe event - /// - public class KillMeEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte PlayerId { get; set; } - /// - /// The direction the damage is coming from (?) - /// - public byte Direction { get; set; } - /// - /// Amount of damage delt - /// - public short Damage { get; set; } - /// - /// Player's current pvp setting - /// - public bool Pvp { get; set; } - } - /// - /// KillMe - Terraria's crappy way of handling damage from players - /// - public static HandlerList KillMe; - - private static bool OnKillMe(byte plr, byte direction, short damage, bool pvp) - { - if (KillMe == null) - return false; - - var args = new KillMeEventArgs - { - PlayerId = plr, - Direction = direction, - Damage = damage, - Pvp = pvp, - }; - KillMe.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlayerUpdate event - /// - public class PlayerUpdateEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte PlayerId { get; set; } - /// - /// ??? - /// - public byte Control { get; set; } - /// - /// Current item? - /// - public byte Item { get; set; } - /// - /// Position of the player - /// - public Vector2 Position { get; set; } - /// - /// Velocity of the player - /// - public Vector2 Velocity { get; set; } - } - /// - /// PlayerUpdate - When the player sends it's updated information to the server - /// - public static HandlerList PlayerUpdate; - - private static bool OnPlayerUpdate(byte player, byte control, byte item, Vector2 position, Vector2 velocity) - { - if (PlayerUpdate == null) - return false; - - var args = new PlayerUpdateEventArgs - { - PlayerId = player, - Control = control, - Item = item, - Position = position, - Velocity = velocity, - }; - PlayerUpdate.Invoke(null, args); - return args.Handled; - } - public static bool TSCheckNoclip(Vector2 Position, int Width, int Height) - { - int num = (int)(Position.X / 16f) - 1; - int num2 = (int)((Position.X + (float)Width) / 16f) + 2; - int num3 = (int)(Position.Y / 16f) - 1; - int num4 = (int)((Position.Y + (float)Height) / 16f) + 2; - if (num < 0) - { - num = 0; - } - if (num2 > Main.maxTilesX) - { - num2 = Main.maxTilesX; - } - if (num3 < 0) - { - num3 = 0; - } - if (num4 > Main.maxTilesY) - { - num4 = Main.maxTilesY; - } - for (int i = num; i < num2; i++) - { - for (int j = num3; j < num4; j++) - { - if (Main.tile[i, j] != null && Main.tile[i, j].active && Main.tileSolid[(int)Main.tile[i, j].type] && !Main.tileSolidTop[(int)Main.tile[i, j].type] &&(((int)Main.tile[i,j].type !=53) && ((int)Main.tile[i,j].type !=112) && ((int)Main.tile[i,j].type !=116) && ((int)Main.tile[i,j].type !=123))) - { - Vector2 vector; - vector.X = (float)(i * 16); - vector.Y = (float)(j * 16); - if (Position.X + (float)Width > vector.X && Position.X < vector.X + 16f && Position.Y + (float)Height > vector.Y && Position.Y < vector.Y + 16f) - { - return true; - } - } - } - } - return false; - } - - /// - /// For use in a SendTileSquare event - /// - public class SendTileSquareEventArgs : HandledEventArgs - { - /// - /// Size of the area - /// - public short Size { get; set; } - /// - /// A corner of the section - /// - public int TileX { get; set; } - /// - /// A corner of the section - /// - public int TileY { get; set; } - } - /// - /// SendTileSquare - When the player sends a tile square - /// - public static HandlerList SendTileSquare; - - private static bool OnSendTileSquare(short size, int tilex, int tiley) - { - if (SendTileSquare == null) - return false; - - var args = new SendTileSquareEventArgs - { - Size = size, - TileX = tilex, - TileY = tiley, - }; - SendTileSquare.Invoke(null, args); - return args.Handled; - } - /// - /// For use in a NewProjectile event - /// - public class NewProjectileEventArgs : HandledEventArgs - { - /// - /// ??? - /// - public short Identity { get; set; } - /// - /// Location of the projectile - /// - public Vector2 Position { get; set; } - /// - /// Velocity of the projectile - /// - public Vector2 Velocity { get; set; } - /// - /// Knockback - /// - public float Knockback { get; set; } - /// - /// Damage from the projectile - /// - public short Damage { get; set; } - /// - /// Terraria playerID owner of the projectile - /// - public byte Owner { get; set; } - /// - /// Type of projectile - /// - public byte Type { get; set; } - /// - /// ??? - /// - public int Index { get; set; } - } - /// - /// NewProjectile - Called when a client creates a new projectile - /// - public static HandlerList NewProjectile; - - private static bool OnNewProjectile(short ident, Vector2 pos, Vector2 vel, float knockback, short dmg, byte owner, byte type, int index) - { - if (NewProjectile == null) - return false; - - var args = new NewProjectileEventArgs - { - Identity = ident, - Position = pos, - Velocity = vel, - Knockback = knockback, - Damage = dmg, - Owner = owner, - Type = type, - Index = index, - }; - NewProjectile.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a LiquidSet event - /// - public class LiquidSetEventArgs : HandledEventArgs - { - /// - /// X location of the tile - /// - public int TileX { get; set; } - /// - /// Y location of the tile - /// - public int TileY { get; set; } - /// - /// ??? - /// - public byte Liquid { get; set;} - /// - /// True if lava - /// - public bool Lava { get; set; } - } - /// - /// LiquidSet - When ever a liquid is set - /// - public static HandlerList LiquidSet; - - private static bool OnLiquidSet(int tilex, int tiley, byte liquid, bool lava) - { - if (LiquidSet == null) - return false; - - var args = new LiquidSetEventArgs - { - TileX = tilex, - TileY = tiley, - Liquid = liquid, - Lava = lava, - }; - LiquidSet.Invoke(null, args); - return args.Handled; - } - /// - /// For use in a PlayerSpawn event - /// - public class SpawnEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte Player { get; set; } - /// - /// X location of the player's spawn - /// - public int SpawnX { get; set; } - /// - /// Y location of the player's spawn - /// - public int SpawnY { get; set; } - } - /// - /// PlayerSpawn - When a player spawns - /// - public static HandlerList PlayerSpawn; - - private static bool OnPlayerSpawn(byte player, int spawnX, int spawnY) - { - if (PlayerSpawn == null) - return false; - - var args = new SpawnEventArgs - { - Player = player, - SpawnX = spawnX, - SpawnY = spawnY, - }; - PlayerSpawn.Invoke(null, args); - return args.Handled; - } - /// - /// For use with a ChestOpen event - /// - public class ChestOpenEventArgs : HandledEventArgs - { - /// - /// X location of said chest - /// - public int X { get; set; } - /// - /// Y location of said chest - /// - public int Y { get; set; } - } - /// - /// ChestOpen - Called when any chest is opened - /// - public static HandlerList ChestOpen; - - private static bool OnChestOpen(int x, int y) - { - if (ChestOpen == null) - return false; - - var args = new ChestOpenEventArgs - { - X = x, - Y = y, - }; - ChestOpen.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a ChestItemChange event - /// - public class ChestItemEventArgs : HandledEventArgs - { - /// - /// ChestID - /// - public short ID { get; set; } - /// - /// Slot of the item - /// - public byte Slot { get; set; } - /// - /// How many? - /// - public byte Stacks { get; set; } - /// - /// Item prefix - /// - public byte Prefix { get; set; } - /// - /// Item type - /// - public short Type { get; set; } - } - /// - /// ChestItemChange - Called when an item in a chest changes - /// - public static HandlerList ChestItemChange; - - private static bool OnChestItemChange(short id, byte slot, byte stacks, byte prefix, short type) - { - if (ChestItemChange == null) - return false; - - var args = new ChestItemEventArgs - { - ID = id, - Slot = slot, - Stacks = stacks, - Prefix = prefix, - Type = type, - }; - ChestItemChange.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a Sign event - /// - public class SignEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public short ID { get; set; } - /// - /// X location of the sign - /// - public int X { get; set; } - /// - /// Y location of the sign - /// - public int Y { get; set; } - } - /// - /// Sign - Called when a sign is changed - /// - public static HandlerList Sign; - - private static bool OnSignEvent(short id, int x, int y) - { - if (Sign == null) - return false; - - var args = new SignEventArgs - { - ID = id, - X = x, - Y = y, - }; - Sign.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a NPCHome event - /// - public class NPCHomeChangeEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public short ID { get; set; } - /// - /// X location of the NPC home change - /// - public short X { get; set; } - /// - /// Y location of the NPC home change - /// - public short Y { get; set; } - /// - /// ByteBool homeless - /// - public byte Homeless { get; set; } - } - /// - /// NPCHome - Called when an NPC's home is changed - /// - public static HandlerList NPCHome; - - private static bool OnUpdateNPCHome(short id, short x, short y, byte homeless) - { - if (NPCHome == null) - return false; - - var args = new NPCHomeChangeEventArgs - { - ID = id, - X = x, - Y = y, - Homeless = homeless, - }; - NPCHome.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlayerBuff event - /// - public class PlayerBuffEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte ID { get; set; } - /// - /// Buff Type - /// - public byte Type { get; set; } - /// - /// Time the buff lasts - /// - public short Time { get; set; } - } - /// - /// PlayerBuff - Called when a player is buffed - /// - public static HandlerList PlayerBuff; - - private static bool OnPlayerBuff(byte id, byte type, short time) - { - if (PlayerBuff == null) - return false; - - var args = new PlayerBuffEventArgs - { - ID = id, - Type = type, - Time = time, - }; - PlayerBuff.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in an ItemDrop event - /// - public class ItemDropEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public short ID { get; set; } - /// - /// Position of the item - /// - public Vector2 Position { get; set; } - /// - /// Velocity at which the item is deployed - /// - public Vector2 Velocity { get; set; } - /// - /// Stacks - /// - public byte Stacks { get; set; } - /// - /// Prefix of the item - /// - public byte Prefix { get; set; } - /// - /// Item type - /// - public short Type { get; set; } - } - /// - /// ItemDrop - Called when an item is dropped - /// - public static HandlerList ItemDrop; - - private static bool OnItemDrop(short id, Vector2 pos, Vector2 vel, byte stacks, byte prefix, short type) - { - if (ItemDrop == null) - return false; - - var args = new ItemDropEventArgs - { - ID = id, - Position = pos, - Velocity = vel, - Stacks = stacks, - Prefix = prefix, - Type = type, - }; - ItemDrop.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlayerDamage event - /// - public class PlayerDamageEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte ID { get; set; } - /// - /// The direction the damage is occuring from - /// - public byte Direction { get; set; } - /// - /// Amount of damage - /// - public short Damage { get; set; } - /// - /// If the player has PVP on - /// - public byte PVP { get; set; } - /// - /// Is the damage critical? - /// - public byte Critical { get; set; } - } - /// - /// PlayerDamage - Called when a player is damaged - /// - public static HandlerList PlayerDamage; - - private static bool OnPlayerDamage(byte id, byte dir, short dmg, byte pvp, byte crit) - { - if (PlayerDamage == null) - return false; - - var args = new PlayerDamageEventArgs - { - ID = id, - Direction = dir, - Damage = dmg, - PVP = pvp, - Critical = crit, - }; - PlayerDamage.Invoke(null, args); - return args.Handled; - } - - /// - /// For use with a NPCStrike event - /// - public class NPCStrikeEventArgs : HandledEventArgs - { - /// - /// ??? - /// - public byte ID { get; set; } - /// - /// Direction the damage occurred from - /// - public byte Direction { get; set; } - /// - /// Amount of damage - /// - public short Damage { get; set; } - /// - /// Is PVP enabled...? - /// - public byte PVP { get; set; } - /// - /// Critical? - /// - public byte Critical { get; set; } - } - /// - /// NPCStrike - Called when an NPC is attacked - /// - public static HandlerList NPCStrike; - - private static bool OnNPCStrike(byte id, byte dir, short dmg, byte pvp, byte crit) - { - if (NPCStrike == null) - return false; - - var args = new NPCStrikeEventArgs - { - ID = id, - Direction = dir, - Damage = dmg, - PVP = pvp, - Critical = crit, - }; - NPCStrike.Invoke(null, args); - return args.Handled; - } - - /// - /// For use with a NPCSpecial event - /// - public class NPCSpecialEventArgs : HandledEventArgs - { - /// - /// ??? - /// - public byte ID { get; set; } - /// - /// Type...? - /// - public byte Type { get; set; } - } - /// - /// NPCSpecial - Called at some point - /// - public static HandlerList NPCSpecial; - - private static bool OnNPCSpecial(byte id, byte type) - { - if (NPCSpecial == null) - return false; - - var args = new NPCSpecialEventArgs - { - ID = id, - Type = type, - }; - NPCSpecial.Invoke(null, args); - return args.Handled; - } - - /// - /// For use with a PlayerAnimation event - /// - public class PlayerAnimationEventArgs : HandledEventArgs - { - } - - /// - /// PlayerAnimation - Called when a player animates - /// - public static HandlerList PlayerAnimation; - - private static bool OnPlayerAnimation() - { - if (PlayerAnimation == null) - return false; - - var args = new PlayerAnimationEventArgs {}; - PlayerAnimation.Invoke(null, args); - return args.Handled; - } - - /// - /// For use in a PlayerBuffUpdate event - /// - public class PlayerBuffUpdateEventArgs : HandledEventArgs - { - /// - /// The Terraria playerID of the player - /// - public byte ID { get; set; } - } - /// - /// PlayerBuffUpdate - Called when a player updates buffs - /// - public static HandlerList PlayerBuffUpdate; - - private static bool OnPlayerBuffUpdate(byte id) - { - if (PlayerBuffUpdate == null) - return false; - - var args = new PlayerBuffUpdateEventArgs - { - ID = id, - }; - PlayerBuffUpdate.Invoke(null, args); - return args.Handled; - } - - #endregion - public static void InitGetDataHandler() - { - #region Blacklists - - WhitelistBuffMaxTime = new int[Main.maxBuffs]; - WhitelistBuffMaxTime[20] = 600; - WhitelistBuffMaxTime[0x18] = 1200; - WhitelistBuffMaxTime[0x1f] = 120; - WhitelistBuffMaxTime[0x27] = 420; - - #endregion Blacklists - - GetDataHandlerDelegates = new Dictionary - { - {PacketTypes.PlayerInfo, HandlePlayerInfo}, - {PacketTypes.PlayerUpdate, HandlePlayerUpdate}, - {PacketTypes.Tile, HandleTile}, - {PacketTypes.TileSendSquare, HandleSendTileSquare}, - {PacketTypes.ProjectileNew, HandleProjectileNew}, - {PacketTypes.TogglePvp, HandleTogglePvp}, - {PacketTypes.TileKill, HandleTileKill}, - {PacketTypes.PlayerKillMe, HandlePlayerKillMe}, - {PacketTypes.LiquidSet, HandleLiquidSet}, - {PacketTypes.PlayerSpawn, HandleSpawn}, - {PacketTypes.ChestGetContents, HandleChestOpen}, - {PacketTypes.ChestItem, HandleChestItem}, - {PacketTypes.SignNew, HandleSign}, - {PacketTypes.PlayerSlot, HandlePlayerSlot}, - {PacketTypes.TileGetSection, HandleGetSection}, - {PacketTypes.UpdateNPCHome, UpdateNPCHome}, - {PacketTypes.PlayerAddBuff, HandlePlayerBuff}, - {PacketTypes.ItemDrop, HandleItemDrop}, - {PacketTypes.PlayerHp, HandlePlayerHp}, - {PacketTypes.PlayerMana, HandlePlayerMana}, - {PacketTypes.PlayerDamage, HandlePlayerDamage}, - {PacketTypes.NpcStrike, HandleNpcStrike}, - {PacketTypes.NpcSpecial, HandleSpecial}, - {PacketTypes.PlayerAnimation, HandlePlayerAnimation}, - {PacketTypes.PlayerBuff, HandlePlayerBuffUpdate}, - {PacketTypes.PasswordSend, HandlePassword}, - {PacketTypes.ContinueConnecting2, HandleConnecting}, - {PacketTypes.ProjectileDestroy, HandleProjectileKill}, - {PacketTypes.SpawnBossorInvasion, HandleSpawnBoss} - }; - } - - public static bool HandlerGetData(PacketTypes type, TSPlayer player, MemoryStream data) - { - GetDataHandlerDelegate handler; - if (GetDataHandlerDelegates.TryGetValue(type, out handler)) - { - try - { - return handler(new GetDataHandlerArgs(player, data)); - } - catch (Exception ex) - { - Log.Error(ex.ToString()); - } - } - return false; - } - - private static bool HandlePlayerSlot(GetDataHandlerArgs args) - { - byte plr = args.Data.ReadInt8(); - byte slot = args.Data.ReadInt8(); - byte stack = args.Data.ReadInt8(); - byte prefix = args.Data.ReadInt8(); - short type = args.Data.ReadInt16(); - - if (OnPlayerSlot(plr, slot, stack, prefix, type)) - return true; - - if (plr != args.Player.Index) - { - return true; - } - - if (slot < 0 || slot > NetItem.maxNetInventory) - { - return true; - } - - var item = new Item(); - item.netDefaults(type); - item.Prefix(prefix); - - if (args.Player.IsLoggedIn) - { - args.Player.PlayerData.StoreSlot(slot, type, prefix, stack); - } - - return false; - } - - public static bool HandlePlayerHp(GetDataHandlerArgs args) - { - var plr = args.Data.ReadInt8(); - var cur = args.Data.ReadInt16(); - var max = args.Data.ReadInt16(); - - if (OnPlayerHP(plr, cur, max)) - return true; - - if (args.Player.FirstMaxHP == 0) - args.Player.FirstMaxHP = max; - - if (max > 400 && max > args.Player.FirstMaxHP) - { - TShock.Utils.ForceKick(args.Player, "Hacked Client Detected."); - return false; - } - - if (args.Player.IsLoggedIn) - { - args.Player.PlayerData.maxHealth = max; - } - - return false; - } - - private static bool HandlePlayerMana(GetDataHandlerArgs args) - { - var plr = args.Data.ReadInt8(); - var cur = args.Data.ReadInt16(); - var max = args.Data.ReadInt16(); - - if (OnPlayerMana(plr, cur, max)) - return true; - - if (args.Player.FirstMaxMP == 0) - args.Player.FirstMaxMP = max; - - if (max > 400 && max > args.Player.FirstMaxMP) - { - TShock.Utils.ForceKick(args.Player, "Hacked Client Detected."); - return false; - } - - return false; - } - - private static bool HandlePlayerInfo(GetDataHandlerArgs args) - { - var playerid = args.Data.ReadInt8(); - var hair = args.Data.ReadInt8(); - var male = args.Data.ReadBoolean(); - args.Data.Position += 21; - var difficulty = args.Data.ReadInt8(); - string name = Encoding.UTF8.GetString(args.Data.ReadBytes((int) (args.Data.Length - args.Data.Position - 1))); - - if (OnPlayerInfo(playerid, hair, male, difficulty, name)) - { - TShock.Utils.ForceKick(args.Player, "A plugin cancelled the event."); - return true; - } - - /*if (!TShock.Utils.ValidString(name)) - { - TShock.Utils.ForceKick(args.Player, "Unprintable character in name"); - return true; - }*/ - if (name.Trim().Length == 0) - { - TShock.Utils.ForceKick(args.Player, "Empty Name."); - return true; - } - var ban = TShock.Bans.GetBanByName(name); - if (ban != null) - { - TShock.Utils.ForceKick(args.Player, string.Format("You are banned: {0}", ban.Reason)); - return true; - } - if (args.Player.ReceivedInfo) - { - return true; - } - if (TShock.Config.MediumcoreOnly && difficulty < 1) - { - TShock.Utils.ForceKick(args.Player, "Server is set to mediumcore and above characters only!"); - return true; - } - if (TShock.Config.HardcoreOnly && difficulty < 2) - { - TShock.Utils.ForceKick(args.Player, "Server is set to hardcore characters only!"); - return true; - } - args.Player.Difficulty = difficulty; - args.TPlayer.name = name; - args.Player.ReceivedInfo = true; - - return false; - } - - private static bool HandleConnecting(GetDataHandlerArgs args) - { - var user = TShock.Users.GetUserByName(args.Player.Name); - if (user != null && !TShock.Config.DisableLoginBeforeJoin) - { - args.Player.RequiresPassword = true; - NetMessage.SendData((int) PacketTypes.PasswordRequired, args.Player.Index); - return true; - } - else if (!string.IsNullOrEmpty(TShock.Config.ServerPassword)) - { - args.Player.RequiresPassword = true; - NetMessage.SendData((int) PacketTypes.PasswordRequired, args.Player.Index); - return true; - } - - if (args.Player.State == 1) - args.Player.State = 2; - NetMessage.SendData((int) PacketTypes.WorldInfo, args.Player.Index); - return true; - } - - private static bool HandlePassword(GetDataHandlerArgs args) - { - if (!args.Player.RequiresPassword) - return true; - - string password = Encoding.UTF8.GetString(args.Data.ReadBytes((int) (args.Data.Length - args.Data.Position - 1))); - var user = TShock.Users.GetUserByName(args.Player.Name); - if (user != null) - { - string encrPass = TShock.Utils.HashPassword(password); - if (user.Password.ToUpper() == encrPass.ToUpper()) - { - args.Player.RequiresPassword = false; - args.Player.PlayerData = TShock.InventoryDB.GetPlayerData(args.Player, TShock.Users.GetUserID(args.Player.Name)); - - if (args.Player.State == 1) - args.Player.State = 2; - NetMessage.SendData((int) PacketTypes.WorldInfo, args.Player.Index); - - var group = TShock.Utils.GetGroup(user.Group); - - if (TShock.Config.ServerSideInventory) - { - if (group.HasPermission(Permissions.bypassinventorychecks)) - { - args.Player.IgnoreActionsForClearingTrashCan = false; - } - else if (!TShock.CheckInventory(args.Player)) - { - args.Player.SendMessage("Login Failed, Please fix the above errors then /login again.", Color.Cyan); - args.Player.IgnoreActionsForClearingTrashCan = true; - return true; - } - } - - if (group.HasPermission(Permissions.ignorestackhackdetection)) - args.Player.IgnoreActionsForCheating = "none"; - - if (group.HasPermission(Permissions.usebanneditem)) - args.Player.IgnoreActionsForDisabledArmor = "none"; - - args.Player.Group = group; - args.Player.UserAccountName = args.Player.Name; - args.Player.UserID = TShock.Users.GetUserID(args.Player.UserAccountName); - args.Player.IsLoggedIn = true; - args.Player.IgnoreActionsForInventory = "none"; - - if (!args.Player.IgnoreActionsForClearingTrashCan) - { - args.Player.PlayerData.CopyInventory(args.Player); - TShock.InventoryDB.InsertPlayerData(args.Player); - } - args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen); - Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + args.Player.Name); - return true; - } - TShock.Utils.ForceKick(args.Player, "Invalid user account password.", true); - return true; - } - if (!string.IsNullOrEmpty(TShock.Config.ServerPassword)) - { - if (TShock.Config.ServerPassword == password) - { - args.Player.RequiresPassword = false; - if (args.Player.State == 1) - args.Player.State = 2; - NetMessage.SendData((int) PacketTypes.WorldInfo, args.Player.Index); - return true; - } - TShock.Utils.ForceKick(args.Player, "Incorrect Server Password"); - return true; - } - - TShock.Utils.ForceKick(args.Player, "Bad Password Attempt"); - return true; - } - - private static bool HandleGetSection(GetDataHandlerArgs args) - { - if (args.Player.RequestedSection) - return true; - - args.Player.RequestedSection = true; - if (TShock.HackedHealth(args.Player) && !args.Player.Group.HasPermission(Permissions.ignorestathackdetection)) - { - TShock.Utils.ForceKick(args.Player, "You have Hacked Health/Mana, Please use a different character."); - } - - if (!args.Player.Group.HasPermission(Permissions.ignorestackhackdetection)) - { - TShock.HackedInventory(args.Player); - } - - if (TShock.Utils.ActivePlayers() + 1 > TShock.Config.MaxSlots && - !args.Player.Group.HasPermission(Permissions.reservedslot)) - { - args.Player.SilentKickInProgress = true; - TShock.Utils.ForceKick(args.Player, TShock.Config.ServerFullReason); - return true; - } - - NetMessage.SendData((int) PacketTypes.TimeSet, -1, -1, "", 0, 0, Main.sunModY, Main.moonModY); - - if (TShock.Config.EnableGeoIP && TShock.Geo != null) - { - Log.Info(string.Format("{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})", args.Player.Name, args.Player.IP, - args.Player.Group.Name, args.Player.Country, TShock.Utils.ActivePlayers(), - TShock.Config.MaxSlots)); - TShock.Utils.Broadcast(args.Player.Name + " has joined from the " + args.Player.Country, Color.Yellow); - } - else - { - Log.Info(string.Format("{0} ({1}) from '{2}' group joined. ({3}/{4})", args.Player.Name, args.Player.IP, - args.Player.Group.Name, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots)); - TShock.Utils.Broadcast(args.Player.Name + " has joined", Color.Yellow); - } - - if (TShock.Config.DisplayIPToAdmins) - TShock.Utils.SendLogs(string.Format("{0} has joined. IP: {1}", args.Player.Name, args.Player.IP), Color.Blue); - - return false; - } - - private static bool HandleSendTileSquare(GetDataHandlerArgs args) - { - if (args.Player.Group.HasPermission(Permissions.allowclientsideworldedit)) - return false; - - var size = args.Data.ReadInt16(); - var tileX = args.Data.ReadInt32(); - var tileY = args.Data.ReadInt32(); - - if (OnSendTileSquare(size, tileX, tileY)) - return true; - - if (size > 5) - return true; - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendTileSquare(tileX, tileY, size); - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - var tiles = new NetTile[size,size]; - - for (int x = 0; x < size; x++) - { - for (int y = 0; y < size; y++) - { - tiles[x, y] = new NetTile(args.Data); - } - } - - bool changed = false; - for (int x = 0; x < size; x++) - { - int realx = tileX + x; - if (realx < 0 || realx >= Main.maxTilesX) - continue; - - for (int y = 0; y < size; y++) - { - int realy = tileY + y; - if (realy < 0 || realy >= Main.maxTilesY) - continue; - - var tile = Main.tile[realx, realy]; - var newtile = tiles[x, y]; - if (TShock.CheckTilePermission(args.Player, realx, realy)) - { - continue; - } - // Server now has a range check built in - /*if (TShock.CheckRangePermission(args.Player, realx, realy)) - { - continue; - }*/ - if ((tile.type == 128 && newtile.Type == 128) || (tile.type == 105 && newtile.Type == 105) || (tile.type == 139 && newtile.Type == 139)) - { - if (TShock.Config.EnableInsecureTileFixes) - { - return false; - } - } - - if (tile.type == 0x17 && newtile.Type == 0x2) - { - tile.type = 0x2; - changed = true; - } - else if (tile.type == 0x19 && newtile.Type == 0x1) - { - tile.type = 0x1; - changed = true; - } - else if ((tile.type == 0xF && newtile.Type == 0xF) || - (tile.type == 0x4F && newtile.Type == 0x4F)) - { - tile.frameX = newtile.FrameX; - tile.frameY = newtile.FrameY; - changed = true; - } - // Holy water/Unholy water - else if (tile.type == 1 && newtile.Type == 117) - { - tile.type = 117; - changed = true; - } - else if (tile.type == 1 && newtile.Type == 25) - { - tile.type = 25; - changed = true; - } - else if (tile.type == 117 && newtile.Type == 25) - { - tile.type = 25; - changed = true; - } - else if (tile.type == 25 && newtile.Type == 117) - { - tile.type = 117; - changed = true; - } - else if (tile.type == 2 && newtile.Type == 23) - { - tile.type = 23; - changed = true; - } - else if (tile.type == 2 && newtile.Type == 109) - { - tile.type = 109; - changed = true; - } - else if (tile.type == 23 && newtile.Type == 109) - { - tile.type = 109; - changed = true; - } - else if (tile.type == 109 && newtile.Type == 23) - { - tile.type = 23; - changed = true; - } - else if (tile.type == 23 && newtile.Type == 109) - { - tile.type = 109; - changed = true; - } - else if (tile.type == 53 && newtile.Type == 116) - { - tile.type = 116; - changed = true; - } - else if (tile.type == 53 && newtile.Type == 112) - { - tile.type = 112; - changed = true; - } - else if (tile.type == 112 && newtile.Type == 116) - { - tile.type = 116; - changed = true; - } - else if (tile.type == 116 && newtile.Type == 112) - { - tile.type = 112; - changed = true; - } - else if (tile.type == 112 && newtile.Type == 53) - { - tile.type = 53; - changed = true; - } - else if (tile.type == 109 && newtile.Type == 2) - { - tile.type = 2; - changed = true; - } - else if (tile.type == 116 && newtile.Type == 53) - { - tile.type = 53; - changed = true; - } - else if (tile.type == 117 && newtile.Type == 1) - { - tile.type = 1; - changed = true; - } - } - } - - if (changed) - { - TSPlayer.All.SendTileSquare(tileX, tileY, size); - WorldGen.RangeFrame(tileX, tileY, tileX + size, tileY + size); - } - else - { - args.Player.SendTileSquare(tileX, tileY, size); - } - return true; - } - - private static bool HandleTile(GetDataHandlerArgs args) - { - var type = args.Data.ReadInt8(); - var tileX = args.Data.ReadInt32(); - var tileY = args.Data.ReadInt32(); - var tiletype = args.Data.ReadInt8(); - if (OnTileEdit(args.Player, tileX, tileY, tiletype, type)) - return true; - if (tileX < 0 || tileX >= Main.maxTilesX || tileY < 0 || tileY >= Main.maxTilesY) - return false; - - if (args.Player.AwaitingName) - { - var protectedregions = TShock.Regions.InAreaRegionName(tileX, tileY); - if (protectedregions.Count == 0) - { - args.Player.SendMessage("Region is not protected", Color.Yellow); - } - else - { - string regionlist = string.Join(",", protectedregions.ToArray()); - args.Player.SendMessage("Region Name(s): " + regionlist, Color.Yellow); - } - args.Player.SendTileSquare(tileX, tileY); - args.Player.AwaitingName = false; - return true; - } - - if (args.Player.AwaitingTempPoint > 0) - { - args.Player.TempPoints[args.Player.AwaitingTempPoint - 1].X = tileX; - args.Player.TempPoints[args.Player.AwaitingTempPoint - 1].Y = tileY; - args.Player.SendMessage("Set Temp Point " + args.Player.AwaitingTempPoint, Color.Yellow); - args.Player.SendTileSquare(tileX, tileY); - args.Player.AwaitingTempPoint = 0; - return true; - } - - if (type == 1 || type == 3) - { - if (tiletype >= ((type == 1) ? Main.maxTileSets : Main.maxWallTypes)) - { - return true; - } - if (tiletype == 29 && tiletype == 97 && TShock.Config.ServerSideInventory) - { - args.Player.SendMessage("You cannot place this tile, Server side inventory is enabled.", Color.Red); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - if (tiletype == 48 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned("Spike", args.Player)) - { - args.Player.Disable("Using banned spikes without permissions"); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - if (type == 1 && tiletype == 21 && TShock.Utils.MaxChests()) - { - args.Player.SendMessage("Reached world's max chest limit, unable to place more!", Color.Red); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - if (tiletype == 141 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned("Explosives", args.Player)) - { - args.Player.Disable("Using banned explosives tile without permissions"); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (TShock.CheckTilePermission(args.Player, tileX, tileY, tiletype, type)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if ((tiletype == 127 || Main.tileCut[tiletype]) && (type == 0 || type == 4)) - { - return false; - } - - if (TShock.CheckRangePermission(args.Player, tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (args.Player.TileKillThreshold >= TShock.Config.TileKillThreshold) - { - args.Player.Disable("Reached TileKill threshold"); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold) - { - args.Player.Disable("Reached TilePlace threshold"); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (type == 1 && !args.Player.Group.HasPermission(Permissions.ignoreplacetiledetection)) - { - args.Player.TilePlaceThreshold++; - var coords = new Vector2(tileX, tileY); - if (!args.Player.TilesCreated.ContainsKey(coords)) - args.Player.TilesCreated.Add(coords, Main.tile[tileX, tileY].Data); - } - - if ((type == 0 || type == 4) && Main.tileSolid[Main.tile[tileX, tileY].type] && - !args.Player.Group.HasPermission(Permissions.ignorekilltiledetection)) - { - args.Player.TileKillThreshold++; - var coords = new Vector2(tileX, tileY); - if (!args.Player.TilesDestroyed.ContainsKey(coords)) - args.Player.TilesDestroyed.Add(coords, Main.tile[tileX, tileY].Data); - } - - return false; - } - - private static bool HandleTogglePvp(GetDataHandlerArgs args) - { - byte id = args.Data.ReadInt8(); - bool pvp = args.Data.ReadBoolean(); - if (OnPvpToggled(id, pvp)) - return true; - - if (id != args.Player.Index) - { - return true; - } - - if (TShock.Config.PvPMode == "disabled") - { - return true; - } - - if (args.TPlayer.hostile != pvp) - { - long seconds = (long) (DateTime.UtcNow - args.Player.LastPvpChange).TotalSeconds; - if (seconds > 5) - { - TSPlayer.All.SendMessage(string.Format("{0} has {1} PvP!", args.Player.Name, pvp ? "enabled" : "disabled"), - Main.teamColor[args.Player.Team]); - } - args.Player.LastPvpChange = DateTime.UtcNow; - } - - args.TPlayer.hostile = pvp; - - if (TShock.Config.PvPMode == "always") - { - if (!pvp) - args.Player.Spawn(); - } - - NetMessage.SendData((int) PacketTypes.TogglePvp, -1, -1, "", args.Player.Index); - - return true; - } - - private static bool HandlePlayerUpdate(GetDataHandlerArgs args) - { - var plr = args.Data.ReadInt8(); - var control = args.Data.ReadInt8(); - var item = args.Data.ReadInt8(); - var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - if (OnPlayerUpdate(plr, control, item, pos, vel)) - return true; - if (item < 0 || item >= args.TPlayer.inventory.Length) - { - return true; - } - - if (args.Player.LastNetPosition == Vector2.Zero) - { - return true; - } - - if (!pos.Equals(args.Player.LastNetPosition)) - { - float distance = Vector2.Distance(new Vector2(pos.X/16f, pos.Y/16f), - new Vector2(args.Player.LastNetPosition.X/16f, args.Player.LastNetPosition.Y/16f)); - if (TShock.CheckIgnores(args.Player)) - { - if (distance > TShock.Config.MaxRangeForDisabled) - { - if (args.Player.IgnoreActionsForCheating != "none") - { - args.Player.SendMessage("Disabled for cheating: " + args.Player.IgnoreActionsForCheating, - Color.Red); - } - else if (args.Player.IgnoreActionsForDisabledArmor != "none") - { - args.Player.SendMessage( - "Disabled for banned armor: " + args.Player.IgnoreActionsForDisabledArmor, Color.Red); - } - else if (args.Player.IgnoreActionsForInventory != "none") - { - args.Player.SendMessage( - "Disabled for Server Side Inventory: " + args.Player.IgnoreActionsForInventory, - Color.Red); - } - else if (TShock.Config.RequireLogin && !args.Player.IsLoggedIn) - { - args.Player.SendMessage("Please /register or /login to play!", Color.Red); - } - else if (args.Player.IgnoreActionsForClearingTrashCan) - { - args.Player.SendMessage("You need to rejoin to ensure your trash can is cleared!", Color.Red); - } - else if (TShock.Config.PvPMode == "always" && !args.TPlayer.hostile) - { - args.Player.SendMessage("PvP is forced! Enable PvP else you can't move or do anything!", - Color.Red); - } - int lastTileX = (int) (args.Player.LastNetPosition.X/16f); - int lastTileY = (int) (args.Player.LastNetPosition.Y/16f); - if (!args.Player.Teleport(lastTileX, lastTileY)) - { - args.Player.Spawn(); - } - return true; - } - return true; - } - - if (args.Player.Dead) - { - return true; - } - - if (!args.Player.Group.HasPermission(Permissions.ignorenoclipdetection) && - TSCheckNoclip(pos, args.TPlayer.width, args.TPlayer.height) && !TShock.Config.IgnoreNoClip) - { - int lastTileX = (int) (args.Player.LastNetPosition.X/16f); - int lastTileY = (int) (args.Player.LastNetPosition.Y/16f); - if (!args.Player.Teleport(lastTileX, lastTileY + 3)) - { - args.Player.SendMessage("You got stuck in a solid object, Sent to spawn point."); - args.Player.Spawn(); - } - return true; - } - args.Player.LastNetPosition = pos; - } - - if ((control & 32) == 32) - { - if (!args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned(args.TPlayer.inventory[item].name, args.Player)) - { - control -= 32; - args.Player.Disable("Using banned item"); - args.Player.SendMessage( - string.Format("You cannot use {0} on this server. Your actions are being ignored.", - args.TPlayer.inventory[item].name), Color.Red); - } - } - - args.TPlayer.selectedItem = item; - args.TPlayer.position = pos; - args.TPlayer.velocity = vel; - args.TPlayer.oldVelocity = args.TPlayer.velocity; - args.TPlayer.fallStart = (int) (pos.Y/16f); - args.TPlayer.controlUp = false; - args.TPlayer.controlDown = false; - args.TPlayer.controlLeft = false; - args.TPlayer.controlRight = false; - args.TPlayer.controlJump = false; - args.TPlayer.controlUseItem = false; - args.TPlayer.direction = -1; - if ((control & 1) == 1) - { - args.TPlayer.controlUp = true; - } - if ((control & 2) == 2) - { - args.TPlayer.controlDown = true; - } - if ((control & 4) == 4) - { - args.TPlayer.controlLeft = true; - } - if ((control & 8) == 8) - { - args.TPlayer.controlRight = true; - } - if ((control & 16) == 16) - { - args.TPlayer.controlJump = true; - } - if ((control & 32) == 32) - { - args.TPlayer.controlUseItem = true; - } - if ((control & 64) == 64) - { - args.TPlayer.direction = 1; - } - NetMessage.SendData((int) PacketTypes.PlayerUpdate, -1, args.Player.Index, "", args.Player.Index); - - return true; - } - - private static bool HandleProjectileNew(GetDataHandlerArgs args) - { - var ident = args.Data.ReadInt16(); - var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - var knockback = args.Data.ReadSingle(); - var dmg = args.Data.ReadInt16(); - var owner = args.Data.ReadInt8(); - var type = args.Data.ReadInt8(); - owner = (byte)args.Player.Index; - var index = TShock.Utils.SearchProjectile(ident, owner); - - if (OnNewProjectile(ident, pos, vel, knockback, dmg, owner, type, index)) - return true; - - if (index > Main.maxProjectiles || index < 0) - { - return false; - } - - // Server now checks owner + ident, if owner is different, server will create new projectile. - /*if (args.Player.Index != owner) - { - args.Player.Disable(String.Format("Owner ({0}) and player ID ({1}) does not match to update projectile", owner, args.Player.Index)); - args.Player.RemoveProjectile(ident, owner); - return true; - }*/ - - if (dmg > TShock.Config.MaxProjDamage && !args.Player.Group.HasPermission(Permissions.ignoredamagecap)) - { - args.Player.Disable(String.Format("Projectile damage is higher than {0}", TShock.Config.MaxProjDamage)); - args.Player.RemoveProjectile(ident, owner); - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.RemoveProjectile(ident, owner); - return true; - } - - if (!TShock.Config.IgnoreProjUpdate && TShock.CheckProjectilePermission(args.Player, index, type)) - { - if (type == 100) - { //fix for skele prime - Log.Debug("Skeletron Prime's death laser ignored for cheat detection.."); - } - else - { - args.Player.Disable("Does not have projectile permission to update projectile."); - args.Player.RemoveProjectile(ident, owner); - } - return true; - } - - if (args.Player.ProjectileThreshold >= TShock.Config.ProjectileThreshold) - { - args.Player.Disable("Reached projectile update threshold"); - args.Player.RemoveProjectile(ident, owner); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.RemoveProjectile(ident, owner); - return true; - } - - if (!args.Player.Group.HasPermission(Permissions.ignoreprojectiledetection)) - { - if ((type ==90) && (TShock.Config.ProjIgnoreShrapnel))// ignore shrapnel - { - Log.Debug("Ignoring shrapnel per config.."); - } - else - { - args.Player.ProjectileThreshold++; - } - } - - return false; - } - - private static bool HandleProjectileKill(GetDataHandlerArgs args) - { - var ident = args.Data.ReadInt16(); - var owner = args.Data.ReadInt8(); - owner = (byte)args.Player.Index; - var index = TShock.Utils.SearchProjectile(ident, owner); - - if (index > Main.maxProjectiles || index < 0) - { - return false; - } - - var type = Main.projectile[index].type; - - // Players can no longer destroy projectiles that are not theirs as of 1.1.2 - /*if (args.Player.Index != Main.projectile[index].owner && type != 102 && type != 100 && !TShock.Config.IgnoreProjKill) // workaround for skeletron prime projectiles - { - args.Player.Disable(String.Format("Owner ({0}) and player ID ({1}) does not match to kill projectile of type: {3}", Main.projectile[index].owner, args.Player.Index, type)); - args.Player.RemoveProjectile(ident, owner); - return true; - }*/ - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.RemoveProjectile(ident, owner); - return true; - } - - if (TShock.CheckProjectilePermission(args.Player, index, type) && type != 102 && type != 100 && !TShock.Config.IgnoreProjKill) - { - args.Player.Disable("Does not have projectile permission to kill projectile"); - args.Player.RemoveProjectile(ident, owner); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.RemoveProjectile(ident, owner); - return true; - } - - return false; - } - - private static bool HandlePlayerKillMe(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - var direction = args.Data.ReadInt8(); - var dmg = args.Data.ReadInt16(); - var pvp = args.Data.ReadInt8() == 0; - if (OnKillMe(id, direction, dmg, pvp)) - return true; - int textlength = (int) (args.Data.Length - args.Data.Position - 1); - string deathtext = ""; - if (textlength > 0) - { - deathtext = Encoding.UTF8.GetString(args.Data.ReadBytes(textlength)); - /*if (!TShock.Utils.ValidString(deathtext)) - { - return true; - }*/ - } - - args.Player.LastDeath = DateTime.Now; - args.Player.Dead = true; - - return false; - } - - private static bool HandleLiquidSet(GetDataHandlerArgs args) - { - int tileX = args.Data.ReadInt32(); - int tileY = args.Data.ReadInt32(); - byte liquid = args.Data.ReadInt8(); - bool lava = args.Data.ReadBoolean(); - - if (OnLiquidSet(tileX, tileY, liquid, lava)) - return true; - - //The liquid was picked up. - if (liquid == 0) - return false; - - if (tileX < 0 || tileX >= Main.maxTilesX || tileY < 0 || tileY >= Main.maxTilesY) - return false; - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (args.Player.TileLiquidThreshold >= TShock.Config.TileLiquidThreshold) - { - args.Player.Disable("Reached TileLiquid threshold"); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (!args.Player.Group.HasPermission(Permissions.ignoreliquidsetdetection)) - { - args.Player.TileLiquidThreshold++; - } - - int bucket = 0; - if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 206) - { - bucket = 1; - } - else if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 207) - { - bucket = 2; - } - - if (lava && bucket != 2 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player)) - { - args.Player.Disable("Using banned lava bucket without permissions"); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (!lava && bucket != 1 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned("Water Bucket", args.Player)) - { - args.Player.Disable("Using banned water bucket without permissions"); - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (TShock.CheckTilePermission(args.Player, tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (TShock.CheckRangePermission(args.Player, tileX, tileY, 16)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - return false; - } - - private static bool HandleTileKill(GetDataHandlerArgs args) - { - var tileX = args.Data.ReadInt32(); - var tileY = args.Data.ReadInt32(); - if (OnTileKill(tileX, tileY)) - return true; - if (tileX < 0 || tileX >= Main.maxTilesX || tileY < 0 || tileY >= Main.maxTilesY) - return false; - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (Main.tile[tileX, tileY].type != 0x15 && (!TShock.Utils.MaxChests() && Main.tile[tileX, tileY].type != 0)) //Chest - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (TShock.CheckTilePermission(args.Player, tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - if (TShock.CheckRangePermission(args.Player, tileX, tileY)) - { - args.Player.SendTileSquare(tileX, tileY); - return true; - } - - return false; - } - - private static bool HandleSpawn(GetDataHandlerArgs args) - { - var player = args.Data.ReadInt8(); - var spawnx = args.Data.ReadInt32(); - var spawny = args.Data.ReadInt32(); - - if (OnPlayerSpawn(player, spawnx, spawny)) - return true; - - if (args.Player.InitSpawn && args.TPlayer.inventory[args.TPlayer.selectedItem].type != 50) - { - if (args.TPlayer.difficulty == 1 && (TShock.Config.KickOnMediumcoreDeath || TShock.Config.BanOnMediumcoreDeath)) - { - if (args.TPlayer.selectedItem != 50) - { - if (TShock.Config.BanOnMediumcoreDeath) - { - if (!TShock.Utils.Ban(args.Player, TShock.Config.MediumcoreBanReason)) - TShock.Utils.ForceKick(args.Player, "Death results in a ban, but can't ban you"); - } - else - { - TShock.Utils.ForceKick(args.Player, TShock.Config.MediumcoreKickReason); - } - return true; - } - } - } - else - args.Player.InitSpawn = true; - - args.Player.Dead = false; - return false; - } - - private static bool HandleChestOpen(GetDataHandlerArgs args) - { - var x = args.Data.ReadInt32(); - var y = args.Data.ReadInt32(); - - if (OnChestOpen(x, y)) - return true; - - if (TShock.CheckIgnores(args.Player)) - { - return true; - } - - if (TShock.CheckRangePermission(args.Player, x, y)) - { - return true; - } - - if (TShock.CheckTilePermission(args.Player, x, y) && TShock.Config.RegionProtectChests) - { - return true; - } - - return false; - } - - private static bool HandleChestItem(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt16(); - var slot = args.Data.ReadInt8(); - var stacks = args.Data.ReadInt8(); - var prefix = args.Data.ReadInt8(); - var type = args.Data.ReadInt16(); - - if (OnChestItemChange(id, slot, stacks, prefix, type)) - return true; - - if (args.TPlayer.chest != id) - { - return false; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.ChestItem, "", id, slot); - return true; - } - - Item item = new Item(); - item.netDefaults(type); - if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(item.name, args.Player)) - { - return false; - } - - if (TShock.CheckTilePermission(args.Player, Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests) - { - return false; - } - - if (TShock.CheckRangePermission(args.Player, Main.chest[id].x, Main.chest[id].y)) - { - return false; - } - - return false; - } - - private static bool HandleSign(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt16(); - var x = args.Data.ReadInt32(); - var y = args.Data.ReadInt32(); - - if (OnSignEvent(id, x, y)) - return true; - - if (TShock.CheckTilePermission(args.Player, x, y)) - { - args.Player.SendData(PacketTypes.SignNew, "", id); - return true; - } - - if (TShock.CheckRangePermission(args.Player, x, y)) - { - args.Player.SendData(PacketTypes.SignNew, "", id); - return true; - } - return false; - } - - private static bool UpdateNPCHome(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt16(); - var x = args.Data.ReadInt16(); - var y = args.Data.ReadInt16(); - var homeless = args.Data.ReadInt8(); - - if (OnUpdateNPCHome(id, x, y, homeless)) - return true; - - if (!args.Player.Group.HasPermission(Permissions.movenpc)) - { - args.Player.SendMessage("You do not have permission to relocate NPCs.", Color.Red); - args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, - Convert.ToByte(Main.npc[id].homeless)); - return true; - } - - if (TShock.CheckTilePermission(args.Player, x, y)) - { - args.Player.SendMessage( "You do not have access to modify this area.", Color.Red); - args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, - Convert.ToByte(Main.npc[id].homeless)); - return true; - } - - //removed until NPC Home packet actually sends their home coords. - /*if (TShock.CheckRangePermission(args.Player, x, y)) - { - args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, - Convert.ToByte(Main.npc[id].homeless)); - return true; - }*/ - return false; - } - - private static bool HandlePlayerBuff(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - var type = args.Data.ReadInt8(); - var time = args.Data.ReadInt16(); - - if (OnPlayerBuff(id, type, time)) - return true; - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.PlayerBuff, "", id); - return true; - } - if (!TShock.Players[id].TPlayer.hostile) - { - args.Player.SendData(PacketTypes.PlayerBuff, "", id); - return true; - } - if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 50)) - { - args.Player.SendData(PacketTypes.PlayerBuff, "", id); - return true; - } - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendData(PacketTypes.PlayerBuff, "", id); - return true; - } - - if (WhitelistBuffMaxTime[type] > 0 && time <= WhitelistBuffMaxTime[type]) - { - return false; - } - - args.Player.SendData(PacketTypes.PlayerBuff, "", id); - return true; - } - - private static bool HandleItemDrop(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt16(); - var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); - var stacks = args.Data.ReadInt8(); - var prefix = args.Data.ReadInt8(); - var type = args.Data.ReadInt16(); - - if (OnItemDrop(id, pos, vel, stacks, prefix, type)) - return true; - - if (type == 0) //Item removed, let client do this to prevent item duplication client side - { - return false; - } - - if (TShock.CheckRangePermission(args.Player, (int) (pos.X/16f), (int) (pos.Y/16f))) - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - return true; - } - - Item item = new Item(); - item.netDefaults(type); - if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(item.name, args.Player)) - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - return true; - } - if ((TShock.Config.ServerSideInventory) && (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond - args.Player.LoginMS < TShock.Config.LogonDiscardThreshold)) - { - //Player is probably trying to sneak items onto the server in their hands!!! - Log.ConsoleInfo(string.Format("Player {0} tried to sneak {1} onto the server!", args.Player.Name, item.name)); - args.Player.SendData(PacketTypes.ItemDrop, "", id); - return true; - - } - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.ItemDrop, "", id); - return true; - } - - return false; - } - - private static bool HandlePlayerDamage(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - var direction = args.Data.ReadInt8(); - var dmg = args.Data.ReadInt16(); - var pvp = args.Data.ReadInt8(); - var crit = args.Data.ReadInt8(); - - if (OnPlayerDamage(id, direction, dmg, pvp, crit)) - return true; - - int textlength = (int) (args.Data.Length - args.Data.Position - 1); - string deathtext = ""; - if (textlength > 0) - { - deathtext = Encoding.UTF8.GetString(args.Data.ReadBytes(textlength)); - /*if (!TShock.Utils.ValidString(deathtext)) - { - return true; - }*/ - } - - if (TShock.Players[id] == null) - return true; - - if (dmg > TShock.Config.MaxDamage && !args.Player.Group.HasPermission(Permissions.ignoredamagecap)) - { - args.Player.Disable(String.Format("Player damage exceeded {0}", TShock.Config.MaxDamage ) ); - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if (!TShock.Players[id].TPlayer.hostile) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 100)) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendData(PacketTypes.PlayerHp, "", id); - args.Player.SendData(PacketTypes.PlayerUpdate, "", id); - return true; - } - - return false; - } - - private static bool HandleNpcStrike(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - var direction = args.Data.ReadInt8(); - var dmg = args.Data.ReadInt16(); - var pvp = args.Data.ReadInt8(); - var crit = args.Data.ReadInt8(); - - if (OnNPCStrike(id, direction, dmg, pvp, crit)) - return true; - - if (Main.npc[id] == null) - return true; - - if (dmg > TShock.Config.MaxDamage && !args.Player.Group.HasPermission(Permissions.ignoredamagecap)) - { - args.Player.Disable(String.Format("NPC damage exceeded {0}", TShock.Config.MaxDamage ) ); - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - return true; - } - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - return true; - } - - if (Main.npc[id].townNPC && !args.Player.Group.HasPermission(Permissions.movenpc)) - { - args.Player.SendMessage( "You don't have permission to move the NPC", Color.Yellow); - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - return true; - } - - if (TShock.Config.RangeChecks && - TShock.CheckRangePermission(args.Player, (int) (Main.npc[id].position.X/16f), (int) (Main.npc[id].position.Y/16f), - 128)) - { - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendData(PacketTypes.NpcUpdate, "", id); - return true; - } - - return false; - } - - private static bool HandleSpecial(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - var type = args.Data.ReadInt8(); - - if (OnNPCSpecial(id, type)) - return true; - - if (type == 1 && TShock.Config.DisableDungeonGuardian) - { - args.Player.SendMessage("The Dungeon Guardian returned you to your spawn point", Color.Purple); - args.Player.Spawn(); - return true; - } - - return false; - } - - private static bool HandlePlayerAnimation(GetDataHandlerArgs args) - { - - if (OnPlayerAnimation()) - return true; - - if (TShock.CheckIgnores(args.Player)) - { - args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); - return true; - } - - if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) - { - args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); - return true; - } - - return false; - } - - private static bool HandlePlayerBuffUpdate(GetDataHandlerArgs args) - { - var id = args.Data.ReadInt8(); - - if (OnPlayerBuffUpdate(id)) - return true; - - for (int i = 0; i < 10; i++) - { - var buff = args.Data.ReadInt8(); - - if (buff == 10) - { - if (!args.Player.Group.HasPermission(Permissions.usebanneditem) && - TShock.Itembans.ItemIsBanned("Invisibility Potion", args.Player)) - buff = 0; - else if (TShock.Config.DisableInvisPvP && args.TPlayer.hostile) - buff = 0; - } - - args.TPlayer.buffType[i] = buff; - if (args.TPlayer.buffType[i] > 0) - { - args.TPlayer.buffTime[i] = 60; - } - else - { - args.TPlayer.buffTime[i] = 0; - } - } - NetMessage.SendData((int) PacketTypes.PlayerBuff, -1, args.Player.Index, "", args.Player.Index); - return true; - } - - private static bool HandleSpawnBoss(GetDataHandlerArgs args) - { - var spawnboss = false; - var invasion = -1; - var plr = args.Data.ReadInt32(); - var Type = args.Data.ReadInt32(); - spawnboss = (Type == 4 || Type == 13 || (Type == 50 || Type == 125) || (Type == 126 || Type == 134 || (Type == (int) sbyte.MaxValue || Type == 128))); - if (!spawnboss) - { - switch (Type) - { - case -1: - invasion = 1; - break; - case -2: - invasion = 2; - break; - } - } - if (spawnboss && !args.Player.Group.HasPermission(Permissions.summonboss)) - { - args.Player.SendMessage("You don't have permission to summon a boss.", Color.Red); - return true; - } - if (invasion != -1 && !args.Player.Group.HasPermission(Permissions.startinvasion)) - { - args.Player.SendMessage("You don't have permission to start an invasion.", Color.Red); - return true; - } - if (!spawnboss && invasion == -1) - return true; - if (plr != args.Player.Index) - return true; - - string boss; - switch (Type) - { - case -2: - boss = "the Snow Legion"; - break; - case -1: - boss = "a Goblin Invasion"; - break; - case 4: - boss = "the Eye of Cthulhu"; - break; - case 13: - boss = "the Eater of Worlds"; - break; - case 50: - boss = "the King Slime"; - break; - case 125: - boss = "Retinazer"; - break; - case 126: - boss = "Spazmatism"; - break; - case 134: - boss = "the Destroyer"; - break; - case sbyte.MaxValue: - boss = "Skeleton Prime"; - break; - case 128: - boss = "Skeleton Prime"; - break; - default: - boss = "error"; - break; - } - - TShock.Utils.SendLogs(string.Format("{0} summoned {1}", args.Player.Name, boss), Color.Red); - return false; - } - } -} +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.IO.Streams; +using System.Linq; +using System.Text; +using Terraria; +using TShockAPI.Net; + +namespace TShockAPI +{ + public delegate bool GetDataHandlerDelegate(GetDataHandlerArgs args); + + public class GetDataHandlerArgs : EventArgs + { + public TSPlayer Player { get; private set; } + public MemoryStream Data { get; private set; } + + public Player TPlayer + { + get { return Player.TPlayer; } + } + + public GetDataHandlerArgs(TSPlayer player, MemoryStream data) + { + Player = player; + Data = data; + } + } + + public static class GetDataHandlers + { + private static Dictionary GetDataHandlerDelegates; + public static int[] WhitelistBuffMaxTime; + #region Events + + /// + /// Used when a TileEdit event is called. + /// + public class TileEditEventArgs : HandledEventArgs + { + /// + /// The TSPlayer who made the tile edit + /// + public TSPlayer Player { get; set; } + + /// + /// The tile coordinate on the X plane + /// + public int X { get; set; } + + /// + /// The tile coordinate on the Y plane + /// + public int Y { get; set; } + + /// + /// The Tile ID being edited. + /// + public byte Type { get; set; } + /// + /// The EditType. + /// (KillTile = 0, PlaceTile = 1, KillWall = 2, PlaceWall = 3, KillTileNoItem = 4, PlaceWire = 5, KillWire = 6) + /// + public byte EditType { get; set; } + } + + /// + /// TileEdit - called when a tile is placed or destroyed + /// + public static HandlerList TileEdit; + private static bool OnTileEdit(TSPlayer ply, int x, int y, byte type, byte editType) + { + if (TileEdit == null) + return false; + + var args = new TileEditEventArgs + { + Player = ply, + X = x, + Y = y, + Type = type, + EditType = editType + }; + TileEdit.Invoke(null, args); + return args.Handled; + } + /// + /// For use in a TogglePvp event + /// + public class TogglePvpEventArgs : HandledEventArgs + { + /// + /// The Terraria player ID of the player + /// + public byte PlayerId { get; set; } + /// + /// Enable/disable pvp? + /// + public bool Pvp { get; set; } + } + /// + /// TogglePvp - called when a player toggles pvp + /// + public static HandlerList TogglePvp; + private static bool OnPvpToggled(byte _id, bool _pvp) + { + if (TogglePvp == null) + return false; + + var args = new TogglePvpEventArgs + { + PlayerId = _id, + Pvp = _pvp, + }; + TogglePvp.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerTeam event + /// + public class PlayerTeamEventArgs : HandledEventArgs + { + /// + /// The Terraria player ID of the player + /// + public byte PlayerId { get; set; } + /// + /// Enable/disable pvp? + /// + public byte Team { get; set; } + } + /// + /// TogglePvp - called when a player toggles pvp + /// + public static HandlerList PlayerTeam; + private static bool OnPlayerTeam(byte _id, byte _team) + { + if (PlayerTeam == null) + return false; + + var args = new PlayerTeamEventArgs + { + PlayerId = _id, + Team = _team, + }; + PlayerTeam.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerSlot event + /// + public class PlayerSlotEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID + /// + public byte PlayerId { get; set; } + /// + /// The slot edited + /// + public byte Slot { get; set; } + /// + /// The stack edited + /// + public byte Stack { get; set; } + /// + /// The item prefix + /// + public byte Prefix { get; set; } + /// + /// Item type + /// + public short Type { get; set; } + } + /// + /// PlayerSlot - called at a PlayerSlot event + /// + public static HandlerList PlayerSlot; + private static bool OnPlayerSlot(byte _plr, byte _slot, byte _stack, byte _prefix, short _type) + { + if (PlayerSlot == null) + return false; + + var args = new PlayerSlotEventArgs + { + PlayerId = _plr, + Slot = _slot, + Stack = _stack, + Prefix = _prefix, + Type = _type + }; + PlayerSlot.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerHP event + /// + public class PlayerHPEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte PlayerId { get; set; } + /// + /// Current HP + /// + public short Current { get; set; } + /// + /// Maximum HP + /// + public short Max { get; set; } + } + /// + /// PlayerHP - called at a PlayerHP event + /// + public static HandlerList PlayerHP; + + private static bool OnPlayerHP(byte _plr, short _cur, short _max) + { + if (PlayerHP == null) + return false; + + var args = new PlayerHPEventArgs + { + PlayerId = _plr, + Current = _cur, + Max = _max, + }; + PlayerHP.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerMana event + /// + public class PlayerManaEventArgs : HandledEventArgs + { + public byte PlayerId { get; set; } + public short Current { get; set; } + public short Max { get; set; } + } + /// + /// PlayerMana - called at a PlayerMana event + /// + public static HandlerList PlayerMana; + + private static bool OnPlayerMana(byte _plr, short _cur, short _max) + { + if (PlayerMana == null) + return false; + + var args = new PlayerManaEventArgs + { + PlayerId = _plr, + Current = _cur, + Max = _max, + }; + PlayerMana.Invoke(null, args); + return args.Handled; + } + + public class PlayerInfoEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte PlayerId { get; set; } + /// + /// Hair color + /// + public byte Hair { get; set; } + /// + /// Gender (male = true) + /// + public bool Male { get; set; } + /// + /// Character difficulty + /// + public byte Difficulty { get; set; } + /// + /// Player/character name + /// + public string Name { get; set; } + } + /// + /// PlayerInfo - called at a PlayerInfo event + /// If this is cancelled, the server will ForceKick the player. If this should be changed in the future, let someone know. + /// + public static HandlerList PlayerInfo; + + private static bool OnPlayerInfo(byte _plrid, byte _hair, bool _male, byte _difficulty, string _name) + { + if (PlayerInfo == null) + return false; + + var args = new PlayerInfoEventArgs + { + PlayerId = _plrid, + Hair = _hair, + Male = _male, + Difficulty = _difficulty, + Name = _name, + }; + PlayerInfo.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a TileKill event + /// + public class TileKillEventArgs : HandledEventArgs + { + /// + /// The X coordinate that is being killed + /// + public int TileX { get; set; } + /// + /// The Y coordinate that is being killed + /// + public int TileY { get; set; } + } + /// + /// TileKill - When a tile is removed fromt he world + /// + public static HandlerList TileKill; + + private static bool OnTileKill(int tilex, int tiley) + { + if (TileKill == null) + return false; + + var args = new TileKillEventArgs + { + TileX = tilex, + TileY = tiley, + }; + TileKill.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a KillMe event + /// + public class KillMeEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte PlayerId { get; set; } + /// + /// The direction the damage is coming from (?) + /// + public byte Direction { get; set; } + /// + /// Amount of damage delt + /// + public short Damage { get; set; } + /// + /// Player's current pvp setting + /// + public bool Pvp { get; set; } + } + /// + /// KillMe - Terraria's crappy way of handling damage from players + /// + public static HandlerList KillMe; + + private static bool OnKillMe(byte plr, byte direction, short damage, bool pvp) + { + if (KillMe == null) + return false; + + var args = new KillMeEventArgs + { + PlayerId = plr, + Direction = direction, + Damage = damage, + Pvp = pvp, + }; + KillMe.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerUpdate event + /// + public class PlayerUpdateEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte PlayerId { get; set; } + /// + /// ??? + /// + public byte Control { get; set; } + /// + /// Current item? + /// + public byte Item { get; set; } + /// + /// Position of the player + /// + public Vector2 Position { get; set; } + /// + /// Velocity of the player + /// + public Vector2 Velocity { get; set; } + } + /// + /// PlayerUpdate - When the player sends it's updated information to the server + /// + public static HandlerList PlayerUpdate; + + private static bool OnPlayerUpdate(byte player, byte control, byte item, Vector2 position, Vector2 velocity) + { + if (PlayerUpdate == null) + return false; + + var args = new PlayerUpdateEventArgs + { + PlayerId = player, + Control = control, + Item = item, + Position = position, + Velocity = velocity, + }; + PlayerUpdate.Invoke(null, args); + return args.Handled; + } + public static bool TSCheckNoclip(Vector2 Position, int Width, int Height) + { + int num = (int)(Position.X / 16f) - 1; + int num2 = (int)((Position.X + (float)Width) / 16f) + 2; + int num3 = (int)(Position.Y / 16f) - 1; + int num4 = (int)((Position.Y + (float)Height) / 16f) + 2; + if (num < 0) + { + num = 0; + } + if (num2 > Main.maxTilesX) + { + num2 = Main.maxTilesX; + } + if (num3 < 0) + { + num3 = 0; + } + if (num4 > Main.maxTilesY) + { + num4 = Main.maxTilesY; + } + for (int i = num; i < num2; i++) + { + for (int j = num3; j < num4; j++) + { + if (Main.tile[i, j] != null && Main.tile[i, j].active && Main.tileSolid[(int)Main.tile[i, j].type] && !Main.tileSolidTop[(int)Main.tile[i, j].type] &&(((int)Main.tile[i,j].type !=53) && ((int)Main.tile[i,j].type !=112) && ((int)Main.tile[i,j].type !=116) && ((int)Main.tile[i,j].type !=123)) && ((Main.tile[i,j].liquid == 0 )&& !Main.tile[i,j].lava)) + { + Vector2 vector; + vector.X = (float)(i * 16); + vector.Y = (float)(j * 16); + if (Position.X + (float)Width > vector.X && Position.X < vector.X + 16f && Position.Y + (float)Height > vector.Y && Position.Y < vector.Y + 16f) + { + return true; + } + } + } + } + return false; + } + + /// + /// For use in a SendTileSquare event + /// + public class SendTileSquareEventArgs : HandledEventArgs + { + /// + /// Size of the area + /// + public short Size { get; set; } + /// + /// A corner of the section + /// + public int TileX { get; set; } + /// + /// A corner of the section + /// + public int TileY { get; set; } + } + /// + /// SendTileSquare - When the player sends a tile square + /// + public static HandlerList SendTileSquare; + + private static bool OnSendTileSquare(short size, int tilex, int tiley) + { + if (SendTileSquare == null) + return false; + + var args = new SendTileSquareEventArgs + { + Size = size, + TileX = tilex, + TileY = tiley, + }; + SendTileSquare.Invoke(null, args); + return args.Handled; + } + /// + /// For use in a NewProjectile event + /// + public class NewProjectileEventArgs : HandledEventArgs + { + /// + /// ??? + /// + public short Identity { get; set; } + /// + /// Location of the projectile + /// + public Vector2 Position { get; set; } + /// + /// Velocity of the projectile + /// + public Vector2 Velocity { get; set; } + /// + /// Knockback + /// + public float Knockback { get; set; } + /// + /// Damage from the projectile + /// + public short Damage { get; set; } + /// + /// Terraria playerID owner of the projectile + /// + public byte Owner { get; set; } + /// + /// Type of projectile + /// + public byte Type { get; set; } + /// + /// ??? + /// + public int Index { get; set; } + } + /// + /// NewProjectile - Called when a client creates a new projectile + /// + public static HandlerList NewProjectile; + + private static bool OnNewProjectile(short ident, Vector2 pos, Vector2 vel, float knockback, short dmg, byte owner, byte type, int index) + { + if (NewProjectile == null) + return false; + + var args = new NewProjectileEventArgs + { + Identity = ident, + Position = pos, + Velocity = vel, + Knockback = knockback, + Damage = dmg, + Owner = owner, + Type = type, + Index = index, + }; + NewProjectile.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a LiquidSet event + /// + public class LiquidSetEventArgs : HandledEventArgs + { + /// + /// X location of the tile + /// + public int TileX { get; set; } + /// + /// Y location of the tile + /// + public int TileY { get; set; } + /// + /// ??? + /// + public byte Liquid { get; set;} + /// + /// True if lava + /// + public bool Lava { get; set; } + } + /// + /// LiquidSet - When ever a liquid is set + /// + public static HandlerList LiquidSet; + + private static bool OnLiquidSet(int tilex, int tiley, byte liquid, bool lava) + { + if (LiquidSet == null) + return false; + + var args = new LiquidSetEventArgs + { + TileX = tilex, + TileY = tiley, + Liquid = liquid, + Lava = lava, + }; + LiquidSet.Invoke(null, args); + return args.Handled; + } + /// + /// For use in a PlayerSpawn event + /// + public class SpawnEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte Player { get; set; } + /// + /// X location of the player's spawn + /// + public int SpawnX { get; set; } + /// + /// Y location of the player's spawn + /// + public int SpawnY { get; set; } + } + /// + /// PlayerSpawn - When a player spawns + /// + public static HandlerList PlayerSpawn; + + private static bool OnPlayerSpawn(byte player, int spawnX, int spawnY) + { + if (PlayerSpawn == null) + return false; + + var args = new SpawnEventArgs + { + Player = player, + SpawnX = spawnX, + SpawnY = spawnY, + }; + PlayerSpawn.Invoke(null, args); + return args.Handled; + } + /// + /// For use with a ChestOpen event + /// + public class ChestOpenEventArgs : HandledEventArgs + { + /// + /// X location of said chest + /// + public int X { get; set; } + /// + /// Y location of said chest + /// + public int Y { get; set; } + } + /// + /// ChestOpen - Called when any chest is opened + /// + public static HandlerList ChestOpen; + + private static bool OnChestOpen(int x, int y) + { + if (ChestOpen == null) + return false; + + var args = new ChestOpenEventArgs + { + X = x, + Y = y, + }; + ChestOpen.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a ChestItemChange event + /// + public class ChestItemEventArgs : HandledEventArgs + { + /// + /// ChestID + /// + public short ID { get; set; } + /// + /// Slot of the item + /// + public byte Slot { get; set; } + /// + /// How many? + /// + public byte Stacks { get; set; } + /// + /// Item prefix + /// + public byte Prefix { get; set; } + /// + /// Item type + /// + public short Type { get; set; } + } + /// + /// ChestItemChange - Called when an item in a chest changes + /// + public static HandlerList ChestItemChange; + + private static bool OnChestItemChange(short id, byte slot, byte stacks, byte prefix, short type) + { + if (ChestItemChange == null) + return false; + + var args = new ChestItemEventArgs + { + ID = id, + Slot = slot, + Stacks = stacks, + Prefix = prefix, + Type = type, + }; + ChestItemChange.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a Sign event + /// + public class SignEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public short ID { get; set; } + /// + /// X location of the sign + /// + public int X { get; set; } + /// + /// Y location of the sign + /// + public int Y { get; set; } + } + /// + /// Sign - Called when a sign is changed + /// + public static HandlerList Sign; + + private static bool OnSignEvent(short id, int x, int y) + { + if (Sign == null) + return false; + + var args = new SignEventArgs + { + ID = id, + X = x, + Y = y, + }; + Sign.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a NPCHome event + /// + public class NPCHomeChangeEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public short ID { get; set; } + /// + /// X location of the NPC home change + /// + public short X { get; set; } + /// + /// Y location of the NPC home change + /// + public short Y { get; set; } + /// + /// ByteBool homeless + /// + public byte Homeless { get; set; } + } + /// + /// NPCHome - Called when an NPC's home is changed + /// + public static HandlerList NPCHome; + + private static bool OnUpdateNPCHome(short id, short x, short y, byte homeless) + { + if (NPCHome == null) + return false; + + var args = new NPCHomeChangeEventArgs + { + ID = id, + X = x, + Y = y, + Homeless = homeless, + }; + NPCHome.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerBuff event + /// + public class PlayerBuffEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte ID { get; set; } + /// + /// Buff Type + /// + public byte Type { get; set; } + /// + /// Time the buff lasts + /// + public short Time { get; set; } + } + /// + /// PlayerBuff - Called when a player is buffed + /// + public static HandlerList PlayerBuff; + + private static bool OnPlayerBuff(byte id, byte type, short time) + { + if (PlayerBuff == null) + return false; + + var args = new PlayerBuffEventArgs + { + ID = id, + Type = type, + Time = time, + }; + PlayerBuff.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in an ItemDrop event + /// + public class ItemDropEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public short ID { get; set; } + /// + /// Position of the item + /// + public Vector2 Position { get; set; } + /// + /// Velocity at which the item is deployed + /// + public Vector2 Velocity { get; set; } + /// + /// Stacks + /// + public byte Stacks { get; set; } + /// + /// Prefix of the item + /// + public byte Prefix { get; set; } + /// + /// Item type + /// + public short Type { get; set; } + } + /// + /// ItemDrop - Called when an item is dropped + /// + public static HandlerList ItemDrop; + + private static bool OnItemDrop(short id, Vector2 pos, Vector2 vel, byte stacks, byte prefix, short type) + { + if (ItemDrop == null) + return false; + + var args = new ItemDropEventArgs + { + ID = id, + Position = pos, + Velocity = vel, + Stacks = stacks, + Prefix = prefix, + Type = type, + }; + ItemDrop.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerDamage event + /// + public class PlayerDamageEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte ID { get; set; } + /// + /// The direction the damage is occuring from + /// + public byte Direction { get; set; } + /// + /// Amount of damage + /// + public short Damage { get; set; } + /// + /// If the player has PVP on + /// + public byte PVP { get; set; } + /// + /// Is the damage critical? + /// + public byte Critical { get; set; } + } + /// + /// PlayerDamage - Called when a player is damaged + /// + public static HandlerList PlayerDamage; + + private static bool OnPlayerDamage(byte id, byte dir, short dmg, byte pvp, byte crit) + { + if (PlayerDamage == null) + return false; + + var args = new PlayerDamageEventArgs + { + ID = id, + Direction = dir, + Damage = dmg, + PVP = pvp, + Critical = crit, + }; + PlayerDamage.Invoke(null, args); + return args.Handled; + } + + /// + /// For use with a NPCStrike event + /// + public class NPCStrikeEventArgs : HandledEventArgs + { + /// + /// ??? + /// + public byte ID { get; set; } + /// + /// Direction the damage occurred from + /// + public byte Direction { get; set; } + /// + /// Amount of damage + /// + public short Damage { get; set; } + /// + /// Is PVP enabled...? + /// + public byte PVP { get; set; } + /// + /// Critical? + /// + public byte Critical { get; set; } + } + /// + /// NPCStrike - Called when an NPC is attacked + /// + public static HandlerList NPCStrike; + + private static bool OnNPCStrike(byte id, byte dir, short dmg, byte pvp, byte crit) + { + if (NPCStrike == null) + return false; + + var args = new NPCStrikeEventArgs + { + ID = id, + Direction = dir, + Damage = dmg, + PVP = pvp, + Critical = crit, + }; + NPCStrike.Invoke(null, args); + return args.Handled; + } + + /// + /// For use with a NPCSpecial event + /// + public class NPCSpecialEventArgs : HandledEventArgs + { + /// + /// ??? + /// + public byte ID { get; set; } + /// + /// Type...? + /// + public byte Type { get; set; } + } + /// + /// NPCSpecial - Called at some point + /// + public static HandlerList NPCSpecial; + + private static bool OnNPCSpecial(byte id, byte type) + { + if (NPCSpecial == null) + return false; + + var args = new NPCSpecialEventArgs + { + ID = id, + Type = type, + }; + NPCSpecial.Invoke(null, args); + return args.Handled; + } + + /// + /// For use with a PlayerAnimation event + /// + public class PlayerAnimationEventArgs : HandledEventArgs + { + } + + /// + /// PlayerAnimation - Called when a player animates + /// + public static HandlerList PlayerAnimation; + + private static bool OnPlayerAnimation() + { + if (PlayerAnimation == null) + return false; + + var args = new PlayerAnimationEventArgs {}; + PlayerAnimation.Invoke(null, args); + return args.Handled; + } + + /// + /// For use in a PlayerBuffUpdate event + /// + public class PlayerBuffUpdateEventArgs : HandledEventArgs + { + /// + /// The Terraria playerID of the player + /// + public byte ID { get; set; } + } + /// + /// PlayerBuffUpdate - Called when a player updates buffs + /// + public static HandlerList PlayerBuffUpdate; + + private static bool OnPlayerBuffUpdate(byte id) + { + if (PlayerBuffUpdate == null) + return false; + + var args = new PlayerBuffUpdateEventArgs + { + ID = id, + }; + PlayerBuffUpdate.Invoke(null, args); + return args.Handled; + } + + #endregion + public static void InitGetDataHandler() + { + #region Blacklists + + WhitelistBuffMaxTime = new int[Main.maxBuffs]; + WhitelistBuffMaxTime[20] = 600; + WhitelistBuffMaxTime[0x18] = 1200; + WhitelistBuffMaxTime[0x1f] = 120; + WhitelistBuffMaxTime[0x27] = 420; + + #endregion Blacklists + + GetDataHandlerDelegates = new Dictionary + { + {PacketTypes.PlayerInfo, HandlePlayerInfo}, + {PacketTypes.PlayerUpdate, HandlePlayerUpdate}, + {PacketTypes.Tile, HandleTile}, + {PacketTypes.TileSendSquare, HandleSendTileSquare}, + {PacketTypes.ProjectileNew, HandleProjectileNew}, + {PacketTypes.TogglePvp, HandleTogglePvp}, + {PacketTypes.PlayerTeam, HandlePlayerTeam}, + {PacketTypes.TileKill, HandleTileKill}, + {PacketTypes.PlayerKillMe, HandlePlayerKillMe}, + {PacketTypes.LiquidSet, HandleLiquidSet}, + {PacketTypes.PlayerSpawn, HandleSpawn}, + {PacketTypes.ChestGetContents, HandleChestOpen}, + {PacketTypes.ChestItem, HandleChestItem}, + {PacketTypes.SignNew, HandleSign}, + {PacketTypes.PlayerSlot, HandlePlayerSlot}, + {PacketTypes.TileGetSection, HandleGetSection}, + {PacketTypes.UpdateNPCHome, UpdateNPCHome}, + {PacketTypes.PlayerAddBuff, HandlePlayerBuff}, + {PacketTypes.ItemDrop, HandleItemDrop}, + {PacketTypes.PlayerHp, HandlePlayerHp}, + {PacketTypes.PlayerMana, HandlePlayerMana}, + {PacketTypes.PlayerDamage, HandlePlayerDamage}, + {PacketTypes.NpcStrike, HandleNpcStrike}, + {PacketTypes.NpcSpecial, HandleSpecial}, + {PacketTypes.PlayerAnimation, HandlePlayerAnimation}, + {PacketTypes.PlayerBuff, HandlePlayerBuffUpdate}, + {PacketTypes.PasswordSend, HandlePassword}, + {PacketTypes.ContinueConnecting2, HandleConnecting}, + {PacketTypes.ProjectileDestroy, HandleProjectileKill}, + {PacketTypes.SpawnBossorInvasion, HandleSpawnBoss} + }; + } + + public static bool HandlerGetData(PacketTypes type, TSPlayer player, MemoryStream data) + { + GetDataHandlerDelegate handler; + if (GetDataHandlerDelegates.TryGetValue(type, out handler)) + { + try + { + return handler(new GetDataHandlerArgs(player, data)); + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + } + return false; + } + + private static bool HandlePlayerSlot(GetDataHandlerArgs args) + { + byte plr = args.Data.ReadInt8(); + byte slot = args.Data.ReadInt8(); + byte stack = args.Data.ReadInt8(); + byte prefix = args.Data.ReadInt8(); + short type = args.Data.ReadInt16(); + + if (OnPlayerSlot(plr, slot, stack, prefix, type)) + return true; + + if (plr != args.Player.Index) + { + return true; + } + + if (slot < 0 || slot > NetItem.maxNetInventory) + { + return true; + } + + var item = new Item(); + item.netDefaults(type); + item.Prefix(prefix); + + if (args.Player.IsLoggedIn) + { + args.Player.PlayerData.StoreSlot(slot, type, prefix, stack); + } + + return false; + } + + public static bool HandlePlayerHp(GetDataHandlerArgs args) + { + var plr = args.Data.ReadInt8(); + var cur = args.Data.ReadInt16(); + var max = args.Data.ReadInt16(); + + if (OnPlayerHP(plr, cur, max)) + return true; + + if (args.Player.FirstMaxHP == 0) + args.Player.FirstMaxHP = max; + + if (max > 400 && max > args.Player.FirstMaxHP) + { + TShock.Utils.ForceKick(args.Player, "Hacked Client Detected."); + return false; + } + + if (args.Player.IsLoggedIn) + { + args.Player.PlayerData.maxHealth = max; + } + + return false; + } + + private static bool HandlePlayerMana(GetDataHandlerArgs args) + { + var plr = args.Data.ReadInt8(); + var cur = args.Data.ReadInt16(); + var max = args.Data.ReadInt16(); + + if (OnPlayerMana(plr, cur, max)) + return true; + + if (args.Player.FirstMaxMP == 0) + args.Player.FirstMaxMP = max; + + if (max > 400 && max > args.Player.FirstMaxMP) + { + TShock.Utils.ForceKick(args.Player, "Hacked Client Detected."); + return false; + } + + return false; + } + + private static bool HandlePlayerInfo(GetDataHandlerArgs args) + { + var playerid = args.Data.ReadInt8(); + var hair = args.Data.ReadInt8(); + var male = args.Data.ReadBoolean(); + args.Data.Position += 21; + var difficulty = args.Data.ReadInt8(); + string name = Encoding.UTF8.GetString(args.Data.ReadBytes((int) (args.Data.Length - args.Data.Position - 1))); + + if (OnPlayerInfo(playerid, hair, male, difficulty, name)) + { + TShock.Utils.ForceKick(args.Player, "A plugin cancelled the event."); + return true; + } + + /*if (!TShock.Utils.ValidString(name)) + { + TShock.Utils.ForceKick(args.Player, "Unprintable character in name"); + return true; + }*/ + if (name.Trim().Length == 0) + { + TShock.Utils.ForceKick(args.Player, "Empty Name."); + return true; + } + var ban = TShock.Bans.GetBanByName(name); + if (ban != null) + { + TShock.Utils.ForceKick(args.Player, string.Format("You are banned: {0}", ban.Reason)); + return true; + } + if (args.Player.ReceivedInfo) + { + return true; + } + if (TShock.Config.MediumcoreOnly && difficulty < 1) + { + TShock.Utils.ForceKick(args.Player, "Server is set to mediumcore and above characters only!"); + return true; + } + if (TShock.Config.HardcoreOnly && difficulty < 2) + { + TShock.Utils.ForceKick(args.Player, "Server is set to hardcore characters only!"); + return true; + } + args.Player.Difficulty = difficulty; + args.TPlayer.name = name; + args.Player.ReceivedInfo = true; + + return false; + } + + private static bool HandleConnecting(GetDataHandlerArgs args) + { + var user = TShock.Users.GetUserByName(args.Player.Name); + if (user != null && !TShock.Config.DisableLoginBeforeJoin) + { + args.Player.RequiresPassword = true; + NetMessage.SendData((int) PacketTypes.PasswordRequired, args.Player.Index); + return true; + } + else if (!string.IsNullOrEmpty(TShock.Config.ServerPassword)) + { + args.Player.RequiresPassword = true; + NetMessage.SendData((int) PacketTypes.PasswordRequired, args.Player.Index); + return true; + } + + if (args.Player.State == 1) + args.Player.State = 2; + NetMessage.SendData((int) PacketTypes.WorldInfo, args.Player.Index); + return true; + } + + private static bool HandlePassword(GetDataHandlerArgs args) + { + if (!args.Player.RequiresPassword) + return true; + + string password = Encoding.UTF8.GetString(args.Data.ReadBytes((int) (args.Data.Length - args.Data.Position - 1))); + var user = TShock.Users.GetUserByName(args.Player.Name); + if (user != null) + { + string encrPass = TShock.Utils.HashPassword(password); + if (user.Password.ToUpper() == encrPass.ToUpper()) + { + args.Player.RequiresPassword = false; + args.Player.PlayerData = TShock.InventoryDB.GetPlayerData(args.Player, TShock.Users.GetUserID(args.Player.Name)); + + if (args.Player.State == 1) + args.Player.State = 2; + NetMessage.SendData((int) PacketTypes.WorldInfo, args.Player.Index); + + var group = TShock.Utils.GetGroup(user.Group); + + if (TShock.Config.ServerSideInventory) + { + if (group.HasPermission(Permissions.bypassinventorychecks)) + { + args.Player.IgnoreActionsForClearingTrashCan = false; + } + else if (!TShock.CheckInventory(args.Player)) + { + args.Player.SendMessage("Login Failed, Please fix the above errors then /login again.", Color.Cyan); + args.Player.IgnoreActionsForClearingTrashCan = true; + return true; + } + } + + if (group.HasPermission(Permissions.ignorestackhackdetection)) + args.Player.IgnoreActionsForCheating = "none"; + + if (group.HasPermission(Permissions.usebanneditem)) + args.Player.IgnoreActionsForDisabledArmor = "none"; + + args.Player.Group = group; + args.Player.UserAccountName = args.Player.Name; + args.Player.UserID = TShock.Users.GetUserID(args.Player.UserAccountName); + args.Player.IsLoggedIn = true; + args.Player.IgnoreActionsForInventory = "none"; + + if (!args.Player.IgnoreActionsForClearingTrashCan) + { + args.Player.PlayerData.CopyInventory(args.Player); + TShock.InventoryDB.InsertPlayerData(args.Player); + } + args.Player.SendMessage("Authenticated as " + args.Player.Name + " successfully.", Color.LimeGreen); + Log.ConsoleInfo(args.Player.Name + " authenticated successfully as user: " + args.Player.Name); + return true; + } + TShock.Utils.ForceKick(args.Player, "Invalid user account password.", true); + return true; + } + if (!string.IsNullOrEmpty(TShock.Config.ServerPassword)) + { + if (TShock.Config.ServerPassword == password) + { + args.Player.RequiresPassword = false; + if (args.Player.State == 1) + args.Player.State = 2; + NetMessage.SendData((int) PacketTypes.WorldInfo, args.Player.Index); + return true; + } + TShock.Utils.ForceKick(args.Player, "Incorrect Server Password"); + return true; + } + + TShock.Utils.ForceKick(args.Player, "Bad Password Attempt"); + return true; + } + + private static bool HandleGetSection(GetDataHandlerArgs args) + { + if (args.Player.RequestedSection) + return true; + + args.Player.RequestedSection = true; + if (TShock.HackedHealth(args.Player) && !args.Player.Group.HasPermission(Permissions.ignorestathackdetection)) + { + TShock.Utils.ForceKick(args.Player, "You have Hacked Health/Mana, Please use a different character."); + } + + if (!args.Player.Group.HasPermission(Permissions.ignorestackhackdetection)) + { + TShock.HackedInventory(args.Player); + } + + if (TShock.Utils.ActivePlayers() + 1 > TShock.Config.MaxSlots && + !args.Player.Group.HasPermission(Permissions.reservedslot)) + { + args.Player.SilentKickInProgress = true; + TShock.Utils.ForceKick(args.Player, TShock.Config.ServerFullReason); + return true; + } + + NetMessage.SendData((int) PacketTypes.TimeSet, -1, -1, "", 0, 0, Main.sunModY, Main.moonModY); + + if (TShock.Config.EnableGeoIP && TShock.Geo != null) + { + Log.Info(string.Format("{0} ({1}) from '{2}' group from '{3}' joined. ({4}/{5})", args.Player.Name, args.Player.IP, + args.Player.Group.Name, args.Player.Country, TShock.Utils.ActivePlayers(), + TShock.Config.MaxSlots)); + TShock.Utils.Broadcast(args.Player.Name + " has joined from the " + args.Player.Country, Color.Yellow); + } + else + { + Log.Info(string.Format("{0} ({1}) from '{2}' group joined. ({3}/{4})", args.Player.Name, args.Player.IP, + args.Player.Group.Name, TShock.Utils.ActivePlayers(), TShock.Config.MaxSlots)); + TShock.Utils.Broadcast(args.Player.Name + " has joined", Color.Yellow); + } + + if (TShock.Config.DisplayIPToAdmins) + TShock.Utils.SendLogs(string.Format("{0} has joined. IP: {1}", args.Player.Name, args.Player.IP), Color.Blue); + + return false; + } + + private static bool HandleSendTileSquare(GetDataHandlerArgs args) + { + if (args.Player.Group.HasPermission(Permissions.allowclientsideworldedit)) + return false; + + var size = args.Data.ReadInt16(); + var tileX = args.Data.ReadInt32(); + var tileY = args.Data.ReadInt32(); + + if (OnSendTileSquare(size, tileX, tileY)) + return true; + + if (size > 5) + return true; + + if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + { + args.Player.SendTileSquare(tileX, tileY, size); + return true; + } + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + var tiles = new NetTile[size,size]; + + for (int x = 0; x < size; x++) + { + for (int y = 0; y < size; y++) + { + tiles[x, y] = new NetTile(args.Data); + } + } + + bool changed = false; + for (int x = 0; x < size; x++) + { + int realx = tileX + x; + if (realx < 0 || realx >= Main.maxTilesX) + continue; + + for (int y = 0; y < size; y++) + { + int realy = tileY + y; + if (realy < 0 || realy >= Main.maxTilesY) + continue; + + var tile = Main.tile[realx, realy]; + var newtile = tiles[x, y]; + if (TShock.CheckTilePermission(args.Player, realx, realy)) + { + continue; + } + // Server now has a range check built in + /*if (TShock.CheckRangePermission(args.Player, realx, realy)) + { + continue; + }*/ + if ((tile.type == 128 && newtile.Type == 128) || (tile.type == 105 && newtile.Type == 105) || (tile.type == 139 && newtile.Type == 139)) + { + if (TShock.Config.EnableInsecureTileFixes) + { + return false; + } + } + + if (tile.type == 0x17 && newtile.Type == 0x2) + { + tile.type = 0x2; + changed = true; + } + else if (tile.type == 0x19 && newtile.Type == 0x1) + { + tile.type = 0x1; + changed = true; + } + else if ((tile.type == 0xF && newtile.Type == 0xF) || + (tile.type == 0x4F && newtile.Type == 0x4F)) + { + tile.frameX = newtile.FrameX; + tile.frameY = newtile.FrameY; + changed = true; + } + // Holy water/Unholy water + else if (tile.type == 1 && newtile.Type == 117) + { + tile.type = 117; + changed = true; + } + else if (tile.type == 1 && newtile.Type == 25) + { + tile.type = 25; + changed = true; + } + else if (tile.type == 117 && newtile.Type == 25) + { + tile.type = 25; + changed = true; + } + else if (tile.type == 25 && newtile.Type == 117) + { + tile.type = 117; + changed = true; + } + else if (tile.type == 2 && newtile.Type == 23) + { + tile.type = 23; + changed = true; + } + else if (tile.type == 2 && newtile.Type == 109) + { + tile.type = 109; + changed = true; + } + else if (tile.type == 23 && newtile.Type == 109) + { + tile.type = 109; + changed = true; + } + else if (tile.type == 109 && newtile.Type == 23) + { + tile.type = 23; + changed = true; + } + else if (tile.type == 23 && newtile.Type == 109) + { + tile.type = 109; + changed = true; + } + else if (tile.type == 53 && newtile.Type == 116) + { + tile.type = 116; + changed = true; + } + else if (tile.type == 53 && newtile.Type == 112) + { + tile.type = 112; + changed = true; + } + else if (tile.type == 112 && newtile.Type == 116) + { + tile.type = 116; + changed = true; + } + else if (tile.type == 116 && newtile.Type == 112) + { + tile.type = 112; + changed = true; + } + else if (tile.type == 112 && newtile.Type == 53) + { + tile.type = 53; + changed = true; + } + else if (tile.type == 109 && newtile.Type == 2) + { + tile.type = 2; + changed = true; + } + else if (tile.type == 116 && newtile.Type == 53) + { + tile.type = 53; + changed = true; + } + else if (tile.type == 117 && newtile.Type == 1) + { + tile.type = 1; + changed = true; + } + } + } + + if (changed) + { + TSPlayer.All.SendTileSquare(tileX, tileY, size); + WorldGen.RangeFrame(tileX, tileY, tileX + size, tileY + size); + } + else + { + args.Player.SendTileSquare(tileX, tileY, size); + } + return true; + } + + private static bool HandleTile(GetDataHandlerArgs args) + { + var type = args.Data.ReadInt8(); + var tileX = args.Data.ReadInt32(); + var tileY = args.Data.ReadInt32(); + var tiletype = args.Data.ReadInt8(); + if (OnTileEdit(args.Player, tileX, tileY, tiletype, type)) + return true; + if (tileX < 0 || tileX >= Main.maxTilesX || tileY < 0 || tileY >= Main.maxTilesY) + return false; + + if (args.Player.AwaitingName) + { + var protectedregions = TShock.Regions.InAreaRegionName(tileX, tileY); + if (protectedregions.Count == 0) + { + args.Player.SendMessage("Region is not protected", Color.Yellow); + } + else + { + string regionlist = string.Join(",", protectedregions.ToArray()); + args.Player.SendMessage("Region Name(s): " + regionlist, Color.Yellow); + } + args.Player.SendTileSquare(tileX, tileY); + args.Player.AwaitingName = false; + return true; + } + + if (args.Player.AwaitingTempPoint > 0) + { + args.Player.TempPoints[args.Player.AwaitingTempPoint - 1].X = tileX; + args.Player.TempPoints[args.Player.AwaitingTempPoint - 1].Y = tileY; + args.Player.SendMessage("Set Temp Point " + args.Player.AwaitingTempPoint, Color.Yellow); + args.Player.SendTileSquare(tileX, tileY); + args.Player.AwaitingTempPoint = 0; + return true; + } + + if (type == 1 || type == 3) + { + if (tiletype >= ((type == 1) ? Main.maxTileSets : Main.maxWallTypes)) + { + return true; + } + if (tiletype == 29 && tiletype == 97 && TShock.Config.ServerSideInventory) + { + args.Player.SendMessage("You cannot place this tile, Server side inventory is enabled.", Color.Red); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + if (tiletype == 48 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && + TShock.Itembans.ItemIsBanned("Spike", args.Player)) + { + args.Player.Disable("Using banned spikes without permissions"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + if (type == 1 && tiletype == 21 && TShock.Utils.MaxChests()) + { + args.Player.SendMessage("Reached world's max chest limit, unable to place more!", Color.Red); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + if (tiletype == 141 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && + TShock.Itembans.ItemIsBanned("Explosives", args.Player)) + { + args.Player.Disable("Using banned explosives tile without permissions"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + } + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (TShock.CheckTilePermission(args.Player, tileX, tileY, tiletype, type)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if ((tiletype == 127 || Main.tileCut[tiletype]) && (type == 0 || type == 4)) + { + return false; + } + + if (TShock.CheckRangePermission(args.Player, tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (args.Player.TileKillThreshold >= TShock.Config.TileKillThreshold) + { + args.Player.Disable("Reached TileKill threshold"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (args.Player.TilePlaceThreshold >= TShock.Config.TilePlaceThreshold) + { + args.Player.Disable("Reached TilePlace threshold"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (type == 1 && !args.Player.Group.HasPermission(Permissions.ignoreplacetiledetection)) + { + args.Player.TilePlaceThreshold++; + var coords = new Vector2(tileX, tileY); + if (!args.Player.TilesCreated.ContainsKey(coords)) + args.Player.TilesCreated.Add(coords, Main.tile[tileX, tileY].Data); + } + + if ((type == 0 || type == 4) && Main.tileSolid[Main.tile[tileX, tileY].type] && + !args.Player.Group.HasPermission(Permissions.ignorekilltiledetection)) + { + args.Player.TileKillThreshold++; + var coords = new Vector2(tileX, tileY); + if (!args.Player.TilesDestroyed.ContainsKey(coords)) + args.Player.TilesDestroyed.Add(coords, Main.tile[tileX, tileY].Data); + } + + return false; + } + + private static bool HandleTogglePvp(GetDataHandlerArgs args) + { + byte id = args.Data.ReadInt8(); + bool pvp = args.Data.ReadBoolean(); + if (OnPvpToggled(id, pvp)) + return true; + + if (id != args.Player.Index) + { + return true; + } + + if (TShock.Config.PvPMode == "disabled") + { + return true; + } + + if (args.TPlayer.hostile != pvp) + { + long seconds = (long) (DateTime.UtcNow - args.Player.LastPvpChange).TotalSeconds; + if (seconds > 5) + { + TSPlayer.All.SendMessage(string.Format("{0} has {1} PvP!", args.Player.Name, pvp ? "enabled" : "disabled"), + Main.teamColor[args.Player.Team]); + } + args.Player.LastPvpChange = DateTime.UtcNow; + } + + args.TPlayer.hostile = pvp; + + if (TShock.Config.PvPMode == "always") + { + if (!pvp) + args.Player.Spawn(); + } + + NetMessage.SendData((int) PacketTypes.TogglePvp, -1, -1, "", args.Player.Index); + + return true; + } + + private static bool HandlePlayerTeam(GetDataHandlerArgs args) + { + byte id = args.Data.ReadInt8(); + byte team = args.Data.ReadInt8(); + if (OnPlayerTeam(id, team)) + return true; + + if (id != args.Player.Index) + { + return true; + } + + args.TPlayer.team = team; + + NetMessage.SendData((int)PacketTypes.PlayerTeam, -1, -1, "", args.Player.Index); + + return true; + } + + private static bool HandlePlayerUpdate(GetDataHandlerArgs args) + { + var plr = args.Data.ReadInt8(); + var control = args.Data.ReadInt8(); + var item = args.Data.ReadInt8(); + var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + if (OnPlayerUpdate(plr, control, item, pos, vel)) + return true; + if (item < 0 || item >= args.TPlayer.inventory.Length) + { + return true; + } + + if (args.Player.LastNetPosition == Vector2.Zero) + { + return true; + } + + if (!pos.Equals(args.Player.LastNetPosition)) + { + float distance = Vector2.Distance(new Vector2(pos.X/16f, pos.Y/16f), + new Vector2(args.Player.LastNetPosition.X/16f, args.Player.LastNetPosition.Y/16f)); + if (TShock.CheckIgnores(args.Player)) + { + if (distance > TShock.Config.MaxRangeForDisabled) + { + if (args.Player.IgnoreActionsForCheating != "none") + { + args.Player.SendMessage("Disabled for cheating: " + args.Player.IgnoreActionsForCheating, + Color.Red); + } + else if (args.Player.IgnoreActionsForDisabledArmor != "none") + { + args.Player.SendMessage( + "Disabled for banned armor: " + args.Player.IgnoreActionsForDisabledArmor, Color.Red); + } + else if (args.Player.IgnoreActionsForInventory != "none") + { + args.Player.SendMessage( + "Disabled for Server Side Inventory: " + args.Player.IgnoreActionsForInventory, + Color.Red); + } + else if (TShock.Config.RequireLogin && !args.Player.IsLoggedIn) + { + args.Player.SendMessage("Please /register or /login to play!", Color.Red); + } + else if (args.Player.IgnoreActionsForClearingTrashCan) + { + args.Player.SendMessage("You need to rejoin to ensure your trash can is cleared!", Color.Red); + } + else if (TShock.Config.PvPMode == "always" && !args.TPlayer.hostile) + { + args.Player.SendMessage("PvP is forced! Enable PvP else you can't move or do anything!", + Color.Red); + } + int lastTileX = (int) (args.Player.LastNetPosition.X/16f); + int lastTileY = (int) (args.Player.LastNetPosition.Y/16f); + if (!args.Player.Teleport(lastTileX, lastTileY)) + { + args.Player.Spawn(); + } + return true; + } + return true; + } + + if (args.Player.Dead) + { + return true; + } + + if (!args.Player.Group.HasPermission(Permissions.ignorenoclipdetection) && + TSCheckNoclip(pos, args.TPlayer.width, args.TPlayer.height) && !TShock.Config.IgnoreNoClip) + { + int lastTileX = (int) (args.Player.LastNetPosition.X/16f); + int lastTileY = (int) (args.Player.LastNetPosition.Y/16f); + if (!args.Player.Teleport(lastTileX, lastTileY + 3)) + { + args.Player.SendMessage("You got stuck in a solid object, Sent to spawn point."); + args.Player.Spawn(); + } + return true; + } + args.Player.LastNetPosition = pos; + } + + if ((control & 32) == 32) + { + if (!args.Player.Group.HasPermission(Permissions.usebanneditem) && + TShock.Itembans.ItemIsBanned(args.TPlayer.inventory[item].name, args.Player)) + { + control -= 32; + args.Player.Disable("Using banned item"); + args.Player.SendMessage( + string.Format("You cannot use {0} on this server. Your actions are being ignored.", + args.TPlayer.inventory[item].name), Color.Red); + } + } + + args.TPlayer.selectedItem = item; + args.TPlayer.position = pos; + args.TPlayer.velocity = vel; + args.TPlayer.oldVelocity = args.TPlayer.velocity; + args.TPlayer.fallStart = (int) (pos.Y/16f); + args.TPlayer.controlUp = false; + args.TPlayer.controlDown = false; + args.TPlayer.controlLeft = false; + args.TPlayer.controlRight = false; + args.TPlayer.controlJump = false; + args.TPlayer.controlUseItem = false; + args.TPlayer.direction = -1; + if ((control & 1) == 1) + { + args.TPlayer.controlUp = true; + } + if ((control & 2) == 2) + { + args.TPlayer.controlDown = true; + } + if ((control & 4) == 4) + { + args.TPlayer.controlLeft = true; + } + if ((control & 8) == 8) + { + args.TPlayer.controlRight = true; + } + if ((control & 16) == 16) + { + args.TPlayer.controlJump = true; + } + if ((control & 32) == 32) + { + args.TPlayer.controlUseItem = true; + } + if ((control & 64) == 64) + { + args.TPlayer.direction = 1; + } + NetMessage.SendData((int) PacketTypes.PlayerUpdate, -1, args.Player.Index, "", args.Player.Index); + + return true; + } + + private static bool HandleProjectileNew(GetDataHandlerArgs args) + { + var ident = args.Data.ReadInt16(); + var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var knockback = args.Data.ReadSingle(); + var dmg = args.Data.ReadInt16(); + var owner = args.Data.ReadInt8(); + var type = args.Data.ReadInt8(); + owner = (byte)args.Player.Index; + var index = TShock.Utils.SearchProjectile(ident, owner); + + if (OnNewProjectile(ident, pos, vel, knockback, dmg, owner, type, index)) + return true; + + if (index > Main.maxProjectiles || index < 0) + { + return false; + } + + // Server now checks owner + ident, if owner is different, server will create new projectile. + /*if (args.Player.Index != owner) + { + args.Player.Disable(String.Format("Owner ({0}) and player ID ({1}) does not match to update projectile", owner, args.Player.Index)); + args.Player.RemoveProjectile(ident, owner); + return true; + }*/ + + if (dmg > TShock.Config.MaxProjDamage && !args.Player.Group.HasPermission(Permissions.ignoredamagecap)) + { + args.Player.Disable(String.Format("Projectile damage is higher than {0}", TShock.Config.MaxProjDamage)); + args.Player.RemoveProjectile(ident, owner); + return true; + } + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.RemoveProjectile(ident, owner); + return true; + } + + if (!TShock.Config.IgnoreProjUpdate && TShock.CheckProjectilePermission(args.Player, index, type)) + { + if (type == 100) + { //fix for skele prime + Log.Debug("Skeletron Prime's death laser ignored for cheat detection.."); + } + else + { + args.Player.Disable("Does not have projectile permission to update projectile."); + args.Player.RemoveProjectile(ident, owner); + } + return true; + } + + if (args.Player.ProjectileThreshold >= TShock.Config.ProjectileThreshold) + { + args.Player.Disable("Reached projectile update threshold"); + args.Player.RemoveProjectile(ident, owner); + return true; + } + + if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + { + args.Player.RemoveProjectile(ident, owner); + return true; + } + + if (!args.Player.Group.HasPermission(Permissions.ignoreprojectiledetection)) + { + if ((type ==90) && (TShock.Config.ProjIgnoreShrapnel))// ignore shrapnel + { + Log.Debug("Ignoring shrapnel per config.."); + } + else + { + args.Player.ProjectileThreshold++; + } + } + + return false; + } + + private static bool HandleProjectileKill(GetDataHandlerArgs args) + { + var ident = args.Data.ReadInt16(); + var owner = args.Data.ReadInt8(); + owner = (byte)args.Player.Index; + var index = TShock.Utils.SearchProjectile(ident, owner); + + if (index > Main.maxProjectiles || index < 0) + { + return false; + } + + var type = Main.projectile[index].type; + + // Players can no longer destroy projectiles that are not theirs as of 1.1.2 + /*if (args.Player.Index != Main.projectile[index].owner && type != 102 && type != 100 && !TShock.Config.IgnoreProjKill) // workaround for skeletron prime projectiles + { + args.Player.Disable(String.Format("Owner ({0}) and player ID ({1}) does not match to kill projectile of type: {3}", Main.projectile[index].owner, args.Player.Index, type)); + args.Player.RemoveProjectile(ident, owner); + return true; + }*/ + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.RemoveProjectile(ident, owner); + return true; + } + + if (TShock.CheckProjectilePermission(args.Player, index, type) && type != 102 && type != 100 && !TShock.Config.IgnoreProjKill) + { + args.Player.Disable("Does not have projectile permission to kill projectile"); + args.Player.RemoveProjectile(ident, owner); + return true; + } + + if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + { + args.Player.RemoveProjectile(ident, owner); + return true; + } + + return false; + } + + private static bool HandlePlayerKillMe(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + var direction = args.Data.ReadInt8(); + var dmg = args.Data.ReadInt16(); + var pvp = args.Data.ReadInt8() == 0; + if (OnKillMe(id, direction, dmg, pvp)) + return true; + int textlength = (int) (args.Data.Length - args.Data.Position - 1); + string deathtext = ""; + if (textlength > 0) + { + deathtext = Encoding.UTF8.GetString(args.Data.ReadBytes(textlength)); + /*if (!TShock.Utils.ValidString(deathtext)) + { + return true; + }*/ + } + + args.Player.LastDeath = DateTime.Now; + args.Player.Dead = true; + + return false; + } + + private static bool HandleLiquidSet(GetDataHandlerArgs args) + { + int tileX = args.Data.ReadInt32(); + int tileY = args.Data.ReadInt32(); + byte liquid = args.Data.ReadInt8(); + bool lava = args.Data.ReadBoolean(); + + if (OnLiquidSet(tileX, tileY, liquid, lava)) + return true; + + //The liquid was picked up. + if (liquid == 0) + return false; + + if (tileX < 0 || tileX >= Main.maxTilesX || tileY < 0 || tileY >= Main.maxTilesY) + return false; + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (args.Player.TileLiquidThreshold >= TShock.Config.TileLiquidThreshold) + { + args.Player.Disable("Reached TileLiquid threshold"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (!args.Player.Group.HasPermission(Permissions.ignoreliquidsetdetection)) + { + args.Player.TileLiquidThreshold++; + } + + int bucket = 0; + if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 206) + { + bucket = 1; + } + else if (args.TPlayer.inventory[args.TPlayer.selectedItem].type == 207) + { + bucket = 2; + } + + if (lava && bucket != 2 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && + TShock.Itembans.ItemIsBanned("Lava Bucket", args.Player)) + { + args.Player.Disable("Using banned lava bucket without permissions"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (!lava && bucket != 1 && !args.Player.Group.HasPermission(Permissions.usebanneditem) && + TShock.Itembans.ItemIsBanned("Water Bucket", args.Player)) + { + args.Player.Disable("Using banned water bucket without permissions"); + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (TShock.CheckTilePermission(args.Player, tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (TShock.CheckRangePermission(args.Player, tileX, tileY, 16)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + return false; + } + + private static bool HandleTileKill(GetDataHandlerArgs args) + { + var tileX = args.Data.ReadInt32(); + var tileY = args.Data.ReadInt32(); + if (OnTileKill(tileX, tileY)) + return true; + if (tileX < 0 || tileX >= Main.maxTilesX || tileY < 0 || tileY >= Main.maxTilesY) + return false; + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (Main.tile[tileX, tileY].type != 0x15 && (!TShock.Utils.MaxChests() && Main.tile[tileX, tileY].type != 0)) //Chest + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (TShock.CheckTilePermission(args.Player, tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + if (TShock.CheckRangePermission(args.Player, tileX, tileY)) + { + args.Player.SendTileSquare(tileX, tileY); + return true; + } + + return false; + } + + private static bool HandleSpawn(GetDataHandlerArgs args) + { + var player = args.Data.ReadInt8(); + var spawnx = args.Data.ReadInt32(); + var spawny = args.Data.ReadInt32(); + + if (OnPlayerSpawn(player, spawnx, spawny)) + return true; + + if (args.Player.InitSpawn && args.TPlayer.inventory[args.TPlayer.selectedItem].type != 50) + { + if (args.TPlayer.difficulty == 1 && (TShock.Config.KickOnMediumcoreDeath || TShock.Config.BanOnMediumcoreDeath)) + { + if (args.TPlayer.selectedItem != 50) + { + if (TShock.Config.BanOnMediumcoreDeath) + { + if (!TShock.Utils.Ban(args.Player, TShock.Config.MediumcoreBanReason)) + TShock.Utils.ForceKick(args.Player, "Death results in a ban, but can't ban you"); + } + else + { + TShock.Utils.ForceKick(args.Player, TShock.Config.MediumcoreKickReason); + } + return true; + } + } + } + else + args.Player.InitSpawn = true; + + args.Player.Dead = false; + return false; + } + + private static bool HandleChestOpen(GetDataHandlerArgs args) + { + var x = args.Data.ReadInt32(); + var y = args.Data.ReadInt32(); + + if (OnChestOpen(x, y)) + return true; + + if (TShock.CheckIgnores(args.Player)) + { + return true; + } + + if (TShock.CheckRangePermission(args.Player, x, y)) + { + return true; + } + + if (TShock.CheckTilePermission(args.Player, x, y) && TShock.Config.RegionProtectChests) + { + return true; + } + + return false; + } + + private static bool HandleChestItem(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var slot = args.Data.ReadInt8(); + var stacks = args.Data.ReadInt8(); + var prefix = args.Data.ReadInt8(); + var type = args.Data.ReadInt16(); + + if (OnChestItemChange(id, slot, stacks, prefix, type)) + return true; + + if (args.TPlayer.chest != id) + { + return false; + } + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendData(PacketTypes.ChestItem, "", id, slot); + return true; + } + + Item item = new Item(); + item.netDefaults(type); + if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(item.name, args.Player)) + { + return false; + } + + if (TShock.CheckTilePermission(args.Player, Main.chest[id].x, Main.chest[id].y) && TShock.Config.RegionProtectChests) + { + return false; + } + + if (TShock.CheckRangePermission(args.Player, Main.chest[id].x, Main.chest[id].y)) + { + return false; + } + + return false; + } + + private static bool HandleSign(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var x = args.Data.ReadInt32(); + var y = args.Data.ReadInt32(); + + if (OnSignEvent(id, x, y)) + return true; + + if (TShock.CheckTilePermission(args.Player, x, y)) + { + args.Player.SendData(PacketTypes.SignNew, "", id); + return true; + } + + if (TShock.CheckRangePermission(args.Player, x, y)) + { + args.Player.SendData(PacketTypes.SignNew, "", id); + return true; + } + return false; + } + + private static bool UpdateNPCHome(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var x = args.Data.ReadInt16(); + var y = args.Data.ReadInt16(); + var homeless = args.Data.ReadInt8(); + + if (OnUpdateNPCHome(id, x, y, homeless)) + return true; + + if (!args.Player.Group.HasPermission(Permissions.movenpc)) + { + args.Player.SendMessage("You do not have permission to relocate NPCs.", Color.Red); + args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, + Convert.ToByte(Main.npc[id].homeless)); + return true; + } + + if (TShock.CheckTilePermission(args.Player, x, y)) + { + args.Player.SendMessage( "You do not have access to modify this area.", Color.Red); + args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, + Convert.ToByte(Main.npc[id].homeless)); + return true; + } + + //removed until NPC Home packet actually sends their home coords. + /*if (TShock.CheckRangePermission(args.Player, x, y)) + { + args.Player.SendData(PacketTypes.UpdateNPCHome, "", id, Main.npc[id].homeTileX, Main.npc[id].homeTileY, + Convert.ToByte(Main.npc[id].homeless)); + return true; + }*/ + return false; + } + + private static bool HandlePlayerBuff(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + var type = args.Data.ReadInt8(); + var time = args.Data.ReadInt16(); + + if (OnPlayerBuff(id, type, time)) + return true; + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendData(PacketTypes.PlayerBuff, "", id); + return true; + } + if (!TShock.Players[id].TPlayer.hostile) + { + args.Player.SendData(PacketTypes.PlayerBuff, "", id); + return true; + } + if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 50)) + { + args.Player.SendData(PacketTypes.PlayerBuff, "", id); + return true; + } + if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + { + args.Player.SendData(PacketTypes.PlayerBuff, "", id); + return true; + } + + if (WhitelistBuffMaxTime[type] > 0 && time <= WhitelistBuffMaxTime[type]) + { + return false; + } + + args.Player.SendData(PacketTypes.PlayerBuff, "", id); + return true; + } + + private static bool HandleItemDrop(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt16(); + var pos = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var vel = new Vector2(args.Data.ReadSingle(), args.Data.ReadSingle()); + var stacks = args.Data.ReadInt8(); + var prefix = args.Data.ReadInt8(); + var type = args.Data.ReadInt16(); + + if (OnItemDrop(id, pos, vel, stacks, prefix, type)) + return true; + + if (type == 0) //Item removed, let client do this to prevent item duplication client side + { + return false; + } + + if (TShock.CheckRangePermission(args.Player, (int) (pos.X/16f), (int) (pos.Y/16f))) + { + args.Player.SendData(PacketTypes.ItemDrop, "", id); + return true; + } + + Item item = new Item(); + item.netDefaults(type); + if (stacks > item.maxStack || TShock.Itembans.ItemIsBanned(item.name, args.Player)) + { + args.Player.SendData(PacketTypes.ItemDrop, "", id); + return true; + } + if ((TShock.Config.ServerSideInventory) && (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond - args.Player.LoginMS < TShock.Config.LogonDiscardThreshold)) + { + //Player is probably trying to sneak items onto the server in their hands!!! + Log.ConsoleInfo(string.Format("Player {0} tried to sneak {1} onto the server!", args.Player.Name, item.name)); + args.Player.SendData(PacketTypes.ItemDrop, "", id); + return true; + + } + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendData(PacketTypes.ItemDrop, "", id); + return true; + } + + return false; + } + + private static bool HandlePlayerDamage(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + var direction = args.Data.ReadInt8(); + var dmg = args.Data.ReadInt16(); + var pvp = args.Data.ReadInt8(); + var crit = args.Data.ReadInt8(); + + if (OnPlayerDamage(id, direction, dmg, pvp, crit)) + return true; + + int textlength = (int) (args.Data.Length - args.Data.Position - 1); + string deathtext = ""; + if (textlength > 0) + { + deathtext = Encoding.UTF8.GetString(args.Data.ReadBytes(textlength)); + /*if (!TShock.Utils.ValidString(deathtext)) + { + return true; + }*/ + } + + if (TShock.Players[id] == null) + return true; + + if (dmg > TShock.Config.MaxDamage && !args.Player.Group.HasPermission(Permissions.ignoredamagecap)) + { + args.Player.Disable(String.Format("Player damage exceeded {0}", TShock.Config.MaxDamage ) ); + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + return true; + } + + if (!TShock.Players[id].TPlayer.hostile) + { + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + return true; + } + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + return true; + } + + if (TShock.CheckRangePermission(args.Player, TShock.Players[id].TileX, TShock.Players[id].TileY, 100)) + { + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + return true; + } + + if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + { + args.Player.SendData(PacketTypes.PlayerHp, "", id); + args.Player.SendData(PacketTypes.PlayerUpdate, "", id); + return true; + } + + return false; + } + + private static bool HandleNpcStrike(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + var direction = args.Data.ReadInt8(); + var dmg = args.Data.ReadInt16(); + var pvp = args.Data.ReadInt8(); + var crit = args.Data.ReadInt8(); + + if (OnNPCStrike(id, direction, dmg, pvp, crit)) + return true; + + if (Main.npc[id] == null) + return true; + + if (dmg > TShock.Config.MaxDamage && !args.Player.Group.HasPermission(Permissions.ignoredamagecap)) + { + args.Player.Disable(String.Format("NPC damage exceeded {0}", TShock.Config.MaxDamage ) ); + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + return true; + } + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + return true; + } + + if (Main.npc[id].townNPC && !args.Player.Group.HasPermission(Permissions.movenpc)) + { + args.Player.SendMessage( "You don't have permission to move the NPC", Color.Yellow); + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + return true; + } + + if (TShock.Config.RangeChecks && + TShock.CheckRangePermission(args.Player, (int) (Main.npc[id].position.X/16f), (int) (Main.npc[id].position.Y/16f), + 128)) + { + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + return true; + } + + if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + { + args.Player.SendData(PacketTypes.NpcUpdate, "", id); + return true; + } + + return false; + } + + private static bool HandleSpecial(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + var type = args.Data.ReadInt8(); + + if (OnNPCSpecial(id, type)) + return true; + + if (type == 1 && TShock.Config.DisableDungeonGuardian) + { + args.Player.SendMessage("The Dungeon Guardian returned you to your spawn point", Color.Purple); + args.Player.Spawn(); + return true; + } + + return false; + } + + private static bool HandlePlayerAnimation(GetDataHandlerArgs args) + { + + if (OnPlayerAnimation()) + return true; + + if (TShock.CheckIgnores(args.Player)) + { + args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); + return true; + } + + if ((DateTime.UtcNow - args.Player.LastThreat).TotalMilliseconds < 5000) + { + args.Player.SendData(PacketTypes.PlayerAnimation, "", args.Player.Index); + return true; + } + + return false; + } + + private static bool HandlePlayerBuffUpdate(GetDataHandlerArgs args) + { + var id = args.Data.ReadInt8(); + + if (OnPlayerBuffUpdate(id)) + return true; + + for (int i = 0; i < 10; i++) + { + var buff = args.Data.ReadInt8(); + + if (buff == 10) + { + if (!args.Player.Group.HasPermission(Permissions.usebanneditem) && + TShock.Itembans.ItemIsBanned("Invisibility Potion", args.Player)) + buff = 0; + else if (TShock.Config.DisableInvisPvP && args.TPlayer.hostile) + buff = 0; + } + + args.TPlayer.buffType[i] = buff; + if (args.TPlayer.buffType[i] > 0) + { + args.TPlayer.buffTime[i] = 60; + } + else + { + args.TPlayer.buffTime[i] = 0; + } + } + NetMessage.SendData((int) PacketTypes.PlayerBuff, -1, args.Player.Index, "", args.Player.Index); + return true; + } + + private static bool HandleSpawnBoss(GetDataHandlerArgs args) + { + var spawnboss = false; + var invasion = -1; + var plr = args.Data.ReadInt32(); + var Type = args.Data.ReadInt32(); + spawnboss = (Type == 4 || Type == 13 || (Type == 50 || Type == 125) || (Type == 126 || Type == 134 || (Type == (int) sbyte.MaxValue || Type == 128))); + if (!spawnboss) + { + switch (Type) + { + case -1: + invasion = 1; + break; + case -2: + invasion = 2; + break; + } + } + if (spawnboss && !args.Player.Group.HasPermission(Permissions.summonboss)) + { + args.Player.SendMessage("You don't have permission to summon a boss.", Color.Red); + return true; + } + if (invasion != -1 && !args.Player.Group.HasPermission(Permissions.startinvasion)) + { + args.Player.SendMessage("You don't have permission to start an invasion.", Color.Red); + return true; + } + if (!spawnboss && invasion == -1) + return true; + if (plr != args.Player.Index) + return true; + + string boss; + switch (Type) + { + case -2: + boss = "the Snow Legion"; + break; + case -1: + boss = "a Goblin Invasion"; + break; + case 4: + boss = "the Eye of Cthulhu"; + break; + case 13: + boss = "the Eater of Worlds"; + break; + case 50: + boss = "the King Slime"; + break; + case 125: + boss = "Retinazer"; + break; + case 126: + boss = "Spazmatism"; + break; + case 134: + boss = "the Destroyer"; + break; + case sbyte.MaxValue: + boss = "Skeleton Prime"; + break; + case 128: + boss = "Skeleton Prime"; + break; + default: + boss = "error"; + break; + } + + TShock.Utils.SendLogs(string.Format("{0} summoned {1}", args.Player.Name, boss), Color.Red); + return false; + } + } +} diff --git a/TShockAPI/Group.cs b/TShockAPI/Group.cs index 01131a5e..264570fe 100644 --- a/TShockAPI/Group.cs +++ b/TShockAPI/Group.cs @@ -1,106 +1,211 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011 The TShock Team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -using System; -using System.Collections.Generic; - -namespace TShockAPI -{ - public class Group - { - public readonly List permissions = new List(); - public readonly List negatedpermissions = new List(); - - public string Name { get; set; } - public Group Parent { get; set; } - public int Order { get; set; } - public string Prefix { get; set; } - public string Suffix { get; set; } - - public byte R = 255; - public byte G = 255; - public byte B = 255; - - public Group(string groupname, Group parentgroup = null, string chatcolor = "255,255,255") - { - Name = groupname; - Parent = parentgroup; - byte.TryParse(chatcolor.Split(',')[0], out R); - byte.TryParse(chatcolor.Split(',')[1], out G); - byte.TryParse(chatcolor.Split(',')[2], out B); - } - - public virtual bool HasPermission(string permission) - { - var cur = this; - var traversed = new List(); - while (cur != null) - { - if (string.IsNullOrEmpty(permission)) - return true; - if (cur.negatedpermissions.Contains(permission)) - return false; - if (cur.permissions.Contains(permission)) - return true; - if (traversed.Contains(cur)) - { - throw new Exception("Infinite group parenting ({0})".SFormat(cur.Name)); - } - traversed.Add(cur); - cur = cur.Parent; - } - return false; - } - - public void NegatePermission(string permission) - { - negatedpermissions.Add(permission); - } - - public void AddPermission(string permission) - { - permissions.Add(permission); - } - - public void SetPermission(List permission) - { - permissions.Clear(); - foreach (string s in permission) - { - permissions.Add(s); - } - } - } - - public class SuperAdminGroup : Group - { - public SuperAdminGroup() - : base("superadmin") - { - R = (byte) TShock.Config.SuperAdminChatRGB[0]; - G = (byte) TShock.Config.SuperAdminChatRGB[1]; - B = (byte) TShock.Config.SuperAdminChatRGB[2]; - Prefix = TShock.Config.SuperAdminChatPrefix; - Suffix = TShock.Config.SuperAdminChatSuffix; - } - - public override bool HasPermission(string permission) - { - return true; - } - } +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.Linq; +using System.Collections.Generic; + +namespace TShockAPI +{ + public class Group + { + // NOTE: Using a const still suffers from needing to recompile to change the default + // ideally we would use a static but this means it can't be used for the default parameter :( + public const string defaultChatColor = "255.255.255"; + public readonly List permissions = new List(); + public readonly List negatedpermissions = new List(); + + public string Name { get; set; } + public Group Parent { get; set; } + public int Order { get; set; } + public string Prefix { get; set; } + public string Suffix { get; set; } + public string ParentName { get { return (null == Parent) ? "" : Parent.Name; } } + public string ChatColor + { + get { return string.Format("{0}{1}{2}", R.ToString("X2"), G.ToString("X2"), B.ToString("X2")); } + set + { + if (null != value) + { + string[] parts = value.Split(','); + if (3 == parts.Length) + { + byte r, g, b; + if (byte.TryParse(parts[0], out r) && byte.TryParse(parts[1], out g) && byte.TryParse(parts[2], out b)) + { + R = r; + G = g; + B = b; + return; + } + } + } + } + } + + public string Permissions + { + get + { + List all = new List(permissions); + negatedpermissions.ForEach(p => all.Add("!" + p)); + return string.Join(",", all); + } + set + { + permissions.Clear(); + negatedpermissions.Clear(); + if (null != value) + value.Split(',').ForEach(p => AddPermission(p.Trim())); + } + } + + public List TotalPermissions + { + get + { + var cur = this; + var traversed = new List(); + HashSet all = new HashSet(); + while (cur != null) + { + foreach (var perm in cur.permissions) + { + all.Add(perm); + } + + foreach (var perm in cur.negatedpermissions) + { + all.Remove(perm); + } + + if (traversed.Contains(cur)) + { + throw new Exception("Infinite group parenting ({0})".SFormat(cur.Name)); + } + traversed.Add(cur); + cur = cur.Parent; + } + return all.ToList(); + } + } + + public byte R = 255; + public byte G = 255; + public byte B = 255; + +#if COMPAT_SIGS + [Obsolete("This constructor is for signature compatibility for external code only")] + public Group(string groupname, Group parentgroup, string chatcolor) + : this(groupname, parentgroup, chatcolor, null) + { + } +#endif + + public Group(string groupname, Group parentgroup = null, string chatcolor = "255,255,255", string permissions = null) + { + Name = groupname; + Parent = parentgroup; + ChatColor = chatcolor; + Permissions = permissions; + } + + public virtual bool HasPermission(string permission) + { + if (string.IsNullOrEmpty(permission)) + return true; + + var cur = this; + var traversed = new List(); + while (cur != null) + { + if (cur.negatedpermissions.Contains(permission)) + return false; + if (cur.permissions.Contains(permission)) + return true; + if (traversed.Contains(cur)) + { + throw new Exception("Infinite group parenting ({0})".SFormat(cur.Name)); + } + traversed.Add(cur); + cur = cur.Parent; + } + return false; + } + + public void NegatePermission(string permission) + { + // Avoid duplicates + if (!negatedpermissions.Contains(permission)) + { + negatedpermissions.Add(permission); + permissions.Remove(permission); // Ensure we don't have conflicting definitions for a permissions + } + } + + public void AddPermission(string permission) + { + if (permission.StartsWith("!")) + { + NegatePermission(permission.Substring(1)); + return; + } + // Avoid duplicates + if (!permissions.Contains(permission)) + { + permissions.Add(permission); + negatedpermissions.Remove(permission); // Ensure we don't have conflicting definitions for a permissions + } + } + + public void SetPermission(List permission) + { + permissions.Clear(); + negatedpermissions.Clear(); + permission.ForEach(p => AddPermission(p)); + } + + public void RemovePermission(string permission) + { + if (permission.StartsWith("!")) + { + negatedpermissions.Remove(permission.Substring(1)); + return; + } + permissions.Remove(permission); + } + } + + public class SuperAdminGroup : Group + { + public SuperAdminGroup() + : base("superadmin") + { + R = (byte) TShock.Config.SuperAdminChatRGB[0]; + G = (byte) TShock.Config.SuperAdminChatRGB[1]; + B = (byte) TShock.Config.SuperAdminChatRGB[2]; + Prefix = TShock.Config.SuperAdminChatPrefix; + Suffix = TShock.Config.SuperAdminChatSuffix; + } + + public override bool HasPermission(string permission) + { + return true; + } + } } \ No newline at end of file diff --git a/TShockAPI/Permissions.cs b/TShockAPI/Permissions.cs index fb79bdca..9933b95b 100644 --- a/TShockAPI/Permissions.cs +++ b/TShockAPI/Permissions.cs @@ -200,7 +200,7 @@ namespace TShockAPI c => c.Name + (c.Names.Count > 1 ? "({0})".SFormat(string.Join(" ", c.Names.ToArray(), 1, c.Names.Count - 1)) : "")); - sb.AppendLine("## {0} ".SFormat(name)); + sb.AppendLine("## {0} ".SFormat(name)); sb.AppendLine("**Description:** {0} ".SFormat(desc)); sb.AppendLine("**Commands:** {0} ".SFormat(strs.Count() > 0 ? string.Join(" ", strs) : "None")); sb.AppendLine(); diff --git a/TShockAPI/Properties/AssemblyInfo.cs b/TShockAPI/Properties/AssemblyInfo.cs index 5a72e5d3..ef64ab0f 100644 --- a/TShockAPI/Properties/AssemblyInfo.cs +++ b/TShockAPI/Properties/AssemblyInfo.cs @@ -48,5 +48,5 @@ using System.Runtime.InteropServices; // Build Number // MMdd of the build -[assembly: AssemblyVersion("3.7.0.0204")] -[assembly: AssemblyFileVersion("3.7.0.0204")] +[assembly: AssemblyVersion("3.8.0.0304")] +[assembly: AssemblyFileVersion("3.8.0.0304")] diff --git a/TShockAPI/RconHandler.cs b/TShockAPI/RconHandler.cs index 9da51328..539e87b7 100644 --- a/TShockAPI/RconHandler.cs +++ b/TShockAPI/RconHandler.cs @@ -253,9 +253,7 @@ namespace TShockAPI WorldGen.genRand = new Random(); if (text.StartsWith("exit")) { - TShock.Utils.ForceKickAll("Server shutting down!"); - WorldGen.saveWorld(false); - Netplay.disconnect = true; + TShock.Utils.StopServer(); return "Server shutting down."; } else if (text.StartsWith("playing") || text.StartsWith("/playing")) diff --git a/TShockAPI/Rest/Rest.cs b/TShockAPI/Rest/Rest.cs index 719c501d..0875b3b4 100644 --- a/TShockAPI/Rest/Rest.cs +++ b/TShockAPI/Rest/Rest.cs @@ -21,6 +21,7 @@ using System.ComponentModel; using System.Net; using System.Text; using System.Text.RegularExpressions; +using System.Reflection; using HttpServer; using HttpServer.Headers; using Newtonsoft.Json; @@ -41,6 +42,7 @@ namespace Rests { private readonly List commands = new List(); private HttpListener listener; + private StringHeader serverHeader; public IPAddress Ip { get; set; } public int Port { get; set; } @@ -48,6 +50,9 @@ namespace Rests { Ip = ip; Port = port; + string appName = this.GetType().Assembly.GetName().Version.ToString(); + AssemblyName ass = this.GetType().Assembly.GetName(); + serverHeader = new StringHeader("Server", String.Format("{0}/{1}", ass.Name, ass.Version)); } public virtual void Start() @@ -117,13 +122,14 @@ namespace Rests throw new NullReferenceException("obj"); if (OnRestRequestCall(e)) - return; + return; var str = JsonConvert.SerializeObject(obj, Formatting.Indented); e.Response.Connection.Type = ConnectionType.Close; + e.Response.ContentType = new ContentTypeHeader("application/json"); + e.Response.Add(serverHeader); e.Response.Body.Write(Encoding.ASCII.GetBytes(str), 0, str.Length); e.Response.Status = HttpStatusCode.OK; - return; } protected virtual object ProcessRequest(object sender, RequestEventArgs e) diff --git a/TShockAPI/Rest/RestCommand.cs b/TShockAPI/Rest/RestCommand.cs index 13c6b129..3e54a673 100644 --- a/TShockAPI/Rest/RestCommand.cs +++ b/TShockAPI/Rest/RestCommand.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011 The TShock Team diff --git a/TShockAPI/Rest/RestManager.cs b/TShockAPI/Rest/RestManager.cs index 50e029a6..e653bffe 100644 --- a/TShockAPI/Rest/RestManager.cs +++ b/TShockAPI/Rest/RestManager.cs @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using HttpServer; @@ -36,211 +37,269 @@ namespace TShockAPI public void RegisterRestfulCommands() { - Rest.Register(new RestCommand("/status", Status) {RequiresToken = false}); - Rest.Register(new RestCommand("/tokentest", TokenTest) {RequiresToken = true}); + // Server Commands + Rest.Register(new RestCommand("/v2/server/broadcast", ServerBroadcast)); + Rest.Register(new RestCommand("/v2/server/off", ServerOff)); + Rest.Register(new RestCommand("/v2/server/rawcmd", ServerCommand)); + Rest.Register(new RestCommand("/v2/server/status", ServerStatusV2) { RequiresToken = false }); + Rest.Register(new RestCommand("/tokentest", ServerTokenTest)); + Rest.Register(new RestCommand("/status", ServerStatus) { RequiresToken = false }); - Rest.Register(new RestCommand("/v2/users/activelist", UserListV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/users/read", UserInfoV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/users/destroy", UserDestroyV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/users/update", UserUpdateV2) { RequiresToken = true }); + // User Commands + Rest.Register(new RestCommand("/v2/users/activelist", UserActiveListV2)); + Rest.Register(new RestCommand("/v2/users/create", UserCreateV2)); + Rest.Register(new RestCommand("/v2/users/list", UserListV2)); + Rest.Register(new RestCommand("/v2/users/read", UserInfoV2)); + Rest.Register(new RestCommand("/v2/users/destroy", UserDestroyV2)); + Rest.Register(new RestCommand("/v2/users/update", UserUpdateV2)); - Rest.Register(new RestCommand("/bans/create", BanCreate) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/bans/read", BanInfoV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/bans/destroy", BanDestroyV2) { RequiresToken = true }); + // Ban Commands + Rest.Register(new RestCommand("/bans/create", BanCreate)); + Rest.Register(new RestCommand("/v2/bans/list", BanListV2)); + Rest.Register(new RestCommand("/v2/bans/read", BanInfoV2)); + Rest.Register(new RestCommand("/v2/bans/destroy", BanDestroyV2)); - Rest.Register(new RestCommand("/lists/players", PlayerList) {RequiresToken = true}); + // World Commands + Rest.Register(new RestCommand("/world/read", WorldRead)); + Rest.Register(new RestCommand("/world/meteor", WorldMeteor)); + Rest.Register(new RestCommand("/world/bloodmoon/{bool}", WorldBloodmoon)); + Rest.Register(new RestCommand("/v2/world/save", WorldSave)); + Rest.Register(new RestCommand("/v2/world/autosave/state/{bool}", WorldChangeSaveSettings)); + Rest.Register(new RestCommand("/v2/world/butcher", WorldButcher)); - Rest.Register(new RestCommand("/world/read", WorldRead) {RequiresToken = true}); - Rest.Register(new RestCommand("/world/meteor", WorldMeteor) {RequiresToken = true}); - Rest.Register(new RestCommand("/world/bloodmoon/{bool}", WorldBloodmoon) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/world/save", WorldSave) { RequiresToken = true}); - Rest.Register(new RestCommand("/v2/world/autosave/state/{bool}", ChangeWorldSaveSettings) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/world/butcher", Butcher) {RequiresToken = true}); + // Player Commands + Rest.Register(new RestCommand("/lists/players", PlayerList)); + Rest.Register(new RestCommand("/v2/players/list", PlayerListV2)); + Rest.Register(new RestCommand("/v2/players/read", PlayerReadV2)); + Rest.Register(new RestCommand("/v2/players/kick", PlayerKickV2)); + Rest.Register(new RestCommand("/v2/players/ban", PlayerBanV2)); + Rest.Register(new RestCommand("/v2/players/kill", PlayerKill)); + Rest.Register(new RestCommand("/v2/players/mute", PlayerMute)); + Rest.Register(new RestCommand("/v2/players/unmute", PlayerUnMute)); - Rest.Register(new RestCommand("/v2/players/read", PlayerReadV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/players/kick", PlayerKickV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/players/ban", PlayerBanV2) { RequiresToken = true }); - Rest.Register(new RestCommand("/v2/players/kill", PlayerKill) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/players/mute", PlayerMute) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/players/unmute", PlayerUnMute) {RequiresToken = true}); - - Rest.Register(new RestCommand("/v2/server/broadcast", Broadcast) { RequiresToken = true}); - Rest.Register(new RestCommand("/v2/server/off", Off) {RequiresToken = true}); - Rest.Register(new RestCommand("/v2/server/rawcmd", ServerCommand) {RequiresToken = true}); + // Group Commands + Rest.Register(new RestCommand("/v2/groups/list", GroupList)); + Rest.Register(new RestCommand("/v2/groups/read", GroupInfo)); + Rest.Register(new RestCommand("/v2/groups/destroy", GroupDestroy)); + Rest.Register(new RestCommand("/v2/groups/create", GroupCreate)); + Rest.Register(new RestCommand("/v2/groups/update", GroupUpdate)); } #region RestServerMethods private object ServerCommand(RestVerbs verbs, IParameterCollection parameters) { - if (parameters["cmd"] != null && parameters["cmd"].Trim() != "") - { - TSRestPlayer tr = new TSRestPlayer(); - RestObject ro = new RestObject("200"); - Commands.HandleCommand(tr, parameters["cmd"]); - foreach (string s in tr.GetCommandOutput()) - { - ro.Add("response", s); - } - return ro; - } - RestObject fail = new RestObject("400"); - fail["response"] = "Missing or blank cmd parameter."; - return fail; + if (string.IsNullOrWhiteSpace(parameters["cmd"])) + return RestMissingParam("cmd"); + + TSRestPlayer tr = new TSRestPlayer(); + Commands.HandleCommand(tr, parameters["cmd"]); + return RestResponse(string.Join("\n", tr.GetCommandOutput())); } - private object Off(RestVerbs verbs, IParameterCollection parameters) + private object ServerOff(RestVerbs verbs, IParameterCollection parameters) { - bool confirm; - bool.TryParse(parameters["confirm"], out confirm); - bool nosave; - bool.TryParse(parameters["nosave"], out nosave); + if (!GetBool(parameters["confirm"], false)) + return RestInvalidParam("confirm"); - if (confirm == true) - { - if (!nosave) - WorldGen.saveWorld(); - Netplay.disconnect = true; - RestObject reply = new RestObject("200"); - reply["response"] = "The server is shutting down."; - return reply; - } - RestObject fail = new RestObject("400"); - fail["response"] = "Invalid/missing confirm switch, and/or missing nosave switch."; - return fail; + // Inform players the server is shutting down + var msg = string.IsNullOrWhiteSpace(parameters["message"]) ? "Server is shutting down" : parameters["message"]; + TShock.Utils.StopServer(!GetBool(parameters["nosave"], false), msg); + + return RestResponse("The server is shutting down"); } - private object Broadcast(RestVerbs verbs, IParameterCollection parameters) + private object ServerBroadcast(RestVerbs verbs, IParameterCollection parameters) { - if (parameters["msg"] != null && parameters["msg"].Trim() != "") - { - TShock.Utils.Broadcast(parameters["msg"]); - RestObject reply = new RestObject("200"); - reply["response"] = "The message was broadcasted successfully."; - return reply; - } - RestObject fail = new RestObject("400"); - fail["response"] = "Broadcast failed."; - return fail; + var msg = parameters["msg"]; + if (string.IsNullOrWhiteSpace(msg)) + return RestMissingParam("msg"); + TShock.Utils.Broadcast(msg); + return RestResponse("The message was broadcasted successfully"); } - #endregion - - #region RestMethods - - private object TokenTest(RestVerbs verbs, IParameterCollection parameters) - { - return new Dictionary - {{"status", "200"}, {"response", "Token is valid and was passed through correctly."}}; - } - - private object Status(RestVerbs verbs, IParameterCollection parameters) + private object ServerStatus(RestVerbs verbs, IParameterCollection parameters) { if (TShock.Config.EnableTokenEndpointAuthentication) - return new RestObject("403") {Error = "Server settings require a token for this API call."}; + return RestError("Server settings require a token for this API call"); - var activeplayers = Main.player.Where(p => p != null && p.active).ToList(); - string currentPlayers = string.Join(", ", activeplayers.Select(p => p.name)); + var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); + return new RestObject() + { + {"name", TShock.Config.ServerNickname}, + {"port", Convert.ToString(TShock.Config.ServerPort)}, + {"playercount", Convert.ToString(activeplayers.Count())}, + {"players", string.Join(", ", activeplayers.Select(p => p.name))}, + }; + } - var ret = new RestObject("200"); - ret["name"] = TShock.Config.ServerNickname; - ret["port"] = Convert.ToString(TShock.Config.ServerPort); - ret["playercount"] = Convert.ToString(activeplayers.Count()); - ret["players"] = currentPlayers; + private object ServerStatusV2(RestVerbs verbs, IParameterCollection parameters) + { + if (TShock.Config.EnableTokenEndpointAuthentication) + return RestError("Server settings require a token for this API call"); + var ret = new RestObject() + { + {"name", TShock.Config.ServerNickname}, + {"port", TShock.Config.ServerPort}, + {"playercount", Main.player.Where(p => null != p && p.active).Count()}, + {"maxplayers", TShock.Config.MaxSlots}, + {"world", Main.worldName} + }; + + if (GetBool(parameters["players"], false)) + { + var players = new ArrayList(); + foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p)) + { + var p = PlayerFilter(tsPlayer, parameters); + if (null != p) + players.Add(p); + } + ret.Add("players", players); + } + + if (GetBool(parameters["rules"], false)) + { + var rules = new Dictionary(); + rules.Add("AutoSave", TShock.Config.AutoSave); + rules.Add("DisableBuild", TShock.Config.DisableBuild); + rules.Add("DisableClownBombs", TShock.Config.DisableClownBombs); + rules.Add("DisableDungeonGuardian", TShock.Config.DisableDungeonGuardian); + rules.Add("DisableInvisPvP", TShock.Config.DisableInvisPvP); + rules.Add("DisableSnowBalls", TShock.Config.DisableSnowBalls); + rules.Add("DisableTombstones", TShock.Config.DisableTombstones); + rules.Add("EnableWhitelist", TShock.Config.EnableWhitelist); + rules.Add("HardcoreOnly", TShock.Config.HardcoreOnly); + rules.Add("PvPMode", TShock.Config.PvPMode); + rules.Add("SpawnProtection", TShock.Config.SpawnProtection); + rules.Add("SpawnProtectionRadius", TShock.Config.SpawnProtectionRadius); + + ret.Add("rules", rules); + } return ret; } + private object ServerTokenTest(RestVerbs verbs, IParameterCollection parameters) + { + return RestResponse("Token is valid and was passed through correctly"); + } + #endregion #region RestUserMethods + private object UserActiveListV2(RestVerbs verbs, IParameterCollection parameters) + { + return new RestObject() { { "activeusers", string.Join("\t", TShock.Players.Where(p => null != p && null != p.UserAccountName && p.Active).Select(p => p.UserAccountName)) } }; + } + private object UserListV2(RestVerbs verbs, IParameterCollection parameters) { - string playerlist = ""; - foreach (var TSPlayer in TShock.Players) + return new RestObject() { { "users", TShock.Users.GetUsers().Select(p => new Dictionary(){ + {"name", p.Name}, + {"id", p.ID}, + {"group", p.Group}, + {"ip", p.Address}, + }) } }; + } + + private object UserCreateV2(RestVerbs verbs, IParameterCollection parameters) + { + var username = parameters["user"]; + if (string.IsNullOrWhiteSpace(username)) + return RestMissingParam("user"); + + var group = parameters["group"]; + if (string.IsNullOrWhiteSpace(group)) + return RestMissingParam("group"); + + var password = parameters["password"]; + if (string.IsNullOrWhiteSpace(password)) + return RestMissingParam("password"); + + // NOTE: ip can be blank + User user = new User(parameters["ip"], username, password, group); + try { - playerlist += playerlist == "" ? TSPlayer.UserAccountName : "\t" + TSPlayer.UserAccountName; + TShock.Users.AddUser(user); } - var returnBlock = new Dictionary(); - returnBlock.Add("status", "200"); - returnBlock.Add("activeusers", playerlist); - return returnBlock; + catch (Exception e) + { + return RestError(e.Message); + } + + return RestResponse("User was successfully created"); } private object UserUpdateV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); + var ret = UserFind(parameters); + if (ret is RestObject) + return ret; + var password = parameters["password"]; var group = parameters["group"]; + if (string.IsNullOrWhiteSpace(group) && string.IsNullOrWhiteSpace(password)) + return RestMissingParam("group", "password"); - if (group == null && password == null) + User user = (User)ret; + var response = new RestObject(); + if (!string.IsNullOrWhiteSpace(password)) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "No parameters were passed."); - return returnBlock; + try + { + TShock.Users.SetUserPassword(user, password); + response.Add("password-response", "Password updated successfully"); + } + catch (Exception e) + { + return RestError("Failed to update user password (" + e.Message + ")"); + } } - var user = TShock.Users.GetUserByName(parameters["user"]); - if (user == null) + if (!string.IsNullOrWhiteSpace(group)) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specefied user doesn't exist."); - return returnBlock; + try + { + TShock.Users.SetUserGroup(user, group); + response.Add("group-response", "Group updated successfully"); + } + catch (Exception e) + { + return RestError("Failed to update user group (" + e.Message + ")"); + } } - if (password != null) - { - TShock.Users.SetUserPassword(user, password); - returnBlock.Add("password-response", "Password updated successfully."); - } - - if (group != null) - { - TShock.Users.SetUserGroup(user, group); - returnBlock.Add("group-response", "Group updated successfully."); - } - - returnBlock.Add("status", "200"); - return returnBlock; + return response; } private object UserDestroyV2(RestVerbs verbs, IParameterCollection parameters) { - var user = TShock.Users.GetUserByName(parameters["user"]); - if (user == null) - { - return new Dictionary {{"status", "400"}, {"error", "The specified user account does not exist."}}; - } - var returnBlock = new Dictionary(); + var ret = UserFind(parameters); + if (ret is RestObject) + return ret; + try { - TShock.Users.RemoveUser(user); + TShock.Users.RemoveUser((User)ret); } - catch (Exception) + catch (Exception e) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specified user was unable to be removed."); - return returnBlock; + return RestError(e.Message); } - returnBlock.Add("status", "200"); - returnBlock.Add("response", "User deleted successfully."); - return returnBlock; + + return RestResponse("User deleted successfully"); } private object UserInfoV2(RestVerbs verbs, IParameterCollection parameters) { - var user = TShock.Users.GetUserByName(parameters["user"]); - if (user == null) - { - return new Dictionary {{"status", "400"}, {"error", "The specified user account does not exist."}}; - } + var ret = UserFind(parameters); + if (ret is RestObject) + return ret; - var returnBlock = new Dictionary(); - returnBlock.Add("status", "200"); - returnBlock.Add("group", user.Group); - returnBlock.Add("id", user.ID.ToString()); - return returnBlock; + User user = (User)ret; + return new RestObject() { { "group", user.Group }, { "id", user.ID.ToString() }, { "name", user.Name } }; } #endregion @@ -249,167 +308,116 @@ namespace TShockAPI private object BanCreate(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); var ip = parameters["ip"]; var name = parameters["name"]; - var reason = parameters["reason"]; - if (ip == null && name == null) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Required parameters were missing from this API endpoint."); - return returnBlock; - } - - if (ip == null) - { - ip = ""; - } - - if (name == null) - { - name = ""; - } - - if (reason == null) - { - reason = ""; - } + if (string.IsNullOrWhiteSpace(ip) && string.IsNullOrWhiteSpace(name)) + return RestMissingParam("ip", "name"); try { - TShock.Bans.AddBan(ip, name, reason); + TShock.Bans.AddBan(ip, name, parameters["reason"], true); } - catch (Exception) + catch (Exception e) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specified ban was unable to be created."); - return returnBlock; + return RestError(e.Message); } - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Ban created successfully."); - return returnBlock; + return RestResponse("Ban created successfully"); } private object BanDestroyV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - - var type = parameters["type"]; - if (type == null) - { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; - } - - var ban = new Ban(); - if (type == "ip") ban = TShock.Bans.GetBanByIp(parameters["user"]); - else if (type == "name") ban = TShock.Bans.GetBanByName(parameters["user"]); - else - { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; - } - - if (ban == null) - { - return new Dictionary {{"status", "400"}, {"error", "The specified ban does not exist."}}; - } + var ret = BanFind(parameters); + if (ret is RestObject) + return ret; try { - TShock.Bans.RemoveBan(ban.IP); + Ban ban = (Ban)ret; + switch (parameters["type"]) + { + case "ip": + if (!TShock.Bans.RemoveBan(ban.IP, false, false, true)) + return RestResponse("Failed to delete ban (already deleted?)"); + break; + case "name": + if (!TShock.Bans.RemoveBan(ban.Name, true, GetBool(parameters["caseinsensitive"], true))) + return RestResponse("Failed to delete ban (already deleted?)"); + break; + default: + return RestError("Invalid Type: '" + parameters["type"] + "'"); + } + } - catch (Exception) + catch (Exception e) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "The specified ban was unable to be removed."); - return returnBlock; + return RestError(e.Message); } - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Ban deleted successfully."); - return returnBlock; + + return RestResponse("Ban deleted successfully"); } private object BanInfoV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); + var ret = BanFind(parameters); + if (ret is RestObject) + return ret; - var type = parameters["type"]; - if (type == null) + Ban ban = (Ban)ret; + return new RestObject() { + {"name", null == ban.Name ? "" : ban.Name}, + {"ip", null == ban.IP ? "" : ban.IP}, + {"reason", null == ban.Reason ? "" : ban.Reason}, + }; + } + + private object BanListV2(RestVerbs verbs, IParameterCollection parameters) + { + var banList = new ArrayList(); + foreach (var ban in TShock.Bans.GetBans()) { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; + banList.Add( + new Dictionary + { + {"name", null == ban.Name ? "" : ban.Name}, + {"ip", null == ban.IP ? "" : ban.IP}, + {"reason", null == ban.Reason ? "" : ban.Reason}, + } + ); } - var ban = new Ban(); - if (type == "ip") ban = TShock.Bans.GetBanByIp(parameters["user"]); - else if (type == "name") ban = TShock.Bans.GetBanByName(parameters["user"]); - else - { - returnBlock.Add("Error", "Invalid Type"); - return returnBlock; - } - - if (ban == null) - { - return new Dictionary { { "status", "400" }, { "error", "The specified ban does not exist." } }; - } - - returnBlock.Add("status", "200"); - returnBlock.Add("name", ban.Name); - returnBlock.Add("ip", ban.IP); - returnBlock.Add("reason", ban.Reason); - return returnBlock; + return new RestObject() { { "bans", banList } }; } #endregion #region RestWorldMethods - private object ChangeWorldSaveSettings(RestVerbs verbs, IParameterCollection parameters) + private object WorldChangeSaveSettings(RestVerbs verbs, IParameterCollection parameters) { - bool state; - bool.TryParse(verbs["state"], out state); + bool autoSave; + if (!bool.TryParse(verbs["bool"], out autoSave)) + return RestInvalidParam("state"); + TShock.Config.AutoSave = autoSave; - if (state == true) - { - TShock.Config.AutoSave = true; - } - else - { - TShock.Config.AutoSave = false; - } - - RestObject rj = new RestObject("200"); - rj["response"] = "Value changed"; - rj["state"] = state; - - return rj; + return RestResponse("AutoSave has been set to " + autoSave); } private object WorldSave(RestVerbs verbs, IParameterCollection parameters) { - TShock.Utils.SaveWorld(); + SaveManager.Instance.SaveWorld(); - RestObject rj = new RestObject("200"); - rj["response"] = "World saved."; - return rj; + return RestResponse("World saved"); } - private object Butcher(RestVerbs verbs, IParameterCollection parameters) + private object WorldButcher(RestVerbs verbs, IParameterCollection parameters) { bool killFriendly; if (!bool.TryParse(parameters["killfriendly"], out killFriendly)) - { - RestObject fail = new RestObject("400"); - fail["response"] = "The given value for killfriendly wasn't a boolean value."; - return fail; - } + return RestInvalidParam("killfriendly"); + if (killFriendly) - { killFriendly = !killFriendly; - } int killcount = 0; for (int i = 0; i < Main.npc.Length; i++) @@ -421,54 +429,38 @@ namespace TShockAPI } } - RestObject rj = new RestObject("200"); - rj["response"] = killcount + " NPCs have been killed."; - return rj; + return RestResponse(killcount + " NPCs have been killed"); } private object WorldRead(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - returnBlock.Add("status", "200"); - returnBlock.Add("name", Main.worldName); - returnBlock.Add("size", Main.maxTilesX + "*" + Main.maxTilesY); - returnBlock.Add("time", Main.time); - returnBlock.Add("daytime", Main.dayTime); - returnBlock.Add("bloodmoon", Main.bloodMoon); - returnBlock.Add("invasionsize", Main.invasionSize); - return returnBlock; + return new RestObject() + { + {"name", Main.worldName}, + {"size", Main.maxTilesX + "*" + Main.maxTilesY}, + {"time", Main.time}, + {"daytime", Main.dayTime}, + {"bloodmoon", Main.bloodMoon}, + {"invasionsize", Main.invasionSize} + }; } private object WorldMeteor(RestVerbs verbs, IParameterCollection parameters) { - if (WorldGen.genRand == null) + if (null == WorldGen.genRand) WorldGen.genRand = new Random(); WorldGen.dropMeteor(); - var returnBlock = new Dictionary {{"status", "200"}, {"response", "Meteor has been spawned."}}; - return returnBlock; + return RestResponse("Meteor has been spawned"); } private object WorldBloodmoon(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var bloodmoonVerb = verbs["bool"]; bool bloodmoon; - if (bloodmoonVerb == null) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "No parameter was passed."); - return returnBlock; - } - if (!bool.TryParse(bloodmoonVerb, out bloodmoon)) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Unable to parse parameter."); - return returnBlock; - } + if (!bool.TryParse(verbs["bool"], out bloodmoon)) + return RestInvalidParam("bloodmoon"); Main.bloodMoon = bloodmoon; - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Blood Moon has been set to " + bloodmoon); - return returnBlock; + + return RestResponse("Blood Moon has been set to " + bloodmoon); } #endregion @@ -477,178 +469,345 @@ namespace TShockAPI private object PlayerUnMute(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - player.mute = false; - player.SendMessage("You have been remotely unmuted."); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was muted."); - } - return returnBlock; + return PlayerSetMute(parameters, false); } private object PlayerMute(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - player.mute = true; - player.SendMessage("You have been remotely muted."); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was muted."); - } - return returnBlock; + return PlayerSetMute(parameters, true); } private object PlayerList(RestVerbs verbs, IParameterCollection parameters) { - var activeplayers = Main.player.Where(p => p != null && p.active).ToList(); - string currentPlayers = string.Join(", ", activeplayers.Select(p => p.name)); - var ret = new RestObject("200"); - ret["players"] = currentPlayers; - return ret; + var activeplayers = Main.player.Where(p => null != p && p.active).ToList(); + return new RestObject() { { "players", string.Join(", ", activeplayers.Select(p => p.name)) } }; + } + + private object PlayerListV2(RestVerbs verbs, IParameterCollection parameters) + { + var playerList = new ArrayList(); + foreach (TSPlayer tsPlayer in TShock.Players.Where(p => null != p)) + { + var p = PlayerFilter(tsPlayer, parameters); + if (null != p) + playerList.Add(p); + } + return new RestObject() { { "players", playerList } }; } private object PlayerReadV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - if (found.Count == 0) + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + var activeItems = player.TPlayer.inventory.Where(p => p.active).ToList(); + return new RestObject() { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - returnBlock.Add("status", "200"); - returnBlock.Add("nickname", player.Name); - returnBlock.Add("username", player.UserAccountName == null ? "" : player.UserAccountName); - returnBlock.Add("ip", player.IP); - returnBlock.Add("group", player.Group.Name); - returnBlock.Add("position", player.TileX + "," + player.TileY); - var activeItems = player.TPlayer.inventory.Where(p => p.active).ToList(); - returnBlock.Add("inventory", string.Join(", ", activeItems.Select(p => (p.name + ":" + p.stack)))); - returnBlock.Add("buffs", string.Join(", ", player.TPlayer.buffType)); - } - return returnBlock; + {"nickname", player.Name}, + {"username", null == player.UserAccountName ? "" : player.UserAccountName}, + {"ip", player.IP}, + {"group", player.Group.Name}, + {"position", player.TileX + "," + player.TileY}, + {"inventory", string.Join(", ", activeItems.Select(p => (p.name + ":" + p.stack)))}, + {"buffs", string.Join(", ", player.TPlayer.buffType)} + }; } private object PlayerKickV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - TShock.Utils.ForceKick(player, reason == null ? "Kicked via web" : reason); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was kicked"); - } - return returnBlock; + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + TShock.Utils.ForceKick(player, null == parameters["reason"] ? "Kicked via web" : parameters["reason"]); + return RestResponse("Player " + player.Name + " was kicked"); } private object PlayerBanV2(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var reason = parameters["reason"]; - if (found.Count == 0) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); - } - else if (found.Count > 1) - { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); - } - else if (found.Count == 1) - { - var player = found[0]; - TShock.Bans.AddBan(player.IP, player.Name, reason == null ? "Banned via web" : reason); - TShock.Utils.ForceKick(player, reason == null ? "Banned via web" : reason); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was banned"); - } - return returnBlock; + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + var reason = null == parameters["reason"] ? "Banned via web" : parameters["reason"]; + TShock.Bans.AddBan(player.IP, player.Name, reason); + TShock.Utils.ForceKick(player, reason); + return RestResponse("Player " + player.Name + " was banned"); } private object PlayerKill(RestVerbs verbs, IParameterCollection parameters) { - var returnBlock = new Dictionary(); - var playerParam = parameters["player"]; - var found = TShock.Utils.FindPlayer(playerParam); - var from = verbs["from"]; - if (found.Count == 0) + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + player.DamagePlayer(999999); + var from = string.IsNullOrWhiteSpace(parameters["from"]) ? "Server Admin" : parameters["from"]; + player.SendMessage(string.Format("{0} just killed you!", from)); + return RestResponse("Player " + player.Name + " was killed"); + } + + #endregion + + #region RestGroupMethods + + private object GroupList(RestVerbs verbs, IParameterCollection parameters) + { + var groups = new ArrayList(); + foreach (Group group in TShock.Groups) { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " was not found"); + groups.Add(new Dictionary {{"name", group.Name}, {"parent", group.ParentName}, {"chatcolor", group.ChatColor}}); } - else if (found.Count > 1) + return new RestObject() { { "groups", groups } }; + } + + private object GroupInfo(RestVerbs verbs, IParameterCollection parameters) + { + var ret = GroupFind(parameters); + if (ret is RestObject) + return ret; + + Group group = (Group)ret; + return new RestObject() { + {"name", group.Name}, + {"parent", group.ParentName}, + {"chatcolor", group.ChatColor}, + {"permissions", group.permissions}, + {"negatedpermissions", group.negatedpermissions}, + {"totalpermissions", group.TotalPermissions} + }; + } + + private object GroupDestroy(RestVerbs verbs, IParameterCollection parameters) + { + var ret = GroupFind(parameters); + if (ret is RestObject) + return ret; + + Group group = (Group)ret; + try { - returnBlock.Add("status", "400"); - returnBlock.Add("error", "Name " + playerParam + " matches " + playerParam.Count() + " players"); + TShock.Groups.DeleteGroup(group.Name, true); } - else if (found.Count == 1) + catch (Exception e) { - var player = found[0]; - player.DamagePlayer(999999); - player.SendMessage(string.Format("{0} just killed you!", from)); - returnBlock.Add("status", "200"); - returnBlock.Add("response", "Player " + player.Name + " was killed."); + return RestError(e.Message); } - return returnBlock; + + return RestResponse("Group '" + group.Name + "' deleted successfully"); + } + + private object GroupCreate(RestVerbs verbs, IParameterCollection parameters) + { + var name = parameters["group"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("group"); + try + { + TShock.Groups.AddGroup(name, parameters["parent"], parameters["permissions"], parameters["chatcolor"], true); + } + catch (Exception e) + { + return RestError(e.Message); + } + + return RestResponse("Group '" + name + "' created successfully"); + } + + private object GroupUpdate(RestVerbs verbs, IParameterCollection parameters) + { + var ret = GroupFind(parameters); + if (ret is RestObject) + return ret; + + Group group = (Group)ret; + var parent = (null == parameters["parent"]) ? group.ParentName : parameters["parent"]; + var chatcolor = (null == parameters["chatcolor"]) ? group.ChatColor : parameters["chatcolor"]; + var permissions = (null == parameters["permissions"]) ? group.Permissions : parameters["permissions"]; + try + { + TShock.Groups.UpdateGroup(group.Name, parent, permissions, chatcolor); + } + catch (Exception e) + { + return RestError(e.Message); + } + + return RestResponse("Group '" + group.Name + "' updated successfully"); + } + + #endregion + + #region Utility Methods + + private RestObject RestError(string message, string status = "400") + { + return new RestObject(status) {Error = message}; + } + + private RestObject RestResponse(string message, string status = "200") + { + return new RestObject(status) {Response = message}; + } + + private RestObject RestMissingParam(string var) + { + return RestError("Missing or empty " + var + " parameter"); + } + + private RestObject RestMissingParam(params string[] vars) + { + return RestMissingParam(string.Join(", ", vars)); + } + + private RestObject RestInvalidParam(string var) + { + return RestError("Missing or invalid " + var + " parameter"); + } + + private bool GetBool(string val, bool def) + { + bool ret; + return bool.TryParse(val, out ret) ? ret : def; + } + + private object PlayerFind(IParameterCollection parameters) + { + string name = parameters["player"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("player"); + + var found = TShock.Utils.FindPlayer(name); + switch(found.Count) + { + case 1: + return found[0]; + case 0: + return RestError("Player " + name + " was not found"); + default: + return RestError("Player " + name + " matches " + found.Count + " players"); + } + } + + private object UserFind(IParameterCollection parameters) + { + string name = parameters["user"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("user"); + + User user; + string type = parameters["type"]; + try + { + switch (type) + { + case null: + case "name": + type = "name"; + user = TShock.Users.GetUserByName(name); + break; + case "id": + user = TShock.Users.GetUserByID(Convert.ToInt32(name)); + break; + case "ip": + user = TShock.Users.GetUserByIP(name); + + break; + default: + return RestError("Invalid Type: '" + type + "'"); + } + } + catch (Exception e) + { + return RestError(e.Message); + } + + if (null == user) + return RestError(String.Format("User {0} '{1}' doesn't exist", type, name)); + + return user; + } + + private object BanFind(IParameterCollection parameters) + { + string name = parameters["ban"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("ban"); + + string type = parameters["type"]; + if (string.IsNullOrWhiteSpace(type)) + return RestMissingParam("type"); + + Ban ban; + switch (type) + { + case "ip": + ban = TShock.Bans.GetBanByIp(name); + break; + case "name": + ban = TShock.Bans.GetBanByName(name, GetBool(parameters["caseinsensitive"], true)); + break; + default: + return RestError("Invalid Type: '" + type + "'"); + } + + if (null == ban) + return RestError("Ban " + type + " '" + name + "' doesn't exist"); + + return ban; + } + + private object GroupFind(IParameterCollection parameters) + { + var name = parameters["group"]; + if (string.IsNullOrWhiteSpace(name)) + return RestMissingParam("group"); + + var group = TShock.Groups.GetGroupByName(name); + if (null == group) + return RestError("Group '" + name + "' doesn't exist"); + + return group; + } + + private Dictionary PlayerFilter(TSPlayer tsPlayer, IParameterCollection parameters) + { + var player = new Dictionary + { + {"nickname", tsPlayer.Name}, + {"username", null == tsPlayer.UserAccountName ? "" : tsPlayer.UserAccountName}, + {"ip", tsPlayer.IP}, + {"group", tsPlayer.Group.Name}, + {"active", tsPlayer.Active}, + {"state", tsPlayer.State}, + {"team", tsPlayer.Team}, + }; + foreach (IParameter filter in parameters) + { + if (player.ContainsKey(filter.Name) && !player[filter.Name].Equals(filter.Value)) + return null; + } + return player; + } + + private object PlayerSetMute(IParameterCollection parameters, bool mute) + { + var ret = PlayerFind(parameters); + if (ret is RestObject) + return ret; + + TSPlayer player = (TSPlayer)ret; + player.mute = mute; + var verb = mute ? "muted" : "unmuted"; + player.SendMessage("You have been remotely " + verb); + return RestResponse("Player " + player.Name + " was " + verb); } #endregion } -} \ No newline at end of file +} diff --git a/TShockAPI/Rest/RestObject.cs b/TShockAPI/Rest/RestObject.cs index 6b99d1cc..c2593f44 100644 --- a/TShockAPI/Rest/RestObject.cs +++ b/TShockAPI/Rest/RestObject.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011 The TShock Team @@ -41,7 +41,14 @@ namespace Rests set { this["response"] = value; } } - public RestObject(string status) + // Parameterless constructor for deseralisation required by JavaScriptSerializer.Deserialize in TShockRestTestPlugin + // Note: The constructor with all defaults isn't good enough :( + public RestObject() + { + Status = "200"; + } + + public RestObject(string status = "200") { Status = status; } diff --git a/TShockAPI/Rest/RestVerbs.cs b/TShockAPI/Rest/RestVerbs.cs index 8dd84f50..ddd9a2cd 100644 --- a/TShockAPI/Rest/RestVerbs.cs +++ b/TShockAPI/Rest/RestVerbs.cs @@ -1,4 +1,4 @@ -/* +/* TShock, a server mod for Terraria Copyright (C) 2011 The TShock Team diff --git a/TShockAPI/SaveManager.cs b/TShockAPI/SaveManager.cs new file mode 100644 index 00000000..f411a2ef --- /dev/null +++ b/TShockAPI/SaveManager.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading; +using System.Diagnostics; +using Terraria; + +namespace TShockAPI +{ + class SaveManager : IDisposable + { + // Singleton + private static readonly SaveManager instance = new SaveManager(); + private SaveManager() + { + _saveThread = new Thread(SaveWorker); + _saveThread.Name = "TShock SaveManager Worker"; + _saveThread.Start(); + } + public static SaveManager Instance { get { return instance; } } + + // Producer Consumer + private EventWaitHandle _wh = new AutoResetEvent(false); + private Object _saveLock = new Object(); + private Queue _saveQueue = new Queue(); + private Thread _saveThread; + private int saveQueueCount { get { lock (_saveLock) return _saveQueue.Count; } } + + /// + /// SaveWorld event handler which notifies users that the server may lag + /// + public void OnSaveWorld(bool resettime = false, HandledEventArgs e = null) + { + // Protect against internal errors causing save failures + // These can be caused by an unexpected error such as a bad or out of date plugin + try + { + TShock.Utils.Broadcast("Saving world. Momentary lag might result from this.", Color.Red); + } + catch (Exception ex) + { + Log.Error("World saved notification failed"); + Log.Error(ex.ToString()); + } + } + + /// + /// Saves the map data + /// + /// wait for all pending saves to finish (default: true) + /// reset the last save time counter (default: false) + /// use the realsaveWorld method instead of saveWorld event (default: false) + public void SaveWorld(bool wait = true, bool resetTime = false, bool direct = false) + { + EnqueueTask(new SaveTask(resetTime, direct)); + if (!wait) + return; + + // Wait for all outstanding saves to complete + int count = saveQueueCount; + while (0 != count) + { + Thread.Sleep(50); + count = saveQueueCount; + } + } + + /// + /// Processes any outstanding saves, shutsdown the save thread and returns + /// + public void Dispose() + { + EnqueueTask(null); + _saveThread.Join(); + _wh.Close(); + } + + private void EnqueueTask(SaveTask task) + { + lock (_saveLock) + { + _saveQueue.Enqueue(task); + } + _wh.Set(); + } + + private void SaveWorker() + { + while (true) + { + lock (_saveLock) + { + // NOTE: lock for the entire process so wait works in SaveWorld + if (_saveQueue.Count > 0) + { + SaveTask task = _saveQueue.Dequeue(); + if (null == task) + return; + else + { + // Ensure that save handler errors don't bubble up and cause a recursive call + // These can be caused by an unexpected error such as a bad or out of date plugin + try + { + if (task.direct) + { + OnSaveWorld(); + WorldGen.realsaveWorld(task.resetTime); + } + else + WorldGen.saveWorld(task.resetTime); + TShock.Utils.Broadcast("World saved.", Color.Yellow); + Log.Info(string.Format("World saved at ({0})", Main.worldPathName)); + } + catch (Exception e) + { + Log.Error("World saved failed"); + Log.Error(e.ToString()); + } + } + } + } + _wh.WaitOne(); + } + } + + class SaveTask + { + public bool resetTime { get; set; } + public bool direct { get; set; } + public SaveTask(bool resetTime, bool direct) + { + this.resetTime = resetTime; + this.direct = direct; + } + + public override string ToString() + { + return string.Format("resetTime {0}, direct {1}", resetTime, direct); + } + } + } +} \ No newline at end of file diff --git a/TShockAPI/TSPlayer.cs b/TShockAPI/TSPlayer.cs index 441543c5..3610af2f 100644 --- a/TShockAPI/TSPlayer.cs +++ b/TShockAPI/TSPlayer.cs @@ -1,716 +1,721 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011 The TShock Team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading; -using Terraria; -using TShockAPI.Net; - -namespace TShockAPI -{ - public class TSPlayer - { - public static readonly TSServerPlayer Server = new TSServerPlayer(); - public static readonly TSPlayer All = new TSPlayer("All"); - public int TileKillThreshold { get; set; } - public int TilePlaceThreshold { get; set; } - public int TileLiquidThreshold { get; set; } - public int ProjectileThreshold { get; set; } - public Dictionary TilesDestroyed { get; protected set; } - public Dictionary TilesCreated { get; protected set; } - public int FirstMaxHP { get; set; } - public int FirstMaxMP { get; set; } - public Group Group { get; set; } - public bool ReceivedInfo { get; set; } - public int Index { get; protected set; } - public DateTime LastPvpChange; - public Point[] TempPoints = new Point[2]; - public int AwaitingTempPoint { get; set; } - public bool AwaitingName { get; set; } - public DateTime LastThreat { get; set; } - public DateTime LastTileChangeNotify { get; set; } - public bool InitSpawn; - public bool DisplayLogs = true; - public Vector2 oldSpawn = Vector2.Zero; - public TSPlayer LastWhisper; - public int LoginAttempts { get; set; } - public Vector2 TeleportCoords = new Vector2(-1, -1); - public Vector2 LastNetPosition = Vector2.Zero; - public string UserAccountName { get; set; } - public bool HasBeenSpammedWithBuildMessage; - public bool IsLoggedIn; - public int UserID = -1; - public bool HasBeenNaggedAboutLoggingIn; - public bool TPAllow = true; - public bool mute; - public bool TpLock; - private Player FakePlayer; - public bool RequestedSection; - public DateTime LastDeath { get; set; } - public bool Dead; - public string Country = "??"; - public int Difficulty; - private string CacheIP; - public string IgnoreActionsForInventory = "none"; - public string IgnoreActionsForCheating = "none"; - public string IgnoreActionsForDisabledArmor = "none"; - public bool IgnoreActionsForClearingTrashCan; - public PlayerData PlayerData; - public bool RequiresPassword; - public bool SilentKickInProgress; - public List IceTiles; - public long RPm=1; - public long WPm=1; - public long SPm=1; - public long BPm=1; - public long LoginMS; - - public bool RealPlayer - { - get { return Index >= 0 && Index < Main.maxNetPlayers && Main.player[Index] != null; } - } - - public bool ConnectionAlive - { - get - { - return RealPlayer && - (Netplay.serverSock[Index] != null && Netplay.serverSock[Index].active && !Netplay.serverSock[Index].kill); - } - } - - public int State - { - get { return Netplay.serverSock[Index].state; } - set { Netplay.serverSock[Index].state = value; } - } - - public string IP - { - get - { - if (string.IsNullOrEmpty(CacheIP)) - return - CacheIP = - RealPlayer - ? (Netplay.serverSock[Index].tcpClient.Connected - ? TShock.Utils.GetRealIP(Netplay.serverSock[Index].tcpClient.Client.RemoteEndPoint.ToString()) - : "") - : ""; - else - return CacheIP; - } - } - - /// - /// Terraria Player - /// - public Player TPlayer - { - get { return FakePlayer ?? Main.player[Index]; } - } - - public string Name - { - get { return TPlayer.name; } - } - - public bool Active - { - get { return TPlayer != null && TPlayer.active; } - } - - public int Team - { - get { return TPlayer.team; } - } - - public float X - { - get { return RealPlayer ? TPlayer.position.X : Main.spawnTileX*16; } - } - - public float Y - { - get { return RealPlayer ? TPlayer.position.Y : Main.spawnTileY*16; } - } - - public int TileX - { - get { return (int) (X/16); } - } - - public int TileY - { - get { return (int) (Y/16); } - } - - public bool InventorySlotAvailable - { - get - { - bool flag = false; - if (RealPlayer) - { - for (int i = 0; i < 40; i++) //41 is trash can, 42-45 is coins, 46-49 is ammo - { - if (TPlayer.inventory[i] == null || !TPlayer.inventory[i].active || TPlayer.inventory[i].name == "") - { - flag = true; - break; - } - } - } - return flag; - } - } - - public TSPlayer(int index) - { - TilesDestroyed = new Dictionary(); - TilesCreated = new Dictionary(); - Index = index; - Group = new Group(TShock.Config.DefaultGuestGroupName); - IceTiles = new List(); - } - - protected TSPlayer(String playerName) - { - TilesDestroyed = new Dictionary(); - TilesCreated = new Dictionary(); - Index = -1; - FakePlayer = new Player {name = playerName, whoAmi = -1}; - Group = new Group(TShock.Config.DefaultGuestGroupName); - } - - public virtual void Disconnect(string reason) - { - SendData(PacketTypes.Disconnect, reason); - } - - public virtual void Flush() - { - var sock = Netplay.serverSock[Index]; - if (sock == null) - return; - - TShock.PacketBuffer.Flush(sock); - } - - - private void SendWorldInfo(int tilex, int tiley, bool fakeid) - { - using (var ms = new MemoryStream()) - { - var msg = new WorldInfoMsg - { - Time = (int) Main.time, - DayTime = Main.dayTime, - MoonPhase = (byte) Main.moonPhase, - BloodMoon = Main.bloodMoon, - MaxTilesX = Main.maxTilesX, - MaxTilesY = Main.maxTilesY, - SpawnX = tilex, - SpawnY = tiley, - WorldSurface = (int) Main.worldSurface, - RockLayer = (int) Main.rockLayer, - //Sending a fake world id causes the client to not be able to find a stored spawnx/y. - //This fixes the bed spawn point bug. With a fake world id it wont be able to find the bed spawn. - WorldID = !fakeid ? Main.worldID : -1, - WorldFlags = (WorldGen.shadowOrbSmashed ? WorldInfoFlag.OrbSmashed : WorldInfoFlag.None) | - (NPC.downedBoss1 ? WorldInfoFlag.DownedBoss1 : WorldInfoFlag.None) | - (NPC.downedBoss2 ? WorldInfoFlag.DownedBoss2 : WorldInfoFlag.None) | - (NPC.downedBoss3 ? WorldInfoFlag.DownedBoss3 : WorldInfoFlag.None) | - (Main.hardMode ? WorldInfoFlag.HardMode : WorldInfoFlag.None) | - (NPC.downedClown ? WorldInfoFlag.DownedClown : WorldInfoFlag.None), - WorldName = Main.worldName - }; - msg.PackFull(ms); - SendRawData(ms.ToArray()); - } - } - - public bool Teleport(int tilex, int tiley) - { - InitSpawn = false; - - SendWorldInfo(tilex, tiley, true); - - //150 Should avoid all client crash errors - //The error occurs when a tile trys to update which the client hasnt load yet, Clients only update tiles withen 150 blocks - //Try 300 if it does not work (Higher number - Longer load times - Less chance of error) - //Should we properly send sections so that clients don't get tiles twice? - if (!SendTileSquare(tilex, tiley, 150)) - { - InitSpawn = true; - SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false); - return false; - } - - Spawn(-1, -1); - - SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false); - - TPlayer.position.X = (float)(tilex * 16 + 8 - TPlayer.width /2); - TPlayer.position.Y = (float)(tiley * 16 - TPlayer.height); - //We need to send the tile data again to prevent clients from thinking they *really* destroyed blocks just now. - - SendTileSquare(tilex, tiley, 150); - - return true; - } - - public void Spawn() - { - Spawn(TPlayer.SpawnX, TPlayer.SpawnY); - } - - public void Spawn(int tilex, int tiley) - { - using (var ms = new MemoryStream()) - { - var msg = new SpawnMsg - { - PlayerIndex = (byte) Index, - TileX = tilex, - TileY = tiley - }; - msg.PackFull(ms); - SendRawData(ms.ToArray()); - } - } - - public void RemoveProjectile(int index, int owner) - { - using (var ms = new MemoryStream()) - { - var msg = new ProjectileRemoveMsg - { - Index = (short) index, - Owner = (byte) owner - }; - msg.PackFull(ms); - SendRawData(ms.ToArray()); - } - } - - public virtual bool SendTileSquare(int x, int y, int size = 10) - { - try - { - int num = (size - 1)/2; - int m_x=0; - int m_y=0; - - if (x - num <0){ - m_x=0; - }else{ - m_x = x - num; - } - - if (y - num <0){ - m_y=0; - }else{ - m_y = y - num; - } - - if (m_x + size > Main.maxTilesX){ - m_x=Main.maxTilesX - size; - } - - if (m_y + size > Main.maxTilesY){ - m_y=Main.maxTilesY - size; - } - - SendData(PacketTypes.TileSendSquare, "", size, m_x, m_y); - return true; - } - catch (IndexOutOfRangeException) - { - - // This is expected if square exceeds array. - } - catch (Exception ex) - { - Log.Error(ex.ToString()); - } - return false; - } - - public virtual void GiveItem(int type, string name, int width, int height, int stack, int prefix = 0) - { - int itemid = Item.NewItem((int) X, (int) Y, width, height, type, stack, true, prefix); - // This is for special pickaxe/hammers/swords etc - Main.item[itemid].SetDefaults(name); - // The set default overrides the wet and stack set by NewItem - Main.item[itemid].wet = Collision.WetCollision(Main.item[itemid].position, Main.item[itemid].width, - Main.item[itemid].height); - Main.item[itemid].stack = stack; - Main.item[itemid].owner = Index; - Main.item[itemid].prefix = (byte) prefix; - NetMessage.SendData((int) PacketTypes.ItemDrop, -1, -1, "", itemid, 0f, 0f, 0f); - NetMessage.SendData((int) PacketTypes.ItemOwner, -1, -1, "", itemid, 0f, 0f, 0f); - } - - public virtual void SendMessage(string msg) - { - SendMessage(msg, 0, 255, 0); - } - - public virtual void SendMessage(string msg, Color color) - { - SendMessage(msg, color.R, color.G, color.B); - } - - public virtual void SendMessage(string msg, byte red, byte green, byte blue) - { - SendData(PacketTypes.ChatText, msg, 255, red, green, blue); - } - - public virtual void DamagePlayer(int damage) - { - NetMessage.SendData((int) PacketTypes.PlayerDamage, -1, -1, "", Index, ((new Random()).Next(-1, 1)), damage, - (float) 0); - } - - public virtual void SetTeam(int team) - { - Main.player[Index].team = team; - SendData(PacketTypes.PlayerTeam, "", Index); - } - - public virtual void Disable(string reason = "") - { - LastThreat = DateTime.UtcNow; - SetBuff(33, 330, true); //Weak - SetBuff(32, 330, true); //Slow - SetBuff(23, 330, true); //Cursed - if (!string.IsNullOrEmpty(reason)) - Log.ConsoleInfo(string.Format("Player {0} has been disabled for {1}", Name, reason)); - - var trace = new StackTrace(); - StackFrame frame = null; - frame = trace.GetFrame(1); - if (frame != null && frame.GetMethod().DeclaringType != null) - Log.Debug(frame.GetMethod().DeclaringType.Name + " called Disable()"); - } - - public virtual void Whoopie(object time) - { - var time2 = (int) time; - var launch = DateTime.UtcNow; - var startname = Name; - SendMessage("You are now being annoyed.", Color.Red); - while ((DateTime.UtcNow - launch).TotalSeconds < time2 && startname == Name) - { - SendData(PacketTypes.NpcSpecial, number: Index, number2: 2f); - Thread.Sleep(50); - } - } - - public virtual void SetBuff(int type, int time = 3600, bool bypass = false) - { - if ((DateTime.UtcNow - LastThreat).TotalMilliseconds < 5000 && !bypass) - return; - - SendData(PacketTypes.PlayerAddBuff, number: Index, number2: type, number3: time); - } - - //Todo: Separate this into a few functions. SendTo, SendToAll, etc - public virtual void SendData(PacketTypes msgType, string text = "", int number = 0, float number2 = 0f, - float number3 = 0f, float number4 = 0f, int number5 = 0) - { - if (RealPlayer && !ConnectionAlive) - return; - - NetMessage.SendData((int) msgType, Index, -1, text, number, number2, number3, number4, number5); - } - - public virtual bool SendRawData(byte[] data) - { - if (!RealPlayer || !ConnectionAlive) - return false; - - return TShock.SendBytes(Netplay.serverSock[Index], data); - } - } - - public class TSRestPlayer : TSServerPlayer - { - internal List CommandReturn = new List(); - - public TSRestPlayer() - { - Group = new SuperAdminGroup(); - } - - public override void SendMessage(string msg) - { - SendMessage(msg, 0, 255, 0); - } - - public override void SendMessage(string msg, Color color) - { - SendMessage(msg, color.R, color.G, color.B); - } - - public override void SendMessage(string msg, byte red, byte green, byte blue) - { - CommandReturn.Add(msg); - } - - public List GetCommandOutput() - { - return CommandReturn; - } - } - - public class TSServerPlayer : TSPlayer - { - public TSServerPlayer() - : base("Server") - { - Group = new SuperAdminGroup(); - } - - public override void SendMessage(string msg) - { - SendMessage(msg, 0, 255, 0); - } - - public override void SendMessage(string msg, Color color) - { - SendMessage(msg, color.R, color.G, color.B); - } - - public override void SendMessage(string msg, byte red, byte green, byte blue) - { - Console.WriteLine(msg); - //RconHandler.Response += msg + "\n"; - } - - public void SetFullMoon(bool fullmoon) - { - Main.moonPhase = 0; - SetTime(false, 0); - } - - public void SetBloodMoon(bool bloodMoon) - { - Main.bloodMoon = bloodMoon; - SetTime(false, 0); - } - - public void SetTime(bool dayTime, double time) - { - Main.dayTime = dayTime; - Main.time = time; - NetMessage.SendData((int) PacketTypes.TimeSet, -1, -1, "", 0, 0, Main.sunModY, Main.moonModY); - NetMessage.syncPlayers(); - } - - public void SpawnNPC(int type, string name, int amount, int startTileX, int startTileY, int tileXRange = 100, - int tileYRange = 50) - { - for (int i = 0; i < amount; i++) - { - int spawnTileX; - int spawnTileY; - TShock.Utils.GetRandomClearTileWithInRange(startTileX, startTileY, tileXRange, tileYRange, out spawnTileX, - out spawnTileY); - int npcid = NPC.NewNPC(spawnTileX*16, spawnTileY*16, type, 0); - // This is for special slimes - Main.npc[npcid].SetDefaults(name); - } - } - - public void StrikeNPC(int npcid, int damage, float knockBack, int hitDirection) - { - Main.npc[npcid].StrikeNPC(damage, knockBack, hitDirection); - NetMessage.SendData((int) PacketTypes.NpcStrike, -1, -1, "", npcid, damage, knockBack, hitDirection); - } - - public void RevertTiles(Dictionary tiles) - { - // Update Main.Tile first so that when tile sqaure is sent it is correct - foreach (KeyValuePair entry in tiles) - { - Main.tile[(int) entry.Key.X, (int) entry.Key.Y].Data = entry.Value; - } - // Send all players updated tile sqaures - foreach (Vector2 coords in tiles.Keys) - { - All.SendTileSquare((int) coords.X, (int) coords.Y, 3); - } - } - } - - public class PlayerData - { - public NetItem[] inventory = new NetItem[NetItem.maxNetInventory]; - public int maxHealth = 100; - //public int maxMana = 100; - public bool exists; - - public PlayerData(TSPlayer player) - { - for (int i = 0; i < NetItem.maxNetInventory; i++) - { - this.inventory[i] = new NetItem(); - } - this.inventory[0].netID = -15; - this.inventory[0].stack = 1; - if (player.TPlayer.inventory[0] != null && player.TPlayer.inventory[0].netID == -15) - this.inventory[0].prefix = player.TPlayer.inventory[0].prefix; - this.inventory[1].netID = -13; - this.inventory[1].stack = 1; - if (player.TPlayer.inventory[1] != null && player.TPlayer.inventory[1].netID == -13) - this.inventory[1].prefix = player.TPlayer.inventory[1].prefix; - this.inventory[2].netID = -16; - this.inventory[2].stack = 1; - if (player.TPlayer.inventory[2] != null && player.TPlayer.inventory[2].netID == -16) - this.inventory[2].prefix = player.TPlayer.inventory[2].prefix; - } - - public void StoreSlot(int slot, int netID, int prefix, int stack) - { - if(slot > (this.inventory.Length - 1)) //if the slot is out of range then dont save - { - return; - } - - this.inventory[slot].netID = netID; - if (this.inventory[slot].netID != 0) - { - this.inventory[slot].stack = stack; - this.inventory[slot].prefix = prefix; - } - else - { - this.inventory[slot].stack = 0; - this.inventory[slot].prefix = 0; - } - } - - public void CopyInventory(TSPlayer player) - { - this.maxHealth = player.TPlayer.statLifeMax; - Item[] inventory = player.TPlayer.inventory; - Item[] armor = player.TPlayer.armor; - for (int i = 0; i < NetItem.maxNetInventory; i++) - { - if (i < 49) - { - if (player.TPlayer.inventory[i] != null) - { - this.inventory[i].netID = inventory[i].netID; - } - else - { - this.inventory[i].netID = 0; - } - - if (this.inventory[i].netID != 0) - { - this.inventory[i].stack = inventory[i].stack; - this.inventory[i].prefix = inventory[i].prefix; - } - else - { - this.inventory[i].stack = 0; - this.inventory[i].prefix = 0; - } - } - else - { - if (player.TPlayer.armor[i - 48] != null) - { - this.inventory[i].netID = armor[i - 48].netID; - } - else - { - this.inventory[i].netID = 0; - } - - if (this.inventory[i].netID != 0) - { - this.inventory[i].stack = armor[i - 48].stack; - this.inventory[i].prefix = armor[i - 48].prefix; - } - else - { - this.inventory[i].stack = 0; - this.inventory[i].prefix = 0; - } - } - } - } - } - - public class NetItem - { - public static int maxNetInventory = 59; - public int netID; - public int stack; - public int prefix; - - public static string ToString(NetItem[] inventory) - { - string inventoryString = ""; - for (int i = 0; i < maxNetInventory; i++) - { - if (i != 0) - inventoryString += "~"; - inventoryString += inventory[i].netID; - if (inventory[i].netID != 0) - { - inventoryString += "," + inventory[i].stack; - inventoryString += "," + inventory[i].prefix; - } - else - { - inventoryString += ",0,0"; - } - } - return inventoryString; - } - - public static NetItem[] Parse(string data) - { - NetItem[] inventory = new NetItem[maxNetInventory]; - int i; - for (i = 0; i < maxNetInventory; i++) - { - inventory[i] = new NetItem(); - } - string[] items = data.Split('~'); - i = 0; - foreach (string item in items) - { - string[] idata = item.Split(','); - inventory[i].netID = int.Parse(idata[0]); - inventory[i].stack = int.Parse(idata[1]); - inventory[i].prefix = int.Parse(idata[2]); - i++; - } - return inventory; - } - } -} +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using Terraria; +using TShockAPI.Net; + +namespace TShockAPI +{ + public class TSPlayer + { + public static readonly TSServerPlayer Server = new TSServerPlayer(); + public static readonly TSPlayer All = new TSPlayer("All"); + public int TileKillThreshold { get; set; } + public int TilePlaceThreshold { get; set; } + public int TileLiquidThreshold { get; set; } + public int ProjectileThreshold { get; set; } + public Dictionary TilesDestroyed { get; protected set; } + public Dictionary TilesCreated { get; protected set; } + public int FirstMaxHP { get; set; } + public int FirstMaxMP { get; set; } + public Group Group { get; set; } + public bool ReceivedInfo { get; set; } + public int Index { get; protected set; } + public DateTime LastPvpChange; + public Point[] TempPoints = new Point[2]; + public int AwaitingTempPoint { get; set; } + public bool AwaitingName { get; set; } + public DateTime LastThreat { get; set; } + public DateTime LastTileChangeNotify { get; set; } + public bool InitSpawn; + public bool DisplayLogs = true; + public Vector2 oldSpawn = Vector2.Zero; + public TSPlayer LastWhisper; + public int LoginAttempts { get; set; } + public Vector2 TeleportCoords = new Vector2(-1, -1); + public Vector2 LastNetPosition = Vector2.Zero; + public string UserAccountName { get; set; } + public bool HasBeenSpammedWithBuildMessage; + public bool IsLoggedIn; + public int UserID = -1; + public bool HasBeenNaggedAboutLoggingIn; + public bool TPAllow = true; + public bool mute; + public bool TpLock; + private Player FakePlayer; + public bool RequestedSection; + public DateTime LastDeath { get; set; } + public bool Dead; + public string Country = "??"; + public int Difficulty; + private string CacheIP; + public string IgnoreActionsForInventory = "none"; + public string IgnoreActionsForCheating = "none"; + public string IgnoreActionsForDisabledArmor = "none"; + public bool IgnoreActionsForClearingTrashCan; + public PlayerData PlayerData; + public bool RequiresPassword; + public bool SilentKickInProgress; + public List IceTiles; + public long RPm = 1; + public long WPm = 1; + public long SPm = 1; + public long BPm = 1; + public long LoginMS; + public bool LoginHarassed = false; + public bool RealPlayer + { + get { return Index >= 0 && Index < Main.maxNetPlayers && Main.player[Index] != null; } + } + + public bool ConnectionAlive + { + get + { + return RealPlayer && + (Netplay.serverSock[Index] != null && Netplay.serverSock[Index].active && !Netplay.serverSock[Index].kill); + } + } + + public int State + { + get { return Netplay.serverSock[Index].state; } + set { Netplay.serverSock[Index].state = value; } + } + + public string IP + { + get + { + if (string.IsNullOrEmpty(CacheIP)) + return + CacheIP = + RealPlayer + ? (Netplay.serverSock[Index].tcpClient.Connected + ? TShock.Utils.GetRealIP(Netplay.serverSock[Index].tcpClient.Client.RemoteEndPoint.ToString()) + : "") + : ""; + else + return CacheIP; + } + } + + /// + /// Terraria Player + /// + public Player TPlayer + { + get { return FakePlayer ?? Main.player[Index]; } + } + + public string Name + { + get { return TPlayer.name; } + } + + public bool Active + { + get { return TPlayer != null && TPlayer.active; } + } + + public int Team + { + get { return TPlayer.team; } + } + + public float X + { + get { return RealPlayer ? TPlayer.position.X : Main.spawnTileX*16; } + } + + public float Y + { + get { return RealPlayer ? TPlayer.position.Y : Main.spawnTileY*16; } + } + + public int TileX + { + get { return (int) (X/16); } + } + + public int TileY + { + get { return (int) (Y/16); } + } + + public bool InventorySlotAvailable + { + get + { + bool flag = false; + if (RealPlayer) + { + for (int i = 0; i < 40; i++) //41 is trash can, 42-45 is coins, 46-49 is ammo + { + if (TPlayer.inventory[i] == null || !TPlayer.inventory[i].active || TPlayer.inventory[i].name == "") + { + flag = true; + break; + } + } + } + return flag; + } + } + + public TSPlayer(int index) + { + TilesDestroyed = new Dictionary(); + TilesCreated = new Dictionary(); + Index = index; + Group = new Group(TShock.Config.DefaultGuestGroupName); + IceTiles = new List(); + } + + protected TSPlayer(String playerName) + { + TilesDestroyed = new Dictionary(); + TilesCreated = new Dictionary(); + Index = -1; + FakePlayer = new Player {name = playerName, whoAmi = -1}; + Group = new Group(TShock.Config.DefaultGuestGroupName); + } + + public virtual void Disconnect(string reason) + { + SendData(PacketTypes.Disconnect, reason); + } + + public virtual void Flush() + { + var sock = Netplay.serverSock[Index]; + if (sock == null) + return; + + TShock.PacketBuffer.Flush(sock); + } + + + private void SendWorldInfo(int tilex, int tiley, bool fakeid) + { + using (var ms = new MemoryStream()) + { + var msg = new WorldInfoMsg + { + Time = (int) Main.time, + DayTime = Main.dayTime, + MoonPhase = (byte) Main.moonPhase, + BloodMoon = Main.bloodMoon, + MaxTilesX = Main.maxTilesX, + MaxTilesY = Main.maxTilesY, + SpawnX = tilex, + SpawnY = tiley, + WorldSurface = (int) Main.worldSurface, + RockLayer = (int) Main.rockLayer, + //Sending a fake world id causes the client to not be able to find a stored spawnx/y. + //This fixes the bed spawn point bug. With a fake world id it wont be able to find the bed spawn. + WorldID = !fakeid ? Main.worldID : -1, + WorldFlags = (WorldGen.shadowOrbSmashed ? WorldInfoFlag.OrbSmashed : WorldInfoFlag.None) | + (NPC.downedBoss1 ? WorldInfoFlag.DownedBoss1 : WorldInfoFlag.None) | + (NPC.downedBoss2 ? WorldInfoFlag.DownedBoss2 : WorldInfoFlag.None) | + (NPC.downedBoss3 ? WorldInfoFlag.DownedBoss3 : WorldInfoFlag.None) | + (Main.hardMode ? WorldInfoFlag.HardMode : WorldInfoFlag.None) | + (NPC.downedClown ? WorldInfoFlag.DownedClown : WorldInfoFlag.None), + WorldName = Main.worldName + }; + msg.PackFull(ms); + SendRawData(ms.ToArray()); + } + } + + public bool Teleport(int tilex, int tiley) + { + InitSpawn = false; + + SendWorldInfo(tilex, tiley, true); + + //150 Should avoid all client crash errors + //The error occurs when a tile trys to update which the client hasnt load yet, Clients only update tiles withen 150 blocks + //Try 300 if it does not work (Higher number - Longer load times - Less chance of error) + //Should we properly send sections so that clients don't get tiles twice? + SendTileSquare(tilex, tiley, 150); + +/* //We shouldn't need this section anymore -it can prevent otherwise acceptable teleportation under some circumstances. + + if (!SendTileSquare(tilex, tiley, 150)) + { + InitSpawn = true; + SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false); + return false; + } + +*/ + Spawn(-1, -1); + + SendWorldInfo(Main.spawnTileX, Main.spawnTileY, false); + + TPlayer.position.X = (float)(tilex * 16 + 8 - TPlayer.width /2); + TPlayer.position.Y = (float)(tiley * 16 - TPlayer.height); + //We need to send the tile data again to prevent clients from thinking they *really* destroyed blocks just now. + + SendTileSquare(tilex, tiley, 10); + + return true; + } + + public void Spawn() + { + Spawn(TPlayer.SpawnX, TPlayer.SpawnY); + } + + public void Spawn(int tilex, int tiley) + { + using (var ms = new MemoryStream()) + { + var msg = new SpawnMsg + { + PlayerIndex = (byte) Index, + TileX = tilex, + TileY = tiley + }; + msg.PackFull(ms); + SendRawData(ms.ToArray()); + } + } + + public void RemoveProjectile(int index, int owner) + { + using (var ms = new MemoryStream()) + { + var msg = new ProjectileRemoveMsg + { + Index = (short) index, + Owner = (byte) owner + }; + msg.PackFull(ms); + SendRawData(ms.ToArray()); + } + } + + public virtual bool SendTileSquare(int x, int y, int size = 10) + { + try + { + int num = (size - 1)/2; + int m_x=0; + int m_y=0; + + if (x - num <0){ + m_x=0; + }else{ + m_x = x - num; + } + + if (y - num <0){ + m_y=0; + }else{ + m_y = y - num; + } + + if (m_x + size > Main.maxTilesX){ + m_x=Main.maxTilesX - size; + } + + if (m_y + size > Main.maxTilesY){ + m_y=Main.maxTilesY - size; + } + + SendData(PacketTypes.TileSendSquare, "", size, m_x, m_y); + return true; + } + catch (IndexOutOfRangeException) + { + + // This is expected if square exceeds array. + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + return false; + } + + public virtual void GiveItem(int type, string name, int width, int height, int stack, int prefix = 0) + { + int itemid = Item.NewItem((int) X, (int) Y, width, height, type, stack, true, prefix); + // This is for special pickaxe/hammers/swords etc + Main.item[itemid].SetDefaults(name); + // The set default overrides the wet and stack set by NewItem + Main.item[itemid].wet = Collision.WetCollision(Main.item[itemid].position, Main.item[itemid].width, + Main.item[itemid].height); + Main.item[itemid].stack = stack; + Main.item[itemid].owner = Index; + Main.item[itemid].prefix = (byte) prefix; + NetMessage.SendData((int) PacketTypes.ItemDrop, -1, -1, "", itemid, 0f, 0f, 0f); + NetMessage.SendData((int) PacketTypes.ItemOwner, -1, -1, "", itemid, 0f, 0f, 0f); + } + + public virtual void SendMessage(string msg) + { + SendMessage(msg, 0, 255, 0); + } + + public virtual void SendMessage(string msg, Color color) + { + SendMessage(msg, color.R, color.G, color.B); + } + + public virtual void SendMessage(string msg, byte red, byte green, byte blue) + { + SendData(PacketTypes.ChatText, msg, 255, red, green, blue); + } + + public virtual void DamagePlayer(int damage) + { + NetMessage.SendData((int) PacketTypes.PlayerDamage, -1, -1, "", Index, ((new Random()).Next(-1, 1)), damage, + (float) 0); + } + + public virtual void SetTeam(int team) + { + Main.player[Index].team = team; + SendData(PacketTypes.PlayerTeam, "", Index); + } + + public virtual void Disable(string reason = "") + { + LastThreat = DateTime.UtcNow; + SetBuff(33, 330, true); //Weak + SetBuff(32, 330, true); //Slow + SetBuff(23, 330, true); //Cursed + if (!string.IsNullOrEmpty(reason)) + Log.ConsoleInfo(string.Format("Player {0} has been disabled for {1}", Name, reason)); + + var trace = new StackTrace(); + StackFrame frame = null; + frame = trace.GetFrame(1); + if (frame != null && frame.GetMethod().DeclaringType != null) + Log.Debug(frame.GetMethod().DeclaringType.Name + " called Disable()"); + } + + public virtual void Whoopie(object time) + { + var time2 = (int) time; + var launch = DateTime.UtcNow; + var startname = Name; + SendMessage("You are now being annoyed.", Color.Red); + while ((DateTime.UtcNow - launch).TotalSeconds < time2 && startname == Name) + { + SendData(PacketTypes.NpcSpecial, number: Index, number2: 2f); + Thread.Sleep(50); + } + } + + public virtual void SetBuff(int type, int time = 3600, bool bypass = false) + { + if ((DateTime.UtcNow - LastThreat).TotalMilliseconds < 5000 && !bypass) + return; + + SendData(PacketTypes.PlayerAddBuff, number: Index, number2: type, number3: time); + } + + //Todo: Separate this into a few functions. SendTo, SendToAll, etc + public virtual void SendData(PacketTypes msgType, string text = "", int number = 0, float number2 = 0f, + float number3 = 0f, float number4 = 0f, int number5 = 0) + { + if (RealPlayer && !ConnectionAlive) + return; + + NetMessage.SendData((int) msgType, Index, -1, text, number, number2, number3, number4, number5); + } + + public virtual bool SendRawData(byte[] data) + { + if (!RealPlayer || !ConnectionAlive) + return false; + + return TShock.SendBytes(Netplay.serverSock[Index], data); + } + } + + public class TSRestPlayer : TSServerPlayer + { + internal List CommandReturn = new List(); + + public TSRestPlayer() + { + Group = new SuperAdminGroup(); + } + + public override void SendMessage(string msg) + { + SendMessage(msg, 0, 255, 0); + } + + public override void SendMessage(string msg, Color color) + { + SendMessage(msg, color.R, color.G, color.B); + } + + public override void SendMessage(string msg, byte red, byte green, byte blue) + { + CommandReturn.Add(msg); + } + + public List GetCommandOutput() + { + return CommandReturn; + } + } + + public class TSServerPlayer : TSPlayer + { + public TSServerPlayer() + : base("Server") + { + Group = new SuperAdminGroup(); + } + + public override void SendMessage(string msg) + { + SendMessage(msg, 0, 255, 0); + } + + public override void SendMessage(string msg, Color color) + { + SendMessage(msg, color.R, color.G, color.B); + } + + public override void SendMessage(string msg, byte red, byte green, byte blue) + { + Console.WriteLine(msg); + //RconHandler.Response += msg + "\n"; + } + + public void SetFullMoon(bool fullmoon) + { + Main.moonPhase = 0; + SetTime(false, 0); + } + + public void SetBloodMoon(bool bloodMoon) + { + Main.bloodMoon = bloodMoon; + SetTime(false, 0); + } + + public void SetTime(bool dayTime, double time) + { + Main.dayTime = dayTime; + Main.time = time; + NetMessage.SendData((int) PacketTypes.TimeSet, -1, -1, "", 0, 0, Main.sunModY, Main.moonModY); + NetMessage.syncPlayers(); + } + + public void SpawnNPC(int type, string name, int amount, int startTileX, int startTileY, int tileXRange = 100, + int tileYRange = 50) + { + for (int i = 0; i < amount; i++) + { + int spawnTileX; + int spawnTileY; + TShock.Utils.GetRandomClearTileWithInRange(startTileX, startTileY, tileXRange, tileYRange, out spawnTileX, + out spawnTileY); + int npcid = NPC.NewNPC(spawnTileX*16, spawnTileY*16, type, 0); + // This is for special slimes + Main.npc[npcid].SetDefaults(name); + } + } + + public void StrikeNPC(int npcid, int damage, float knockBack, int hitDirection) + { + Main.npc[npcid].StrikeNPC(damage, knockBack, hitDirection); + NetMessage.SendData((int) PacketTypes.NpcStrike, -1, -1, "", npcid, damage, knockBack, hitDirection); + } + + public void RevertTiles(Dictionary tiles) + { + // Update Main.Tile first so that when tile sqaure is sent it is correct + foreach (KeyValuePair entry in tiles) + { + Main.tile[(int) entry.Key.X, (int) entry.Key.Y].Data = entry.Value; + } + // Send all players updated tile sqaures + foreach (Vector2 coords in tiles.Keys) + { + All.SendTileSquare((int) coords.X, (int) coords.Y, 3); + } + } + } + + public class PlayerData + { + public NetItem[] inventory = new NetItem[NetItem.maxNetInventory]; + public int maxHealth = 100; + //public int maxMana = 100; + public bool exists; + + public PlayerData(TSPlayer player) + { + for (int i = 0; i < NetItem.maxNetInventory; i++) + { + this.inventory[i] = new NetItem(); + } + this.inventory[0].netID = -15; + this.inventory[0].stack = 1; + if (player.TPlayer.inventory[0] != null && player.TPlayer.inventory[0].netID == -15) + this.inventory[0].prefix = player.TPlayer.inventory[0].prefix; + this.inventory[1].netID = -13; + this.inventory[1].stack = 1; + if (player.TPlayer.inventory[1] != null && player.TPlayer.inventory[1].netID == -13) + this.inventory[1].prefix = player.TPlayer.inventory[1].prefix; + this.inventory[2].netID = -16; + this.inventory[2].stack = 1; + if (player.TPlayer.inventory[2] != null && player.TPlayer.inventory[2].netID == -16) + this.inventory[2].prefix = player.TPlayer.inventory[2].prefix; + } + + public void StoreSlot(int slot, int netID, int prefix, int stack) + { + if(slot > (this.inventory.Length - 1)) //if the slot is out of range then dont save + { + return; + } + + this.inventory[slot].netID = netID; + if (this.inventory[slot].netID != 0) + { + this.inventory[slot].stack = stack; + this.inventory[slot].prefix = prefix; + } + else + { + this.inventory[slot].stack = 0; + this.inventory[slot].prefix = 0; + } + } + + public void CopyInventory(TSPlayer player) + { + this.maxHealth = player.TPlayer.statLifeMax; + Item[] inventory = player.TPlayer.inventory; + Item[] armor = player.TPlayer.armor; + for (int i = 0; i < NetItem.maxNetInventory; i++) + { + if (i < 49) + { + if (player.TPlayer.inventory[i] != null) + { + this.inventory[i].netID = inventory[i].netID; + } + else + { + this.inventory[i].netID = 0; + } + + if (this.inventory[i].netID != 0) + { + this.inventory[i].stack = inventory[i].stack; + this.inventory[i].prefix = inventory[i].prefix; + } + else + { + this.inventory[i].stack = 0; + this.inventory[i].prefix = 0; + } + } + else + { + if (player.TPlayer.armor[i - 48] != null) + { + this.inventory[i].netID = armor[i - 48].netID; + } + else + { + this.inventory[i].netID = 0; + } + + if (this.inventory[i].netID != 0) + { + this.inventory[i].stack = armor[i - 48].stack; + this.inventory[i].prefix = armor[i - 48].prefix; + } + else + { + this.inventory[i].stack = 0; + this.inventory[i].prefix = 0; + } + } + } + } + } + + public class NetItem + { + public static int maxNetInventory = 59; + public int netID; + public int stack; + public int prefix; + + public static string ToString(NetItem[] inventory) + { + string inventoryString = ""; + for (int i = 0; i < maxNetInventory; i++) + { + if (i != 0) + inventoryString += "~"; + inventoryString += inventory[i].netID; + if (inventory[i].netID != 0) + { + inventoryString += "," + inventory[i].stack; + inventoryString += "," + inventory[i].prefix; + } + else + { + inventoryString += ",0,0"; + } + } + return inventoryString; + } + + public static NetItem[] Parse(string data) + { + NetItem[] inventory = new NetItem[maxNetInventory]; + int i; + for (i = 0; i < maxNetInventory; i++) + { + inventory[i] = new NetItem(); + } + string[] items = data.Split('~'); + i = 0; + foreach (string item in items) + { + string[] idata = item.Split(','); + inventory[i].netID = int.Parse(idata[0]); + inventory[i].stack = int.Parse(idata[1]); + inventory[i].prefix = int.Parse(idata[2]); + i++; + } + return inventory; + } + } +} diff --git a/TShockAPI/TShock.cs b/TShockAPI/TShock.cs old mode 100644 new mode 100755 index 6c8e8054..9fc3aabb --- a/TShockAPI/TShock.cs +++ b/TShockAPI/TShock.cs @@ -1,1458 +1,1491 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011 The TShock Team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.IO; -using System.Net; -using System.Reflection; -using System.Threading; -using Hooks; -using MaxMind; -using Mono.Data.Sqlite; -using MySql.Data.MySqlClient; -using Rests; -using Terraria; -using TShockAPI.DB; -using TShockAPI.Net; - -namespace TShockAPI -{ - [APIVersion(1, 11)] - public class TShock : TerrariaPlugin - { - public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version; - public static readonly string VersionCodename = "Squashing bugs, and adding suggestions"; - - public static string SavePath = "tshock"; - - public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers]; - public static BanManager Bans; - public static WarpManager Warps; - public static RegionManager Regions; - public static BackupManager Backups; - public static GroupManager Groups; - public static UserManager Users; - public static ItemManager Itembans; - public static RemeberedPosManager RememberedPos; - public static InventoryManager InventoryDB; - public static ConfigFile Config { get; set; } - public static IDbConnection DB; - public static bool OverridePort; - public static PacketBufferer PacketBuffer; - public static GeoIPCountry Geo; - public static SecureRest RestApi; - public static RestManager RestManager; - public static Utils Utils = new Utils(); - public static StatTracker StatTracker = new StatTracker(); - /// - /// Used for implementing REST Tokens prior to the REST system starting up. - /// - public static Dictionary RESTStartupTokens = new Dictionary(); - - /// - /// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded. - /// - public static event Action Initialized; - - - public override Version Version - { - get { return VersionNum; } - } - - public override string Name - { - get { return "TShock"; } - } - - public override string Author - { - get { return "The Nyx Team"; } - } - - public override string Description - { - get { return "The administration modification of the future."; } - } - - public TShock(Main game) - : base(game) - { - Config = new ConfigFile(); - Order = 0; - } - - - [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] - public override void Initialize() - { - HandleCommandLine(Environment.GetCommandLineArgs()); - - if (!Directory.Exists(SavePath)) - Directory.CreateDirectory(SavePath); - - DateTime now = DateTime.Now; -#if DEBUG - Log.Initialize(Path.Combine(SavePath, now.ToString("yyyyMMddHHmmss")+".log"), LogLevel.All, false); -#else - Log.Initialize(Path.Combine(SavePath, now.ToString("yyyyMMddHHmmss")+".log"), LogLevel.All & ~LogLevel.Debug, false); -#endif - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - - try - { - if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) - { - Log.ConsoleInfo( - "TShock was improperly shut down. Please avoid this in the future, world corruption may result from this."); - File.Delete(Path.Combine(SavePath, "tshock.pid")); - } - File.WriteAllText(Path.Combine(SavePath, "tshock.pid"), Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture)); - - ConfigFile.ConfigRead += OnConfigRead; - FileTools.SetupConfig(); - - HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs()); - - if (Config.StorageType.ToLower() == "sqlite") - { - string sql = Path.Combine(SavePath, "tshock.sqlite"); - DB = new SqliteConnection(string.Format("uri=file://{0},Version=3", sql)); - } - else if (Config.StorageType.ToLower() == "mysql") - { - try - { - var hostport = Config.MySqlHost.Split(':'); - DB = new MySqlConnection(); - DB.ConnectionString = - String.Format("Server={0}; Port={1}; Database={2}; Uid={3}; Pwd={4};", - hostport[0], - hostport.Length > 1 ? hostport[1] : "3306", - Config.MySqlDbName, - Config.MySqlUsername, - Config.MySqlPassword - ); - } - catch (MySqlException ex) - { - Log.Error(ex.ToString()); - throw new Exception("MySql not setup correctly"); - } - } - else - { - throw new Exception("Invalid storage type"); - } - - Backups = new BackupManager(Path.Combine(SavePath, "backups")); - Backups.KeepFor = Config.BackupKeepFor; - Backups.Interval = Config.BackupInterval; - Bans = new BanManager(DB); - Warps = new WarpManager(DB); - Users = new UserManager(DB); - Groups = new GroupManager(DB); - Groups.LoadPermisions(); - Regions = new RegionManager(DB); - Itembans = new ItemManager(DB); - RememberedPos = new RemeberedPosManager(DB); - InventoryDB = new InventoryManager(DB); - RestApi = new SecureRest(Netplay.serverListenIP, Config.RestApiPort); - RestApi.Verify += RestApi_Verify; - RestApi.Port = Config.RestApiPort; - RestManager = new RestManager(RestApi); - RestManager.RegisterRestfulCommands(); - - var geoippath = Path.Combine(SavePath, "GeoIP.dat"); - if (Config.EnableGeoIP && File.Exists(geoippath)) - Geo = new GeoIPCountry(geoippath); - - Console.Title = string.Format("TerrariaShock Version {0} ({1})", Version, VersionCodename); - Log.ConsoleInfo(string.Format("TerrariaShock Version {0} ({1}) now running.", Version, VersionCodename)); - - GameHooks.PostInitialize += OnPostInit; - GameHooks.Update += OnUpdate; - ServerHooks.Connect += OnConnect; - ServerHooks.Join += OnJoin; - ServerHooks.Leave += OnLeave; - ServerHooks.Chat += OnChat; - ServerHooks.Command += ServerHooks_OnCommand; - NetHooks.GetData += OnGetData; - NetHooks.SendData += NetHooks_SendData; - NetHooks.GreetPlayer += OnGreetPlayer; - NpcHooks.StrikeNpc += NpcHooks_OnStrikeNpc; - NpcHooks.SetDefaultsInt += OnNpcSetDefaults; - ProjectileHooks.SetDefaults += OnProjectileSetDefaults; - WorldHooks.StartHardMode += OnStartHardMode; - WorldHooks.SaveWorld += OnSaveWorld; - - GetDataHandlers.InitGetDataHandler(); - Commands.InitCommands(); - //RconHandler.StartThread(); - - if (Config.BufferPackets) - PacketBuffer = new PacketBufferer(); - - Log.ConsoleInfo("AutoSave " + (Config.AutoSave ? "Enabled" : "Disabled")); - Log.ConsoleInfo("Backups " + (Backups.Interval > 0 ? "Enabled" : "Disabled")); - - if (Initialized != null) - Initialized(); - } - catch (Exception ex) - { - Log.Error("Fatal Startup Exception"); - Log.Error(ex.ToString()); - Environment.Exit(1); - } - } - - private RestObject RestApi_Verify(string username, string password) - { - var userAccount = Users.GetUserByName(username); - if (userAccount == null) - { - return new RestObject("401") - {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; - } - - if (Utils.HashPassword(password).ToUpper() != userAccount.Password.ToUpper()) - { - return new RestObject("401") - {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; - } - - if (!Utils.GetGroup(userAccount.Group).HasPermission("api") && userAccount.Group != "superadmin") - { - return new RestObject("403") - { - Error = - "Although your account was successfully found and identified, your account lacks the permission required to use the API. (api)" - }; - } - - return new RestObject("200") {Response = "Successful login"}; //Maybe return some user info too? - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - if (Geo != null) - { - Geo.Dispose(); - } - GameHooks.PostInitialize -= OnPostInit; - GameHooks.Update -= OnUpdate; - ServerHooks.Connect -= OnConnect; - ServerHooks.Join -= OnJoin; - ServerHooks.Leave -= OnLeave; - ServerHooks.Chat -= OnChat; - ServerHooks.Command -= ServerHooks_OnCommand; - NetHooks.GetData -= OnGetData; - NetHooks.SendData -= NetHooks_SendData; - NetHooks.GreetPlayer -= OnGreetPlayer; - NpcHooks.StrikeNpc -= NpcHooks_OnStrikeNpc; - NpcHooks.SetDefaultsInt -= OnNpcSetDefaults; - ProjectileHooks.SetDefaults -= OnProjectileSetDefaults; - WorldHooks.StartHardMode -= OnStartHardMode; - WorldHooks.SaveWorld -= OnSaveWorld; - if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) - { - File.Delete(Path.Combine(SavePath, "tshock.pid")); - } - RestApi.Dispose(); - Log.Dispose(); - } - - base.Dispose(disposing); - } - - /// - /// Handles exceptions that we didn't catch or that Red fucked up - /// - /// - /// - private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) - { - Log.Error(e.ExceptionObject.ToString()); - - if (e.ExceptionObject.ToString().Contains("Terraria.Netplay.ListenForClients") || - e.ExceptionObject.ToString().Contains("Terraria.Netplay.ServerLoop")) - { - var sb = new List(); - for (int i = 0; i < Netplay.serverSock.Length; i++) - { - if (Netplay.serverSock[i] == null) - { - sb.Add("Sock[" + i + "]"); - } - else if (Netplay.serverSock[i].tcpClient == null) - { - sb.Add("Tcp[" + i + "]"); - } - } - Log.Error(string.Join(", ", sb)); - } - - if (e.IsTerminating) - { - if (Main.worldPathName != null && Config.SaveWorldOnCrash) - { - Main.worldPathName += ".crash"; - WorldGen.saveWorld(); - } - } - } - - private void HandleCommandLine(string[] parms) - { - for (int i = 0; i < parms.Length; i++) - { - if (parms[i].ToLower() == "-configpath") - { - var path = parms[++i]; - if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1) - { - SavePath = path; - Log.ConsoleInfo("Config path has been set to " + path); - } - } - if (parms[i].ToLower() == "-worldpath") - { - var path = parms[++i]; - if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1) - { - Main.WorldPath = path; - Log.ConsoleInfo("World path has been set to " + path); - } - } - if (parms[i].ToLower() == "-dump") - { - ConfigFile.DumpDescriptions(); - Permissions.DumpDescriptions(); - } - } - } - - private void HandleCommandLinePostConfigLoad(string[] parms) - { - for (int i = 0; i < parms.Length; i++) - { - if (parms[i].ToLower() == "-port") - { - int port = Convert.ToInt32(parms[++i]); - Netplay.serverPort = port; - Config.ServerPort = port; - OverridePort = true; - Log.ConsoleInfo("Port overridden by startup argument. Set to " + port); - } - if (parms[i].ToLower() == "-rest-token") - { - string token = Convert.ToString(parms[++i]); - RESTStartupTokens.Add(token, "null"); - Console.WriteLine("Startup parameter overrode REST token."); - } - if (parms[i].ToLower() == "-rest-enabled") - { - Config.RestApiEnabled = Convert.ToBoolean(parms[++i]); - Console.WriteLine("Startup parameter overrode REST enable."); - - } - if (parms[i].ToLower() == "-rest-port") - { - Config.RestApiPort = Convert.ToInt32(parms[++i]); - Console.WriteLine("Startup parameter overrode REST port."); - - } - } - } - - /* - * Hooks: - * - */ - - public static int AuthToken = -1; - - private void OnPostInit() - { - if (!File.Exists(Path.Combine(SavePath, "auth.lck")) && !File.Exists(Path.Combine(SavePath, "authcode.txt"))) - { - var r = new Random((int) DateTime.Now.ToBinary()); - AuthToken = r.Next(100000, 10000000); - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine("TShock Notice: To become SuperAdmin, join the game and type /auth " + AuthToken); - Console.WriteLine("This token will display until disabled by verification. (/auth-verify)"); - Console.ForegroundColor = ConsoleColor.Gray; - FileTools.CreateFile(Path.Combine(SavePath, "authcode.txt")); - using (var tw = new StreamWriter(Path.Combine(SavePath, "authcode.txt"))) - { - tw.WriteLine(AuthToken); - } - } - else if (File.Exists(Path.Combine(SavePath, "authcode.txt"))) - { - using (var tr = new StreamReader(Path.Combine(SavePath, "authcode.txt"))) - { - AuthToken = Convert.ToInt32(tr.ReadLine()); - } - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine( - "TShock Notice: authcode.txt is still present, and the AuthToken located in that file will be used."); - Console.WriteLine("To become superadmin, join the game and type /auth " + AuthToken); - Console.WriteLine("This token will display until disabled by verification. (/auth-verify)"); - Console.ForegroundColor = ConsoleColor.Gray; - } - else - { - AuthToken = 0; - } - Regions.ReloadAllRegions(); - if (Config.RestApiEnabled) - RestApi.Start(); - - StatTracker.CheckIn(); - FixChestStacks(); - } - - private void FixChestStacks() - { - foreach (Chest chest in Main.chest) - { - if (chest != null) - { - foreach (Item item in chest.item) - { - if (item != null && item.stack > item.maxStack) - item.stack = item.maxStack; - } - } - } - } - - private DateTime LastCheck = DateTime.UtcNow; - private DateTime LastSave = DateTime.UtcNow; - - private void OnUpdate() - { - UpdateManager.UpdateProcedureCheck(); - StatTracker.CheckIn(); - if (Backups.IsBackupTime) - Backups.Backup(); - - //call these every second, not every update - if ((DateTime.UtcNow - LastCheck).TotalSeconds >= 1) - { - OnSecondUpdate(); - LastCheck = DateTime.UtcNow; - } - - if ((DateTime.UtcNow - LastSave).TotalMinutes >= Config.ServerSideInventorySave) - { - foreach (TSPlayer player in Players) - { - // prevent null point exceptions - if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) - { - - InventoryDB.InsertPlayerData(player); - } - } - LastSave = DateTime.UtcNow; - } - } - - private void OnSecondUpdate() - { - if (Config.ForceTime != "normal") - { - switch (Config.ForceTime) - { - case "day": - TSPlayer.Server.SetTime(true, 27000.0); - break; - case "night": - TSPlayer.Server.SetTime(false, 16200.0); - break; - } - } - int count = 0; - foreach (TSPlayer player in Players) - { - if (player != null && player.Active) - { - count++; - if (player.TilesDestroyed != null) - { - if (player.TileKillThreshold >= Config.TileKillThreshold) - { - player.Disable("Reached TileKill threshold"); - TSPlayer.Server.RevertTiles(player.TilesDestroyed); - player.TilesDestroyed.Clear(); - } - } - if (player.TileKillThreshold > 0) - { - player.TileKillThreshold = 0; - //We don't want to revert the entire map in case of a disable. - player.TilesDestroyed.Clear(); - } - if (player.TilesCreated != null) - { - if (player.TilePlaceThreshold >= Config.TilePlaceThreshold) - { - player.Disable("Reached TilePlace threshold"); - TSPlayer.Server.RevertTiles(player.TilesCreated); - player.TilesCreated.Clear(); - } - } - if (player.TilePlaceThreshold > 0) - { - player.TilePlaceThreshold = 0; - } - if (player.TileLiquidThreshold >= Config.TileLiquidThreshold) - { - player.Disable("Reached TileLiquid threshold"); - } - if (player.TileLiquidThreshold > 0) - { - player.TileLiquidThreshold = 0; - } - if (player.ProjectileThreshold >= Config.ProjectileThreshold) - { - player.Disable("Reached Projectile threshold"); - } - if (player.ProjectileThreshold > 0) - { - player.ProjectileThreshold = 0; - } - if (player.Dead && (DateTime.Now - player.LastDeath).Seconds >= 3 && player.Difficulty != 2) - { - player.Spawn(); - } - string check = "none"; - foreach (Item item in player.TPlayer.inventory) - { - if (!player.Group.HasPermission(Permissions.ignorestackhackdetection) && item.stack > item.maxStack && - item.type != 0) - { - check = "Remove Item " + item.name + " (" + item.stack + ") exceeds max stack of " + item.maxStack; - } - } - player.IgnoreActionsForCheating = check; - check = "none"; - foreach (Item item in player.TPlayer.armor) - { - if (!player.Group.HasPermission(Permissions.usebanneditem) && Itembans.ItemIsBanned(item.name, player)) - { - player.SetBuff(30, 120); //Bleeding - player.SetBuff(36, 120); //Broken Armor - check = "Remove Armor/Accessory " + item.name; - } - } - player.IgnoreActionsForDisabledArmor = check; - if (CheckIgnores(player)) - { - player.SetBuff(33, 120); //Weak - player.SetBuff(32, 120); //Slow - player.SetBuff(23, 120); //Cursed - } - else if (!player.Group.HasPermission(Permissions.usebanneditem) && - Itembans.ItemIsBanned(player.TPlayer.inventory[player.TPlayer.selectedItem].name, player)) - { - player.SetBuff(23, 120); //Cursed - } - } - } - Console.Title = string.Format("TerrariaShock Version {0} ({1}) ({2}/{3})", Version, VersionCodename, count, - Config.MaxSlots); - } - - private void OnConnect(int ply, HandledEventArgs handler) - { - var player = new TSPlayer(ply); - if (Config.EnableDNSHostResolution) - { - player.Group = Users.GetGroupForIPExpensive(player.IP); - } - else - { - player.Group = Users.GetGroupForIP(player.IP); - } - - if (Utils.ActivePlayers() + 1 > Config.MaxSlots + 20) - { - Utils.ForceKick(player, Config.ServerFullNoReservedReason); - handler.Handled = true; - return; - } - - var ipban = Bans.GetBanByIp(player.IP); - Ban ban = null; - if (ipban != null && Config.EnableIPBans) - ban = ipban; - - if (ban != null) - { - Utils.ForceKick(player, string.Format("You are banned: {0}", ban.Reason)); - handler.Handled = true; - return; - } - - if (!FileTools.OnWhitelist(player.IP)) - { - Utils.ForceKick(player, "Not on whitelist."); - handler.Handled = true; - return; - } - - if (Geo != null) - { - var code = Geo.TryGetCountryCode(IPAddress.Parse(player.IP)); - player.Country = code == null ? "N/A" : GeoIPCountry.GetCountryNameByCode(code); - if (code == "A1") - { - if (Config.KickProxyUsers) - { - Utils.ForceKick(player, "Proxies are not allowed"); - handler.Handled = true; - return; - } - } - } - Players[ply] = player; - } - - private void OnJoin(int ply, HandledEventArgs handler) - { - var player = Players[ply]; - if (player == null) - { - handler.Handled = true; - return; - } - - var nameban = Bans.GetBanByName(player.Name); - Ban ban = null; - if (nameban != null && Config.EnableBanOnUsernames) - ban = nameban; - - if (ban != null) - { - Utils.ForceKick(player, string.Format("You are banned: {0}", ban.Reason)); - handler.Handled = true; - return; - } - } - - private void OnLeave(int ply) - { - - var tsplr = Players[ply]; - Players[ply] = null; - - if (tsplr != null && tsplr.ReceivedInfo) - { - if (!tsplr.SilentKickInProgress) - { - Utils.Broadcast(tsplr.Name + " left", Color.Yellow); - } - Log.Info(string.Format("{0} left.", tsplr.Name)); - - if (tsplr.IsLoggedIn && !tsplr.IgnoreActionsForClearingTrashCan) - { - tsplr.PlayerData.CopyInventory(tsplr); - InventoryDB.InsertPlayerData(tsplr); - } - - if (Config.RememberLeavePos) - { - RememberedPos.InsertLeavePos(tsplr.Name, tsplr.IP, (int) (tsplr.X/16), (int) (tsplr.Y/16)); - } - } - } - - private void OnChat(messageBuffer msg, int ply, string text, HandledEventArgs e) - { - if (e.Handled) - return; - - var tsplr = Players[msg.whoAmI]; - if (tsplr == null) - { - e.Handled = true; - return; - } - - /*if (!Utils.ValidString(text)) - { - e.Handled = true; - return; - }*/ - - if (text.StartsWith("/")) - { - try - { - e.Handled = Commands.HandleCommand(tsplr, text); - } - catch (Exception ex) - { - Log.ConsoleError("Command exception"); - Log.Error(ex.ToString()); - } - } - else if (!tsplr.mute) - { - Utils.Broadcast( - String.Format(Config.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix, text), - tsplr.Group.R, tsplr.Group.G, tsplr.Group.B); - e.Handled = true; - } - else if (tsplr.mute) - { - tsplr.SendMessage("You are muted!"); - e.Handled = true; - } - } - - /// - /// When a server command is run. - /// - /// - /// - private void ServerHooks_OnCommand(string text, HandledEventArgs e) - { - if (e.Handled) - return; - - // Damn you ThreadStatic and Redigit - if (Main.rand == null) - { - Main.rand = new Random(); - } - if (WorldGen.genRand == null) - { - WorldGen.genRand = new Random(); - } - - if (text.StartsWith("playing") || text.StartsWith("/playing")) - { - int count = 0; - foreach (TSPlayer player in Players) - { - if (player != null && player.Active) - { - count++; - TSPlayer.Server.SendMessage(string.Format("{0} ({1}) [{2}] <{3}>", player.Name, player.IP, - player.Group.Name, player.UserAccountName)); - } - } - TSPlayer.Server.SendMessage(string.Format("{0} players connected.", count)); - } - else if (text == "autosave") - { - Main.autoSave = Config.AutoSave = !Config.AutoSave; - Log.ConsoleInfo("AutoSave " + (Config.AutoSave ? "Enabled" : "Disabled")); - } - else if (text.StartsWith("/")) - { - Commands.HandleCommand(TSPlayer.Server, text); - } - else - { - Commands.HandleCommand(TSPlayer.Server, "/" + text); - } - e.Handled = true; - } - - private void OnGetData(GetDataEventArgs e) - { - if (e.Handled) - return; - - PacketTypes type = e.MsgID; - - Debug.WriteLine("Recv: {0:X}: {2} ({1:XX})", e.Msg.whoAmI, (byte) type, type); - - var player = Players[e.Msg.whoAmI]; - if (player == null) - { - e.Handled = true; - return; - } - - if (!player.ConnectionAlive) - { - e.Handled = true; - return; - } - - if (player.RequiresPassword && type != PacketTypes.PasswordSend) - { - e.Handled = true; - return; - } - - if ((player.State < 10 || player.Dead) && (int) type > 12 && (int) type != 16 && (int) type != 42 && (int) type != 50 && - (int) type != 38 && (int) type != 5 && (int) type != 21) - { - e.Handled = true; - return; - } - - using (var data = new MemoryStream(e.Msg.readBuffer, e.Index, e.Length)) - { - try - { - if (GetDataHandlers.HandlerGetData(type, player, data)) - e.Handled = true; - } - catch (Exception ex) - { - Log.Error(ex.ToString()); - } - } - } - - private void OnGreetPlayer(int who, HandledEventArgs e) - { - var player = Players[who]; - if (player == null) - { - e.Handled = true; - return; - } - player.LoginMS= DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - - Utils.ShowFileToUser(player, "motd.txt"); - - if (Config.PvPMode == "always" && !player.TPlayer.hostile) - { - player.SendMessage("PvP is forced! Enable PvP else you can't move or do anything!", Color.Red); - } - - if (!player.IsLoggedIn) - { - if (Config.ServerSideInventory) - { - player.SendMessage( - player.IgnoreActionsForInventory = "Server Side Inventory is enabled! Please /register or /login to play!", - Color.Red); - } - else if (Config.RequireLogin) - { - player.SendMessage("Please /register or /login to play!", Color.Red); - } - } - - if (player.Group.HasPermission(Permissions.causeevents) && Config.InfiniteInvasion) - { - StartInvasion(); - } - - player.LastNetPosition = new Vector2(Main.spawnTileX*16f, Main.spawnTileY*16f); - - if (Config.RememberLeavePos) - { - if (RememberedPos.GetLeavePos(player.Name, player.IP) != Vector2.Zero){ - var pos = RememberedPos.GetLeavePos(player.Name, player.IP); - - player.Teleport((int) pos.X, (int) pos.Y + 3); - }} - - e.Handled = true; - } - - private void NpcHooks_OnStrikeNpc(NpcStrikeEventArgs e) - { - if (Config.InfiniteInvasion) - { - IncrementKills(); - if (Main.invasionSize < 10) - { - Main.invasionSize = 20000000; - } - } - } - - private void OnProjectileSetDefaults(SetDefaultsEventArgs e) - { - if (e.Info == 43) - if (Config.DisableTombstones) - e.Object.SetDefaults(0); - if (e.Info == 75) - if (Config.DisableClownBombs) - e.Object.SetDefaults(0); - if (e.Info == 109) - if (Config.DisableSnowBalls) - e.Object.SetDefaults(0); - } - - private void OnNpcSetDefaults(SetDefaultsEventArgs e) - { - if (Itembans.ItemIsBanned(e.Object.name, null)) - { - e.Object.SetDefaults(0); - } - } - - /// - /// Send bytes to client using packetbuffering if available - /// - /// socket to send to - /// bytes to send - /// False on exception - public static bool SendBytes(ServerSock client, byte[] bytes) - { - if (PacketBuffer != null) - { - PacketBuffer.BufferBytes(client, bytes); - return true; - } - - return SendBytesBufferless(client, bytes); - } - - /// - /// Send bytes to a client ignoring the packet buffer - /// - /// socket to send to - /// bytes to send - /// False on exception - public static bool SendBytesBufferless(ServerSock client, byte[] bytes) - { - try - { - if (client.tcpClient.Connected) - client.networkStream.Write(bytes, 0, bytes.Length); - return true; - } - catch (Exception ex) - { - Log.Warn("This is a normal exception"); - Log.Warn(ex.ToString()); - } - return false; - } - - private void NetHooks_SendData(SendDataEventArgs e) - { - if (e.MsgID == PacketTypes.Disconnect) - { - Action senddisconnect = (sock, str) => - { - if (sock == null || !sock.active) - return; - sock.kill = true; - using (var ms = new MemoryStream()) - { - new DisconnectMsg {Reason = str}.PackFull(ms); - SendBytesBufferless(sock, ms.ToArray()); - } - }; - - if (e.remoteClient != -1) - { - senddisconnect(Netplay.serverSock[e.remoteClient], e.text); - } - else - { - for (int i = 0; i < Netplay.serverSock.Length; i++) - { - if (e.ignoreClient != -1 && e.ignoreClient == i) - continue; - - senddisconnect(Netplay.serverSock[i], e.text); - } - } - e.Handled = true; - } - } - - private void OnStartHardMode(HandledEventArgs e) - { - if (Config.DisableHardmode) - e.Handled = true; - } - - void OnSaveWorld(bool resettime, HandledEventArgs e) - { - if (!Utils.saving) - { - Utils.Broadcast("Saving world. Momentary lag might result from this.", Color.Red); - var SaveWorld = new Thread(Utils.SaveWorld); - SaveWorld.Start(); - } - e.Handled = true; - } - - /* - * Useful stuff: - * */ - - public static void StartInvasion() - { - Main.invasionType = 1; - if (Config.InfiniteInvasion) - { - Main.invasionSize = 20000000; - } - else - { - Main.invasionSize = 100 + (Config.InvasionMultiplier*Utils.ActivePlayers()); - } - - Main.invasionWarn = 0; - if (new Random().Next(2) == 0) - { - Main.invasionX = 0.0; - } - else - { - Main.invasionX = Main.maxTilesX; - } - } - - private static int KillCount; - - public static void IncrementKills() - { - KillCount++; - Random r = new Random(); - int random = r.Next(5); - if (KillCount%100 == 0) - { - switch (random) - { - case 0: - Utils.Broadcast(string.Format("You call that a lot? {0} goblins killed!", KillCount)); - break; - case 1: - Utils.Broadcast(string.Format("Fatality! {0} goblins killed!", KillCount)); - break; - case 2: - Utils.Broadcast(string.Format("Number of 'noobs' killed to date: {0}", KillCount)); - break; - case 3: - Utils.Broadcast(string.Format("Duke Nukem would be proud. {0} goblins killed.", KillCount)); - break; - case 4: - Utils.Broadcast(string.Format("You call that a lot? {0} goblins killed!", KillCount)); - break; - case 5: - Utils.Broadcast(string.Format("{0} copies of Call of Duty smashed.", KillCount)); - break; - } - } - } - - public static bool CheckProjectilePermission(TSPlayer player, int index, int type) - { - if (type == 43) - { - return true; - } - - if (type == 17 && !player.Group.HasPermission(Permissions.usebanneditem) && Itembans.ItemIsBanned("Dirt Rod", player)) - //Dirt Rod Projectile - { - return true; - } - - if ((type == 42 || type == 65 || type == 68) && !player.Group.HasPermission(Permissions.usebanneditem) && - Itembans.ItemIsBanned("Sandgun", player)) //Sandgun Projectiles - { - return true; - } - - Projectile proj = new Projectile(); - proj.SetDefaults(type); - - if (!player.Group.HasPermission(Permissions.usebanneditem) && Itembans.ItemIsBanned(proj.name, player)) - { - return true; - } - - if (Main.projHostile[type]) - { - //player.SendMessage( proj.name, Color.Yellow); - return true; - } - - return false; - } - - public static bool CheckRangePermission(TSPlayer player, int x, int y, int range = 32) - { - if (Config.RangeChecks && ((Math.Abs(player.TileX - x) > range) || (Math.Abs(player.TileY - y) > range))) - { - return true; - } - return false; - } - - public static bool CheckTilePermission( TSPlayer player, int tileX, int tileY, byte tileType, byte actionType ) - { - if (!player.Group.HasPermission(Permissions.canbuild)) - { - if (TShock.Config.AllowIce && actionType != 1) - { - foreach (Point p in player.IceTiles) - { - if (p.X == tileX && p.Y == tileY && (Main.tile[p.X, p.Y].type == 0 || Main.tile[p.X, p.Y].type == 127)) - { - player.IceTiles.Remove(p); - return false; - } - } - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000){ - player.SendMessage("You do not have permission to build!", Color.Red); - player.BPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - -} - return true; - } - - if (TShock.Config.AllowIce && actionType == 1 && tileType == 127) - { - player.IceTiles.Add(new Point(tileX, tileY)); - return false; - } - - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000){ - player.SendMessage("You do not have permission to build!", Color.Red); - player.BPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - -} - return true; - - } - if (!player.Group.HasPermission(Permissions.editspawn) && !Regions.CanBuild(tileX, tileY, player) && - Regions.InArea(tileX, tileY)) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000){ - player.SendMessage("Region protected from changes.", Color.Red); - player.RPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - -} - return true; - } - if (Config.DisableBuild) - { - if (!player.Group.HasPermission(Permissions.editspawn)) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000){ - player.SendMessage("World protected from changes.", Color.Red); - player.WPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - -} - return true; - } - } - if (Config.SpawnProtection) - { - if (!player.Group.HasPermission(Permissions.editspawn)) - { - var flag = CheckSpawn(tileX, tileY); - if (flag) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 2000){ - player.SendMessage("Spawn protected from changes.", Color.Red); - player.SPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - } - } - return false; - } - - public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY) - { - if (!player.Group.HasPermission(Permissions.canbuild)) - { - - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000){ - player.SendMessage("You do not have permission to build!", Color.Red); - player.BPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - - if (!player.Group.HasPermission(Permissions.editspawn) && !Regions.CanBuild(tileX, tileY, player) && - Regions.InArea(tileX, tileY)) - { - - - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000){ - player.SendMessage("Region protected from changes.", Color.Red); - player.RPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - - if (Config.DisableBuild) - { - if (!player.Group.HasPermission(Permissions.editspawn)) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000){ - player.SendMessage("World protected from changes.", Color.Red); - player.WPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - } - return true; - } - } - if (Config.SpawnProtection) - { - if (!player.Group.HasPermission(Permissions.editspawn)) - { - var flag = CheckSpawn(tileX, tileY); - if (flag) - { - if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 1000){ - player.SendMessage("Spawn protected from changes.", Color.Red); - player.SPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - - } - - - return true; - } - } - } - return false; - } - public static bool CheckSpawn(int x, int y) - { - Vector2 tile = new Vector2(x, y); - Vector2 spawn = new Vector2(Main.spawnTileX, Main.spawnTileY); - return Distance(spawn, tile) <= Config.SpawnProtectionRadius; - } - - public static float Distance(Vector2 value1, Vector2 value2) - { - float num2 = value1.X - value2.X; - float num = value1.Y - value2.Y; - float num3 = (num2*num2) + (num*num); - return (float) Math.Sqrt(num3); - } - - public static bool HackedHealth(TSPlayer player) - { - return (player.TPlayer.statManaMax > 400) || - (player.TPlayer.statMana > 400) || - (player.TPlayer.statLifeMax > 400) || - (player.TPlayer.statLife > 400); - } - - public static bool HackedInventory(TSPlayer player) - { - bool check = false; - - Item[] inventory = player.TPlayer.inventory; - Item[] armor = player.TPlayer.armor; - for (int i = 0; i < NetItem.maxNetInventory; i++) - { - if (i < 49) - { - Item item = new Item(); - if (inventory[i] != null && inventory[i].netID != 0) - { - item.netDefaults(inventory[i].netID); - item.Prefix(inventory[i].prefix); - item.AffixName(); - if (inventory[i].stack > item.maxStack) - { - check = true; - player.SendMessage( - String.Format("Stack cheat detected. Remove item {0} ({1}) and then rejoin", item.name, inventory[i].stack), - Color.Cyan); - } - } - } - else - { - Item item = new Item(); - if (armor[i - 48] != null && armor[i - 48].netID != 0) - { - item.netDefaults(armor[i - 48].netID); - item.Prefix(armor[i - 48].prefix); - item.AffixName(); - if (armor[i - 48].stack > item.maxStack) - { - check = true; - player.SendMessage( - String.Format("Stack cheat detected. Remove armor {0} ({1}) and then rejoin", item.name, armor[i - 48].stack), - Color.Cyan); - } - } - } - } - - return check; - } - - public static bool CheckInventory(TSPlayer player) - { - PlayerData playerData = player.PlayerData; - bool check = true; - - if (player.TPlayer.statLifeMax > playerData.maxHealth) - { - player.SendMessage("Error: Your max health exceeded (" + playerData.maxHealth + ") which is stored on server", - Color.Cyan); - check = false; - } - - Item[] inventory = player.TPlayer.inventory; - Item[] armor = player.TPlayer.armor; - for (int i = 0; i < NetItem.maxNetInventory; i++) - { - if (i < 49) - { - Item item = new Item(); - Item serverItem = new Item(); - if (inventory[i] != null && inventory[i].netID != 0) - { - if (playerData.inventory[i].netID != inventory[i].netID) - { - item.netDefaults(inventory[i].netID); - item.Prefix(inventory[i].prefix); - item.AffixName(); - player.SendMessage(player.IgnoreActionsForInventory = "Your item (" + item.name + ") needs to be deleted.", - Color.Cyan); - check = false; - } - else if (playerData.inventory[i].prefix != inventory[i].prefix) - { - item.netDefaults(inventory[i].netID); - item.Prefix(inventory[i].prefix); - item.AffixName(); - player.SendMessage(player.IgnoreActionsForInventory = "Your item (" + item.name + ") needs to be deleted.", - Color.Cyan); - check = false; - } - else if (inventory[i].stack > playerData.inventory[i].stack) - { - item.netDefaults(inventory[i].netID); - item.Prefix(inventory[i].prefix); - item.AffixName(); - player.SendMessage( - player.IgnoreActionsForInventory = - "Your item (" + item.name + ") (" + inventory[i].stack + ") needs to have it's stack decreased to (" + - playerData.inventory[i].stack + ").", Color.Cyan); - check = false; - } - } - } - else - { - Item item = new Item(); - Item serverItem = new Item(); - if (armor[i - 48] != null && armor[i - 48].netID != 0) - { - if (playerData.inventory[i].netID != armor[i - 48].netID) - { - item.netDefaults(armor[i - 48].netID); - item.Prefix(armor[i - 48].prefix); - item.AffixName(); - player.SendMessage(player.IgnoreActionsForInventory = "Your armor (" + item.name + ") needs to be deleted.", - Color.Cyan); - check = false; - } - else if (playerData.inventory[i].prefix != armor[i - 48].prefix) - { - item.netDefaults(armor[i - 48].netID); - item.Prefix(armor[i - 48].prefix); - item.AffixName(); - player.SendMessage(player.IgnoreActionsForInventory = "Your armor (" + item.name + ") needs to be deleted.", - Color.Cyan); - check = false; - } - else if (armor[i - 48].stack > playerData.inventory[i].stack) - { - item.netDefaults(armor[i - 48].netID); - item.Prefix(armor[i - 48].prefix); - item.AffixName(); - player.SendMessage( - player.IgnoreActionsForInventory = - "Your armor (" + item.name + ") (" + inventory[i].stack + ") needs to have it's stack decreased to (" + - playerData.inventory[i].stack + ").", Color.Cyan); - check = false; - } - } - } - } - - return check; - } - - public static bool CheckIgnores(TSPlayer player) - { - bool check = false; - if (Config.PvPMode == "always" && !player.TPlayer.hostile) - check = true; - if (player.IgnoreActionsForInventory != "none") - check = true; - if (player.IgnoreActionsForCheating != "none") - check = true; - if (player.IgnoreActionsForDisabledArmor != "none") - check = true; - if (player.IgnoreActionsForClearingTrashCan) - check = true; - if (!player.IsLoggedIn && Config.RequireLogin) - check = true; - return check; - } - - public void OnConfigRead(ConfigFile file) - { - NPC.defaultMaxSpawns = file.DefaultMaximumSpawns; - NPC.defaultSpawnRate = file.DefaultSpawnRate; - - Main.autoSave = file.AutoSave; - if (Backups != null) - { - Backups.KeepFor = file.BackupKeepFor; - Backups.Interval = file.BackupInterval; - } - if (!OverridePort) - { - Netplay.serverPort = file.ServerPort; - } - - if (file.MaxSlots > 235) - file.MaxSlots = 235; - Main.maxNetPlayers = file.MaxSlots + 20; - Netplay.password = ""; - Netplay.spamCheck = false; - - RconHandler.Password = file.RconPassword; - RconHandler.ListenPort = file.RconPort; - - Utils.HashAlgo = file.HashAlgorithm; - } - } -} +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Net; +using System.Reflection; +using System.Threading; +using Hooks; +using MaxMind; +using Mono.Data.Sqlite; +using MySql.Data.MySqlClient; +using Rests; +using Terraria; +using TShockAPI.DB; +using TShockAPI.Net; + +namespace TShockAPI +{ + [APIVersion(1, 11)] + public class TShock : TerrariaPlugin + { + private const string LogFormatDefault = "yyyyMMddHHmmss"; + private static string LogFormat = LogFormatDefault; + private static bool LogClear = false; + public static readonly Version VersionNum = Assembly.GetExecutingAssembly().GetName().Version; + public static readonly string VersionCodename = "Squashing bugs, and adding suggestions"; + + public static string SavePath = "tshock"; + + public static TSPlayer[] Players = new TSPlayer[Main.maxPlayers]; + public static BanManager Bans; + public static WarpManager Warps; + public static RegionManager Regions; + public static BackupManager Backups; + public static GroupManager Groups; + public static UserManager Users; + public static ItemManager Itembans; + public static RemeberedPosManager RememberedPos; + public static InventoryManager InventoryDB; + public static ConfigFile Config { get; set; } + public static IDbConnection DB; + public static bool OverridePort; + public static PacketBufferer PacketBuffer; + public static GeoIPCountry Geo; + public static SecureRest RestApi; + public static RestManager RestManager; + public static Utils Utils = Utils.Instance; + public static StatTracker StatTracker = new StatTracker(); + /// + /// Used for implementing REST Tokens prior to the REST system starting up. + /// + public static Dictionary RESTStartupTokens = new Dictionary(); + + /// + /// Called after TShock is initialized. Useful for plugins that needs hooks before tshock but also depend on tshock being loaded. + /// + public static event Action Initialized; + + + public override Version Version + { + get { return VersionNum; } + } + + public override string Name + { + get { return "TShock"; } + } + + public override string Author + { + get { return "The Nyx Team"; } + } + + public override string Description + { + get { return "The administration modification of the future."; } + } + + public TShock(Main game) + : base(game) + { + Config = new ConfigFile(); + Order = 0; + } + + + [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] + public override void Initialize() + { + HandleCommandLine(Environment.GetCommandLineArgs()); + + if (!Directory.Exists(SavePath)) + Directory.CreateDirectory(SavePath); + + DateTime now = DateTime.Now; + string logFilename; + try + { + logFilename = Path.Combine(SavePath, now.ToString(LogFormat)+".log"); + } + catch(Exception) + { + // Problem with the log format use the default + logFilename = Path.Combine(SavePath, now.ToString(LogFormatDefault) + ".log"); + } +#if DEBUG + Log.Initialize(logFilename, LogLevel.All, false); +#else + Log.Initialize(logFilename, LogLevel.All & ~LogLevel.Debug, LogClear); +#endif + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + + try + { + if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) + { + Log.ConsoleInfo( + "TShock was improperly shut down. Please use the exit command in the future to prevent this."); + File.Delete(Path.Combine(SavePath, "tshock.pid")); + } + File.WriteAllText(Path.Combine(SavePath, "tshock.pid"), Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture)); + + ConfigFile.ConfigRead += OnConfigRead; + FileTools.SetupConfig(); + + HandleCommandLinePostConfigLoad(Environment.GetCommandLineArgs()); + + if (Config.StorageType.ToLower() == "sqlite") + { + string sql = Path.Combine(SavePath, "tshock.sqlite"); + DB = new SqliteConnection(string.Format("uri=file://{0},Version=3", sql)); + } + else if (Config.StorageType.ToLower() == "mysql") + { + try + { + var hostport = Config.MySqlHost.Split(':'); + DB = new MySqlConnection(); + DB.ConnectionString = + String.Format("Server={0}; Port={1}; Database={2}; Uid={3}; Pwd={4};", + hostport[0], + hostport.Length > 1 ? hostport[1] : "3306", + Config.MySqlDbName, + Config.MySqlUsername, + Config.MySqlPassword + ); + } + catch (MySqlException ex) + { + Log.Error(ex.ToString()); + throw new Exception("MySql not setup correctly"); + } + } + else + { + throw new Exception("Invalid storage type"); + } + + Backups = new BackupManager(Path.Combine(SavePath, "backups")); + Backups.KeepFor = Config.BackupKeepFor; + Backups.Interval = Config.BackupInterval; + Bans = new BanManager(DB); + Warps = new WarpManager(DB); + Users = new UserManager(DB); + Groups = new GroupManager(DB); + Regions = new RegionManager(DB); + Itembans = new ItemManager(DB); + RememberedPos = new RemeberedPosManager(DB); + InventoryDB = new InventoryManager(DB); + RestApi = new SecureRest(Netplay.serverListenIP, Config.RestApiPort); + RestApi.Verify += RestApi_Verify; + RestApi.Port = Config.RestApiPort; + RestManager = new RestManager(RestApi); + RestManager.RegisterRestfulCommands(); + + var geoippath = Path.Combine(SavePath, "GeoIP.dat"); + if (Config.EnableGeoIP && File.Exists(geoippath)) + Geo = new GeoIPCountry(geoippath); + + Log.ConsoleInfo(string.Format("TerrariaShock Version {0} ({1}) now running.", Version, VersionCodename)); + + GameHooks.PostInitialize += OnPostInit; + GameHooks.Update += OnUpdate; + ServerHooks.Connect += OnConnect; + ServerHooks.Join += OnJoin; + ServerHooks.Leave += OnLeave; + ServerHooks.Chat += OnChat; + ServerHooks.Command += ServerHooks_OnCommand; + NetHooks.GetData += OnGetData; + NetHooks.SendData += NetHooks_SendData; + NetHooks.GreetPlayer += OnGreetPlayer; + NpcHooks.StrikeNpc += NpcHooks_OnStrikeNpc; + NpcHooks.SetDefaultsInt += OnNpcSetDefaults; + ProjectileHooks.SetDefaults += OnProjectileSetDefaults; + WorldHooks.StartHardMode += OnStartHardMode; + WorldHooks.SaveWorld += SaveManager.Instance.OnSaveWorld; + + GetDataHandlers.InitGetDataHandler(); + Commands.InitCommands(); + //RconHandler.StartThread(); + + if (Config.RestApiEnabled) + RestApi.Start(); + + if (Config.BufferPackets) + PacketBuffer = new PacketBufferer(); + + Log.ConsoleInfo("AutoSave " + (Config.AutoSave ? "Enabled" : "Disabled")); + Log.ConsoleInfo("Backups " + (Backups.Interval > 0 ? "Enabled" : "Disabled")); + + if (Initialized != null) + Initialized(); + } + catch (Exception ex) + { + Log.Error("Fatal Startup Exception"); + Log.Error(ex.ToString()); + Environment.Exit(1); + } + } + + private RestObject RestApi_Verify(string username, string password) + { + var userAccount = Users.GetUserByName(username); + if (userAccount == null) + { + return new RestObject("401") + {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; + } + + if (Utils.HashPassword(password).ToUpper() != userAccount.Password.ToUpper()) + { + return new RestObject("401") + {Error = "Invalid username/password combination provided. Please re-submit your query with a correct pair."}; + } + + if (!Utils.GetGroup(userAccount.Group).HasPermission("api") && userAccount.Group != "superadmin") + { + return new RestObject("403") + { + Error = + "Although your account was successfully found and identified, your account lacks the permission required to use the API. (api)" + }; + } + + return new RestObject("200") {Response = "Successful login"}; //Maybe return some user info too? + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + // NOTE: order is important here + if (Geo != null) + { + Geo.Dispose(); + } + SaveManager.Instance.Dispose(); + + GameHooks.PostInitialize -= OnPostInit; + GameHooks.Update -= OnUpdate; + ServerHooks.Connect -= OnConnect; + ServerHooks.Join -= OnJoin; + ServerHooks.Leave -= OnLeave; + ServerHooks.Chat -= OnChat; + ServerHooks.Command -= ServerHooks_OnCommand; + NetHooks.GetData -= OnGetData; + NetHooks.SendData -= NetHooks_SendData; + NetHooks.GreetPlayer -= OnGreetPlayer; + NpcHooks.StrikeNpc -= NpcHooks_OnStrikeNpc; + NpcHooks.SetDefaultsInt -= OnNpcSetDefaults; + ProjectileHooks.SetDefaults -= OnProjectileSetDefaults; + WorldHooks.StartHardMode -= OnStartHardMode; + WorldHooks.SaveWorld -= SaveManager.Instance.OnSaveWorld; + + if (File.Exists(Path.Combine(SavePath, "tshock.pid"))) + { + File.Delete(Path.Combine(SavePath, "tshock.pid")); + } + + RestApi.Dispose(); + Log.Dispose(); + } + base.Dispose(disposing); + } + + /// + /// Handles exceptions that we didn't catch or that Red fucked up + /// + /// + /// + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Log.Error(e.ExceptionObject.ToString()); + + if (e.ExceptionObject.ToString().Contains("Terraria.Netplay.ListenForClients") || + e.ExceptionObject.ToString().Contains("Terraria.Netplay.ServerLoop")) + { + var sb = new List(); + for (int i = 0; i < Netplay.serverSock.Length; i++) + { + if (Netplay.serverSock[i] == null) + { + sb.Add("Sock[" + i + "]"); + } + else if (Netplay.serverSock[i].tcpClient == null) + { + sb.Add("Tcp[" + i + "]"); + } + } + Log.Error(string.Join(", ", sb)); + } + + if (e.IsTerminating) + { + if (Main.worldPathName != null && Config.SaveWorldOnCrash) + { + Main.worldPathName += ".crash"; + SaveManager.Instance.SaveWorld(); + } + } + } + + private void HandleCommandLine(string[] parms) + { + string path; + for (int i = 0; i < parms.Length; i++) + { + switch(parms[i].ToLower()) + { + case "-configpath": + path = parms[++i]; + if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1) + { + SavePath = path; + Log.ConsoleInfo("Config path has been set to " + path); + } + break; + + case "-worldpath": + path = parms[++i]; + if (path.IndexOfAny(Path.GetInvalidPathChars()) == -1) + { + Main.WorldPath = path; + Log.ConsoleInfo("World path has been set to " + path); + } + break; + + case "-dump": + ConfigFile.DumpDescriptions(); + Permissions.DumpDescriptions(); + break; + + case "-logformat": + LogFormat = parms[++i]; + break; + + case "-logclear": + bool.TryParse(parms[++i], out LogClear); + break; + } + } + } + + public static void HandleCommandLinePostConfigLoad(string[] parms) + { + for (int i = 0; i < parms.Length; i++) + { + switch(parms[i].ToLower()) + { + case "-port": + int port = Convert.ToInt32(parms[++i]); + Netplay.serverPort = port; + Config.ServerPort = port; + OverridePort = true; + Log.ConsoleInfo("Port overridden by startup argument. Set to " + port); + break; + case "-rest-token": + string token = Convert.ToString(parms[++i]); + RESTStartupTokens.Add(token, "null"); + Console.WriteLine("Startup parameter overrode REST token."); + break; + case "-rest-enabled": + Config.RestApiEnabled = Convert.ToBoolean(parms[++i]); + Console.WriteLine("Startup parameter overrode REST enable."); + break; + case "-rest-port": + Config.RestApiPort = Convert.ToInt32(parms[++i]); + Console.WriteLine("Startup parameter overrode REST port."); + break; + case "-maxplayers": + case "-players": + Config.MaxSlots = Convert.ToInt32(parms[++i]); + Console.WriteLine("Startup parameter overrode maximum player slot configuration value."); + break; + } + } + } + + /* + * Hooks: + * + */ + + public static int AuthToken = -1; + + private void OnPostInit() + { + SetConsoleTitle(); + if (!File.Exists(Path.Combine(SavePath, "auth.lck")) && !File.Exists(Path.Combine(SavePath, "authcode.txt"))) + { + var r = new Random((int) DateTime.Now.ToBinary()); + AuthToken = r.Next(100000, 10000000); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("TShock Notice: To become SuperAdmin, join the game and type /auth " + AuthToken); + Console.WriteLine("This token will display until disabled by verification. (/auth-verify)"); + Console.ForegroundColor = ConsoleColor.Gray; + FileTools.CreateFile(Path.Combine(SavePath, "authcode.txt")); + using (var tw = new StreamWriter(Path.Combine(SavePath, "authcode.txt"))) + { + tw.WriteLine(AuthToken); + } + } + else if (File.Exists(Path.Combine(SavePath, "authcode.txt"))) + { + using (var tr = new StreamReader(Path.Combine(SavePath, "authcode.txt"))) + { + AuthToken = Convert.ToInt32(tr.ReadLine()); + } + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine( + "TShock Notice: authcode.txt is still present, and the AuthToken located in that file will be used."); + Console.WriteLine("To become superadmin, join the game and type /auth " + AuthToken); + Console.WriteLine("This token will display until disabled by verification. (/auth-verify)"); + Console.ForegroundColor = ConsoleColor.Gray; + } + else + { + AuthToken = 0; + } + Regions.ReloadAllRegions(); + + StatTracker.CheckIn(); + FixChestStacks(); + } + + private void FixChestStacks() + { + foreach (Chest chest in Main.chest) + { + if (chest != null) + { + foreach (Item item in chest.item) + { + if (item != null && item.stack > item.maxStack) + item.stack = item.maxStack; + } + } + } + } + + private DateTime LastCheck = DateTime.UtcNow; + private DateTime LastSave = DateTime.UtcNow; + + private void OnUpdate() + { + UpdateManager.UpdateProcedureCheck(); + StatTracker.CheckIn(); + if (Backups.IsBackupTime) + Backups.Backup(); + //call these every second, not every update + if ((DateTime.UtcNow - LastCheck).TotalSeconds >= 1) + { + OnSecondUpdate(); + LastCheck = DateTime.UtcNow; + } + + if ((DateTime.UtcNow - LastSave).TotalMinutes >= Config.ServerSideInventorySave) + { + foreach (TSPlayer player in Players) + { + // prevent null point exceptions + if (player != null && player.IsLoggedIn && !player.IgnoreActionsForClearingTrashCan) + { + + InventoryDB.InsertPlayerData(player); + } + } + LastSave = DateTime.UtcNow; + } + } + + private void OnSecondUpdate() + { + if (Config.ForceTime != "normal") + { + switch (Config.ForceTime) + { + case "day": + TSPlayer.Server.SetTime(true, 27000.0); + break; + case "night": + TSPlayer.Server.SetTime(false, 16200.0); + break; + } + } + int count = 0; + foreach (TSPlayer player in Players) + { + if (player != null && player.Active) + { + count++; + if (player.TilesDestroyed != null) + { + if (player.TileKillThreshold >= Config.TileKillThreshold) + { + player.Disable("Reached TileKill threshold"); + TSPlayer.Server.RevertTiles(player.TilesDestroyed); + player.TilesDestroyed.Clear(); + } + } + if (player.TileKillThreshold > 0) + { + player.TileKillThreshold = 0; + //We don't want to revert the entire map in case of a disable. + player.TilesDestroyed.Clear(); + } + if (player.TilesCreated != null) + { + if (player.TilePlaceThreshold >= Config.TilePlaceThreshold) + { + player.Disable("Reached TilePlace threshold"); + TSPlayer.Server.RevertTiles(player.TilesCreated); + player.TilesCreated.Clear(); + } + } + if (player.TilePlaceThreshold > 0) + { + player.TilePlaceThreshold = 0; + } + if (player.TileLiquidThreshold >= Config.TileLiquidThreshold) + { + player.Disable("Reached TileLiquid threshold"); + } + if (player.TileLiquidThreshold > 0) + { + player.TileLiquidThreshold = 0; + } + if (player.ProjectileThreshold >= Config.ProjectileThreshold) + { + player.Disable("Reached Projectile threshold"); + } + if (player.ProjectileThreshold > 0) + { + player.ProjectileThreshold = 0; + } + if (player.Dead && (DateTime.Now - player.LastDeath).Seconds >= 3 && player.Difficulty != 2) + { + player.Spawn(); + } + string check = "none"; + foreach (Item item in player.TPlayer.inventory) + { + if (!player.Group.HasPermission(Permissions.ignorestackhackdetection) && item.stack > item.maxStack && + item.type != 0) + { + check = "Remove Item " + item.name + " (" + item.stack + ") exceeds max stack of " + item.maxStack; + } + } + player.IgnoreActionsForCheating = check; + check = "none"; + foreach (Item item in player.TPlayer.armor) + { + if (!player.Group.HasPermission(Permissions.usebanneditem) && Itembans.ItemIsBanned(item.name, player)) + { + player.SetBuff(30, 120); //Bleeding + player.SetBuff(36, 120); //Broken Armor + check = "Remove Armor/Accessory " + item.name; + } + } + player.IgnoreActionsForDisabledArmor = check; + if (CheckIgnores(player)) + { + player.SetBuff(33, 120); //Weak + player.SetBuff(32, 120); //Slow + player.SetBuff(23, 120); //Cursed + } + else if (!player.Group.HasPermission(Permissions.usebanneditem) && + Itembans.ItemIsBanned(player.TPlayer.inventory[player.TPlayer.selectedItem].name, player)) + { + player.SetBuff(23, 120); //Cursed + } + } + } + SetConsoleTitle(); + } + + private void SetConsoleTitle() + { + Console.Title = string.Format("{0} - {1}/{2} @ {3}:{4} (TerrariaShock v{5})", Config.ServerName, Utils.ActivePlayers(), + Config.MaxSlots, Netplay.serverListenIP, Config.ServerPort, Version); + } + + private void OnConnect(int ply, HandledEventArgs handler) + { + var player = new TSPlayer(ply); + if (Config.EnableDNSHostResolution) + { + player.Group = Users.GetGroupForIPExpensive(player.IP); + } + else + { + player.Group = Users.GetGroupForIP(player.IP); + } + + if (Utils.ActivePlayers() + 1 > Config.MaxSlots + 20) + { + Utils.ForceKick(player, Config.ServerFullNoReservedReason); + handler.Handled = true; + return; + } + + var ipban = Bans.GetBanByIp(player.IP); + Ban ban = null; + if (ipban != null && Config.EnableIPBans) + ban = ipban; + + if (ban != null) + { + Utils.ForceKick(player, string.Format("You are banned: {0}", ban.Reason)); + handler.Handled = true; + return; + } + + if (!FileTools.OnWhitelist(player.IP)) + { + Utils.ForceKick(player, "Not on whitelist."); + handler.Handled = true; + return; + } + + if (Geo != null) + { + var code = Geo.TryGetCountryCode(IPAddress.Parse(player.IP)); + player.Country = code == null ? "N/A" : GeoIPCountry.GetCountryNameByCode(code); + if (code == "A1") + { + if (Config.KickProxyUsers) + { + Utils.ForceKick(player, "Proxies are not allowed"); + handler.Handled = true; + return; + } + } + } + Players[ply] = player; + } + + private void OnJoin(int ply, HandledEventArgs handler) + { + var player = Players[ply]; + if (player == null) + { + handler.Handled = true; + return; + } + + Ban ban = null; + if (Config.EnableBanOnUsernames) + { + var newban = Bans.GetBanByName(player.Name); + if (null != newban) + ban = newban; + } + + if (Config.EnableIPBans && null == ban) + { + ban = Bans.GetBanByIp(player.IP); + } + + if (ban != null) + { + Utils.ForceKick(player, string.Format("You are banned: {0}", ban.Reason)); + handler.Handled = true; + return; + } + } + + private void OnLeave(int ply) + { + + var tsplr = Players[ply]; + Players[ply] = null; + + if (tsplr != null && tsplr.ReceivedInfo) + { + if (!tsplr.SilentKickInProgress) + { + Utils.Broadcast(tsplr.Name + " left", Color.Yellow); + } + Log.Info(string.Format("{0} left.", tsplr.Name)); + + if (tsplr.IsLoggedIn && !tsplr.IgnoreActionsForClearingTrashCan) + { + tsplr.PlayerData.CopyInventory(tsplr); + InventoryDB.InsertPlayerData(tsplr); + } + + if ((Config.RememberLeavePos) &&(!tsplr.LoginHarassed)) + { + RememberedPos.InsertLeavePos(tsplr.Name, tsplr.IP, (int) (tsplr.X/16), (int) (tsplr.Y/16)); + } + } + } + + private void OnChat(messageBuffer msg, int ply, string text, HandledEventArgs e) + { + if (e.Handled) + return; + + var tsplr = Players[msg.whoAmI]; + if (tsplr == null) + { + e.Handled = true; + return; + } + + /*if (!Utils.ValidString(text)) + { + e.Handled = true; + return; + }*/ + + if (text.StartsWith("/")) + { + try + { + e.Handled = Commands.HandleCommand(tsplr, text); + } + catch (Exception ex) + { + Log.ConsoleError("Command exception"); + Log.Error(ex.ToString()); + } + } + else if (!tsplr.mute) + { + Utils.Broadcast( + String.Format(Config.ChatFormat, tsplr.Group.Name, tsplr.Group.Prefix, tsplr.Name, tsplr.Group.Suffix, text), + tsplr.Group.R, tsplr.Group.G, tsplr.Group.B); + e.Handled = true; + } + else if (tsplr.mute) + { + tsplr.SendMessage("You are muted!"); + e.Handled = true; + } + } + + /// + /// When a server command is run. + /// + /// + /// + private void ServerHooks_OnCommand(string text, HandledEventArgs e) + { + if (e.Handled) + return; + + // Damn you ThreadStatic and Redigit + if (Main.rand == null) + { + Main.rand = new Random(); + } + if (WorldGen.genRand == null) + { + WorldGen.genRand = new Random(); + } + + if (text.StartsWith("playing") || text.StartsWith("/playing")) + { + int count = 0; + foreach (TSPlayer player in Players) + { + if (player != null && player.Active) + { + count++; + TSPlayer.Server.SendMessage(string.Format("{0} ({1}) [{2}] <{3}>", player.Name, player.IP, + player.Group.Name, player.UserAccountName)); + } + } + TSPlayer.Server.SendMessage(string.Format("{0} players connected.", count)); + } + else if (text == "autosave") + { + Main.autoSave = Config.AutoSave = !Config.AutoSave; + Log.ConsoleInfo("AutoSave " + (Config.AutoSave ? "Enabled" : "Disabled")); + } + else if (text.StartsWith("/")) + { + Commands.HandleCommand(TSPlayer.Server, text); + } + else + { + Commands.HandleCommand(TSPlayer.Server, "/" + text); + } + e.Handled = true; + } + + private void OnGetData(GetDataEventArgs e) + { + if (e.Handled) + return; + + PacketTypes type = e.MsgID; + + Debug.WriteLine("Recv: {0:X}: {2} ({1:XX})", e.Msg.whoAmI, (byte) type, type); + + var player = Players[e.Msg.whoAmI]; + if (player == null) + { + e.Handled = true; + return; + } + + if (!player.ConnectionAlive) + { + e.Handled = true; + return; + } + + if (player.RequiresPassword && type != PacketTypes.PasswordSend) + { + e.Handled = true; + return; + } + + if ((player.State < 10 || player.Dead) && (int) type > 12 && (int) type != 16 && (int) type != 42 && (int) type != 50 && + (int) type != 38 && (int) type != 5 && (int) type != 21) + { + e.Handled = true; + return; + } + + using (var data = new MemoryStream(e.Msg.readBuffer, e.Index, e.Length)) + { + try + { + if (GetDataHandlers.HandlerGetData(type, player, data)) + e.Handled = true; + } + catch (Exception ex) + { + Log.Error(ex.ToString()); + } + } + } + + private void OnGreetPlayer(int who, HandledEventArgs e) + { + var player = Players[who]; + if (player == null) + { + e.Handled = true; + return; + } + player.LoginMS= DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + + Utils.ShowFileToUser(player, "motd.txt"); + + if (Config.PvPMode == "always" && !player.TPlayer.hostile) + { + player.SendMessage("PvP is forced! Enable PvP else you can't move or do anything!", Color.Red); + } + + if (!player.IsLoggedIn) + { + if (Config.ServerSideInventory) + { + player.SendMessage( + player.IgnoreActionsForInventory = "Server Side Inventory is enabled! Please /register or /login to play!", + Color.Red); + player.LoginHarassed = true; + } + else if (Config.RequireLogin) + { + player.SendMessage("Please /register or /login to play!", Color.Red); + player.LoginHarassed = true; + } + } + + if (player.Group.HasPermission(Permissions.causeevents) && Config.InfiniteInvasion) + { + StartInvasion(); + } + + player.LastNetPosition = new Vector2(Main.spawnTileX*16f, Main.spawnTileY*16f); + + if (Config.RememberLeavePos) + { + if (RememberedPos.GetLeavePos(player.Name, player.IP) != Vector2.Zero){ + var pos = RememberedPos.GetLeavePos(player.Name, player.IP); + + player.Teleport((int) pos.X, (int) pos.Y + 3); + }} + + e.Handled = true; + } + + private void NpcHooks_OnStrikeNpc(NpcStrikeEventArgs e) + { + if (Config.InfiniteInvasion) + { + IncrementKills(); + if (Main.invasionSize < 10) + { + Main.invasionSize = 20000000; + } + } + } + + private void OnProjectileSetDefaults(SetDefaultsEventArgs e) + { + if (e.Info == 43) + if (Config.DisableTombstones) + e.Object.SetDefaults(0); + if (e.Info == 75) + if (Config.DisableClownBombs) + e.Object.SetDefaults(0); + if (e.Info == 109) + if (Config.DisableSnowBalls) + e.Object.SetDefaults(0); + } + + private void OnNpcSetDefaults(SetDefaultsEventArgs e) + { + if (Itembans.ItemIsBanned(e.Object.name, null)) + { + e.Object.SetDefaults(0); + } + } + + /// + /// Send bytes to client using packetbuffering if available + /// + /// socket to send to + /// bytes to send + /// False on exception + public static bool SendBytes(ServerSock client, byte[] bytes) + { + if (PacketBuffer != null) + { + PacketBuffer.BufferBytes(client, bytes); + return true; + } + + return SendBytesBufferless(client, bytes); + } + + /// + /// Send bytes to a client ignoring the packet buffer + /// + /// socket to send to + /// bytes to send + /// False on exception + public static bool SendBytesBufferless(ServerSock client, byte[] bytes) + { + try + { + if (client.tcpClient.Connected) + client.networkStream.Write(bytes, 0, bytes.Length); + return true; + } + catch (Exception ex) + { + Log.Warn("This is a normal exception"); + Log.Warn(ex.ToString()); + } + return false; + } + + private void NetHooks_SendData(SendDataEventArgs e) + { + if (e.MsgID == PacketTypes.Disconnect) + { + Action senddisconnect = (sock, str) => + { + if (sock == null || !sock.active) + return; + sock.kill = true; + using (var ms = new MemoryStream()) + { + new DisconnectMsg {Reason = str}.PackFull(ms); + SendBytesBufferless(sock, ms.ToArray()); + } + }; + + if (e.remoteClient != -1) + { + senddisconnect(Netplay.serverSock[e.remoteClient], e.text); + } + else + { + for (int i = 0; i < Netplay.serverSock.Length; i++) + { + if (e.ignoreClient != -1 && e.ignoreClient == i) + continue; + + senddisconnect(Netplay.serverSock[i], e.text); + } + } + e.Handled = true; + } + } + + private void OnStartHardMode(HandledEventArgs e) + { + if (Config.DisableHardmode) + e.Handled = true; + } + + /* + * Useful stuff: + * */ + + public static void StartInvasion() + { + Main.invasionType = 1; + if (Config.InfiniteInvasion) + { + Main.invasionSize = 20000000; + } + else + { + Main.invasionSize = 100 + (Config.InvasionMultiplier*Utils.ActivePlayers()); + } + + Main.invasionWarn = 0; + if (new Random().Next(2) == 0) + { + Main.invasionX = 0.0; + } + else + { + Main.invasionX = Main.maxTilesX; + } + } + + private static int KillCount; + + public static void IncrementKills() + { + KillCount++; + Random r = new Random(); + int random = r.Next(5); + if (KillCount%100 == 0) + { + switch (random) + { + case 0: + Utils.Broadcast(string.Format("You call that a lot? {0} goblins killed!", KillCount)); + break; + case 1: + Utils.Broadcast(string.Format("Fatality! {0} goblins killed!", KillCount)); + break; + case 2: + Utils.Broadcast(string.Format("Number of 'noobs' killed to date: {0}", KillCount)); + break; + case 3: + Utils.Broadcast(string.Format("Duke Nukem would be proud. {0} goblins killed.", KillCount)); + break; + case 4: + Utils.Broadcast(string.Format("You call that a lot? {0} goblins killed!", KillCount)); + break; + case 5: + Utils.Broadcast(string.Format("{0} copies of Call of Duty smashed.", KillCount)); + break; + } + } + } + + public static bool CheckProjectilePermission(TSPlayer player, int index, int type) + { + if (type == 43) + { + return true; + } + + if (type == 17 && !player.Group.HasPermission(Permissions.usebanneditem) && Itembans.ItemIsBanned("Dirt Rod", player)) + //Dirt Rod Projectile + { + return true; + } + + if ((type == 42 || type == 65 || type == 68) && !player.Group.HasPermission(Permissions.usebanneditem) && + Itembans.ItemIsBanned("Sandgun", player)) //Sandgun Projectiles + { + return true; + } + + Projectile proj = new Projectile(); + proj.SetDefaults(type); + + if (!player.Group.HasPermission(Permissions.usebanneditem) && Itembans.ItemIsBanned(proj.name, player)) + { + return true; + } + + if (Main.projHostile[type]) + { + //player.SendMessage( proj.name, Color.Yellow); + return true; + } + + return false; + } + + public static bool CheckRangePermission(TSPlayer player, int x, int y, int range = 32) + { + if (Config.RangeChecks && ((Math.Abs(player.TileX - x) > range) || (Math.Abs(player.TileY - y) > range))) + { + return true; + } + return false; + } + + public static bool CheckTilePermission( TSPlayer player, int tileX, int tileY, byte tileType, byte actionType ) + { + if (!player.Group.HasPermission(Permissions.canbuild)) + { + if (TShock.Config.AllowIce && actionType != 1) + { + foreach (Point p in player.IceTiles) + { + if (p.X == tileX && p.Y == tileY && (Main.tile[p.X, p.Y].type == 0 || Main.tile[p.X, p.Y].type == 127)) + { + player.IceTiles.Remove(p); + return false; + } + } + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000){ + player.SendMessage("You do not have permission to build!", Color.Red); + player.BPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + +} + return true; + } + + if (TShock.Config.AllowIce && actionType == 1 && tileType == 127) + { + player.IceTiles.Add(new Point(tileX, tileY)); + return false; + } + + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000){ + player.SendMessage("You do not have permission to build!", Color.Red); + player.BPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + +} + return true; + + } + if (!player.Group.HasPermission(Permissions.editspawn) && !Regions.CanBuild(tileX, tileY, player) && + Regions.InArea(tileX, tileY)) + { + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000){ + player.SendMessage("Region protected from changes.", Color.Red); + player.RPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + +} + return true; + } + if (Config.DisableBuild) + { + if (!player.Group.HasPermission(Permissions.editspawn)) + { + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000){ + player.SendMessage("World protected from changes.", Color.Red); + player.WPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + +} + return true; + } + } + if (Config.SpawnProtection) + { + if (!player.Group.HasPermission(Permissions.editspawn)) + { + var flag = CheckSpawn(tileX, tileY); + if (flag) + { + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 2000){ + player.SendMessage("Spawn protected from changes.", Color.Red); + player.SPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + } + return true; + } + } + } + return false; + } + + public static bool CheckTilePermission(TSPlayer player, int tileX, int tileY) + { + if (!player.Group.HasPermission(Permissions.canbuild)) + { + + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.BPm) > 2000){ + player.SendMessage("You do not have permission to build!", Color.Red); + player.BPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + } + return true; + } + + if (!player.Group.HasPermission(Permissions.editspawn) && !Regions.CanBuild(tileX, tileY, player) && + Regions.InArea(tileX, tileY)) + { + + + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.RPm) > 2000){ + player.SendMessage("Region protected from changes.", Color.Red); + player.RPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + } + return true; + } + + if (Config.DisableBuild) + { + if (!player.Group.HasPermission(Permissions.editspawn)) + { + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.WPm) > 2000){ + player.SendMessage("World protected from changes.", Color.Red); + player.WPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + } + return true; + } + } + if (Config.SpawnProtection) + { + if (!player.Group.HasPermission(Permissions.editspawn)) + { + var flag = CheckSpawn(tileX, tileY); + if (flag) + { + if (((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - player.SPm) > 1000){ + player.SendMessage("Spawn protected from changes.", Color.Red); + player.SPm=DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; + + } + + + return true; + } + } + } + return false; + } + public static bool CheckSpawn(int x, int y) + { + Vector2 tile = new Vector2(x, y); + Vector2 spawn = new Vector2(Main.spawnTileX, Main.spawnTileY); + return Distance(spawn, tile) <= Config.SpawnProtectionRadius; + } + + public static float Distance(Vector2 value1, Vector2 value2) + { + float num2 = value1.X - value2.X; + float num = value1.Y - value2.Y; + float num3 = (num2*num2) + (num*num); + return (float) Math.Sqrt(num3); + } + + public static bool HackedHealth(TSPlayer player) + { + return (player.TPlayer.statManaMax > 400) || + (player.TPlayer.statMana > 400) || + (player.TPlayer.statLifeMax > 400) || + (player.TPlayer.statLife > 400); + } + + public static bool HackedInventory(TSPlayer player) + { + bool check = false; + + Item[] inventory = player.TPlayer.inventory; + Item[] armor = player.TPlayer.armor; + for (int i = 0; i < NetItem.maxNetInventory; i++) + { + if (i < 49) + { + Item item = new Item(); + if (inventory[i] != null && inventory[i].netID != 0) + { + item.netDefaults(inventory[i].netID); + item.Prefix(inventory[i].prefix); + item.AffixName(); + if (inventory[i].stack > item.maxStack) + { + check = true; + player.SendMessage( + String.Format("Stack cheat detected. Remove item {0} ({1}) and then rejoin", item.name, inventory[i].stack), + Color.Cyan); + } + } + } + else + { + Item item = new Item(); + if (armor[i - 48] != null && armor[i - 48].netID != 0) + { + item.netDefaults(armor[i - 48].netID); + item.Prefix(armor[i - 48].prefix); + item.AffixName(); + if (armor[i - 48].stack > item.maxStack) + { + check = true; + player.SendMessage( + String.Format("Stack cheat detected. Remove armor {0} ({1}) and then rejoin", item.name, armor[i - 48].stack), + Color.Cyan); + } + } + } + } + + return check; + } + + public static bool CheckInventory(TSPlayer player) + { + PlayerData playerData = player.PlayerData; + bool check = true; + + if (player.TPlayer.statLifeMax > playerData.maxHealth) + { + player.SendMessage("Error: Your max health exceeded (" + playerData.maxHealth + ") which is stored on server", + Color.Cyan); + check = false; + } + + Item[] inventory = player.TPlayer.inventory; + Item[] armor = player.TPlayer.armor; + for (int i = 0; i < NetItem.maxNetInventory; i++) + { + if (i < 49) + { + Item item = new Item(); + Item serverItem = new Item(); + if (inventory[i] != null && inventory[i].netID != 0) + { + if (playerData.inventory[i].netID != inventory[i].netID) + { + item.netDefaults(inventory[i].netID); + item.Prefix(inventory[i].prefix); + item.AffixName(); + player.SendMessage(player.IgnoreActionsForInventory = "Your item (" + item.name + ") needs to be deleted.", + Color.Cyan); + check = false; + } + else if (playerData.inventory[i].prefix != inventory[i].prefix) + { + item.netDefaults(inventory[i].netID); + item.Prefix(inventory[i].prefix); + item.AffixName(); + player.SendMessage(player.IgnoreActionsForInventory = "Your item (" + item.name + ") needs to be deleted.", + Color.Cyan); + check = false; + } + else if (inventory[i].stack > playerData.inventory[i].stack) + { + item.netDefaults(inventory[i].netID); + item.Prefix(inventory[i].prefix); + item.AffixName(); + player.SendMessage( + player.IgnoreActionsForInventory = + "Your item (" + item.name + ") (" + inventory[i].stack + ") needs to have it's stack decreased to (" + + playerData.inventory[i].stack + ").", Color.Cyan); + check = false; + } + } + } + else + { + Item item = new Item(); + Item serverItem = new Item(); + if (armor[i - 48] != null && armor[i - 48].netID != 0) + { + if (playerData.inventory[i].netID != armor[i - 48].netID) + { + item.netDefaults(armor[i - 48].netID); + item.Prefix(armor[i - 48].prefix); + item.AffixName(); + player.SendMessage(player.IgnoreActionsForInventory = "Your armor (" + item.name + ") needs to be deleted.", + Color.Cyan); + check = false; + } + else if (playerData.inventory[i].prefix != armor[i - 48].prefix) + { + item.netDefaults(armor[i - 48].netID); + item.Prefix(armor[i - 48].prefix); + item.AffixName(); + player.SendMessage(player.IgnoreActionsForInventory = "Your armor (" + item.name + ") needs to be deleted.", + Color.Cyan); + check = false; + } + else if (armor[i - 48].stack > playerData.inventory[i].stack) + { + item.netDefaults(armor[i - 48].netID); + item.Prefix(armor[i - 48].prefix); + item.AffixName(); + player.SendMessage( + player.IgnoreActionsForInventory = + "Your armor (" + item.name + ") (" + inventory[i].stack + ") needs to have it's stack decreased to (" + + playerData.inventory[i].stack + ").", Color.Cyan); + check = false; + } + } + } + } + + return check; + } + + public static bool CheckIgnores(TSPlayer player) + { + bool check = false; + if (Config.PvPMode == "always" && !player.TPlayer.hostile) + check = true; + if (player.IgnoreActionsForInventory != "none") + check = true; + if (player.IgnoreActionsForCheating != "none") + check = true; + if (player.IgnoreActionsForDisabledArmor != "none") + check = true; + if (player.IgnoreActionsForClearingTrashCan) + check = true; + if (!player.IsLoggedIn && Config.RequireLogin) + check = true; + return check; + } + + public void OnConfigRead(ConfigFile file) + { + NPC.defaultMaxSpawns = file.DefaultMaximumSpawns; + NPC.defaultSpawnRate = file.DefaultSpawnRate; + + Main.autoSave = file.AutoSave; + if (Backups != null) + { + Backups.KeepFor = file.BackupKeepFor; + Backups.Interval = file.BackupInterval; + } + if (!OverridePort) + { + Netplay.serverPort = file.ServerPort; + } + + if (file.MaxSlots > 235) + file.MaxSlots = 235; + Main.maxNetPlayers = file.MaxSlots + 20; + Netplay.password = ""; + Netplay.spamCheck = false; + + RconHandler.Password = file.RconPassword; + RconHandler.ListenPort = file.RconPort; + + Utils.HashAlgo = file.HashAlgorithm; + } + } +} diff --git a/TShockAPI/TShockAPI.csproj b/TShockAPI/TShockAPI.csproj index 3968ad3f..de62800c 100644 --- a/TShockAPI/TShockAPI.csproj +++ b/TShockAPI/TShockAPI.csproj @@ -43,7 +43,7 @@ pdbonly true bin\Release\ - TRACE + TRACE;COMPAT_SIGS prompt 4 true @@ -85,6 +85,7 @@ + @@ -197,4 +198,4 @@ --> - \ No newline at end of file + diff --git a/TShockAPI/UpdateManager.cs b/TShockAPI/UpdateManager.cs index dcc28f20..cf0b375b 100644 --- a/TShockAPI/UpdateManager.cs +++ b/TShockAPI/UpdateManager.cs @@ -1,102 +1,102 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011 The TShock Team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using Newtonsoft.Json; - -namespace TShockAPI -{ - internal class UpdateManager - { - private static string updateUrl = "http://shankshock.com/tshock-update.json"; - public static DateTime lastcheck = DateTime.MinValue; - - /// - /// Check once every X minutes. - /// - private static readonly int CheckXMinutes = 30; - - public static void UpdateProcedureCheck() - { - if ((DateTime.Now - lastcheck).TotalMinutes >= CheckXMinutes) - { - ThreadPool.QueueUserWorkItem(CheckUpdate); - lastcheck = DateTime.Now; - } - } - - public static void CheckUpdate(object o) - { - var updates = ServerIsOutOfDate(); - if (updates != null) - { - NotifyAdministrators(updates); - } - } - - /// - /// Checks to see if the server is out of date. - /// - /// - private static Dictionary ServerIsOutOfDate() - { - using (var client = new WebClient()) - { - client.Headers.Add("user-agent", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705;)"); - try - { - string updatejson = client.DownloadString(updateUrl); - var update = JsonConvert.DeserializeObject>(updatejson); - var version = new Version(update["version"]); - if (TShock.VersionNum.CompareTo(version) < 0) - return update; - } - catch (Exception e) - { - Log.Error(e.ToString()); - } - return null; - } - } - - private static void NotifyAdministrators(Dictionary update) - { - var changes = update["changes"].Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries); - NotifyAdministrator(TSPlayer.Server, changes); - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.Active && player.Group.HasPermission(Permissions.maintenance)) - { - NotifyAdministrator(player, changes); - } - } - } - - private static void NotifyAdministrator(TSPlayer player, string[] changes) - { - player.SendMessage("The server is out of date.", Color.Red); - for (int j = 0; j < changes.Length; j++) - { - player.SendMessage(changes[j], Color.Red); - } - } - } +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using Newtonsoft.Json; + +namespace TShockAPI +{ + internal class UpdateManager + { + private static string updateUrl = "http://shankshock.com/tshock-update.json"; + public static DateTime lastcheck = DateTime.MinValue; + + /// + /// Check once every X minutes. + /// + private static readonly int CheckXMinutes = 30; + + public static void UpdateProcedureCheck() + { + if ((DateTime.Now - lastcheck).TotalMinutes >= CheckXMinutes) + { + ThreadPool.QueueUserWorkItem(CheckUpdate); + lastcheck = DateTime.Now; + } + } + + public static void CheckUpdate(object o) + { + var updates = ServerIsOutOfDate(); + if (updates != null) + { + NotifyAdministrators(updates); + } + } + + /// + /// Checks to see if the server is out of date. + /// + /// + private static Dictionary ServerIsOutOfDate() + { + using (var client = new WebClient()) + { + client.Headers.Add("user-agent", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705;)"); + try + { + string updatejson = client.DownloadString(updateUrl); + var update = JsonConvert.DeserializeObject>(updatejson); + var version = new Version(update["version"]); + if (TShock.VersionNum.CompareTo(version) < 0) + return update; + } + catch (Exception e) + { + Log.Error(e.ToString()); + } + return null; + } + } + + private static void NotifyAdministrators(Dictionary update) + { + var changes = update["changes"].Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries); + NotifyAdministrator(TSPlayer.Server, changes); + foreach (TSPlayer player in TShock.Players) + { + if (player != null && player.Active && player.Group.HasPermission(Permissions.maintenance)) + { + NotifyAdministrator(player, changes); + } + } + } + + private static void NotifyAdministrator(TSPlayer player, string[] changes) + { + player.SendMessage("The server is out of date.", Color.Red); + for (int j = 0; j < changes.Length; j++) + { + player.SendMessage(changes[j], Color.Red); + } + } + } } \ No newline at end of file diff --git a/TShockAPI/Utils.cs b/TShockAPI/Utils.cs index d5c723b9..f6ead281 100644 --- a/TShockAPI/Utils.cs +++ b/TShockAPI/Utils.cs @@ -1,763 +1,804 @@ -/* -TShock, a server mod for Terraria -Copyright (C) 2011 The TShock Team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Security.Cryptography; -using System.Text; -using Terraria; - -namespace TShockAPI -{ - public class Utils - { - public static bool saving = false; - - public Utils() - { - } - - public Random Random = new Random(); - //private static List groups = new List(); - - /// - /// Provides the real IP address from a RemoteEndPoint string that contains a port and an IP - /// - /// A string IPv4 address in IP:PORT form. - /// A string IPv4 address. - public string GetRealIP(string mess) - { - return mess.Split(':')[0]; - } - - /// - /// Used for some places where a list of players might be used. - /// - /// String of players seperated by commas. - public string GetPlayers() - { - var sb = new StringBuilder(); - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.Active) - { - if (sb.Length != 0) - { - sb.Append(", "); - } - sb.Append(player.Name); - } - } - return sb.ToString(); - } - - /// - /// Used for some places where a list of players might be used. - /// - /// String of players and their id seperated by commas. - public string GetPlayersWithIds() - { - var sb = new StringBuilder(); - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.Active) - { - if (sb.Length != 0) - { - sb.Append(", "); - } - sb.Append(player.Name); - string id = "( " + Convert.ToString(TShock.Users.GetUserID(player.UserAccountName)) + " )"; - sb.Append(id); - } - } - return sb.ToString(); - } - - /// - /// Finds a player and gets IP as string - /// - /// Player name - public string GetPlayerIP(string playername) - { - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.Active) - { - if (playername.ToLower() == player.Name.ToLower()) - { - return player.IP; - } - } - } - return null; - } - - /// - /// It's a clamp function - /// - /// - /// Value to clamp - /// Maximum bounds of the clamp - /// Minimum bounds of the clamp - /// - public T Clamp(T value, T max, T min) - where T : IComparable - { - T result = value; - if (value.CompareTo(max) > 0) - result = max; - if (value.CompareTo(min) < 0) - result = min; - return result; - } - - /// - /// Saves the map data - /// - public void SaveWorld() - { - saving = true; - WorldGen.realsaveWorld(); - Broadcast("World saved.", Color.Yellow); - Log.Info(string.Format("World saved at ({0})", Main.worldPathName)); - saving = false; - } - - /// - /// Broadcasts a message to all players - /// - /// string message - public void Broadcast(string msg) - { - Broadcast(msg, Color.Green); - } - - public void Broadcast(string msg, byte red, byte green, byte blue) - { - TSPlayer.All.SendMessage(msg, red, green, blue); - TSPlayer.Server.SendMessage(msg, red, green, blue); - Log.Info(string.Format("Broadcast: {0}", msg)); - } - - public void Broadcast(string msg, Color color) - { - Broadcast(msg, color.R, color.G, color.B); - } - - /// - /// Sends message to all users with 'logs' permission. - /// - /// Message to send - /// Color of the message - public void SendLogs(string log, Color color) - { - Log.Info(log); - TSPlayer.Server.SendMessage(log, color); - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.Active && player.Group.HasPermission(Permissions.logs) && player.DisplayLogs && - TShock.Config.DisableSpewLogs == false) - player.SendMessage(log, color); - } - } - - /// - /// The number of active players on the server. - /// - /// int playerCount - public int ActivePlayers() - { - int num = 0; - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.Active) - { - num++; - } - } - return num; - } - - /// - /// Finds a player ID based on name - /// - /// Player name - /// - public List FindPlayer(string ply) - { - var found = new List(); - ply = ply.ToLower(); - foreach (TSPlayer player in TShock.Players) - { - if (player == null) - continue; - - string name = player.Name.ToLower(); - if (name.Equals(ply)) - return new List {player}; - if (name.Contains(ply)) - found.Add(player); - } - return found; - } - - /// - /// Gets a random clear tile in range - /// - /// Bound X - /// Bound Y - /// Range on the X axis - /// Range on the Y axis - /// X location - /// Y location - public void GetRandomClearTileWithInRange(int startTileX, int startTileY, int tileXRange, int tileYRange, - out int tileX, out int tileY) - { - int j = 0; - do - { - if (j == 100) - { - tileX = startTileX; - tileY = startTileY; - break; - } - - tileX = startTileX + Random.Next(tileXRange*-1, tileXRange); - tileY = startTileY + Random.Next(tileYRange*-1, tileYRange); - j++; - } while (TileValid(tileX, tileY) && !TileClear(tileX, tileY)); - } - - /// - /// Determines if a tile is valid - /// - /// Location X - /// Location Y - /// If the tile is valid - private bool TileValid(int tileX, int tileY) - { - return tileX >= 0 && tileX <= Main.maxTilesX && tileY >= 0 && tileY <= Main.maxTilesY; - } - - /// - /// Clears a tile - /// - /// Location X - /// Location Y - /// The state of the tile - private bool TileClear(int tileX, int tileY) - { - return !Main.tile[tileX, tileY].active; - } - - /// - /// Gets a list of items by ID or name - /// - /// Item ID or name - /// List of Items - public List GetItemByIdOrName(string idOrName) - { - int type = -1; - if (int.TryParse(idOrName, out type)) - { - return new List {GetItemById(type)}; - } - return GetItemByName(idOrName); - } - - /// - /// Gets an item by ID - /// - /// ID - /// Item - public Item GetItemById(int id) - { - Item item = new Item(); - item.netDefaults(id); - return item; - } - - /// - /// Gets items by name - /// - /// name - /// List of Items - public List GetItemByName(string name) - { - //Method #1 - must be exact match, allows support for different pickaxes/hammers/swords etc - for (int i = 1; i < Main.maxItemTypes; i++) - { - Item item = new Item(); - item.SetDefaults(name); - if (item.name == name) - return new List {item}; - } - //Method #2 - allows impartial matching - var found = new List(); - for (int i = -24; i < Main.maxItemTypes; i++) - { - try - { - Item item = new Item(); - item.netDefaults(i); - if (item.name.ToLower() == name.ToLower()) - return new List {item}; - if (item.name.ToLower().StartsWith(name.ToLower())) - found.Add(item); - } - catch - { - } - } - return found; - } - - /// - /// Gets an NPC by ID or Name - /// - /// - /// List of NPCs - public List GetNPCByIdOrName(string idOrName) - { - int type = -1; - if (int.TryParse(idOrName, out type)) - { - return new List {GetNPCById(type)}; - } - return GetNPCByName(idOrName); - } - - /// - /// Gets an NPC by ID - /// - /// ID - /// NPC - public NPC GetNPCById(int id) - { - NPC npc = new NPC(); - npc.netDefaults(id); - return npc; - } - - /// - /// Gets a NPC by name - /// - /// Name - /// List of matching NPCs - public List GetNPCByName(string name) - { - //Method #1 - must be exact match, allows support for different coloured slimes - for (int i = -17; i < Main.maxNPCTypes; i++) - { - NPC npc = new NPC(); - npc.SetDefaults(name); - if (npc.name == name) - return new List {npc}; - } - //Method #2 - allows impartial matching - var found = new List(); - for (int i = 1; i < Main.maxNPCTypes; i++) - { - NPC npc = new NPC(); - npc.netDefaults(i); - if (npc.name.ToLower() == name.ToLower()) - return new List {npc}; - if (npc.name.ToLower().StartsWith(name.ToLower())) - found.Add(npc); - } - return found; - } - - /// - /// Gets a buff name by id - /// - /// ID - /// name - public string GetBuffName(int id) - { - return (id > 0 && id < Main.maxBuffs) ? Main.buffName[id] : "null"; - } - - /// - /// Gets the description of a buff - /// - /// ID - /// description - public string GetBuffDescription(int id) - { - return (id > 0 && id < Main.maxBuffs) ? Main.buffTip[id] : "null"; - } - - /// - /// Gets a list of buffs by name - /// - /// name - /// Matching list of buff ids - public List GetBuffByName(string name) - { - for (int i = 1; i < Main.maxBuffs; i++) - { - if (Main.buffName[i].ToLower() == name) - return new List {i}; - } - var found = new List(); - for (int i = 1; i < Main.maxBuffs; i++) - { - if (Main.buffName[i].ToLower().StartsWith(name.ToLower())) - found.Add(i); - } - return found; - } - - /// - /// Gets a prefix based on its id - /// - /// ID - /// Prefix name - public string GetPrefixById(int id) - { - var item = new Item(); - item.SetDefaults(0); - item.prefix = (byte) id; - item.AffixName(); - return item.name.Trim(); - } - - /// - /// Gets a list of prefixes by name - /// - /// Name - /// List of prefix IDs - public List GetPrefixByName(string name) - { - Item item = new Item(); - item.SetDefaults(0); - for (int i = 1; i < 83; i++) - { - item.prefix = (byte) i; - if (item.AffixName().Trim() == name) - return new List {i}; - } - var found = new List(); - for (int i = 1; i < 83; i++) - { - try - { - item.prefix = (byte) i; - if (item.AffixName().Trim().ToLower() == name.ToLower()) - return new List {i}; - if (item.AffixName().Trim().ToLower().StartsWith(name.ToLower())) - found.Add(i); - } - catch - { - } - } - return found; - } - - /// - /// Gets a prefix by ID or name - /// - /// ID or name - /// List of prefix IDs - public List GetPrefixByIdOrName(string idOrName) - { - int type = -1; - if (int.TryParse(idOrName, out type) && type > 0 && type < 84) - { - return new List {type}; - } - return GetPrefixByName(idOrName); - } - - /// - /// Kicks all player from the server without checking for immunetokick permission. - /// - /// int player - /// string reason - public void ForceKickAll(string reason) - { - foreach (TSPlayer player in TShock.Players) - { - if (player != null && player.Active) - { - ForceKick(player, reason); - } - } - } - - /// - /// Kicks a player from the server without checking for immunetokick permission. - /// - /// int player - /// string reason - public void ForceKick(TSPlayer player, string reason) - { - if (!player.ConnectionAlive) - return; - player.Disconnect(reason); - Log.ConsoleInfo(string.Format("{0} was force kicked for : {1}", player.IP, reason)); - } - - public void ForceKick(TSPlayer player, string reason, bool silent) - { - player.SilentKickInProgress = true; - if (!player.ConnectionAlive) - return; - player.Disconnect(reason); - Log.ConsoleInfo(string.Format("{0} was force kicked for : {1}", player.IP, reason)); - } - - /// - /// Kicks a player from the server. - /// - /// int player - /// string reason - public bool Kick(TSPlayer player, string reason, string adminUserName = "") - { - if (!player.ConnectionAlive) - return true; - if (!player.Group.HasPermission(Permissions.immunetokick)) - { - string playerName = player.Name; - player.Disconnect(string.Format("Kicked: {0}", reason)); - Log.ConsoleInfo(string.Format("Kicked {0} for : {1}", playerName, reason)); - if (adminUserName.Length == 0) - Broadcast(string.Format("{0} was kicked for {1}", playerName, reason.ToLower())); - else - Broadcast(string.Format("{0} kicked {1} for {2}", adminUserName, playerName, reason.ToLower())); - return true; - } - return false; - } - - /// - /// Bans and kicks a player from the server. - /// - /// int player - /// string reason - public bool Ban(TSPlayer player, string reason, string adminUserName = "") - { - if (!player.ConnectionAlive) - return true; - if (!player.Group.HasPermission(Permissions.immunetoban)) - { - string ip = player.IP; - string playerName = player.Name; - TShock.Bans.AddBan(ip, playerName, reason); - player.Disconnect(string.Format("Banned: {0}", reason)); - Log.ConsoleInfo(string.Format("Banned {0} for : {1}", playerName, reason)); - if (adminUserName.Length == 0) - Broadcast(string.Format("{0} was banned for {1}", playerName, reason.ToLower())); - else - Broadcast(string.Format("{0} banned {1} for {2}", adminUserName, playerName, reason.ToLower())); - return true; - } - return false; - } - - /// - /// Shows a file to the user. - /// - /// int player - /// string filename reletave to savedir - //Todo: Fix this - public void ShowFileToUser(TSPlayer player, string file) - { - string foo = ""; - using (var tr = new StreamReader(Path.Combine(TShock.SavePath, file))) - { - while ((foo = tr.ReadLine()) != null) - { - foo = foo.Replace("%map%", Main.worldName); - foo = foo.Replace("%players%", GetPlayers()); - //foo = SanitizeString(foo); - if (foo.Substring(0, 1) == "%" && foo.Substring(12, 1) == "%") //Look for a beginning color code. - { - string possibleColor = foo.Substring(0, 13); - foo = foo.Remove(0, 13); - float[] pC = {0, 0, 0}; - possibleColor = possibleColor.Replace("%", ""); - string[] pCc = possibleColor.Split(','); - if (pCc.Length == 3) - { - try - { - player.SendMessage(foo, (byte) Convert.ToInt32(pCc[0]), (byte) Convert.ToInt32(pCc[1]), - (byte) Convert.ToInt32(pCc[2])); - continue; - } - catch (Exception e) - { - Log.Error(e.ToString()); - } - } - } - player.SendMessage(foo); - } - } - } - - /// - /// Returns a Group from the name of the group - /// - /// string groupName - public Group GetGroup(string groupName) - { - //first attempt on cached groups - for (int i = 0; i < TShock.Groups.groups.Count; i++) - { - if (TShock.Groups.groups[i].Name.Equals(groupName)) - { - return TShock.Groups.groups[i]; - } - } - return new Group(TShock.Config.DefaultGuestGroupName); - } - - /// - /// Returns an IPv4 address from a DNS query - /// - /// string ip - public string GetIPv4Address(string hostname) - { - try - { - //Get the ipv4 address from GetHostAddresses, if an ip is passed it will return that ip - var ip = Dns.GetHostAddresses(hostname).FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork); - //if the dns query was successful then return it, otherwise return an empty string - return ip != null ? ip.ToString() : ""; - } - catch (SocketException) - { - } - return ""; - } - - public string HashAlgo = "sha512"; - - public readonly Dictionary> HashTypes = new Dictionary> - { - {"sha512", () => new SHA512Managed()}, - {"sha256", () => new SHA256Managed()}, - {"md5", () => new MD5Cng()}, - {"sha512-xp", () => SHA512.Create()}, - {"sha256-xp", () => SHA256.Create()}, - {"md5-xp", () => MD5.Create()}, - }; - - /// - /// Returns a Sha256 string for a given string - /// - /// bytes to hash - /// string sha256 - public string HashPassword(byte[] bytes) - { - if (bytes == null) - throw new NullReferenceException("bytes"); - Func func; - if (!HashTypes.TryGetValue(HashAlgo.ToLower(), out func)) - throw new NotSupportedException("Hashing algorithm {0} is not supported".SFormat(HashAlgo.ToLower())); - - using (var hash = func()) - { - var ret = hash.ComputeHash(bytes); - return ret.Aggregate("", (s, b) => s + b.ToString("X2")); - } - } - - /// - /// Returns a Sha256 string for a given string - /// - /// bytes to hash - /// string sha256 - public string HashPassword(string password) - { - if (string.IsNullOrEmpty(password) || password == "non-existant password") - return "non-existant password"; - return HashPassword(Encoding.UTF8.GetBytes(password)); - } - - /// - /// Checks if the string contains any unprintable characters - /// - /// String to check - /// True if the string only contains printable characters - public bool ValidString(string str) - { - foreach (var c in str) - { - if (c < 0x20 || c > 0xA9) - return false; - } - return true; - } - - /// - /// Checks if world has hit the max number of chests - /// - /// True if the entire chest array is used - public bool MaxChests() - { - for (int i = 0; i < Main.chest.Length; i++) - { - if (Main.chest[i] == null) - return false; - } - return true; - } - - /// - /// Searches for a projectile by identity and owner - /// - /// identity - /// owner - /// projectile ID - public int SearchProjectile(short identity, int owner) - { - for (int i = 0; i < Main.maxProjectiles; i++) - { - if (Main.projectile[i].identity == identity && Main.projectile[i].owner == owner) - return i; - } - return 1000; - } - - /// - /// Sanitizes input strings - /// - /// string - /// sanitized string - public string SanitizeString(string str) - { - var returnstr = str.ToCharArray(); - for (int i = 0; i < str.Length; i++) - { - if (!ValidString(str[i].ToString())) - returnstr[i] = ' '; - } - return new string(returnstr); - } - } -} \ No newline at end of file +/* +TShock, a server mod for Terraria +Copyright (C) 2011 The TShock Team + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Text; +using Terraria; + +namespace TShockAPI +{ + public class Utils + { + private readonly static int firstItemPrefix = 1; + private readonly static int lastItemPrefix = 83; + // Utils is a Singleton + private static readonly Utils instance = new Utils(); + private Utils() {} + public static Utils Instance { get { return instance; } } + + public Random Random = new Random(); + //private static List groups = new List(); + + /// + /// Provides the real IP address from a RemoteEndPoint string that contains a port and an IP + /// + /// A string IPv4 address in IP:PORT form. + /// A string IPv4 address. + public string GetRealIP(string mess) + { + return mess.Split(':')[0]; + } + + /// + /// Used for some places where a list of players might be used. + /// + /// String of players seperated by commas. + public string GetPlayers() + { + var sb = new StringBuilder(); + foreach (TSPlayer player in TShock.Players) + { + if (player != null && player.Active) + { + if (sb.Length != 0) + { + sb.Append(", "); + } + sb.Append(player.Name); + } + } + return sb.ToString(); + } + + /// + /// Used for some places where a list of players might be used. + /// + /// String of players and their id seperated by commas. + public string GetPlayersWithIds() + { + var sb = new StringBuilder(); + foreach (TSPlayer player in TShock.Players) + { + if (player != null && player.Active) + { + if (sb.Length != 0) + { + sb.Append(", "); + } + sb.Append(player.Name); + string id = "( " + Convert.ToString(TShock.Users.GetUserID(player.UserAccountName)) + " )"; + sb.Append(id); + } + } + return sb.ToString(); + } + + /// + /// Finds a player and gets IP as string + /// + /// Player name + public string GetPlayerIP(string playername) + { + foreach (TSPlayer player in TShock.Players) + { + if (player != null && player.Active) + { + if (playername.ToLower() == player.Name.ToLower()) + { + return player.IP; + } + } + } + return null; + } + + /// + /// It's a clamp function + /// + /// + /// Value to clamp + /// Maximum bounds of the clamp + /// Minimum bounds of the clamp + /// + public T Clamp(T value, T max, T min) + where T : IComparable + { + T result = value; + if (value.CompareTo(max) > 0) + result = max; + if (value.CompareTo(min) < 0) + result = min; + return result; + } + + /// + /// Saves the map data + /// + public void SaveWorld() + { + SaveManager.Instance.SaveWorld(); + } + + /// + /// Broadcasts a message to all players + /// + /// string message + public void Broadcast(string msg) + { + Broadcast(msg, Color.Green); + } + + public void Broadcast(string msg, byte red, byte green, byte blue) + { + TSPlayer.All.SendMessage(msg, red, green, blue); + TSPlayer.Server.SendMessage(msg, red, green, blue); + Log.Info(string.Format("Broadcast: {0}", msg)); + } + + public void Broadcast(string msg, Color color) + { + Broadcast(msg, color.R, color.G, color.B); + } + + /// + /// Sends message to all users with 'logs' permission. + /// + /// Message to send + /// Color of the message + public void SendLogs(string log, Color color) + { + Log.Info(log); + TSPlayer.Server.SendMessage(log, color); + foreach (TSPlayer player in TShock.Players) + { + if (player != null && player.Active && player.Group.HasPermission(Permissions.logs) && player.DisplayLogs && + TShock.Config.DisableSpewLogs == false) + player.SendMessage(log, color); + } + } + + /// + /// The number of active players on the server. + /// + /// int playerCount + public int ActivePlayers() + { + return Main.player.Where(p => null != p && p.active).Count(); + } + + /// + /// Finds a player ID based on name + /// + /// Player name + /// + public List FindPlayer(string ply) + { + var found = new List(); + // Avoid errors caused by null search + if (null == ply) + return found; + ply = ply.ToLower(); + foreach (TSPlayer player in TShock.Players) + { + if (player == null) + continue; + + string name = player.Name.ToLower(); + if (name.Equals(ply)) + return new List {player}; + if (name.Contains(ply)) + found.Add(player); + } + return found; + } + + /// + /// Gets a random clear tile in range + /// + /// Bound X + /// Bound Y + /// Range on the X axis + /// Range on the Y axis + /// X location + /// Y location + public void GetRandomClearTileWithInRange(int startTileX, int startTileY, int tileXRange, int tileYRange, + out int tileX, out int tileY) + { + int j = 0; + do + { + if (j == 100) + { + tileX = startTileX; + tileY = startTileY; + break; + } + + tileX = startTileX + Random.Next(tileXRange*-1, tileXRange); + tileY = startTileY + Random.Next(tileYRange*-1, tileYRange); + j++; + } while (TileValid(tileX, tileY) && !TileClear(tileX, tileY)); + } + + /// + /// Determines if a tile is valid + /// + /// Location X + /// Location Y + /// If the tile is valid + private bool TileValid(int tileX, int tileY) + { + return tileX >= 0 && tileX <= Main.maxTilesX && tileY >= 0 && tileY <= Main.maxTilesY; + } + + /// + /// Clears a tile + /// + /// Location X + /// Location Y + /// The state of the tile + private bool TileClear(int tileX, int tileY) + { + return !Main.tile[tileX, tileY].active; + } + + /// + /// Gets a list of items by ID or name + /// + /// Item ID or name + /// List of Items + public List GetItemByIdOrName(string idOrName) + { + int type = -1; + if (int.TryParse(idOrName, out type)) + { + return new List {GetItemById(type)}; + } + return GetItemByName(idOrName); + } + + /// + /// Gets an item by ID + /// + /// ID + /// Item + public Item GetItemById(int id) + { + Item item = new Item(); + item.netDefaults(id); + return item; + } + + /// + /// Gets items by name + /// + /// name + /// List of Items + public List GetItemByName(string name) + { + //Method #1 - must be exact match, allows support for different pickaxes/hammers/swords etc + for (int i = 1; i < Main.maxItemTypes; i++) + { + Item item = new Item(); + item.SetDefaults(name); + if (item.name == name) + return new List {item}; + } + //Method #2 - allows impartial matching + var found = new List(); + for (int i = -24; i < Main.maxItemTypes; i++) + { + try + { + Item item = new Item(); + item.netDefaults(i); + if (item.name.ToLower() == name.ToLower()) + return new List {item}; + if (item.name.ToLower().StartsWith(name.ToLower())) + found.Add(item); + } + catch + { + } + } + return found; + } + + /// + /// Gets an NPC by ID or Name + /// + /// + /// List of NPCs + public List GetNPCByIdOrName(string idOrName) + { + int type = -1; + if (int.TryParse(idOrName, out type)) + { + return new List {GetNPCById(type)}; + } + return GetNPCByName(idOrName); + } + + /// + /// Gets an NPC by ID + /// + /// ID + /// NPC + public NPC GetNPCById(int id) + { + NPC npc = new NPC(); + npc.netDefaults(id); + return npc; + } + + /// + /// Gets a NPC by name + /// + /// Name + /// List of matching NPCs + public List GetNPCByName(string name) + { + //Method #1 - must be exact match, allows support for different coloured slimes + for (int i = -17; i < Main.maxNPCTypes; i++) + { + NPC npc = new NPC(); + npc.SetDefaults(name); + if (npc.name == name) + return new List {npc}; + } + //Method #2 - allows impartial matching + var found = new List(); + for (int i = 1; i < Main.maxNPCTypes; i++) + { + NPC npc = new NPC(); + npc.netDefaults(i); + if (npc.name.ToLower() == name.ToLower()) + return new List {npc}; + if (npc.name.ToLower().StartsWith(name.ToLower())) + found.Add(npc); + } + return found; + } + + /// + /// Gets a buff name by id + /// + /// ID + /// name + public string GetBuffName(int id) + { + return (id > 0 && id < Main.maxBuffs) ? Main.buffName[id] : "null"; + } + + /// + /// Gets the description of a buff + /// + /// ID + /// description + public string GetBuffDescription(int id) + { + return (id > 0 && id < Main.maxBuffs) ? Main.buffTip[id] : "null"; + } + + /// + /// Gets a list of buffs by name + /// + /// name + /// Matching list of buff ids + public List GetBuffByName(string name) + { + for (int i = 1; i < Main.maxBuffs; i++) + { + if (Main.buffName[i].ToLower() == name) + return new List {i}; + } + var found = new List(); + for (int i = 1; i < Main.maxBuffs; i++) + { + if (Main.buffName[i].ToLower().StartsWith(name.ToLower())) + found.Add(i); + } + return found; + } + + /// + /// Gets a prefix based on its id + /// + /// ID + /// Prefix name + public string GetPrefixById(int id) + { + var item = new Item(); + item.SetDefaults(0); + item.prefix = (byte) id; + item.AffixName(); + return item.name.Trim(); + } + + /// + /// Gets a list of prefixes by name + /// + /// Name + /// List of prefix IDs + public List GetPrefixByName(string name) + { + Item item = new Item(); + item.SetDefaults(0); + string lowerName = name.ToLower(); + var found = new List(); + for (int i = firstItemPrefix; i <= lastItemPrefix; i++) + { + try + { + item.prefix = (byte)i; + string trimmed = item.AffixName().Trim(); + if (trimmed == name) + { + // Exact match + found.Add(i); + return found; + } + else + { + string trimmedLower = trimmed.ToLower(); + if (trimmedLower == lowerName) + { + // Exact match (caseinsensitive) + found.Add(i); + return found; + } + else if (trimmedLower.StartsWith(lowerName)) // Partial match + found.Add(i); + } + } + catch + { + } + } + return found; + } + + /// + /// Gets a prefix by ID or name + /// + /// ID or name + /// List of prefix IDs + public List GetPrefixByIdOrName(string idOrName) + { + int type = -1; + if (int.TryParse(idOrName, out type) && type >= firstItemPrefix && type <= lastItemPrefix) + { + return new List {type}; + } + return GetPrefixByName(idOrName); + } + + /// + /// Kicks all player from the server without checking for immunetokick permission. + /// + /// int player + /// string reason + public void ForceKickAll(string reason) + { + foreach (TSPlayer player in TShock.Players) + { + if (player != null && player.Active) + { + ForceKick(player, reason); + } + } + } + + /// + /// Stops the server after kicking all players with a reason message, and optionally saving the world + /// + /// bool perform a world save before stop (default: true) + /// string reason (default: "Server shutting down!") + public void StopServer(bool save = true, string reason = "Server shutting down!") + { + ForceKickAll(reason); + if (save) + SaveManager.Instance.SaveWorld(); + + // Save takes a while so kick again + ForceKickAll(reason); + + // Broadcast so console can see we are shutting down as well + TShock.Utils.Broadcast(reason, Color.Red); + + // Disconnect after kick as that signifies server is exiting and could cause a race + Netplay.disconnect = true; + } + +#if COMPAT_SIGS + [Obsolete("This method is for signature compatibility for external code only")] + public void ForceKick(TSPlayer player, string reason) + { + Kick(player, reason, true, false, string.Empty); + } +#endif + /// + /// Kicks a player from the server without checking for immunetokick permission. + /// + /// int player + /// string reason + /// bool silent (default: false) + public void ForceKick(TSPlayer player, string reason, bool silent = false) + { + Kick(player, reason, true, silent); + } + +#if COMPAT_SIGS + [Obsolete("This method is for signature compatibility for external code only")] + public bool Kick(TSPlayer player, string reason, string adminUserName) + { + return Kick(player, reason, false, false, adminUserName); + } +#endif + /// + /// Kicks a player from the server. + /// + /// int player + /// string reason + /// bool force (default: false) + /// bool silent (default: false) + /// bool silent (default: null) + public bool Kick(TSPlayer player, string reason, bool force = false, bool silent = false, string adminUserName = null) + { + if (!player.ConnectionAlive) + return true; + if (force || !player.Group.HasPermission(Permissions.immunetokick)) + { + string playerName = player.Name; + player.SilentKickInProgress = silent; + player.Disconnect(string.Format("Kicked: {0}", reason)); + Log.ConsoleInfo(string.Format("Kicked {0} for : {1}", playerName, reason)); + string verb = force ? "force " : ""; + if (string.IsNullOrWhiteSpace(adminUserName)) + Broadcast(string.Format("{0} was {1}kicked for {2}", playerName, verb, reason.ToLower())); + else + Broadcast(string.Format("{0} {1}kicked {2} for {3}", adminUserName, verb, playerName, reason.ToLower())); + return true; + } + return false; + } + +#if COMPAT_SIGS + [Obsolete("This method is for signature compatibility for external code only")] + public bool Ban(TSPlayer player, string reason, string adminUserName) + { + return Ban(player, reason, false, adminUserName); + } +#endif + /// + /// Bans and kicks a player from the server. + /// + /// int player + /// string reason + /// bool force (default: false) + /// bool silent (default: null) + public bool Ban(TSPlayer player, string reason, bool force = false, string adminUserName = null) + { + if (!player.ConnectionAlive) + return true; + if (force || !player.Group.HasPermission(Permissions.immunetoban)) + { + string ip = player.IP; + string playerName = player.Name; + TShock.Bans.AddBan(ip, playerName, reason); + player.Disconnect(string.Format("Banned: {0}", reason)); + Log.ConsoleInfo(string.Format("Banned {0} for : {1}", playerName, reason)); + string verb = force ? "force " : ""; + if (string.IsNullOrWhiteSpace(adminUserName)) + Broadcast(string.Format("{0} was {1}banned for {1}", playerName, verb, reason.ToLower())); + else + Broadcast(string.Format("{0} {1}banned {1} for {2}", adminUserName, verb, playerName, reason.ToLower())); + return true; + } + return false; + } + + /// + /// Shows a file to the user. + /// + /// int player + /// string filename reletave to savedir + //Todo: Fix this + public void ShowFileToUser(TSPlayer player, string file) + { + string foo = ""; + using (var tr = new StreamReader(Path.Combine(TShock.SavePath, file))) + { + while ((foo = tr.ReadLine()) != null) + { + foo = foo.Replace("%map%", Main.worldName); + foo = foo.Replace("%players%", GetPlayers()); + //foo = SanitizeString(foo); + if (foo.Substring(0, 1) == "%" && foo.Substring(12, 1) == "%") //Look for a beginning color code. + { + string possibleColor = foo.Substring(0, 13); + foo = foo.Remove(0, 13); + float[] pC = {0, 0, 0}; + possibleColor = possibleColor.Replace("%", ""); + string[] pCc = possibleColor.Split(','); + if (pCc.Length == 3) + { + try + { + player.SendMessage(foo, (byte) Convert.ToInt32(pCc[0]), (byte) Convert.ToInt32(pCc[1]), + (byte) Convert.ToInt32(pCc[2])); + continue; + } + catch (Exception e) + { + Log.Error(e.ToString()); + } + } + } + player.SendMessage(foo); + } + } + } + + /// + /// Returns a Group from the name of the group + /// + /// string groupName + public Group GetGroup(string groupName) + { + //first attempt on cached groups + for (int i = 0; i < TShock.Groups.groups.Count; i++) + { + if (TShock.Groups.groups[i].Name.Equals(groupName)) + { + return TShock.Groups.groups[i]; + } + } + return new Group(TShock.Config.DefaultGuestGroupName); + } + + /// + /// Returns an IPv4 address from a DNS query + /// + /// string ip + public string GetIPv4Address(string hostname) + { + try + { + //Get the ipv4 address from GetHostAddresses, if an ip is passed it will return that ip + var ip = Dns.GetHostAddresses(hostname).FirstOrDefault(i => i.AddressFamily == AddressFamily.InterNetwork); + //if the dns query was successful then return it, otherwise return an empty string + return ip != null ? ip.ToString() : ""; + } + catch (SocketException) + { + } + return ""; + } + + public string HashAlgo = "sha512"; + + public readonly Dictionary> HashTypes = new Dictionary> + { + {"sha512", () => new SHA512Managed()}, + {"sha256", () => new SHA256Managed()}, + {"md5", () => new MD5Cng()}, + {"sha512-xp", () => SHA512.Create()}, + {"sha256-xp", () => SHA256.Create()}, + {"md5-xp", () => MD5.Create()}, + }; + + /// + /// Returns a Sha256 string for a given string + /// + /// bytes to hash + /// string sha256 + public string HashPassword(byte[] bytes) + { + if (bytes == null) + throw new NullReferenceException("bytes"); + Func func; + if (!HashTypes.TryGetValue(HashAlgo.ToLower(), out func)) + throw new NotSupportedException("Hashing algorithm {0} is not supported".SFormat(HashAlgo.ToLower())); + + using (var hash = func()) + { + var ret = hash.ComputeHash(bytes); + return ret.Aggregate("", (s, b) => s + b.ToString("X2")); + } + } + + /// + /// Returns a Sha256 string for a given string + /// + /// bytes to hash + /// string sha256 + public string HashPassword(string password) + { + if (string.IsNullOrEmpty(password) || password == "non-existant password") + return "non-existant password"; + return HashPassword(Encoding.UTF8.GetBytes(password)); + } + + /// + /// Checks if the string contains any unprintable characters + /// + /// String to check + /// True if the string only contains printable characters + public bool ValidString(string str) + { + foreach (var c in str) + { + if (c < 0x20 || c > 0xA9) + return false; + } + return true; + } + + /// + /// Checks if world has hit the max number of chests + /// + /// True if the entire chest array is used + public bool MaxChests() + { + for (int i = 0; i < Main.chest.Length; i++) + { + if (Main.chest[i] == null) + return false; + } + return true; + } + + /// + /// Searches for a projectile by identity and owner + /// + /// identity + /// owner + /// projectile ID + public int SearchProjectile(short identity, int owner) + { + for (int i = 0; i < Main.maxProjectiles; i++) + { + if (Main.projectile[i].identity == identity && Main.projectile[i].owner == owner) + return i; + } + return 1000; + } + + /// + /// Sanitizes input strings + /// + /// string + /// sanitized string + public string SanitizeString(string str) + { + var returnstr = str.ToCharArray(); + for (int i = 0; i < str.Length; i++) + { + if (!ValidString(str[i].ToString())) + returnstr[i] = ' '; + } + return new string(returnstr); + } + } +} diff --git a/TShockRestTestPlugin/Properties/AssemblyInfo.cs b/TShockRestTestPlugin/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..f9320bbe --- /dev/null +++ b/TShockRestTestPlugin/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ClassLibrary1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Multiplay")] +[assembly: AssemblyProduct("ClassLibrary1")] +[assembly: AssemblyCopyright("Copyright © Multiplay 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c6aed7ee-6282-49a2-8177-b79cad20d6d3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TShockRestTestPlugin/TShockRestTestPlugin.cs b/TShockRestTestPlugin/TShockRestTestPlugin.cs new file mode 100644 index 00000000..385ba4fc --- /dev/null +++ b/TShockRestTestPlugin/TShockRestTestPlugin.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Web; +using System.Web.Script.Serialization; +using System.Text.RegularExpressions; +using Microsoft.VisualStudio.TestTools.WebTesting; +using Microsoft.VisualStudio.TestTools.WebTesting.Rules; +using Rests; + +namespace TshockRestTestPlugin +{ + [DisplayName("JSON Status")] + [Description("Checks to see the that the JSON response has the specified status response")] + public class JsonValidateStatus : JsonValidate + { + public override void Validate(object sender, ValidationEventArgs e) + { + if (null != ValidateJson(sender, e)) + e.IsValid = true; + } + } + + [DisplayName("JSON Regexp Property")] + [Description("Checks to see the that the JSON response contains the specified property and is matches the specified regexp")] + public class JsonValidateRegexpProperty : JsonValidateProperty + { + // The name of the desired JSON property + [DisplayName("Regexp")] + [DefaultValue(true)] + public new bool UseRegularExpression { get { return base.UseRegularExpression; } set { base.UseRegularExpression = value; } } + } + + [DisplayName("JSON Error")] + [Description("Checks to see the that the JSON response contains the specified error")] + public class JsonValidateError : JsonValidateProperty + { + // The status of the JSON request + [DisplayName("JSON Status")] + [DefaultValue("400")] + public new string JSonStatus { get { return base.JSonStatus; } set { base.JSonStatus = value; } } + + // The name of the desired JSON property + [DisplayName("Property")] + [DefaultValue("error")] + public new string PropertyName { get { return base.PropertyName; } set { base.PropertyName = value; } } + } + + [DisplayName("JSON Missing Parameter")] + [Description("Checks to see the that the JSON response indicates a missing or invalid parameter")] + public class JsonValidateMissingParameter : JsonValidateError + { + // The value of the desired JSON property + [DisplayName("Missing Value")] + public new string PropertyValue { get { return base.PropertyValue; } set { base.PropertyValue = String.Format("Missing or empty {0} parameter", value); } } + } + + [DisplayName("JSON Invalid Parameter")] + [Description("Checks to see the that the JSON response indicates a missing or invalid parameter")] + public class JsonValidateInvalidParameter : JsonValidateError + { + // The value of the desired JSON property + [DisplayName("Invalid Value")] + public new string PropertyValue { get { return base.PropertyValue; } set { base.PropertyValue = String.Format("Missing or invalid {0} parameter", value); } } + } + + [DisplayName("JSON Response")] + [Description("Checks to see the that the JSON response contains the specified message")] + public class JsonValidateResponse : JsonValidateProperty + { + // The name of the desired JSON property + [DisplayName("Response")] + [DefaultValue("response")] + public new string PropertyName { get { return base.PropertyName; } set { base.PropertyName = value; } } + } + + [DisplayName("JSON Property")] + [Description("Checks to see the that the JSON response contains the specified property and is set to the specified value")] + public class JsonValidateProperty : JsonValidate + { + // The name of the desired JSON property + [DisplayName("Property")] + public string PropertyName { get; set; } + + // The value of the desired JSON property + [DisplayName("Value")] + public string PropertyValue { get; set; } + + // Is the value a regexp of the desired JSON property + [DisplayName("Regexp")] + [DefaultValue(false)] + public bool UseRegularExpression { get; set; } + + public override void Validate(object sender, ValidationEventArgs e) + { + RestObject response = ValidateJson(sender, e); + if (null == response) + return; + + if (null == response[PropertyName]) + { + e.Message = String.Format("{0} Not Found", PropertyName); + e.IsValid = false; + return; + } + + if (UseRegularExpression) + { + var re = new Regex(PropertyValue); + if (!re.IsMatch((string)response[PropertyName])) + { + e.Message = String.Format("{0} => '{1}' !~ '{2}'", PropertyName, response[PropertyName], PropertyValue); + e.IsValid = false; + return; + } + } + else + { + if (PropertyValue != (string)response[PropertyName]) + { + e.Message = String.Format("{0} => '{1}' != '{2}'", PropertyName, response[PropertyName], PropertyValue); + e.IsValid = false; + return; + } + } + + e.IsValid = true; + //e.WebTest.Context.Add(ContextParameterName, propertyValue); + } + } + + [DisplayName("JSON Has Properties")] + [Description("Checks to see the that the JSON response contains the specified properties (comma seperated)")] + public class JsonHasProperties : JsonValidate + { + // The name of the desired JSON properties to check + [DisplayName("Properties")] + [Description("A comma seperated list of property names to check exist")] + public string PropertyNames { get; set; } + + //--------------------------------------------------------------------- + public override void Validate(object sender, ValidationEventArgs e) + { + RestObject response = ValidateJson(sender, e); + if (null == response) + return; + foreach (var p in PropertyNames.Split(',')) + { + if (null == response[p]) + { + e.Message = String.Format("'{0}' Not Found", p); + e.IsValid = false; + return; + } + } + e.IsValid = true; + + //e.WebTest.Context.Add(ContextParameterName, propertyValue); + } + } + + public abstract class JsonValidate : ValidationRule + { + // The status of the JSON request + [DisplayName("JSON Status")] + [DefaultValue("200")] + public string JSonStatus { get; set; } + + public RestObject ValidateJson(object sender, ValidationEventArgs e) + { + if (string.IsNullOrWhiteSpace(e.Response.BodyString)) + { + e.IsValid = false; + e.Message = String.Format("Empty or null response {0}", e.Response.StatusCode); + return null; + } + JavaScriptSerializer serialiser = new JavaScriptSerializer(); + //dynamic data = serialiser.Deserialize(e.Response.BodyString); + RestObject response = serialiser.Deserialize(e.Response.BodyString); + + if (JSonStatus != response.Status) + { + e.IsValid = false; + e.Message = String.Format("Response Status '{0}' not '{1}'", response.Status, JSonStatus); + return null; + } + + return response; + } + } +} \ No newline at end of file diff --git a/TShockRestTestPlugin/TShockRestTestPlugin.csproj b/TShockRestTestPlugin/TShockRestTestPlugin.csproj new file mode 100644 index 00000000..e315c7d3 --- /dev/null +++ b/TShockRestTestPlugin/TShockRestTestPlugin.csproj @@ -0,0 +1,62 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {F2FEDAFB-58DE-4611-9168-A86112C346C7} + Library + Properties + TshockRestTestPlugin + TshockRestTestPlugin + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + {49606449-072B-4CF5-8088-AA49DA586694} + TShockAPI + + + + + \ No newline at end of file diff --git a/TerrariaServerBins/TerrariaServer.exe b/TerrariaServerBins/TerrariaServer.exe index f5226745..253a7e1f 100644 Binary files a/TerrariaServerBins/TerrariaServer.exe and b/TerrariaServerBins/TerrariaServer.exe differ diff --git a/UnitTests/BanManagerTest.cs b/UnitTests/BanManagerTest.cs index b1c94d87..2f4daa67 100644 --- a/UnitTests/BanManagerTest.cs +++ b/UnitTests/BanManagerTest.cs @@ -61,10 +61,7 @@ namespace UnitTests public void FindBanTest() { Assert.IsNotNull(Bans.GetBanByIp("127.0.0.1")); - TShock.Config.EnableBanOnUsernames = true; Assert.IsNotNull(Bans.GetBanByName("BanTest")); - TShock.Config.EnableBanOnUsernames = false; - Assert.IsNull(Bans.GetBanByName("BanTest")); } } } diff --git a/UnitTests/RestApiTests.webtest b/UnitTests/RestApiTests.webtest new file mode 100644 index 00000000..29e27981 --- /dev/null +++ b/UnitTests/RestApiTests.webtest @@ -0,0 +1,1542 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index d9b9c7f9..923856ac 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -49,6 +49,7 @@ + ..\SqlBins\Mono.Data.Sqlite.dll @@ -87,6 +88,10 @@ {49606449-072B-4CF5-8088-AA49DA586694} TShockAPI + + {F2FEDAFB-58DE-4611-9168-A86112C346C7} + TShockRestTestPlugin + @@ -102,6 +107,9 @@ Always + + Always + diff --git a/docs/generate.bat b/docs/generate.bat new file mode 100644 index 00000000..b5bfc3d0 --- /dev/null +++ b/docs/generate.bat @@ -0,0 +1,6 @@ +@echo off +:main +echo Generating Pandoc docs +cd src +for %%F in (*.md) do pandoc %%F > %%F.html +pause \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 00000000..7f29d689 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,15 @@ +# Building + +1. Install [Pandoc](http://johnmacfarlane.net/pandoc). +2. cd to docs/src +3. pandoc index.md -o ../index.html +4. pandoc permissions.md -o ../permissions.html + +# Adding stuff + +1. Everything is [Markdown](http://daringfireball.net/projects/markdown/) +2. If you add a new page, include [Markdown-CSS](https://github.com/clownfart/Markdown-CSS) for best results. + +# Demo + +You can access the docs at the [TShock Github Page](http://tshock.github.com/). \ No newline at end of file diff --git a/docs/src/config.md b/docs/src/config.md new file mode 100644 index 00000000..af47422d --- /dev/null +++ b/docs/src/config.md @@ -0,0 +1,110 @@ + + +# The config file + +[Back to index](index.md.html) + +---- + +Each TShock installation automatically generates a configuration file which can edit basic settings when the server starts. + +The file "config.json" is located in the *tshock* folder, in the same directory as TerrariaServer.exe. + +Being a JSON file, it is extremely critical that you edit the file in a standard text editor. Use [Notepad++](http://notepad-plus-plus.org/) to edit your file, or another text editor used for programming. Before restarting TShock, check the syntax of your file using [JSONLint](http://jsonlint.com/), and verify that it contains no errors. + +An example configuration file is below. + + { + "InvasionMultiplier": 1, + "DefaultMaximumSpawns": 0, + "DefaultSpawnRate": 600, + "ServerPort": 7777, + "EnableWhitelist": false, + "InfiniteInvasion": false, + "PvPMode": "normal", + "SpawnProtection": true, + "SpawnProtectionRadius": 5, + "MaxSlots": 8, + "RangeChecks": true, + "DisableBuild": false, + "SuperAdminChatRGB": [ + 255.0, + 0.0, + 0.0 + ], + "SuperAdminChatPrefix": "(Admin) ", + "SuperAdminChatSuffix": "", + "BackupInterval": 0, + "BackupKeepFor": 60, + "RememberLeavePos": false, + "HardcoreOnly": false, + "MediumcoreOnly": false, + "KickOnMediumcoreDeath": false, + "BanOnMediumcoreDeath": false, + "AutoSave": true, + "MaximumLoginAttempts": 3, + "RconPassword": "", + "RconPort": 7777, + "ServerName": "", + "MasterServer": "127.0.0.1", + "StorageType": "sqlite", + "MySqlHost": "localhost:3306", + "MySqlDbName": "", + "MySqlUsername": "", + "MySqlPassword": "", + "MediumcoreBanReason": "Death results in a ban", + "MediumcoreKickReason": "Death results in a kick", + "EnableDNSHostResolution": false, + "EnableIPBans": true, + "EnableBanOnUsernames": false, + "DefaultRegistrationGroupName": "default", + "DefaultGuestGroupName": "guest", + "DisableSpewLogs": true, + "HashAlgorithm": "sha512", + "BufferPackets": true, + "ServerFullReason": "Server is full", + "ServerFullNoReservedReason": "Server is full. No reserved slots open.", + "SaveWorldOnCrash": true, + "EnableGeoIP": false, + "EnableTokenEndpointAuthentication": false, + "ServerNickname": "TShock Server", + "RestApiEnabled": false, + "RestApiPort": 7878, + "DisableTombstones": true, + "DisplayIPToAdmins": false, + "EnableInsecureTileFixes": true, + "KickProxyUsers": true, + "DisableHardmode": false, + "DisableDungeonGuardian": false, + "ServerSideInventory": false, + "ServerSideInventorySave": 15, + "LogonDiscardThreshold": 250, + "DisablePlayerCountReporting": false, + "DisableClownBombs": false, + "DisableSnowBalls": false, + "ChatFormat": "{1}{2}{3}: {4}", + "ForceTime": "normal", + "TileKillThreshold": 60, + "TilePlaceThreshold": 20, + "TileLiquidThreshold": 15, + "ProjectileThreshold": 50, + "ProjIgnoreShrapnel": true, + "RequireLogin": true, + "DisableInvisPvP": false, + "MaxRangeForDisabled": 10, + "ServerPassword": "", + "RegionProtectChests": false, + "DisableLoginBeforeJoin": false, + "AllowRegisterAnyUsername": false, + "AllowLoginAnyUsername": true, + "MaxDamage": 175, + "MaxProjDamage": 175, + "IgnoreProjUpdate": false, + "IgnoreProjKill": false, + "IgnoreNoClip": false, + "AllowIce": true + } + +In this file, if you wanted to change the maximum players to 64, you would edit that the file so that the line referring to max players looked like so: + + "MaxSlots": 64, \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 00000000..4c7dd880 --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,43 @@ + + +# TShock Downloaded Documentation + +*Created for TShock version: 3.5.x.x* + +*Last updated: 2/25/2012* + +---- + +## Preface + +Welcome to the official TShock for Terraria downloaded documentation. This guide will walk through the installation and basic configuration of your newly downloaded TShock server, and should provide a basic knowledge as to how to get help from outside resources if needed. + +## Resources + +* [The Confluence wiki](http://develop.tshock.co:8080/) contains the most up to date information compiled by the community members. If your question isn't answered here, you might find it there. +* [The forums](http://tshock.co/xf/) provide an excellent place to ask other TShock users and developers questions. Please refrain from making posts about questions that may be answered here, however. +* [Our Github page](http://github.com/TShock/TShock) is where you'll be able to find the source code and the bug tracker. +* [IRC](irc://irc.shankshock.com/terraria) is our IRC channel, if you prefer that medium for support. +* Lastly, we can be found in the "Nyx" channel on the Teamspeak 3 server: ts3.shankshock.com, port 9987. + +---- + +## Table of contents + +1. [Installation & basic usage](install.md.html) +2. [Permissions](perms.md.html) +3. [The config file](config.md.html) +4. [Credits](#Credits) + +---- + +## Credits + +TShock wouldn't be possible without: + +* [Xenon Servers](http://xns.cc/) +* [Kerplunc Gaming](http://kerpluncgaming.com/) +* [Multiplay UK](http://multiplay.co.uk/) +* [Atlassian](http://www.atlassian.com/) +* [Github](http://github.com/) +* You :) \ No newline at end of file diff --git a/docs/src/install.md b/docs/src/install.md new file mode 100644 index 00000000..99d0a349 --- /dev/null +++ b/docs/src/install.md @@ -0,0 +1,35 @@ + + +# Install instructions & basic usage + +[Back to index](index.md.html) + +---- + +1. Assuming you've extracted TShock, you're done as far as files go. Run the TerrariaServer.exe file in the folder you've extracted TShock into. +2. Check to verify that the server window states that some version of TerrariaShock is now running. If this is not the case, stop. Re-download all files and extract them to a new folder, being sure to keep the file structure in tact. +3. Select a world and port to start the server. *TShock now uses its configuration file to control the number of players on the server. You can edit this value in the configuration file, discussed later. If you are generating a new world, you may experience a significantly longer time as the world creates itself. This is normal. +4. Once the server is finished starting, you will be greeted with TShock's main console window. You will see a message in yellow that states "*To become superadmin, join the game and type /auth*" preceding a series of numbers. This set of numbers we will refer to as the "authcode" in succeeding steps. +5. Connect to the server. Your IP address is 127.0.0.1, and the port will by default be on what you entered in the server console. +6. Immediately chat the following: "**/auth [authcode]**". Replace [authcode] with the code given in the server console. Example: /auth 123456. +7. Next, we will create a user account that you can login to. In the game, chat the command "**/user add [username]:[password] superadmin**". Replace [username] and [password] respectively with your appropriate details. You should be able to remeber your password, and it shouldn't be easily guessed. From now on, the phrase "run the command" is synonymous with "chat in the game chat box". In addition, where brackets ([]) are, we will assume you will replace those brackets with information that you have created. +8. Assuming the last step was a success, login. Run the command "**/login [username] [password]**". +9. To finalize installation, run the command "**/auth-verify**". This will disable the authcode, enable any previously disabled functionality, and allow the server to be used in production. + +---- + +### Basic Usage + +Now that TShock is running, you may be interested in a few other features prior to playing Terraria. + +* You can add admins through two methods. If the user is already registered, you can use "**/user group [username] [group-to-change-to]**". By default, TShock comes with the "vip" group, the "trustedadmin" group, and the "newadmin" group. If the user has yet to register, you can use "**/user add [username]:[password] [group]**" to generate an account with elevated permissions for them. +* When you join the server and already have an account, the server will ask for your account password, even if the server has no password setup. In the event that you set a password on the server, unregistered users will be required to enter it. Users that already have an account must enter their own password. +* If a user wishes to change accounts but retain their group, a config option exists that will allow you to allow users to login to accounts with any username. + +---- + +## Closing remarks + +Thanks for downloading TShock. Your continued support helps make TShock what it is today. We wouldn't be here without you. + +From everyone at TShock, thank-you. \ No newline at end of file diff --git a/docs/src/permissions.md b/docs/src/permissions.md new file mode 100644 index 00000000..49242fa9 --- /dev/null +++ b/docs/src/permissions.md @@ -0,0 +1,248 @@ + + +# Permission Nodes + +These are the permissions that TShock currently supports, with associated commands. [Back to permissions](perms.md.html) + +---- + +## allowclientsideworldedit +**Description:** Allow unrestricted Send Tile Square usage, for client side world editing +**Commands:** None + +## annoy +**Description:** None +**Commands:** /annoy + +## ban +**Description:** User can ban others +**Commands:** /ban /banip /unban /unbanip + +## buff +**Description:** User can buff self +**Commands:** /buff + +## buffplayer +**Description:** User can buff other players +**Commands:** /gbuff(/buffplayer) + +## butcher +**Description:** User can kill all enemy npcs +**Commands:** /butcher + +## bypassinventorychecks +**Description:** Bypass Server Side Inventory checks +**Commands:** None + +## canbuild +**Description:** Required to be able to build (modify tiles and liquid) +**Commands:** None + +## canchangepassword +**Description:** User can change password in game +**Commands:** /password + +## canlogin +**Description:** User can login in game +**Commands:** /login + +## canpartychat +**Description:** User can use party chat in game +**Commands:** /p + +## canregister +**Description:** User can register account in game +**Commands:** /register + +## cantalkinthird +**Description:** User can talk in third person +**Commands:** /me + +## causeevents +**Description:** None +**Commands:** /dropmeteor /star /genore /fullmoon /bloodmoon /invade + +## cfg +**Description:** User can edit sevrer configurations +**Commands:** /setspawn /reload /serverpassword /save /settle /maxspawns /spawnrate /broadcast(/bc /say) /stats /world + +## clearitems +**Description:** User can clear item drops. +**Commands:** /clear(/clearitems) + +## converthardmode +**Description:** User can convert hallow into corruption and vice-versa +**Commands:** /convertcorruption /converthallow /removespecial + +## editspawn +**Description:** Allows you to edit the spawn +**Commands:** /antibuild /protectspawn + +## grow +**Description:** None +**Commands:** /grow + +## hardmode +**Description:** User can change hardmode state. +**Commands:** /hardmode /stophardmode(/disablehardmode) + +## heal +**Description:** None +**Commands:** /heal + +## ignoredamagecap +**Description:** Prevents your actions from being ignored if damage is too high +**Commands:** None + +## ignorekilltiledetection +**Description:** Prevents you from being reverted by kill tile abuse detection +**Commands:** None + +## ignoreliquidsetdetection +**Description:** Prevents you from being disabled by liquid set abuse detection +**Commands:** None + +## ignorenoclipdetection +**Description:** Prevents you from being reverted by no clip detection +**Commands:** None + +## ignoreplacetiledetection +**Description:** Prevents you from being reverted by place tile abuse detection +**Commands:** None + +## ignoreprojectiledetection +**Description:** Prevents you from being disabled by liquid set abuse detection +**Commands:** None + +## ignorestackhackdetection +**Description:** Prevents you from being disabled by stack hack detection +**Commands:** None + +## ignorestathackdetection +**Description:** Prevents you from being kicked by hacked health detection +**Commands:** None + +## immunetoban +**Description:** Prevents you from being banned +**Commands:** None + +## immunetokick +**Description:** Prevents you from being kicked +**Commands:** None + +## item +**Description:** User can spawn items +**Commands:** /item(/i) /give(/g) + +## kick +**Description:** User can kick others +**Commands:** /kick + +## kill +**Description:** None +**Commands:** /kill + +## logs +**Description:** Specific log messages are sent to users with this permission +**Commands:** /displaylogs + +## maintenance +**Description:** User is notified when an update is available +**Commands:** /clearbans /off(/exit) /restart /off-nosave(/exit-nosave) /checkupdates + +## managegroup +**Description:** User can manage groups +**Commands:** /addgroup /delgroup /modgroup /group + +## manageitem +**Description:** User can manage item bans +**Commands:** /additem(/banitem) /delitem(/unbanitem) /listitems(/listbanneditems) /additemgroup /delitemgroup + +## manageregion +**Description:** User can edit regions +**Commands:** /region /debugreg + +## managewarp +**Description:** User can manage warps +**Commands:** /setwarp /delwarp /hidewarp + +## movenpc +**Description:** User can change the homes of NPCs. +**Commands:** None + +## mute +**Description:** User can mute and unmute users +**Commands:** /mute(/unmute) + +## pvpfun +**Description:** None +**Commands:** /slap + +## reservedslot +**Description:** Allows you to bypass the max slots for up to 5 slots above your max +**Commands:** None + +## rootonly +**Description:** Meant for super admins only +**Commands:** /user /userinfo(/ui) /auth-verify + +## seeids +**Description:** User can see the id of players with /who +**Commands:** None + +## spawnboss +**Description:** User can spawn bosses +**Commands:** /eater /eye /king /skeletron /wof(/wallofflesh) /twins /destroyer /skeletronp(/prime) /hardcore + +## spawnmob +**Description:** User can spawn npcs +**Commands:** /spawnmob(/sm) + +## startinvasion +**Description:** User can start invasions (Goblin/Snow Legion) using items +**Commands:** None + +## summonboss +**Description:** User can summon bosses using items +**Commands:** None + +## time +**Description:** None +**Commands:** /time + +## tp +**Description:** User can teleport +**Commands:** /home /spawn /tp + +## tpall +**Description:** Users can tp to anyone +**Commands:** None + +## tpallow +**Description:** Users can stop people from TPing to them +**Commands:** /tpallow + +## tphere +**Description:** User can teleport people to them +**Commands:** /tphere /sendwarp(/sw) + +## tphide +**Description:** Users can tp to people without showing a notice +**Commands:** None + +## usebanneditem +**Description:** Allows you to use banned items +**Commands:** None + +## warp +**Description:** User can use warps +**Commands:** /warp + +## whisper +**Description:** User can whisper to others +**Commands:** /whisper(/w /tell) /reply(/r) + +## whitelist +**Description:** User can modify the whitelist +**Commands:** /whitelist + diff --git a/docs/src/perms.md b/docs/src/perms.md new file mode 100644 index 00000000..c51fdde0 --- /dev/null +++ b/docs/src/perms.md @@ -0,0 +1,17 @@ + + +# Permissions + +[Back to index](index.md.html) + +## Permissions + +Like Bukkit and other administrative modifications, TShock supports adding groups and permissions. In the current implementation, you can only edit groups ingame, adding and removing them isn't supported *yet*. + +## Adding permissions: + +To add a permission to a given group, use the command "**/modgroup [add|del] [group] [permission]**". Example: */modgroup add trustedadmin tpall*. + +## Permission nodes: + +[A list of permission nodes can be found here.](permissions.md.html) \ No newline at end of file diff --git a/release-docs/changes.txt b/release-docs/changes.txt deleted file mode 100644 index 0b4503ce..00000000 --- a/release-docs/changes.txt +++ /dev/null @@ -1,128 +0,0 @@ -For the full list of changes, please take a look at GitHub: -https://github.com/TShock/TShock/commits/master -From now on, all release notes aren't put here. It's too much to track, but new features will be tossed here. - -Changes in API release 3: - - Added support for SQLite - - Added support for MySQL - - Added /user command, supports adding users via in game command - - Added database editor for editing the MySQL + SQLite DB systems - - Fixed /region list and /warp list - - Fixed Jexius's font exploit - - Added /annoy - - Added canbuild permission - - Fixed mysterious chair rotation system - - Added /ip to retrieve a player's IPv4 address - - Removed /buff - - Added command line paramater -worldpath, which changes the location where Terraria checks for worlds - - Fixed save world race conditions - - Added /login - - Fixed an instance where NPC.maxSpawns was incorrectly referenced in favor of NPC.defaultMaxSpawns - - Chests are ignored from kill tile abuse - - Added /reply (/r) to reply to a /whisper - - Fixed /broadcast spacing - - User names and passwords are now accepted. Passwords are hashed with SHA512 - - Added MaximumLoginAttempts to configuration - - Added /tphere * and /tphere all - - Added /auth-verify to verify and turn off the auth code system - - Added the ability to Log/notify admins when commands are executed. - - Added a new Configuration Flag called "DisableBuild". - - Added command to toggle anti-build. - - Added -ip commandline - - Fixed hair exploit - - Added /rules, reads from ./tshock/rules.txt - - Added AdminChatPrefix configuration option. - - Added ForceKillAll to kick all players. - - Added support to spawn all types of slimes (have to use the full exact name) - - Added /king to spawn king slime. - - Adds protected regions. Use /region help ingame for extra help - - Added warps to tshock, edited some region commands - - Added the ability to ban specific items for being in inventory when joining server - - Added /setspawn command,Sets the spawn point of the server - - Added HardcoreOnly - - Sandgun and Dirt Rod no longer triggers a Impossible to place block. - - Added /displaylogs. Toggles log output to player who executed the command. - - Added -configpath command line parameter to set config path. - - Added broadcasting on map saves - - Added /tphere * and /tphere all - -Still Lots More To Add! :) - -Changes in API release 2.0.0.0: - - Added update checker. - -Changes in API release 1.8.0.0: - - Added permissions system for managing different levels of admins - -- Added one time auth system for new permissions. When you start the server and load the map, you get a one time auth code. Use /auth to become superadmin. - - Check the wiki on Github for more information on Permissions. - - Fixed BanExplosives not doing anything. - - All ban lists have been consolidated into one file, where reasons, IPs, and player names are stored together. - - Fixed spawnrate and max spawns - -Changes in API release 1.6.0.0: - - Added spawn protection - - Fixed numerous bugs - - Added a few commands - -Changes in API release 1.5.0.1: - - Fixed cheat detection - -Changes in API release 1.5.0.0: - - Added /time - - Added /kill - - Fixed /item - - Added /slap [dmg] - - Added broadcast event for kill tile abuse - - Fixed teleport somewhat - - More cheat detection - - Extended new cheat protection to mana - - Update player exploit patched - - Fixed /spawn - - Made /invasion a toggle - -Changes in API release 1.4.0.0: - - The configuration file is now located at config.json - - Something else. - -Changes in API release 1.3.0.1: - - Re-coded the entire command system - - Re-coded the help command - - Implemented what seems to be the most recurring blacklist ever - -Changes in API release 1.3.0.0: - - Added /maxspawns - - Added /spawnrate - - Resetup the configuration file to read spawn rate information. - - Patched the ability for clients to spawn NPCs - - Patched the ability for clients to rewrite the server through SendSection - - Make sure to use this Terraria.exe for the server: (http://dl.dropbox.com/u/29760911/Terraria.exe) - -- Allows spawn rates to be changed - -Changes in API release 1.2.0.1: - - New update system - -Changes in API release 1.2: - - Added /butcher - - Added /heal - - /spawnmob now takes another argument for the amount - - /item now adds items to the inventory directly - - This update credit to Deathmax - -Changes in API release 1.1: - - Added /tp - - Added /tphere - - Added /spawnmob - - Added /item - - Fixed /spawn - - Updated /help - - Everything in this update credit to Deathmax, High, and Shank - -Changes in API release 0.1: - - Added /save to save the world - - Added /spawn to teleport to spawn - - Added broken teleport stuff - - Major re-write of the anti-tnt code (now uses a blacklist instead of a whitelist) - - Fixed server crashing bug of the anti-tnt code - - Made the anti-tnt code decrease the threshold instantaniously - - Re added the update checker. \ No newline at end of file diff --git a/release-docs/documentation.txt b/release-docs/documentation.txt deleted file mode 100644 index ebee2aa4..00000000 --- a/release-docs/documentation.txt +++ /dev/null @@ -1,3 +0,0 @@ -Documentation, NPC lists, Spawn lists, and more can be found on GitHub at: - -https://github.com/TShock/TShock/wiki \ No newline at end of file diff --git a/release-docs/installation.txt b/release-docs/installation.txt deleted file mode 100644 index 26b88e24..00000000 --- a/release-docs/installation.txt +++ /dev/null @@ -1,3 +0,0 @@ -For installation instructions, please refer to: - -https://github.com/TShock/TShock/wiki/Installation-instructions \ No newline at end of file