diff --git a/const.h b/const.h
index cd479e4..8d46b23 100644
--- a/const.h
+++ b/const.h
@@ -21,9 +21,12 @@

 enum OperatingSystem_t
 {
-	CLIENTOS_LINUX		= 0x01,
-	CLIENTOS_WINDOWS	= 0x02,
-	CLIENTOS_FLASH		= 0x03
+	CLIENTOS_LINUX				= 0x01,
+	CLIENTOS_WINDOWS			= 0x02,
+	CLIENTOS_FLASH				= 0x03,
+	CLIENTOS_OTCLIENT_LINUX		= 0x0A,
+	CLIENTOS_OTCLIENT_WINDOWS	= 0x0B,
+	CLIENTOS_OTCLIENT_MAC		= 0x0C,
 };

 enum ChannelEvent_t
diff --git a/creatureevent.cpp b/creatureevent.cpp
index 842b237..e9b2200 100644
--- a/creatureevent.cpp
+++ b/creatureevent.cpp
@@ -211,6 +211,8 @@ CreatureEventType_t CreatureEvents::getType(const std::string& type)
 		_type = CREATURE_EVENT_DEATH;
 	else if(type == "preparedeath")
 		_type = CREATURE_EVENT_PREPAREDEATH;
+	else if(type == "extendedopcode")
+		_type = CREATURE_EVENT_EXTENDED_OPCODE;

 	return _type;
 }
@@ -330,6 +332,8 @@ std::string CreatureEvent::getScriptEventName() const
 			return "onDeath";
 		case CREATURE_EVENT_PREPAREDEATH:
 			return "onPrepareDeath";
+		case CREATURE_EVENT_EXTENDED_OPCODE:
+			return "onExtendedOpcode";
 		case CREATURE_EVENT_NONE:
 		default:
 			break;
@@ -401,6 +405,8 @@ std::string CreatureEvent::getScriptEventParams() const
 			return "cid, corpse, deathList";
 		case CREATURE_EVENT_PREPAREDEATH:
 			return "cid, deathList";
+		case CREATURE_EVENT_EXTENDED_OPCODE:
+			return "cid, opcode, buffer";
 		case CREATURE_EVENT_NONE:
 		default:
 			break;
@@ -2145,3 +2151,57 @@ uint32_t CreatureEvent::executeAction(Creature* creature, Creature* target)
 		return 0;
 	}
 }
+
+uint32_t CreatureEvent::executeExtendedOpcode(Creature* creature, uint8_t opcode, const std::string& buffer)
+{
+	//onExtendedOpcode(cid, opcode, buffer)
+	if(m_interface->reserveEnv())
+	{
+		ScriptEnviroment* env = m_interface->getEnv();
+		if(m_scripted == EVENT_SCRIPT_BUFFER)
+		{
+			env->setRealPos(creature->getPosition());
+			std::stringstream scriptstream;
+			scriptstream << "local cid = " << env->addThing(creature) << std::endl;
+			scriptstream << "local opcode = " << (int)opcode << std::endl;
+			scriptstream << "local buffer = " << buffer.c_str() << std::endl;
+
+			scriptstream << m_scriptData;
+			bool result = true;
+			if(m_interface->loadBuffer(scriptstream.str()))
+			{
+				lua_State* L = m_interface->getState();
+				result = m_interface->getGlobalBool(L, "_result", true);
+			}
+
+			m_interface->releaseEnv();
+			return result;
+		}
+		else
+		{
+			#ifdef __DEBUG_LUASCRIPTS__
+			char desc[35];
+			sprintf(desc, "%s", player->getName().c_str());
+			env->setEvent(desc);
+			#endif
+
+			env->setScriptId(m_scriptId, m_interface);
+			env->setRealPos(creature->getPosition());
+
+			lua_State* L = m_interface->getState();
+			m_interface->pushFunction(m_scriptId);
+			lua_pushnumber(L, env->addThing(creature));
+			lua_pushnumber(L, opcode);
+			lua_pushlstring(L, buffer.c_str(), buffer.length());
+
+			bool result = m_interface->callFunction(3);
+			m_interface->releaseEnv();
+			return result;
+		}
+	}
+	else
+	{
+		std::cout << "[Error - CreatureEvent::executeRemoved] Call stack overflow." << std::endl;
+		return 0;
+	}
+}
diff --git a/creatureevent.h b/creatureevent.h
index f1ff4b2..cc5171c 100644
--- a/creatureevent.h
+++ b/creatureevent.h
@@ -57,7 +57,8 @@ enum CreatureEventType_t
 	CREATURE_EVENT_CAST,
 	CREATURE_EVENT_KILL,
 	CREATURE_EVENT_DEATH,
-	CREATURE_EVENT_PREPAREDEATH
+	CREATURE_EVENT_PREPAREDEATH,
+	CREATURE_EVENT_EXTENDED_OPCODE // otclient additional network opcodes
 };

 enum StatsChange_t
@@ -150,6 +151,7 @@ class CreatureEvent : public Event
 		uint32_t executeKill(Creature* creature, Creature* target, const DeathEntry& entry);
 		uint32_t executeDeath(Creature* creature, Item* corpse, DeathList deathList);
 		uint32_t executePrepareDeath(Creature* creature, DeathList deathList);
+		uint32_t executeExtendedOpcode(Creature* creature, uint8_t opcode, const std::string& buffer);
 		//

 	protected:
diff --git a/data/creaturescripts/creaturescripts.xml b/data/creaturescripts/creaturescripts.xml
index 363c62b..c706f10 100644
--- a/data/creaturescripts/creaturescripts.xml
+++ b/data/creaturescripts/creaturescripts.xml
@@ -14,4 +14,6 @@

 	<event type="think" name="Idle" event="script" value="idle.lua"/>
 	<event type="think" name="SkullCheck" event="script" value="skullcheck.lua"/>
+
+	<event type="extendedopcode" name="ExtendedOpcode" event="script" value="extendedopcode.lua"/>
 </creaturescripts>
diff --git a/data/creaturescripts/scripts/extendedopcode.lua b/data/creaturescripts/scripts/extendedopcode.lua
new file mode 100644
index 0000000..c488a4d
--- /dev/null
+++ b/data/creaturescripts/scripts/extendedopcode.lua
@@ -0,0 +1,13 @@
+OPCODE_LANGUAGE = 1
+
+function onExtendedOpcode(cid, opcode, buffer)
+	if opcode == OPCODE_LANGUAGE then
+	  -- otclient language
+	  if buffer == 'en' or buffer == 'pt' then
+		  -- example, setting player language, because otclient is multi-language...
+		  --doCreatureSetStorage(cid, CREATURE_STORAGE_LANGUAGE, buffer)
+	  end
+	else
+	  -- other opcodes can be ignored, and the server will just work fine...
+	end
+end
diff --git a/game.cpp b/game.cpp
index 2e4dc2c..7508591 100644
--- a/game.cpp
+++ b/game.cpp
@@ -6951,3 +6951,12 @@ void Game::checkExpiredMarketOffers()

 	Scheduler::getInstance().addEvent(createSchedulerTask(checkExpiredMarketOffersEachMinutes * 60 * 1000, boost::bind(&Game::checkExpiredMarketOffers, this)));
 }
+
+void Game::parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer)
+{
+ Player* player = getPlayerByID(playerId);
+ if(!player || player->isRemoved())
+  return;
+
+	CreatureEventList extendedOpcodeEvents = player->getCreatureEvents(CREATURE_EVENT_EXTENDED_OPCODE);
+	for(CreatureEventList::iterator it = extendedOpcodeEvents.begin(); it != extendedOpcodeEvents.end(); ++it)
+		(*it)->executeExtendedOpcode(player, opcode, buffer);
+}
diff --git a/game.h b/game.h
index 51fa397..7192549 100644
--- a/game.h
+++ b/game.h
@@ -646,6 +646,8 @@ class Game
 		std::map<Item*, int32_t> grounds;
 #endif

+		void parsePlayerExtendedOpcode(uint32_t playerId, uint8_t opcode, const std::string& buffer);
+
 	protected:
 		bool playerWhisper(Player* player, const std::string& text, uint32_t statementId);
 		bool playerYell(Player* player, const std::string& text, uint32_t statementId);
diff --git a/luascript.cpp b/luascript.cpp
index 4cb8c8d..4ed9391 100644
--- a/luascript.cpp
+++ b/luascript.cpp
@@ -2476,6 +2476,12 @@ void LuaInterface::registerFunctions()
 	//getConfigFile()
 	lua_register(m_luaState, "getConfigFile", LuaInterface::luaGetConfigFile);

+	//isPlayerUsingOtclient(cid)
+	lua_register(m_luaState, "isPlayerUsingOtclient", LuaInterface::luaIsPlayerUsingOtclient);
+
+	//doSendPlayerExtendedOpcode(cid, opcode, buffer)
+	lua_register(m_luaState, "doSendPlayerExtendedOpcode", LuaInterface::luaDoSendPlayerExtendedOpcode);
+
 	//getConfigValue(key)
 	lua_register(m_luaState, "getConfigValue", LuaInterface::luaGetConfigValue);

@@ -9471,6 +9477,32 @@ int32_t LuaInterface::luaGetMountInfo(lua_State* L)
 	return 1;
 }

+int32_t LuaInterface::luaIsPlayerUsingOtclient(lua_State* L)
+{
+	//isPlayerUsingOtclient(cid)
+	ScriptEnviroment* env = getEnv();
+	if(Player* player = env->getPlayerByUID(popNumber(L))) {
+		lua_pushboolean(L, player->isUsingOtclient());
+	}
+	lua_pushboolean(L, false);
+	return 1;
+}
+
+int32_t LuaInterface::luaDoSendPlayerExtendedOpcode(lua_State* L)
+{
+	//doSendPlayerExtendedOpcode(cid, opcode, buffer)
+	std::string buffer = popString(L);
+	int opcode = popNumber(L);
+
+	ScriptEnviroment* env = getEnv();
+	if(Player* player = env->getPlayerByUID(popNumber(L))) {
+		player->sendExtendedOpcode(opcode, buffer);
+		lua_pushboolean(L, true);
+	}
+	lua_pushboolean(L, false);
+	return 1;
+}
+
 int32_t LuaInterface::luaGetPartyMembers(lua_State* L)
 {
 	//getPartyMembers(cid)
diff --git a/luascript.h b/luascript.h
index 234091a..0a0046f 100644
--- a/luascript.h
+++ b/luascript.h
@@ -697,6 +697,9 @@ class LuaInterface
 		static int32_t luaDoPlayerSetMounted(lua_State* L);
 		static int32_t luaGetMountInfo(lua_State* L);

+		static int32_t luaIsPlayerUsingOtclient(lua_State* L);
+		static int32_t luaDoSendPlayerExtendedOpcode(lua_State* L);
+
 		static int32_t luaL_errors(lua_State* L);
 		static int32_t luaL_loadmodlib(lua_State* L);
 		static int32_t luaL_domodlib(lua_State* L);
diff --git a/networkmessage.cpp b/networkmessage.cpp
index 917e36a..3671750 100644
--- a/networkmessage.cpp
+++ b/networkmessage.cpp
@@ -171,16 +171,16 @@ Position NetworkMessage::getPosition()
 	return pos;
 }

-void NetworkMessage::putString(const char* value, bool addSize/* = true*/)
+void NetworkMessage::putString(const char* value, int length, bool addSize/* = true*/)
 {
-	uint32_t size = (uint32_t)strlen(value);
+	uint32_t size = (uint32_t)length;
 	if(!hasSpace(size + (addSize ? 2 : 0)) || size > 8192)
 		return;

 	if(addSize)
 		put<uint16_t>(size);

-	strcpy((char*)(m_buffer + m_position), value);
+	memcpy((char*)(m_buffer + m_position), value, length);
 	m_position += size;
 	m_size += size;
 }
diff --git a/networkmessage.h b/networkmessage.h
index 6cf8ee1..615f094 100644
--- a/networkmessage.h
+++ b/networkmessage.h
@@ -80,8 +80,8 @@ class NetworkMessage
 			m_size += sizeof(T);
 		}

-		void putString(const std::string& value, bool addSize = true) {putString(value.c_str(), addSize);}
-		void putString(const char* value, bool addSize = true);
+		void putString(const std::string& value, bool addSize = true) {putString(value.c_str(), value.length(), addSize);}
+		void putString(const char* value, int length, bool addSize = true);

 		void putPadding(uint32_t amount);

diff --git a/player.h b/player.h
index 63e9183..7cb8313 100644
--- a/player.h
+++ b/player.h
@@ -228,6 +228,7 @@ class Player : public Creature, public Cylinder
 		bool hasPVPBlessing() const {return pvpBlessing;}
 		uint16_t getBlessings() const;

+		bool isUsingOtclient() const { return operatingSystem >= CLIENTOS_OTCLIENT_LINUX; }
 		OperatingSystem_t getOperatingSystem() const {return operatingSystem;}
 		void setOperatingSystem(OperatingSystem_t os) {operatingSystem = os;}
 		uint32_t getClientVersion() const {return clientVersion;}
@@ -580,6 +581,9 @@ class Player : public Creature, public Cylinder
 		void sendSpellGroupCooldown(SpellGroup_t groupId, uint32_t cooldown)
 			{if(client) client->sendSpellGroupCooldown(groupId, cooldown);}

+		void sendExtendedOpcode(uint8_t opcode, const std::string& buffer)
+			{if(client) client->sendExtendedOpcode(opcode, buffer);}
+
 		//container
 		void sendAddContainerItem(const Container* container, const Item* item);
 		void sendUpdateContainerItem(const Container* container, uint8_t slot, const Item* oldItem, const Item* newItem);
diff --git a/protocolgame.cpp b/protocolgame.cpp
index b980be0..7a84f61 100644
--- a/protocolgame.cpp
+++ b/protocolgame.cpp
@@ -263,6 +263,11 @@ bool ProtocolGame::login(const std::string& name, uint32_t id, const std::string
 			return false;
 		}

+		if(player->isUsingOtclient())
+		{
+			player->registerCreatureEvent("ExtendedOpcode");
+		}
+
 		player->lastIP = player->getIP();
 		player->lastLoad = OTSYS_TIME();
 		player->lastLogin = std::max(time(NULL), player->lastLogin + 1);
@@ -427,6 +432,10 @@ void ProtocolGame::onRecvFirstMessage(NetworkMessage& msg)
 	enableXTEAEncryption();
 	setXTEAKey(key);

+	// notifies to otclient that this server can receive extended game protocol opcodes
+	if(operatingSystem >= CLIENTOS_OTCLIENT_LINUX)
+		sendExtendedOpcode(0x00, std::string());
+
 	bool gamemaster = (msg.get<char>() != (char)0);
 	std::string name = msg.getString(), character = msg.getString(), password = msg.getString();

@@ -578,6 +587,10 @@ void ProtocolGame::parsePacket(NetworkMessage &msg)
 				parseReceivePing(msg);
 				break;

+			case 0x32: // otclient extended opcode
+				parseExtendedOpcode(msg);
+				break;
+
 			case 0x64: // move with steps
 				parseAutoWalk(msg);
 				break;
@@ -3705,3 +3718,28 @@ void ProtocolGame::AddShopItem(NetworkMessage_ptr msg, const ShopInfo& item)
 	msg->put<uint32_t>(item.buyPrice);
 	msg->put<uint32_t>(item.sellPrice);
 }
+
+void ProtocolGame::parseExtendedOpcode(NetworkMessage& msg)
+{
+	uint8_t opcode = msg.get<char>();
+	std::string buffer = msg.getString();
+
+	// process additional opcodes via lua script event
+	addGameTask(&Game::parsePlayerExtendedOpcode, player->getID(), opcode, buffer);
+}
+
+void ProtocolGame::sendExtendedOpcode(uint8_t opcode, const std::string& buffer)
+{
+	// extended opcodes can only be send to players using otclient, cipsoft's tibia can't understand them
+	if(player && !player->isUsingOtclient())
+		return;
+
+	NetworkMessage_ptr msg = getOutputBuffer();
+	if(msg)
+	{
+		TRACK_MESSAGE(msg);
+		msg->put<char>(0x32);
+		msg->put<char>(opcode);
+		msg->putString(buffer);
+	}
+}
diff --git a/protocolgame.h b/protocolgame.h
index 7691174..48b9bf1 100644
--- a/protocolgame.h
+++ b/protocolgame.h
@@ -326,6 +326,9 @@ class ProtocolGame : public Protocol
 		//shop
 		void AddShopItem(NetworkMessage_ptr msg, const ShopInfo& item);

+		void parseExtendedOpcode(NetworkMessage& msg);
+		void sendExtendedOpcode(uint8_t opcode, const std::string& buffer);
+
 		#define addGameTask(f, ...) addGameTaskInternal(0, boost::bind(f, &g_game, __VA_ARGS__))
 		#define addGameTaskTimed(delay, f, ...) addGameTaskInternal(delay, boost::bind(f, &g_game, __VA_ARGS__))
 		template<class FunctionType>
