📄 clientthread.java
字号:
/*
* 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 2, 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* http://www.gnu.org/copyleft/gpl.html
*/
package l1j.server.server;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import l1j.server.Config;
import l1j.server.server.datatables.CharBuffTable;
import l1j.server.server.encryptions.ClientIdExistsException;
import l1j.server.server.encryptions.LineageEncryption;
import l1j.server.server.encryptions.LineageKeys;
import l1j.server.server.model.Getback;
import l1j.server.server.model.L1Trade;
import l1j.server.server.model.L1World;
import l1j.server.server.model.Instance.L1DollInstance;
import l1j.server.server.model.Instance.L1FollowerInstance;
import l1j.server.server.model.Instance.L1PcInstance;
import l1j.server.server.model.Instance.L1PetInstance;
import l1j.server.server.model.Instance.L1SummonInstance;
import l1j.server.server.serverpackets.S_Disconnect;
import l1j.server.server.serverpackets.S_PacketBox;
import l1j.server.server.serverpackets.S_SummonPack;
import l1j.server.server.serverpackets.ServerBasePacket;
import l1j.server.server.types.UByte8;
import l1j.server.server.types.UChar8;
import l1j.server.server.utils.StreamUtil;
import l1j.server.server.utils.SystemUtil;
// Referenced classes of package l1j.server.server:
// PacketHandler, Logins, IpTable, LoginController,
// ClanTable, IdFactory
//
public class ClientThread implements Runnable, PacketOutput {
private static l1j.eric.EricLogger _log = l1j.eric.EricLogger.getLogger2(ClientThread.class.getName());
private InputStream _in;
private OutputStream _out;
private PacketHandler _handler;
private Account _account;
private L1PcInstance _activeChar;
private String _ip;
private String _hostname;
private Socket _csocket;
private int _loginStatus = 0;
// private static final byte[] FIRST_PACKET = { 10, 0, 38, 58, -37, 112, 46,
// 90, 120, 0 }; // for Episode5
// private static final byte[] FIRST_PACKET =
// { (byte) 0x12, (byte) 0x00, (byte) 0x14, (byte) 0x1D,
// (byte) 0x82,
// (byte) 0xF1,
// (byte) 0x0C, // = 0x0cf1821d
// (byte) 0x87, (byte) 0x7D, (byte) 0x75, (byte) 0x7D,
// (byte) 0xA1, (byte) 0x3B, (byte) 0x62, (byte) 0x2C,
// (byte) 0x5E, (byte) 0x3E, (byte) 0x9F }; // for Episode 6
// private static final byte[] FIRST_PACKET = { 2.70C
// (byte) 0xb1, (byte) 0x3c, (byte) 0x2c, (byte) 0x28,
// (byte) 0xf6, (byte) 0x65, (byte) 0x1d, (byte) 0xdd,
// (byte) 0x56, (byte) 0xe3, (byte) 0xef };
private static final byte[] FIRST_PACKET = { // 3.0
(byte) 0xec, (byte) 0x64, (byte) 0x3e, (byte) 0x0d,
(byte) 0xc0, (byte) 0x82, (byte) 0x00, (byte) 0x00,
(byte) 0x02, (byte) 0x08, (byte) 0x00 };
/**
* for Test
*/
protected ClientThread() {}
public ClientThread(Socket socket) throws IOException {
_csocket = socket;
_ip = socket.getInetAddress().getHostAddress();
if (Config.HOSTNAME_LOOKUPS) {
_hostname = socket.getInetAddress().getHostName();
} else {
_hostname = _ip;
}
_in = socket.getInputStream();
_out = new BufferedOutputStream(socket.getOutputStream());
// PacketHandler 初期設定
_handler = new PacketHandler(this);
}
public String getIp() {
return _ip;
}
public String getHostname() {
return _hostname;
}
// ClientThreadによる一定間隔自動セーブを制限する為のフラグ(true:制限 false:制限無し)
// 現在はC_LoginToServerが実行された際にfalseとなり、
// C_NewCharSelectが実行された際にtrueとなる
private boolean _charRestart = true;
public void CharReStart(boolean flag) {
_charRestart = flag;
}
private LineageKeys _clkey;
private byte[] readPacket() throws Exception {
try {
int hiByte = _in.read();
int loByte = _in.read();
if (loByte < 0) {
throw new RuntimeException();
}
int dataLength = (loByte * 256 + hiByte) - 2;
byte data[] = new byte[dataLength];
int readSize = 0;
for (int i = 0; i != -1 && readSize < dataLength; readSize += i) {
i = _in.read(data, readSize, dataLength - readSize);
}
if (readSize != dataLength) {
_log
.warning("Incomplete Packet is sent to the server, closing connection.");
throw new RuntimeException();
}
return LineageEncryption.decrypt(data, dataLength, _clkey);
} catch (IOException e) {
throw e;
}
}
private long _lastSavedTime = System.currentTimeMillis();
private long _lastSavedTime_inventory = System.currentTimeMillis();
private void doAutoSave() throws Exception {
if (_activeChar == null || _charRestart) {
return;
}
try {
// キャラクター情報
if (Config.AUTOSAVE_INTERVAL * 1000
< System.currentTimeMillis() - _lastSavedTime) {
_activeChar.save();
_lastSavedTime = System.currentTimeMillis();
}
// 所持アイテム情報
if (Config.AUTOSAVE_INTERVAL_INVENTORY * 1000
< System.currentTimeMillis() - _lastSavedTime_inventory) {
_activeChar.saveInventory();
_lastSavedTime_inventory = System.currentTimeMillis();
}
} catch (Exception e) {
_log.warning("Client autosave failure.");
_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
throw e;
}
}
@Override
public void run() {
_log.info("來自於 (" + _hostname + ") 的使用者開始連線。");
l1j.eric.gui.J_Main.getInstance().addConsolPost("使用記憶體: " + SystemUtil.getUsedMemoryMB() + "MB");
l1j.eric.gui.J_Main.getInstance().addConsolPost("等待玩家連線...");
Socket socket = _csocket;
/*
* クライアントからのパケットをある程度制限する。 理由:不正の誤検出が多発する恐れがあるため
* ex1.サーバに過負荷が掛かっている場合、負荷が落ちたときにクライアントパケットを一気に処理し、結果的に不正扱いとなる。
* ex2.サーバ側のネットワーク(下り)にラグがある場合、クライアントパケットが一気に流れ込み、結果的に不正扱いとなる。
* ex3.クライアント側のネットワーク(上り)にラグがある場合、以下同様。
*
* 無制限にする前に不正検出方法を見直す必要がある。
*/
HcPacket movePacket = new HcPacket(M_CAPACITY);
HcPacket hcPacket = new HcPacket(H_CAPACITY);
GeneralThreadPool.getInstance().execute(movePacket);
GeneralThreadPool.getInstance().execute(hcPacket);
ClientThreadObserver observer =
new ClientThreadObserver(Config.AUTOMATIC_KICK * 60 * 1000); // 自動切断までの時間(単位:ms)
// クライアントスレッドの監視
if (Config.AUTOMATIC_KICK > 0) {
observer.start();
}
try {
// long seed = 0x5cc690ecL; // 2.70C
long seed = 0x7c98bdfa; // 3.0
byte Bogus = (byte)(FIRST_PACKET.length + 7);
_out.write(Bogus & 0xFF);
_out.write(Bogus >> 8 & 0xFF);
// _out.write(0x20); // 2.70C
_out.write(0x7d); // 3.0
_out.write((byte)(seed & 0xFF));
_out.write((byte)(seed >> 8 & 0xFF));
_out.write((byte)(seed >> 16 & 0xFF));
_out.write((byte)(seed >> 24 & 0xFF));
_out.write(FIRST_PACKET);
_out.flush();
try {
// long seed = 0x2e70db3aL; // for Episode5
// long seed = 0x0cf1821dL; // for Episode6
_clkey = LineageEncryption.initKeys(socket, seed);
} catch (ClientIdExistsException e) {}
while (true) {
doAutoSave();
byte data[] = null;
try {
data = readPacket();
} catch (Exception e) {
break;
}
// _log.finest("[C]\n" + new
// ByteArrayUtil(data).dumpToString());
int opcode = data[0] & 0xFF;
//L1World.getInstance().broadcastServerMessage("OPCODE="+opcode);
// 多重ログイン対策
if (opcode == Opcodes.C_OPCODE_COMMONCLICK
|| opcode == Opcodes.C_OPCODE_CHANGECHAR) {
_loginStatus = 1;
}
if (opcode == Opcodes.C_OPCODE_LOGINTOSERVER) {
if (_loginStatus != 1) {
continue;
}
}
if (opcode == Opcodes.C_OPCODE_LOGINTOSERVEROK
|| opcode == Opcodes.C_OPCODE_RETURNTOLOGIN) {
_loginStatus = 0;
}
if (opcode != Opcodes.C_OPCODE_KEEPALIVE) {
// C_OPCODE_KEEPALIVE以外の何かしらのパケットを受け取ったらObserverへ通知
observer.packetReceived();
}
// nullの場合はキャラクター選択前なのでOpcodeの取捨選択はせず全て実行
if (_activeChar == null) {
_handler.handlePacket(data, _activeChar);
continue;
}
// 以降、PacketHandlerの処理状況がClientThreadに影響を与えないようにする為の処理
// 目的はOpcodeの取捨選択とClientThreadとPacketHandlerの切り離し
// 破棄してはいけないOpecode群
// リスタート、アイテムドロップ、アイテム削除
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -