diff --git a/modules/client/client.otmod b/modules/client/client.otmod index f2ee64b9..73f6b025 100644 --- a/modules/client/client.otmod +++ b/modules/client/client.otmod @@ -16,6 +16,7 @@ Module - client_terminal - client_modulemanager - client_entergame + - client_stats @onLoad: | dofile 'client' diff --git a/modules/client_stats/stats.lua b/modules/client_stats/stats.lua new file mode 100644 index 00000000..a7b9b80a --- /dev/null +++ b/modules/client_stats/stats.lua @@ -0,0 +1,84 @@ +HOST = '189.55.105.125' +PORT = 3000 + +function init() + connect(g_game, { onGameStart = onGameStart }) + + protocolHttp = ProtocolHttp.create() + connect(protocolHttp, { onConnect = onConnect, + onRecv = onRecv, + onError = onError }) +end + +function terminate() + disconnect(g_game, { onGameStart = onGameStart }) + + disconnect(protocolHttp, { onConnect = onConnect, + onRecv = onRecv, + onError = onError }) + protocolHttp = nil +end + +function sendInfo() + protocolHttp:connect(HOST, PORT) +end + +-- events +function onGameStart() + scheduleEvent(sendInfo, 5*1000) +end + +function onConnect(protocol) + pinfo('Connected to stats server.') + + if not g_game.isOnline() then + perror('Could not send stats. Game not online.') + protocol:disconnect() + return + end + + local post = '' + post = post .. 'os=' .. g_app.getOs() + post = post .. '&graphics_vendor=' .. g_graphics.getVendor() + post = post .. '&graphics_renderer=' .. g_graphics.getRenderer() + post = post .. '&graphics_version=' .. g_graphics.getVersion() + post = post .. '&painter_engine=' .. g_graphics.getPainterEngine() + post = post .. '&fps=' .. g_app.getBackgroundPaneFps() + post = post .. '&max_fps=' .. g_app.getBackgroundPaneMaxFps() + post = post .. '&fullscreen=' .. fromboolean(g_window.isFullscreen()) + post = post .. '&window_width=' .. g_window.getWidth() + post = post .. '&window_height=' .. g_window.getHeight() + post = post .. '&player_name=' .. g_game.getLocalPlayer():getName() + post = post .. '&otserv_host=' .. G.host + post = post .. '&otserv_port=' .. G.port + post = post .. '&otserv_protocol=' .. g_game.getClientVersion() + post = post .. '&build_version=' .. g_app.getVersion() + post = post .. '&build_revision=' .. g_app.getBuildRevision() + post = post .. '&build_commit=' .. g_app.getBuildCommit() + post = post .. '&build_date=' .. g_app.getBuildDate() + post = post .. '&display_width=' .. g_window.getDisplayWidth() + post = post .. '&display_height=' .. g_window.getDisplayHeight() + + local message = '' + message = message .. "POST /report HTTP/1.0\r\n" + message = message .. "Host: " .. HOST .. "\r\n" + message = message .. "Accept: */*\r\n" + message = message .. "Connection: close\r\n" + message = message .. "Content-Type: application/x-www-form-urlencoded\r\n" + message = message .. "Content-Length: " .. post:len() .. "\r\n\r\n" + message = message .. post + + protocol:send(message) + protocol:recv() +end + +function onRecv(protocol, message) + if string.find(message, 'HTTP/1.1 200 OK') then + pinfo('Stats sent to server successfully!') + end + protocol:disconnect() +end + +function onError(protocol, message, code) + perror('Could not send statistics. ' .. message .. 'Code: ' .. code) +end diff --git a/modules/client_stats/stats.otmod b/modules/client_stats/stats.otmod new file mode 100644 index 00000000..32e1b391 --- /dev/null +++ b/modules/client_stats/stats.otmod @@ -0,0 +1,9 @@ +Module + name: client_stats + description: Sends client statistics to a server + author: baxnie + website: www.otclient.info + sandboxed: true + scripts: [ stats.lua ] + @onLoad: init() + @onUnload: terminate() diff --git a/modules/corelib/util.lua b/modules/corelib/util.lua index 97089875..be0d5f6e 100644 --- a/modules/corelib/util.lua +++ b/modules/corelib/util.lua @@ -236,6 +236,14 @@ function toboolean(str) return false end +function fromboolean(boolean) + if boolean then + return 'true' + else + return 'false' + end +end + function signalcall(param, ...) if type(param) == 'function' then local status, ret = pcall(param, ...) diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index 44352404..28366b4b 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -432,6 +432,8 @@ if(FRAMEWORK_NET) ${CMAKE_CURRENT_LIST_DIR}/net/outputmessage.h ${CMAKE_CURRENT_LIST_DIR}/net/protocol.cpp ${CMAKE_CURRENT_LIST_DIR}/net/protocol.h + ${CMAKE_CURRENT_LIST_DIR}/net/protocolhttp.cpp + ${CMAKE_CURRENT_LIST_DIR}/net/protocolhttp.h ${CMAKE_CURRENT_LIST_DIR}/net/server.cpp ${CMAKE_CURRENT_LIST_DIR}/net/server.h ) diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index b73d4a11..2eee7751 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -45,6 +44,8 @@ #ifdef FW_NET #include +#include +#include #endif #ifdef FW_SQL @@ -682,6 +683,14 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("enableXteaEncryption", &Protocol::enableXteaEncryption); g_lua.bindClassMemberFunction("enableChecksum", &Protocol::enableChecksum); + // ProtocolHttp + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", []{ return ProtocolHttpPtr(new ProtocolHttp); }); + g_lua.bindClassMemberFunction("connect", &ProtocolHttp::connect); + g_lua.bindClassMemberFunction("disconnect", &ProtocolHttp::disconnect); + g_lua.bindClassMemberFunction("send", &ProtocolHttp::send); + g_lua.bindClassMemberFunction("recv", &ProtocolHttp::recv); + // InputMessage g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return InputMessagePtr(new InputMessage); }); @@ -744,7 +753,7 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("next", &DBResult::next); // Mysql - g_lua.registerClass(); + g_lua.registerClass(); g_lua.bindClassStaticFunction("create", []{ return DatabaseMySQLPtr(new DatabaseMySQL); }); g_lua.bindClassMemberFunction("connect", &DatabaseMySQL::connect); g_lua.bindClassMemberFunction("executeQuery", &DatabaseMySQL::executeQuery); diff --git a/src/framework/net/connection.cpp b/src/framework/net/connection.cpp index de3b1019..c44442b7 100644 --- a/src/framework/net/connection.cpp +++ b/src/framework/net/connection.cpp @@ -156,13 +156,31 @@ void Connection::read(uint16 bytes, const RecvCallback& callback) m_recvCallback = callback; asio::async_read(m_socket, - asio::buffer(m_recvBuffer, bytes), + asio::buffer(m_streamBuffer.prepare(bytes)), std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, bytes)); m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT)); m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); } +void Connection::read_until(const std::string& what, const RecvCallback& callback) +{ + m_readTimer.cancel(); + + if(!m_connected) + return; + + m_recvCallback = callback; + + asio::async_read_until(m_socket, + m_streamBuffer, + what.c_str(), + std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2)); + + m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT)); + m_readTimer.async_wait(std::bind(&Connection::onTimeout, asConnection(), std::placeholders::_1)); +} + void Connection::read_some(const RecvCallback& callback) { m_readTimer.cancel(); @@ -172,7 +190,7 @@ void Connection::read_some(const RecvCallback& callback) m_recvCallback = callback; - m_socket.async_read_some(asio::buffer(m_recvBuffer, RECV_BUFFER_SIZE), + m_socket.async_read_some(asio::buffer(m_streamBuffer.prepare(RECV_BUFFER_SIZE)), std::bind(&Connection::onRecv, asConnection(), std::placeholders::_1, std::placeholders::_2)); m_readTimer.expires_from_now(boost::posix_time::seconds(READ_TIMEOUT)); @@ -217,10 +235,15 @@ void Connection::onRecv(const boost::system::error_code& error, size_t recvSize) return; if(!error) { - if(m_recvCallback) - m_recvCallback(m_recvBuffer, recvSize); - } else + if(m_recvCallback) { + const char* header = boost::asio::buffer_cast(m_streamBuffer.data()); + m_recvCallback((uint8*)header, recvSize); + } + } + else handleError(error); + + m_streamBuffer.consume(recvSize); } void Connection::onTimeout(const boost::system::error_code& error) diff --git a/src/framework/net/connection.h b/src/framework/net/connection.h index 3bf99bf0..dc567bb1 100644 --- a/src/framework/net/connection.h +++ b/src/framework/net/connection.h @@ -54,6 +54,7 @@ public: void write(uint8* buffer, uint16 size); void read(uint16 bytes, const RecvCallback& callback); + void read_until(const std::string& what, const RecvCallback& callback); void read_some(const RecvCallback& callback); void setErrorCallback(const ErrorCallback& errorCallback) { m_errorCallback = errorCallback; } @@ -81,7 +82,7 @@ protected: asio::ip::tcp::socket m_socket; uint8 m_sendBuffer[SEND_BUFFER_SIZE]; - uint8 m_recvBuffer[RECV_BUFFER_SIZE]; + asio::streambuf m_streamBuffer; bool m_connected; bool m_connecting; boost::system::error_code m_error; diff --git a/src/framework/net/declarations.h b/src/framework/net/declarations.h index e1c189ff..cfd9d8fe 100644 --- a/src/framework/net/declarations.h +++ b/src/framework/net/declarations.h @@ -32,12 +32,14 @@ class InputMessage; class OutputMessage; class Connection; class Protocol; +class ProtocolHttp; class Server; typedef stdext::shared_object_ptr InputMessagePtr; typedef stdext::shared_object_ptr OutputMessagePtr; typedef stdext::shared_object_ptr ConnectionPtr; typedef stdext::shared_object_ptr ProtocolPtr; +typedef stdext::shared_object_ptr ProtocolHttpPtr; typedef stdext::shared_object_ptr ServerPtr; #endif diff --git a/src/framework/net/protocol.h b/src/framework/net/protocol.h index 139cefed..5edc06d1 100644 --- a/src/framework/net/protocol.h +++ b/src/framework/net/protocol.h @@ -53,7 +53,7 @@ public: void enableChecksum() { m_checksumEnabled = true; } virtual void send(const OutputMessagePtr& outputMessage); - void recv(); + virtual void recv(); ProtocolPtr asProtocol() { return static_self_cast(); } diff --git a/src/framework/net/protocolhttp.cpp b/src/framework/net/protocolhttp.cpp new file mode 100644 index 00000000..6f3d9404 --- /dev/null +++ b/src/framework/net/protocolhttp.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "protocolhttp.h" +#include "connection.h" +#include + +ProtocolHttp::ProtocolHttp() +{ +} + +ProtocolHttp::~ProtocolHttp() +{ +#ifndef NDEBUG + assert(!g_app.isTerminated()); +#endif + disconnect(); +} + +void ProtocolHttp::connect(const std::string& host, uint16 port) +{ + m_connection = ConnectionPtr(new Connection); + m_connection->setErrorCallback(std::bind(&ProtocolHttp::onError, asProtocolHttp(), std::placeholders::_1)); + m_connection->connect(host, port, std::bind(&ProtocolHttp::onConnect, asProtocolHttp())); +} + +void ProtocolHttp::disconnect() +{ + if(m_connection) { + m_connection->close(); + m_connection.reset(); + } +} + +void ProtocolHttp::send(const std::string& message) +{ + if(m_connection) + m_connection->write((uint8*)message.c_str(), message.length()); +} + +void ProtocolHttp::recv() +{ + if(m_connection) + m_connection->read_until("\r\n\r\n", std::bind(&ProtocolHttp::onRecv, asProtocolHttp(), std::placeholders::_1, std::placeholders::_2)); +} + +void ProtocolHttp::onConnect() +{ + callLuaField("onConnect"); +} + +void ProtocolHttp::onRecv(uint8* buffer, uint16 size) +{ + std::string string = std::string((char*)buffer, (size_t)size); + callLuaField("onRecv", string); +} + +void ProtocolHttp::onError(const boost::system::error_code& err) +{ + callLuaField("onError", err.message(), err.value()); + disconnect(); +} diff --git a/src/framework/net/protocolhttp.h b/src/framework/net/protocolhttp.h new file mode 100644 index 00000000..1348543e --- /dev/null +++ b/src/framework/net/protocolhttp.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010-2012 OTClient + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef PROTOCOLHTTP_H +#define PROTOCOLHTTP_H + +#include "declarations.h" +#include "connection.h" + +#include + +// @bindclass +class ProtocolHttp : public LuaObject +{ +public: + ProtocolHttp(); + virtual ~ProtocolHttp(); + + void connect(const std::string& host, uint16 port); + void disconnect(); + + void send(const std::string &message); + void recv(); + + ProtocolHttpPtr asProtocolHttp() { return static_self_cast(); } + +protected: + void onConnect(); + void onRecv(uint8* buffer, uint16 size); + void onError(const boost::system::error_code& err); + +private: + ConnectionPtr m_connection; +}; + +#endif diff --git a/src/framework/otml/otmlnode.cpp b/src/framework/otml/otmlnode.cpp index 9c873cf4..f88ed880 100644 --- a/src/framework/otml/otmlnode.cpp +++ b/src/framework/otml/otmlnode.cpp @@ -193,4 +193,3 @@ std::string OTMLNode::emit() { return OTMLEmitter::emitNode(asOTMLNode(), 0); } -