From da33062f433aea13093d30f423954806379916fd Mon Sep 17 00:00:00 2001 From: MobiusDevelopment <8391001+MobiusDevelopment@users.noreply.github.com> Date: Sat, 4 May 2019 07:25:09 +0000 Subject: [PATCH] Command line login support. Contributed by facab. --- .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ .../dist/login/config/LoginServer.ini | 13 ++ .../java/org/l2jmobius/Config.java | 5 + .../loginserver/network/IncomingPackets.java | 2 + .../clientpackets/RequestAuthLogin.java | 8 +- .../clientpackets/RequestCmdLogin.java | 165 ++++++++++++++++++ 60 files changed, 2280 insertions(+), 36 deletions(-) create mode 100644 L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java create mode 100644 L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java diff --git a/L2J_Mobius_1.0_Ertheia/dist/login/config/LoginServer.ini b/L2J_Mobius_1.0_Ertheia/dist/login/config/LoginServer.ini index c17814d8dd..4841207266 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/login/config/LoginServer.ini +++ b/L2J_Mobius_1.0_Ertheia/dist/login/config/LoginServer.ini @@ -105,6 +105,19 @@ FastConnectionTime = 350 MaxConnectionPerIP = 50 +# --------------------------------------------------------------------------- +# Command Line Login Method +# --------------------------------------------------------------------------- +# Start client with "account=YourAccount password=YourPassword" parameters. +# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file. + +# Default: False +EnableCmdLineLogin = False + +# Default: False +OnlyCmdLineLogin = False + + # --------------------------------------------------------------------------- # Misc Login Settings # --------------------------------------------------------------------------- diff --git a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/Config.java b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/Config.java index 25cc57a932..e6df226619 100644 --- a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/Config.java +++ b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/Config.java @@ -833,6 +833,8 @@ public final class Config public static int NORMAL_CONNECTION_TIME; public static int FAST_CONNECTION_TIME; public static int MAX_CONNECTION_PER_IP; + public static boolean ENABLE_CMD_LINE_LOGIN; + public static boolean ONLY_CMD_LINE_LOGIN; // GrandBoss Settings @@ -2891,6 +2893,9 @@ public final class Config NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700); FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350); MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50); + + ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false); + ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false); } else { diff --git a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/IncomingPackets.java index 1e873f738e..e9da7d4346 100644 --- a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/IncomingPackets.java +++ b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/IncomingPackets.java @@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket; import org.l2jmobius.commons.network.IIncomingPackets; import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard; import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin; +import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin; import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement; import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck; import org.l2jmobius.loginserver.network.clientpackets.RequestServerList; @@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets { AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED), REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG), + REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG), REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN), REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN), REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN), diff --git a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java index e05ce4639b..c83da365c3 100644 --- a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java +++ b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java @@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk; import org.l2jmobius.loginserver.network.serverpackets.ServerList; /** - *
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_1.0_Ertheia/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_2.5_Underground/dist/login/config/LoginServer.ini b/L2J_Mobius_2.5_Underground/dist/login/config/LoginServer.ini
index c17814d8dd..4841207266 100644
--- a/L2J_Mobius_2.5_Underground/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_2.5_Underground/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/Config.java b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/Config.java
index fc3b04205e..58aab63c61 100644
--- a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/Config.java
@@ -840,6 +840,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2907,6 +2909,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index e05ce4639b..c83da365c3 100644
--- a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_2.5_Underground/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_3.0_Helios/dist/login/config/LoginServer.ini b/L2J_Mobius_3.0_Helios/dist/login/config/LoginServer.ini
index c17814d8dd..4841207266 100644
--- a/L2J_Mobius_3.0_Helios/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_3.0_Helios/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/Config.java b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/Config.java
index 5bd2b5abe8..f321727672 100644
--- a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/Config.java
@@ -841,6 +841,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2922,6 +2924,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_3.0_Helios/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/login/config/LoginServer.ini b/L2J_Mobius_4.0_GrandCrusade/dist/login/config/LoginServer.ini
index c17814d8dd..4841207266 100644
--- a/L2J_Mobius_4.0_GrandCrusade/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_4.0_GrandCrusade/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/Config.java b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/Config.java
index 643da6801e..a6675161d9 100644
--- a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/Config.java
@@ -834,6 +834,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2908,6 +2910,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_4.0_GrandCrusade/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_5.0_Salvation/dist/login/config/LoginServer.ini b/L2J_Mobius_5.0_Salvation/dist/login/config/LoginServer.ini
index c17814d8dd..4841207266 100644
--- a/L2J_Mobius_5.0_Salvation/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_5.0_Salvation/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/Config.java b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/Config.java
index 324e4d82c5..8c54ae78d6 100644
--- a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/Config.java
@@ -829,6 +829,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2911,6 +2913,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_5.0_Salvation/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_5.5_EtinasFate/dist/login/config/LoginServer.ini b/L2J_Mobius_5.5_EtinasFate/dist/login/config/LoginServer.ini
index c17814d8dd..4841207266 100644
--- a/L2J_Mobius_5.5_EtinasFate/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_5.5_EtinasFate/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/Config.java b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/Config.java
index 324e4d82c5..8c54ae78d6 100644
--- a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/Config.java
@@ -829,6 +829,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2911,6 +2913,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_5.5_EtinasFate/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_6.0_Fafurion/dist/login/config/LoginServer.ini b/L2J_Mobius_6.0_Fafurion/dist/login/config/LoginServer.ini
index c17814d8dd..4841207266 100644
--- a/L2J_Mobius_6.0_Fafurion/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_6.0_Fafurion/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/Config.java b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/Config.java
index 8a4884dc81..b852ee52ea 100644
--- a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/Config.java
@@ -830,6 +830,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2924,6 +2926,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_6.0_Fafurion/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/login/config/LoginServer.ini b/L2J_Mobius_Classic_2.0_Saviors/dist/login/config/LoginServer.ini
index 950add6320..7ce0641617 100644
--- a/L2J_Mobius_Classic_2.0_Saviors/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_Classic_2.0_Saviors/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java
index 1e1232c7b8..9318ef510e 100644
--- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/Config.java
@@ -827,6 +827,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2783,6 +2785,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_Classic_2.1_Zaken/dist/login/config/LoginServer.ini b/L2J_Mobius_Classic_2.1_Zaken/dist/login/config/LoginServer.ini
index 950add6320..7ce0641617 100644
--- a/L2J_Mobius_Classic_2.1_Zaken/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_Classic_2.1_Zaken/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java
index ead19d926e..4bb1a7781c 100644
--- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/Config.java
@@ -827,6 +827,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2790,6 +2792,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_Classic_2.1_Zaken/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_Classic_2.2_Antharas/dist/login/config/LoginServer.ini b/L2J_Mobius_Classic_2.2_Antharas/dist/login/config/LoginServer.ini
index 950add6320..7ce0641617 100644
--- a/L2J_Mobius_Classic_2.2_Antharas/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_Classic_2.2_Antharas/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java
index ead19d926e..4bb1a7781c 100644
--- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/Config.java
@@ -827,6 +827,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2790,6 +2792,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_Classic_2.2_Antharas/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/dist/login/config/LoginServer.ini b/L2J_Mobius_Classic_2.3_SevenSigns/dist/login/config/LoginServer.ini
index 950add6320..7ce0641617 100644
--- a/L2J_Mobius_Classic_2.3_SevenSigns/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_Classic_2.3_SevenSigns/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java
index ead19d926e..4bb1a7781c 100644
--- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/Config.java
@@ -827,6 +827,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2790,6 +2792,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}
diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/login/config/LoginServer.ini b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/login/config/LoginServer.ini
index 950add6320..7ce0641617 100644
--- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/login/config/LoginServer.ini
+++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/dist/login/config/LoginServer.ini
@@ -105,6 +105,19 @@ FastConnectionTime = 350
 MaxConnectionPerIP = 50
 
 
+# ---------------------------------------------------------------------------
+# Command Line Login Method
+# ---------------------------------------------------------------------------
+# Start client with "account=YourAccount password=YourPassword" parameters.
+# You need to have enabled ExternalLogin and CmdLineLogin in your l2.ini file.
+
+# Default: False
+EnableCmdLineLogin = False
+
+# Default: False
+OnlyCmdLineLogin = False
+
+
 # ---------------------------------------------------------------------------
 # Misc Login Settings
 # ---------------------------------------------------------------------------
diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java
index ead19d926e..4bb1a7781c 100644
--- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java
+++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/Config.java
@@ -827,6 +827,8 @@ public final class Config
 	public static int NORMAL_CONNECTION_TIME;
 	public static int FAST_CONNECTION_TIME;
 	public static int MAX_CONNECTION_PER_IP;
+	public static boolean ENABLE_CMD_LINE_LOGIN;
+	public static boolean ONLY_CMD_LINE_LOGIN;
 	
 	// GrandBoss Settings
 	
@@ -2790,6 +2792,9 @@ public final class Config
 			NORMAL_CONNECTION_TIME = ServerSettings.getInt("NormalConnectionTime", 700);
 			FAST_CONNECTION_TIME = ServerSettings.getInt("FastConnectionTime", 350);
 			MAX_CONNECTION_PER_IP = ServerSettings.getInt("MaxConnectionPerIP", 50);
+			
+			ENABLE_CMD_LINE_LOGIN = ServerSettings.getBoolean("EnableCmdLineLogin", false);
+			ONLY_CMD_LINE_LOGIN = ServerSettings.getBoolean("OnlyCmdLineLogin", false);
 		}
 		else
 		{
diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/IncomingPackets.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/IncomingPackets.java
index 1e873f738e..e9da7d4346 100644
--- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/IncomingPackets.java
+++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/IncomingPackets.java
@@ -26,6 +26,7 @@ import org.l2jmobius.commons.network.IIncomingPacket;
 import org.l2jmobius.commons.network.IIncomingPackets;
 import org.l2jmobius.loginserver.network.clientpackets.AuthGameGuard;
 import org.l2jmobius.loginserver.network.clientpackets.RequestAuthLogin;
+import org.l2jmobius.loginserver.network.clientpackets.RequestCmdLogin;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreement;
 import org.l2jmobius.loginserver.network.clientpackets.RequestPIAgreementCheck;
 import org.l2jmobius.loginserver.network.clientpackets.RequestServerList;
@@ -38,6 +39,7 @@ public enum IncomingPackets implements IIncomingPackets
 {
 	AUTH_GAME_GUARD(0x07, AuthGameGuard::new, ConnectionState.CONNECTED),
 	REQUEST_AUTH_LOGIN(0x00, RequestAuthLogin::new, ConnectionState.AUTHED_GG),
+	REQUEST_LOGIN(0x0B, RequestCmdLogin::new, ConnectionState.AUTHED_GG),
 	REQUEST_SERVER_LOGIN(0x02, RequestServerLogin::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_SERVER_LIST(0x05, RequestServerList::new, ConnectionState.AUTHED_LOGIN),
 	REQUEST_PI_AGREEMENT_CHECK(0x0E, RequestPIAgreementCheck::new, ConnectionState.AUTHED_LOGIN),
diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
index 0f5ae3a30d..6c31fa3df9 100644
--- a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
+++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/clientpackets/RequestAuthLogin.java
@@ -39,10 +39,7 @@ import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
 import org.l2jmobius.loginserver.network.serverpackets.ServerList;
 
 /**
- * 
  * Format: x 0 (a leading null) x: the rsa encrypted block with the login an password.
- * 
- * 
  */
 public class RequestAuthLogin implements IIncomingPacket
 {
@@ -76,6 +73,11 @@ public class RequestAuthLogin implements IIncomingPacket
 	@Override
 	public void run(LoginClient client)
 	{
+		if (Config.ENABLE_CMD_LINE_LOGIN && Config.ONLY_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
 		byte[] decrypted = new byte[_newAuthMethod ? 256 : 128];
 		try
 		{
diff --git a/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
new file mode 100644
index 0000000000..bcacb6aa87
--- /dev/null
+++ b/L2J_Mobius_Classic_2.4_SecretOfEmpire/java/org/l2jmobius/loginserver/network/clientpackets/RequestCmdLogin.java
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the L2J Mobius project.
+ * 
+ * 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 .
+ */
+package org.l2jmobius.loginserver.network.clientpackets;
+
+import java.net.InetAddress;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.Cipher;
+
+import org.l2jmobius.Config;
+import org.l2jmobius.commons.network.IIncomingPacket;
+import org.l2jmobius.commons.network.PacketReader;
+import org.l2jmobius.loginserver.GameServerTable.GameServerInfo;
+import org.l2jmobius.loginserver.LoginController;
+import org.l2jmobius.loginserver.LoginController.AuthLoginResult;
+import org.l2jmobius.loginserver.model.data.AccountInfo;
+import org.l2jmobius.loginserver.network.ConnectionState;
+import org.l2jmobius.loginserver.network.LoginClient;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked;
+import org.l2jmobius.loginserver.network.serverpackets.AccountKicked.AccountKickedReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginFail.LoginFailReason;
+import org.l2jmobius.loginserver.network.serverpackets.LoginOk;
+import org.l2jmobius.loginserver.network.serverpackets.ServerList;
+
+public class RequestCmdLogin implements IIncomingPacket
+{
+	private static Logger LOGGER = Logger.getLogger(RequestCmdLogin.class.getName());
+	
+	private final byte[] _raw = new byte[128];
+	
+	private String _user;
+	private String _password;
+	
+	@Override
+	public boolean read(LoginClient client, PacketReader packet)
+	{
+		if (packet.getReadableBytes() >= 128)
+		{
+			packet.readD();
+			packet.readB(_raw, 0, _raw.length);
+			return true;
+		}
+		return false;
+	}
+	
+	@Override
+	public void run(LoginClient client)
+	{
+		if (!Config.ENABLE_CMD_LINE_LOGIN)
+		{
+			return;
+		}
+		
+		byte[] decrypted = new byte[128];
+		try
+		{
+			final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
+			rsaCipher.init(Cipher.DECRYPT_MODE, client.getScrambledKeyPair().getPrivateKey());
+			rsaCipher.doFinal(_raw, 0, 128, decrypted, 0);
+		}
+		catch (GeneralSecurityException e)
+		{
+			LOGGER.log(Level.INFO, "", e);
+			return;
+		}
+		
+		try
+		{
+			_user = new String(decrypted, 0x40, 14).trim();
+			_password = new String(decrypted, 0x60, 16).trim();
+		}
+		catch (Exception e)
+		{
+			LOGGER.log(Level.WARNING, "", e);
+			return;
+		}
+		
+		final InetAddress clientAddr = client.getConnectionAddress();
+		final LoginController lc = LoginController.getInstance();
+		final AccountInfo info = lc.retriveAccountInfo(clientAddr, _user, _password);
+		if (info == null)
+		{
+			// user or pass wrong
+			// client.close(LoginFailReason.REASON_SYSTEM_ERROR);
+			// above message crashes client
+			// REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT seems ok as well
+			client.close(LoginFailReason.REASON_ACCESS_FAILED);
+			return;
+		}
+		
+		final AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info);
+		switch (result)
+		{
+			case AUTH_SUCCESS:
+			{
+				client.setAccount(info.getLogin());
+				client.setConnectionState(ConnectionState.AUTHED_LOGIN);
+				client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client));
+				lc.getCharactersOnAccount(info.getLogin());
+				if (Config.SHOW_LICENCE)
+				{
+					client.sendPacket(new LoginOk(client.getSessionKey()));
+				}
+				else
+				{
+					client.sendPacket(new ServerList(client));
+				}
+				break;
+			}
+			case INVALID_PASSWORD:
+			{
+				client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG);
+				break;
+			}
+			case ACCOUNT_BANNED:
+			{
+				client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED));
+				return;
+			}
+			case ALREADY_ON_LS:
+			{
+				final LoginClient oldClient = lc.getAuthedClient(info.getLogin());
+				if (oldClient != null)
+				{
+					// kick the other client
+					oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					lc.removeAuthedLoginClient(info.getLogin());
+				}
+				// kick also current client
+				client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+				break;
+			}
+			case ALREADY_ON_GS:
+			{
+				final GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin());
+				if (gsi != null)
+				{
+					client.close(LoginFailReason.REASON_ACCOUNT_IN_USE);
+					// kick from there
+					if (gsi.isAuthed())
+					{
+						gsi.getGameServerThread().kickPlayer(info.getLogin());
+					}
+				}
+				break;
+			}
+		}
+	}
+}