Source Code

以下のソースコードは自由に利用してもらって構いませんが,

ここからの引用であることの説明や,ブログや鯖HPに当サイトへのリンクを追加してもらえると管理人が喜びます.

なお,リクエストや構築に関する質問等は一切受け付けていません.自力でサーバーを構築,運営している前提で掲載しています.

コンテンツはタイトルをクリックすることで展開できます.

死亡後の切断防止 ロウソクバグの修正 アカウント生成数の制限機能 永久遮断機能 悪質プレイヤーキック機能

死亡後にリスタート場合,10分で切断する問題の修正

変更が必要なファイル

				C_Restart.java
			

C_Restart.javaを以下のように修正する.

				C_Restart.javaのC_Restart()内に以下の行を追加すれば良い.
				pc.stopPcDeleteTimer();//deletetimer
			

ロウソクバグの修正

変更が必要なファイル

				[DB]characters
				L1Character.java
				MySqlCharacterStorage.java
				CharacterTable.java
				C_CharReset.java
				C_LoginToServer.java
			

この修正によって,正しく再振り処理が完了していないPCは,正しく再振り処理が完了するまでリスタートを行っても再振り画面に戻される. また,再振り処理を再開したPCはログに記録されるようにしたので,追跡調査に利用できる.修正手順を以下に示す.

L1Character.javaを以下のように修正し,再振りフラグをキャラクターに持たせる.

				L1Character()メソッドの直後に以下のソースを追加する
				
			 	private boolean _initStats;
			 	
				public boolean getInitStats(){
					return _initStats;
				}
				
				public void setInitStats(boolean b){
					_initStats = b;
				}
			

本来,charactersテーブルにこれ以上カラムを増やすべきではないが,今回の修正ではcharactersテーブルに以下のSQLを実行し,カラムを追加する. また,DB側では01でフラグを管理しているが,JavaではBooleanで管理していることに注意する.

				ALTER TABLE characters
				ADD COLUMN `is_init` INT(3) DEFAULT 0 NOT NULL AFTER BonusStatus
			

MySqlCharacterStorage.javaを以下のように修正する.

				load()
					SQLに is_init=? を追加する.
					pc.setInitStats(rs.getBoolean("is_init")); を追加する (BonusStatusの直後)
				create()
					SQLに is_init=? を追加する.
					pstm.setBoolean(++i, false); を追加する (BonusStatusの直後)
				store()
					SQLに is_init=? を追加する.
					pstm.setBoolean(++i, pc.getInitStats()); を追加する (BonusStatusの直後)
			

CharacterTable.javaを以下のように修正し,DBの再振りフラグを更新する.

				以下のメソッドを追加する.
 	
			 	public static void saveInitStatus(L1PcInstance pc, boolean b) {

					pc.setInitStats(b);

					Connection con = null;
					PreparedStatement pstm = null;
					try {
						con = L1DatabaseFactory.getInstance().getConnection();
						pstm = con
								.prepareStatement("UPDATE characters SET is_init= ?"
										+ " WHERE objid=?");
						pstm.setBoolean(1, b);
						pstm.setInt(2, pc.getId());
						pstm.execute();
					} catch (Exception e) {
						_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
					} finally {
						SQLUtil.close(pstm);
						SQLUtil.close(con);
					}
				}

			

C_CharReset.javaを以下のように修正し,再振りフラグを更新する.

				各ステージに以下のソースを追加する.

			 	stage01
			 		CharacterTable.getInstance().saveCharStatus(pc);//*既存ソース

					if(!pc.getInitStats()){
						CharacterTable.getInstance().saveInitStatus(pc, true);
					}
						
				stage02,stage03のSaveNewCharStatus(pc)の直後
					CharacterTable.getInstance().saveInitStatus(pc, false);
			

C_LoginToServer.javaを以下のように修正し,再振りフラグがtrue(処理未完了)の場合,再振り画面に飛ばすように変更する.ログの追加もここで行う.

				C_LoginToServer()の一番最後に以下の処理を追加(getHellTime()>0の直後)
				キャンセ処理はいらないかもしれない.

				if(pc.getInitStats()){
					_log.info("char=" + charName + " が希望のロウソク処理を再開しています.");

					L1SkillUse l1skilluse = new L1SkillUse();
					l1skilluse.handleCommands(pc, CANCELLATION,
							pc.getId(), pc.getX(), pc.getY(), null, 0,
							L1SkillUse.TYPE_LOGIN);
					pc.getInventory().takeoffEquip(945);
					L1Teleport.teleport(pc, 32737, 32789, (short) 997, 4, false);
					int initStatusPoint = 75 + pc.getElixirStats() + pc.getbElixirStats();
					int pcStatusPoint = pc.getBaseStr() + pc.getBaseInt()
							+ pc.getBaseWis() + pc.getBaseDex() + pc.getBaseCon()
							+ pc.getBaseCha();
					if (pc.getLevel() > 50) {
						pcStatusPoint += (pc.getLevel() - 50 - pc.getBonusStats());
					}
					int diff = pcStatusPoint - initStatusPoint;

					int maxLevel = 1;

					if (diff > 0) {
						maxLevel = Math.min(50 + diff, 99);
					} else {
						maxLevel = pc.getLevel();
					}

					pc.setTempMaxLevel(maxLevel);
					pc.setTempLevel(1);
					pc.setInCharReset(true);
					pc.sendPackets(new S_CharReset(pc));
				}
			

同一IPからのアカウント生成数を制限する

変更が必要なファイル(server.properties,Config.java,GameServer.javaについては,Java内で直接指定するのであれば必要ない)

				server.properties
				Config.java
				GameServer.java
				IpTable.java
				C_AuthLogin.java
			

荒らしの特徴として,アカウントの大量生成が挙げられる(ログインアタックが主目的と考えられる). これを行われると単純に管理の手間が増えるだけでなく,アカウントの生成はDBにINSERTするので鯖への負荷が高くなる.そこで,同一IPからのアカウント生成数に上限を設定する,

server.propertiesに以下の行を追記する.上限数を変更する場合はこの値を変えれば良い.

				#同一IPから作成可能なアカウント数の条件
				EnableCreateAccount = 3
			

Config.javaに以下の修正を適用する.

				変数の追加
				public static int ENABLE_ACCOUNT_NUM;
			
				load()
					server.propertiesのtry内に読み込み部分を追加
					ENABLE_ACCOUNT_NUM = Integer.parseInt(serverSettings
					.getProperty("EnableCreateAccount",
							"3"));
			

GameServer.javaに以下の行を追加する.(鯖起動時にコンソールで設定値を確認したい場合)

				//System.out.println("=================================================");
				//System.out.println("                                  For All User...");
				System.out.println("=================================================");

				int maxOnlineUsers = Config.MAX_ONLINE_USERS;
				System.out.println("接続人数制限: 最大" + (maxOnlineUsers) + "人");
				//		ここから
				System.out.println("アカウント生成上限数:" + Config.ENABLE_ACCOUNT_NUM);
				//		ここまで
			

IpTable.javaに,接続IPから作成されたアカウント数を返すメソッドを追加する.

				/**
				 * 		接続IPのアカウント生成数を返す
				 */
				public int getAccountNumFromIp(String ip) {
					int accountCount = 0;
					Connection con = null;
					PreparedStatement pstm = null;
					ResultSet rs = null;
					try {
						con = L1DatabaseFactory.getInstance().getConnection();
						pstm = con
								.prepareStatement("SELECT count(ip) FROM accounts WHERE ip=? GROUP BY ip");
						pstm.setString(1, ip);
						rs = pstm.executeQuery();
						if (rs.next()) {
							accountCount = Integer.valueOf(rs.getInt(1)).intValue();
						}
					} catch (SQLException e) {
						_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
					} finally {
						SQLUtil.close(rs);
						SQLUtil.close(pstm);
						SQLUtil.close(con);
					}
					return accountCount;
				}
			

C_AuthLogin.javaを以下のように修正する.

				2PCのログイン可否処理の直後の処理を以下のように変更する.
				
				if (account == null) {//新規アカ作成
					//接続してきたIPからアカウント数をチェック
					if(IpTable.getInstance().getAccountNumFromIp(ip) < Config.ENABLE_ACCOUNT_NUM){//アカウントが上限数未満ならアカウント作成可
						if (Config.AUTO_CREATE_ACCOUNTS) {
							account = Account.create(accountName, password, ip, host);
						} else {
							_log.warning("account missing for user " + accountName);
						}
					} else {//既にアカウントを上限まで作成済みならキック
						return;
					}
				}
			

永久遮断機能

変更が必要なファイル

				[DB]char_excludinglistテーブル作成
				L1ExcludingList.java
				C_LoginToServer.java
				C_Exclude.java
			

この修正によって,遮断リストはキャラクターごとに保存され,ログイン時に読み込まれるようになる.遮断方法は通常の遮断と変わらない.同じキャラクター名を入力すると解除となる.

以下のSQLを実行し,char_excludinglistテーブルを作成する.

				DROP TABLE IF EXISTS `char_excludinglist`;
				CREATE TABLE `char_excludinglist` (
				  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
				  `char_id` int(10) unsigned NOT NULL DEFAULT '0',
				  `target_name` varchar(45) CHARACTER SET sjis NOT NULL DEFAULT '',
				  PRIMARY KEY (`id`)
				) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=cp932;
			

L1ExcludingList.javaにメソッド(コンストラクタ)を作成する.

				public void add(L1PcInstance pc, String name) {
					_nameList.add(name);

					Connection con = null;
					PreparedStatement pstm = null;
					try {
						int i = 0;

						con = L1DatabaseFactory.getInstance().getConnection();
						pstm = con.prepareStatement(
						"INSERT INTO char_excludinglist SET char_id = ?, target_name = ?"
						);

						pstm.setInt(++i, pc.getId());
						pstm.setString(++i, name);
						pstm.execute();

					} catch (SQLException e) {
						//_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
					} finally {
						SQLUtil.close(pstm);
						SQLUtil.close(con);
					}
				}
				
				
				public String remove(L1PcInstance pc, String name) {
					for (String each : _nameList) {
						if (each.equalsIgnoreCase(name)) {
							_nameList.remove(each);

							Connection con = null;
							PreparedStatement pstm = null;
							try {
								int i = 0;

								con = L1DatabaseFactory.getInstance().getConnection();
								pstm = con.prepareStatement(
								"DELETE FROM char_excludinglist WHERE char_id = ? AND target_name = ?"
								);

								pstm.setInt(++i, pc.getId());
								pstm.setString(++i, name);
								pstm.execute();

							} catch (SQLException e) {
								//_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
							} finally {
								SQLUtil.close(pstm);
								SQLUtil.close(con);
							}
							return each;
						}
					}
					return null;
				}
			

C_LoginToServer.javaを以下のように修正し,ログイン時に遮断リストを読み込むように変更する.

				bookmarks(pc)の直後に以下の行を追加する.
				excludinglist(pc);
			
				メソッドを追加する.
				private void excludinglist(L1PcInstance pc) {

					Connection con = null;
					PreparedStatement pstm = null;
					ResultSet rs = null;
					try {

						con = L1DatabaseFactory.getInstance().getConnection();
						pstm = con
								.prepareStatement("SELECT * FROM char_excludinglist WHERE char_id=? ORDER BY target_name ASC");
						pstm.setInt(1, pc.getId());

						rs = pstm.executeQuery();
						while (rs.next()) {
							L1ExcludingList list = pc.getExcludingList();
							if (list.isFull()) {
								return;
							}

							list.add(rs.getString("target_name"));
							pc.sendPackets(new S_PacketBox(S_PacketBox.ADD_EXCLUDE, rs.getString("target_name")));
						}

					} catch (SQLException e) {
						_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
					} finally {
						SQLUtil.close(rs);
						SQLUtil.close(pstm);
						SQLUtil.close(con);
					}
				}
			

C_Exclude.javaを以下のように修正し,遮断コマンド時のメソッド呼び出し先を作成したメソッドに変更する.

				if (exList.contains(name)) {
					String temp = exList.remove(pc,name);
					pc.sendPackets(new S_PacketBox(S_PacketBox.REM_EXCLUDE, temp));
				} else {
					exList.add(pc,name);
					pc.sendPackets(new S_PacketBox(S_PacketBox.ADD_EXCLUDE, name));
				}
			

悪質プレイヤーキック機能

変更が必要なファイル

				GameServer.java
			

どの鯖でも荒らし認定されている(神奈川県 光 itscom)を接続させない機能. BANIPで登録しても再起動でIPを変えてくるので,このプロバイダごと規制する. また,150人ほど接続のあった当サーバーで接続IPを全て照合したが,同じプロバイダからの接続は存在しなかった.  

 

GameServer.javaのrunメソッドを以下のように修正する.

				public void run() {
					System.out.println("利用メモリ: " + SystemUtil.getUsedMemoryMB() + "MB");
					//System.out.println("クライアント接続待機中...");
					while (true) {
						try {
							Socket socket = _serverSocket.accept();
							System.out.println("接続試行中IP " + socket.getInetAddress());
							String host = socket.getInetAddress().getHostAddress();
							if (IpTable.getInstance().isBannedIp(host)) {
								_log.info("banned IP(" + host + ")");
							} else if(host.startsWith("219.110.")) {
								_log.info("神奈川県,光,itscom IP(" + host + ")");
							} else {
								ClientThread client = new ClientThread(socket);
								GeneralThreadPool.getInstance().execute(client);
							}
						} catch (IOException ioexception) {
						}
					}
				}