/* OrpheusMS: MapleStory Private Server based on OdinMS Copyright (C) 2012 Aaron Weiss <aaron@deviant-core.net> Patrick Huy <patrick.huy@frz.cc> Matthias Butz <matze@odinms.de> Jan Christian Meyer <vimes@odinms.de> This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.server; import client.MapleCharacter; import client.SkillFactory; import client.command.external.CommandLoader; import constants.ParanoiaConstants; import constants.ServerConstants; import gm.GMPacketCreator; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import net.MaplePacket; import tools.DatabaseConnection; import net.MapleServerHandler; import net.PacketProcessor; import net.mina.MapleCodecFactory; import net.server.guild.MapleAlliance; import net.server.guild.MapleGuild; import net.server.guild.MapleGuildCharacter; import gm.server.GMServer; import server.TimerManager; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.buffer.SimpleBufferAllocator; import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import paranoia.BlacklistHandler; import server.CashShop.CashItemFactory; import server.MapleItemInformationProvider; import server.MapleStocks; import server.WorldRecommendation; import tools.MapleLogger; import tools.MaplePacketCreator; import tools.Output; public class Server implements Runnable { private IoAcceptor acceptor; private List<Map<Byte, String>> channels = new LinkedList<Map<Byte, String>>(); private List<World> worlds = new ArrayList<World>(); private Properties subnetInfo = new Properties(); private static Server instance = null; private ArrayList<Map<Byte, AtomicInteger>> load = new ArrayList<Map<Byte, AtomicInteger>>(); private List<WorldRecommendation> worldRecommendedList = new LinkedList<WorldRecommendation>(); private Map<Integer, MapleGuild> guilds = new LinkedHashMap<Integer, MapleGuild>(); private PlayerBuffStorage buffStorage = new PlayerBuffStorage(); private Map<Integer, MapleAlliance> alliances = new LinkedHashMap<Integer, MapleAlliance>(); private boolean online = false; private boolean gmServerEnabled = false; private boolean debugMode = false; public static Server getInstance() { if (instance == null) { instance = new Server(); } return instance; } public boolean isOnline() { return online; } public List<WorldRecommendation> worldRecommendedList() { return worldRecommendedList; } public void removeChannel(byte worldid, byte channel) { channels.remove(channel); if (load.contains(worldid)) { load.get(worldid).remove(channel); } World world = worlds.get(worldid); if (world != null) { world.removeChannel(channel); } } public Channel getChannel(byte world, byte channel) { return worlds.get(world).getChannel(channel); } public List<Channel> getChannelsFromWorld(byte world) { return worlds.get(world).getChannels(); } public List<Channel> getAllChannels() { List<Channel> channelz = new ArrayList<Channel>(); for (World world : worlds) { for (Channel ch : world.getChannels()) { channelz.add(ch); } } return channelz; } public String getIP(byte world, byte channel) { return channels.get(world).get(channel); } @SuppressWarnings("unused") @Override public void run() { long loadingStartTime = System.currentTimeMillis(); if (ServerConstants.CLEAR_ERROR_LOGS_ON_BOOT) { if (new File(MapleLogger.ACCOUNT_STUCK).exists()) MapleLogger.clearLog(MapleLogger.ACCOUNT_STUCK); if (new File(MapleLogger.EXCEPTION_CAUGHT).exists()) MapleLogger.clearLog(MapleLogger.EXCEPTION_CAUGHT); } if (ParanoiaConstants.CLEAR_LOGS_ON_STARTUP) { if (ParanoiaConstants.PARANOIA_CONSOLE_LOGGER) MapleLogger.clearLog(MapleLogger.PARANOIA_CONSOLE); if (ParanoiaConstants.PARANOIA_CHAT_LOGGER) MapleLogger.clearLog(MapleLogger.PARANOIA_CHAT); if (ParanoiaConstants.PARANOIA_COMMAND_LOGGER) MapleLogger.clearLog(MapleLogger.PARANOIA_COMMAND); } Properties p = new Properties(); try { p.load(new FileInputStream("orpheus.ini")); Output.print("Configuration loaded."); } catch (Exception e) { Output.print("Missing configuration file, please run mksrv script to generate one."); System.exit(0); } if (!ServerConstants.DB_USE_COMPILED_VALUES) { DatabaseConnection.update("jdbc:mysql://" + p.getProperty("mysql_host") + ":" + p.getProperty("mysql_port") + "/Orpheus?autoReconnect=true", p.getProperty("mysql_user"), p.getProperty("mysql_pass")); } Runtime.getRuntime().addShutdownHook(new Thread(shutdown(false))); DatabaseConnection.getConnection(); Output.print("Database connection established."); IoBuffer.setUseDirectBuffer(false); IoBuffer.setAllocator(new SimpleBufferAllocator()); acceptor = new NioSocketAcceptor(); acceptor.getFilterChain().addLast("codec", (IoFilter) new ProtocolCodecFilter(new MapleCodecFactory())); TimerManager tMan = TimerManager.getInstance(); tMan.start(); tMan.register(tMan.purge(), 300000);// Purging ftw... boolean bindRankings = true; String[] events = ServerConstants.EVENTS.split(" "); for (int i = 0; i < events.length; i++) { if (events[i].equalsIgnoreCase("rankings")) { bindRankings = false; } } if (bindRankings) tMan.register(new RankingWorker(), ServerConstants.RANKING_INTERVAL); try { gmServerEnabled = Boolean.parseBoolean(p.getProperty("gmserver")); debugMode = Boolean.parseBoolean(p.getProperty("debug")); for (byte i = 0; i < Byte.parseByte(p.getProperty("worlds")); i++) { long startTime = System.currentTimeMillis(); Output.print("Server is loading world" + i + "."); World world = new World(i, Byte.parseByte(p.getProperty("flag" + i)), p.getProperty("eventmessage" + i), Byte.parseByte(p.getProperty("exprate" + i)), Byte.parseByte(p.getProperty("droprate" + i)), Byte.parseByte(p.getProperty("mesorate" + i)), Byte.parseByte(p.getProperty("bossdroprate" + i)));// ohlol worldRecommendedList.add(new WorldRecommendation(i, p.getProperty("recommendmessage" + i))); worlds.add(world); channels.add(new LinkedHashMap<Byte, String>()); load.add(new LinkedHashMap<Byte, AtomicInteger>()); for (byte j = 0; j < Byte.parseByte(p.getProperty("channels" + i)); j++) { byte channelid = (byte) (j + 1); Channel channel = new Channel(i, channelid); world.addChannel(channel); channels.get(i).put(channelid, channel.getIP()); load.get(i).put(channelid, new AtomicInteger()); } world.setServerMessage(p.getProperty("servermessage" + i)); Output.print("Loading completed in " + ((System.currentTimeMillis() - startTime)) + "ms."); } } catch (Exception e) { Output.print("Corrupted configuration file, please run mksrv script to generate a new one."); e.printStackTrace(); System.exit(0); } acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30); acceptor.setHandler(new MapleServerHandler(PacketProcessor.getProcessor())); try { acceptor.bind(new InetSocketAddress(8484)); } catch (IOException ex) { } Output.print("Login Server: Listening on port 8484."); long startTime; // Used to time loading phases. Output.print("Loading skills."); startTime = System.currentTimeMillis(); SkillFactory.loadAllSkills(); Output.print("Loading completed in " + ((System.currentTimeMillis() - startTime)) + "ms."); Output.print("Loading items."); startTime = System.currentTimeMillis(); CashItemFactory.getSpecialCashItems();// just load who cares o.o MapleItemInformationProvider.getInstance().getAllItems(); Output.print("Loading completed in " + ((System.currentTimeMillis() - startTime)) + "ms."); if (isGMServerEnabled()) { GMServer.getInstance(); } if (ParanoiaConstants.ENABLE_BLACKLISTING && ParanoiaConstants.LOAD_BLACKLIST_ON_STARTUP) { Output.print("Loading Paranoia blacklist."); startTime = System.currentTimeMillis(); BlacklistHandler.getBlacklistedAccountIds(); Output.print("Loading completed in " + ((System.currentTimeMillis() - startTime)) + "ms."); } if (ServerConstants.LOAD_STOCKS_ON_BOOT && ServerConstants.USE_MAPLE_STOCKS) { Output.print("Loading MapleStocks."); startTime = System.currentTimeMillis(); MapleStocks.getInstance(false).loadAll(); Output.print("Loading completed in " + ((System.currentTimeMillis() - startTime)) + "ms."); } if (ServerConstants.LOAD_COMMANDS_ON_BOOT && ServerConstants.USE_EXTERNAL_COMMAND_LOADER) { try { Output.print("Loading commands."); startTime = System.currentTimeMillis(); CommandLoader.getInstance().load(ServerConstants.COMMAND_JAR_PATH); Output.print("Loading completed in " + ((System.currentTimeMillis() - startTime)) + "ms."); } catch (Exception e) { Output.print("Failed to load commands."); MapleLogger.print(MapleLogger.EXCEPTION_CAUGHT, e); } } Output.print("Server is now online! (Took " + ((System.currentTimeMillis() - loadingStartTime)) + "ms)"); online = true; } public void shutdown() { try { worlds.clear(); worlds = null; channels.clear(); channels = null; worldRecommendedList.clear(); worldRecommendedList = null; load.clear(); load = null; TimerManager.getInstance().stop(); acceptor.unbind(); Output.print("Server is now offline."); } catch (NullPointerException e) { // We're already off. Let's get out of here... } System.exit(0);// BOEIEND :D } public static void main(String args[]) { Server.getInstance().run(); } public boolean isGMServerEnabled() { return gmServerEnabled; } public boolean isDebugging() { return debugMode; } public Properties getSubnetInfo() { return subnetInfo; } public Map<Byte, AtomicInteger> getLoad(byte i) { return load.get(i); } public List<Map<Byte, AtomicInteger>> getLoad() { return load; } public MapleAlliance getAlliance(int id) { synchronized (alliances) { if (alliances.containsKey(id)) { return alliances.get(id); } return null; } } public void addAlliance(int id, MapleAlliance alliance) { synchronized (alliances) { if (!alliances.containsKey(id)) { alliances.put(id, alliance); } } } public void disbandAlliance(int id) { synchronized (alliances) { MapleAlliance alliance = alliances.get(id); if (alliance != null) { for (Integer gid : alliance.getGuilds()) { guilds.get(gid).setAllianceId(0); } alliances.remove(id); } } } public void allianceMessage(int id, MaplePacket packet, int exception, int guildex) { MapleAlliance alliance = alliances.get(id); if (alliance != null) { for (Integer gid : alliance.getGuilds()) { if (guildex == gid) { continue; } MapleGuild guild = guilds.get(gid); if (guild != null) { guild.broadcast(packet, exception); } } } } public boolean addGuildtoAlliance(int aId, int guildId) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.addGuild(guildId); return true; } return false; } public boolean removeGuildFromAlliance(int aId, int guildId) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.removeGuild(guildId); return true; } return false; } public boolean setAllianceRanks(int aId, String[] ranks) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.setRankTitle(ranks); return true; } return false; } public boolean setAllianceNotice(int aId, String notice) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.setNotice(notice); return true; } return false; } public boolean increaseAllianceCapacity(int aId, int inc) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.increaseCapacity(inc); return true; } return false; } public Set<Byte> getChannelServer(byte world) { return new HashSet<Byte>(channels.get(world).keySet()); } public byte getHighestChannelId() { byte highest = 0; for (Byte channel : channels.get(0).keySet()) { if (channel != null && channel.intValue() > highest) { highest = channel.byteValue(); } } return highest; } public int createGuild(int leaderId, String name) { return MapleGuild.createGuild(leaderId, name); } public MapleGuild getGuild(int id, MapleGuildCharacter mgc) { synchronized (guilds) { if (guilds.get(id) != null) { return guilds.get(id); } if (mgc == null) { return null; } MapleGuild g = new MapleGuild(mgc); if (g.getId() == -1) { return null; } guilds.put(id, g); return g; } } public void clearGuilds() {// remake synchronized (guilds) { guilds.clear(); } // for (List<Channel> world : worlds.values()) { // reloadGuildCharacters(); } public void setGuildMemberOnline(MapleGuildCharacter mgc, boolean bOnline, byte channel) { MapleGuild g = getGuild(mgc.getGuildId(), mgc); g.setOnline(mgc.getId(), bOnline, channel); } public int addGuildMember(MapleGuildCharacter mgc) { MapleGuild g = guilds.get(mgc.getGuildId()); if (g != null) { return g.addGuildMember(mgc); } return 0; } public boolean setGuildAllianceId(int gId, int aId) { MapleGuild guild = guilds.get(gId); if (guild != null) { guild.setAllianceId(aId); return true; } return false; } public void leaveGuild(MapleGuildCharacter mgc) { MapleGuild g = guilds.get(mgc.getGuildId()); if (g != null) { g.leaveGuild(mgc); } } public void guildChat(int gid, String name, int cid, String msg) { MapleGuild g = guilds.get(gid); if (g != null) { g.guildChat(name, cid, msg); } } public void changeRank(int gid, int cid, int newRank) { MapleGuild g = guilds.get(gid); if (g != null) { g.changeRank(cid, newRank); } } public void expelMember(MapleGuildCharacter initiator, String name, int cid) { MapleGuild g = guilds.get(initiator.getGuildId()); if (g != null) { g.expelMember(initiator, name, cid); } } public void setGuildNotice(int gid, String notice) { MapleGuild g = guilds.get(gid); if (g != null) { g.setGuildNotice(notice); } } public void memberLevelJobUpdate(MapleGuildCharacter mgc) { MapleGuild g = guilds.get(mgc.getGuildId()); if (g != null) { g.memberLevelJobUpdate(mgc); } } public void changeRankTitle(int gid, String[] ranks) { MapleGuild g = guilds.get(gid); if (g != null) { g.changeRankTitle(ranks); } } public void setGuildEmblem(int gid, short bg, byte bgcolor, short logo, byte logocolor) { MapleGuild g = guilds.get(gid); if (g != null) { g.setGuildEmblem(bg, bgcolor, logo, logocolor); } } public void disbandGuild(int gid) { synchronized (guilds) { MapleGuild g = guilds.get(gid); g.disbandGuild(); guilds.remove(gid); } } public boolean increaseGuildCapacity(int gid) { MapleGuild g = guilds.get(gid); if (g != null) { return g.increaseCapacity(); } return false; } public void gainGP(int gid, int amount) { MapleGuild g = guilds.get(gid); if (g != null) { g.gainGP(amount); } } public PlayerBuffStorage getPlayerBuffStorage() { return buffStorage; } public void deleteGuildCharacter(MapleGuildCharacter mgc) { setGuildMemberOnline(mgc, false, (byte) -1); if (mgc.getGuildRank() > 1) { leaveGuild(mgc); } else { disbandGuild(mgc.getGuildId()); } } public void reloadGuildCharacters(byte world) { World worlda = getWorld(world); for (MapleCharacter mc : worlda.getPlayerStorage().getAllCharacters()) { if (mc.getGuildId() > 0) { setGuildMemberOnline(mc.getMGC(), true, worlda.getId()); memberLevelJobUpdate(mc.getMGC()); } } worlda.reloadGuildSummary(); } public void broadcastMessage(byte world, MaplePacket packet) { for (Channel ch : getChannelsFromWorld(world)) { ch.broadcastPacket(packet); } } public World getWorld(int id) { return worlds.get(id); } public List<World> getWorlds() { return worlds; } public void gmChat(String message, String exclude) { GMServer server = GMServer.getInstance(); server.broadcastInGame(MaplePacketCreator.serverNotice(6, message)); server.broadcastOutGame(GMPacketCreator.chat(message), exclude); } public final Runnable shutdown(final boolean restart) { // only once :D return new Runnable() { @Override public void run() { Output.printNewLine(); Output.print("The server is now " + (restart ? "restarting." : "shutting down.")); for (World w : getWorlds()) { w.shutdown(); } for (World w : getWorlds()) { while (w.getPlayerStorage().getAllCharacters().size() > 0) { try { Thread.sleep(1000); } catch (InterruptedException ie) { // Well, shit. w.getPlayerStorage().disconnectAll(); // try to save us. } } } for (Channel ch : getAllChannels()) { while (ch.getConnectedClients() > 0) { try { Thread.sleep(1000); } catch (InterruptedException ie) { // Well, shit. ch.getPlayerStorage().disconnectAll(); // try to save us. } } } TimerManager.getInstance().purge(); TimerManager.getInstance().stop(); for (Channel ch : getAllChannels()) { while (!ch.finishedShutdown()) { try { Thread.sleep(1000); } catch (InterruptedException ie) { // Well, damn. ch.shutdown(); // try to save us. } } } worlds.clear(); worlds = null; channels.clear(); channels = null; worldRecommendedList.clear(); worldRecommendedList = null; load.clear(); load = null; Output.print("Server is now offline."); acceptor.unbind(); acceptor = null; if (!restart) { shutdown(); System.exit(0); } else { Output.print("\r\nThe server is now restarting."); try { instance.finalize();// FUU I CAN AND IT'S FREE } catch (Throwable ex) { } instance = null; System.gc(); getInstance().run();// DID I DO EVERYTHING?! D: } } }; } }