Login bypass + MapleQuestlineFetcher
Solved an exploit where anyone (via packet editing) could be able to login as any registered character after authenticating and selecting a character. New tool: MapleQuestlineFetcher. It reports ids from quests which quest script files were not found on the scripts folder.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -55,6 +55,10 @@
|
||||
/tools/MapleQuestItemFetcher/dist/
|
||||
/tools/MapleQuestItemFetcher/nbproject/
|
||||
|
||||
/tools/MapleQuestlineFetcher/build/
|
||||
/tools/MapleQuestlineFetcher/dist/
|
||||
/tools/MapleQuestlineFetcher/nbproject/
|
||||
|
||||
/tools/MapleQuestMesoFetcher/build/
|
||||
/tools/MapleQuestMesoFetcher/dist/
|
||||
/tools/MapleQuestMesoFetcher/nbproject/
|
||||
|
||||
10
README.md
10
README.md
@@ -64,16 +64,6 @@ Now install the Java 7 Development Kit:
|
||||
* jdk-7u79-windows-x64.exe
|
||||
* netbeans-8.0.2-javase-windows.exe -> It's a NetBeans project, use other IDE at your own risk.
|
||||
|
||||
Overwrite whenever prompted with the JAR files under "jce_policy-7/UnlimitedJCEPolicy" in these Java folders:
|
||||
|
||||
* C:\Program Files\Java\jre7\lib
|
||||
* C:\Program Files\Java\jre7\lib\ext
|
||||
* C:\Program Files\Java\jre7\lib\security
|
||||
* C:\Program Files\Java\jdk1.7.0_01\lib
|
||||
* C:\Program Files\Java\jdk1.7.0_01\jre\lib
|
||||
* C:\Program Files\Java\jdk1.7.0_01\jre\lib\ext
|
||||
* C:\Program Files\Java\jdk1.7.0_01\jre\lib\security
|
||||
|
||||
Now that the tools have been installed, test if they are working.
|
||||
|
||||
For WampServer:
|
||||
|
||||
@@ -126,6 +126,7 @@ External tools:
|
||||
* MapleMesoFetcher - Creates meso drop data for mobs with more than 4 items (thus overworld mobs), calculations based on mob level and whether it's a boss or not.
|
||||
* MapleMobBookIndexer - Generates a SQL table with all relations of cardid and mobid present in the mob book.
|
||||
* MapleMobBookUpdate - Generates a wz.xml that is a copy of the original MonsterBook.wz.xml, except it updates the drop data info in the book with those currently on DB.
|
||||
* MapleQuestlineFetcher - Searches the quest WZ files and reports in all questids that currently doesn't have script files.
|
||||
* MapleQuestItemCountFetcher - Searches the quest WZ files and reports in all relevant data regarding missing "count" labels on item acts at "complete quest".
|
||||
* MapleQuestItemFetcher - Searches the SQL tables and project files and reports in all relevant data regarding missing/erroneous quest items.
|
||||
* MapleQuestMesoFetcher - Searches the quest WZ files and reports in all relevant data regarding missing/erroneous quest fee checks.
|
||||
@@ -141,6 +142,12 @@ Project:
|
||||
* Heavily reviewed future task management inside the project. Way less trivial schedules are spawned now, relieving task overload on the TimerManager.
|
||||
* ThreadTracker: embedded auditing tool for run-time deadlock scanning throughout the server source (relies heavily on memory usage, designed only for debugging purposes).
|
||||
|
||||
Exploits patched:
|
||||
|
||||
* Player being given free access to any character of any account once they have authenticated their account on login phase.
|
||||
* Player being given permission to delete any character of any account once they have authenticated their account on login phase.
|
||||
* Player being able to start/complete any quest freely.
|
||||
|
||||
Localhost:
|
||||
|
||||
* Removed the 'n' problem within NPC dialog.
|
||||
|
||||
@@ -872,4 +872,8 @@ Adicionado scripts para a questline de Full Swing de Aran.
|
||||
|
||||
19 Março 2018,
|
||||
Tentativa de correção em reactors desconectando jogadores que tentam ativá-los com ataque básico ao mesmo tempo.
|
||||
Adicionado feature de AutoJCE (créditos ao Kradi-a).
|
||||
Adicionado feature de AutoJCE (créditos aos Acernis devs).
|
||||
|
||||
20 - 22 Março 2018,
|
||||
Resolvido exploit com login, onde qualquer um (via packet editing) podia logar livremente com personagem de outras contas.
|
||||
Nova ferramenta: MapleQuestlineFetcher. Busca nos XMLs e registra questids que ainda não possuem quest scripts.
|
||||
@@ -46,8 +46,6 @@ ToDo / Missing features list:
|
||||
|
||||
---------------------------
|
||||
** Jobs **
|
||||
- Check Aran
|
||||
- Check Cygnus Knights
|
||||
---------------------------
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ function action(mode, type, selection) {
|
||||
selStr += "\r\n#L" + i + "# " + info[i] + "#l";
|
||||
cm.sendSimple(selStr);
|
||||
}
|
||||
else if (!cm.getQuestStarted(4911)){
|
||||
else if (!cm.isQuestStarted(4911)){
|
||||
cm.sendNext("Good job! You've solved all of my questions about NLC. Enjoy of your trip!");
|
||||
cm.dispose();
|
||||
return;
|
||||
|
||||
47
scripts/quest/21749.js
Normal file
47
scripts/quest/21749.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server
|
||||
Copyleft (L) 2017 RonanLana
|
||||
|
||||
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 version 3 as published by
|
||||
the Free Software Foundation. You may not use, modify or distribute
|
||||
this program under any other version of the GNU Affero General Public
|
||||
License.
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
var status = -1;
|
||||
|
||||
function start(mode, type, selection) {
|
||||
if (mode == -1) {
|
||||
qm.dispose();
|
||||
} else {
|
||||
if(mode == 0 && type > 0) {
|
||||
qm.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == 1)
|
||||
status++;
|
||||
else
|
||||
status--;
|
||||
|
||||
if (status == 0) {
|
||||
qm.sendNext("So we have lost #btwo seal stones#k so far, from the neighboring areas of #rOrbis#k and #rMu Lung#k... Things are starting to get out of control, it seems.");
|
||||
} else if (status == 1) {
|
||||
qm.sendNext("Aran, your next objective will be to use the #btime gate to Ellin#k again. This time you will be retrieving the long lost #rSeal Stone of Ellin Forest#k. According to informations our network have gathered, #b#p2131002##k of that time have a clue about that gem, #rfind her#k. Please be successful on this task, our world is relying on you more than ever!");
|
||||
} else {
|
||||
qm.gainExp(500 * qm.getPlayer().getExpRate());
|
||||
qm.forceCompleteQuest();
|
||||
qm.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,8 @@ function end(mode, type, selection) {
|
||||
|
||||
if (status == 0) {
|
||||
qm.sendNext("Aran, you're finally back!!! How you've been doing? Where did you go for so long? We have so much to catch up...");
|
||||
} else {
|
||||
qm.forceCompleteQuest();
|
||||
|
||||
qm.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ function end(mode, type, selection) {
|
||||
|
||||
if (status == 0) {
|
||||
qm.sendNext("Oh, a letter for the #rempress#k? From the #bheroes#k?!");
|
||||
} else {
|
||||
qm.gainExp(1000 * qm.getPlayer().getExpRate());
|
||||
qm.gainItem(4032330, -1);
|
||||
qm.forceCompleteQuest();
|
||||
|
||||
@@ -1716,27 +1716,26 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
|
||||
}
|
||||
|
||||
public static boolean deleteCharFromDB(MapleCharacter player, int senderAccId) {
|
||||
int cid = player.getId(), accId = -1, world = 0;
|
||||
int cid = player.getId();
|
||||
if(!Server.getInstance().haveCharacterid(senderAccId, cid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int accId = senderAccId, world = 0;
|
||||
Connection con = null;
|
||||
try {
|
||||
con = DatabaseConnection.getConnection();
|
||||
|
||||
try (PreparedStatement ps = con.prepareStatement("SELECT accountid, world FROM characters WHERE id = ?")) {
|
||||
try (PreparedStatement ps = con.prepareStatement("SELECT world FROM characters WHERE id = ?")) {
|
||||
ps.setInt(1, cid);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if(rs.next()) {
|
||||
accId = rs.getInt("accountid");
|
||||
world = rs.getInt("world");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(senderAccId != accId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try (PreparedStatement ps = con.prepareStatement("SELECT buddyid FROM buddies WHERE characterid = ?")) {
|
||||
ps.setInt(1, cid);
|
||||
|
||||
@@ -1896,6 +1895,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
|
||||
}
|
||||
|
||||
con.close();
|
||||
Server.getInstance().deleteCharacterid(accId, cid);
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -470,7 +470,7 @@ public class Equip extends Item {
|
||||
lvupStr += "+UPGSLOT ";
|
||||
}
|
||||
|
||||
showLevelupMessage(showStr, c); // thx to polaris devs !
|
||||
showLevelupMessage(showStr, c); // thx to Polaris dev team !
|
||||
c.getPlayer().dropMessage(6, lvupStr);
|
||||
|
||||
c.announce(MaplePacketCreator.showEquipmentLevelUp());
|
||||
|
||||
@@ -42,7 +42,6 @@ import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import tools.locks.MonitoredReentrantLock;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import net.MapleServerHandler;
|
||||
import net.mina.MapleCodecFactory;
|
||||
@@ -88,10 +87,13 @@ public class Server implements Runnable {
|
||||
private List<World> worlds = new ArrayList<>();
|
||||
private final Properties subnetInfo = new Properties();
|
||||
private static Server instance = null;
|
||||
private final Map<Integer, Set<Integer>> accountChars = new HashMap<>();
|
||||
private final Map<String, Integer> transitioningChars = new HashMap<>();
|
||||
private List<Pair<Integer, String>> worldRecommendedList = new LinkedList<>();
|
||||
private final Map<Integer, MapleGuild> guilds = new HashMap<>(100);
|
||||
private final Map<MapleClient, Long> inLoginState = new HashMap<>(100);
|
||||
private final Lock srvLock = new MonitoredReentrantLock(MonitoredLockType.SERVER);
|
||||
private final Lock lgnLock = new MonitoredReentrantLock(MonitoredLockType.SERVER);
|
||||
private final PlayerBuffStorage buffStorage = new PlayerBuffStorage();
|
||||
private final Map<Integer, MapleAlliance> alliances = new HashMap<>(100);
|
||||
private final Map<Integer, NewYearCardRecord> newyears = new HashMap<>();
|
||||
@@ -751,6 +753,106 @@ public class Server implements Runnable {
|
||||
return worlds;
|
||||
}
|
||||
|
||||
private static void loadCharacteridsFromDb(Integer accountid, Set<Integer> accChars) {
|
||||
try {
|
||||
try (Connection con = DatabaseConnection.getConnection()) {
|
||||
try (PreparedStatement ps = con.prepareStatement("SELECT id FROM characters WHERE accountid = ?")) {
|
||||
ps.setInt(1, accountid);
|
||||
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while(rs.next()) {
|
||||
accChars.add(rs.getInt("id"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SQLException sqle) {
|
||||
sqle.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean haveCharacterid(Integer accountid, Integer chrid) {
|
||||
lgnLock.lock();
|
||||
try {
|
||||
Set<Integer> accChars = accountChars.get(accountid);
|
||||
if(accChars == null) {
|
||||
accChars = new HashSet<>(5);
|
||||
loadCharacteridsFromDb(accountid, accChars);
|
||||
|
||||
accountChars.put(accountid, accChars);
|
||||
}
|
||||
|
||||
return accChars.contains(chrid);
|
||||
} finally {
|
||||
lgnLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void createCharacterid(Integer accountid, Integer chrid) {
|
||||
lgnLock.lock();
|
||||
try {
|
||||
Set<Integer> accChars = accountChars.get(accountid);
|
||||
if(accChars == null) {
|
||||
accChars = new HashSet<>(5);
|
||||
accountChars.put(accountid, accChars);
|
||||
}
|
||||
|
||||
accChars.add(chrid);
|
||||
} finally {
|
||||
lgnLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteCharacterid(Integer accountid, Integer chrid) {
|
||||
lgnLock.lock();
|
||||
try {
|
||||
Set<Integer> accChars = accountChars.get(accountid);
|
||||
if(accChars != null) {
|
||||
accChars.remove(chrid);
|
||||
}
|
||||
} finally {
|
||||
lgnLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public void deleteAccount(Integer accountid) { is this even a thing?
|
||||
lgnLock.lock();
|
||||
try {
|
||||
accountChars.remove(accountid);
|
||||
} finally {
|
||||
lgnLock.unlock();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private static String getRemoteIp(InetSocketAddress isa) {
|
||||
return isa.getAddress().getHostAddress();
|
||||
}
|
||||
|
||||
public void setCharacteridInTransition(InetSocketAddress isa, int charId) {
|
||||
String remoteIp = getRemoteIp(isa);
|
||||
|
||||
lgnLock.lock();
|
||||
try {
|
||||
transitioningChars.put(remoteIp, charId);
|
||||
} finally {
|
||||
lgnLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean validateCharacteridInTransition(InetSocketAddress isa, int charId) {
|
||||
String remoteIp = getRemoteIp(isa);
|
||||
|
||||
lgnLock.lock();
|
||||
try {
|
||||
Integer cid = transitioningChars.remove(remoteIp);
|
||||
return cid != null && cid.equals(charId);
|
||||
} finally {
|
||||
lgnLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void registerLoginState(MapleClient c) {
|
||||
srvLock.lock();
|
||||
try {
|
||||
|
||||
@@ -53,6 +53,7 @@ import client.inventory.MapleInventoryType;
|
||||
import client.inventory.MaplePet;
|
||||
import constants.GameConstants;
|
||||
import constants.ServerConstants;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
@@ -71,6 +72,11 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
|
||||
MapleCharacter player = c.getWorldServer().getPlayerStorage().getCharacterById(cid);
|
||||
boolean newcomer = false;
|
||||
if (player == null) {
|
||||
if(!server.validateCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), cid)) {
|
||||
c.disconnect(true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
player = MapleCharacter.loadCharFromDB(cid, c, true);
|
||||
newcomer = true;
|
||||
|
||||
@@ -23,6 +23,7 @@ package net.server.handlers.login;
|
||||
|
||||
import client.MapleClient;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import net.AbstractMaplePacketHandler;
|
||||
import net.server.Server;
|
||||
@@ -35,16 +36,26 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler {
|
||||
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
|
||||
int charId = slea.readInt();
|
||||
String macs = slea.readMapleAsciiString();
|
||||
String hwid = slea.readMapleAsciiString();
|
||||
c.updateMacs(macs);
|
||||
if (c.hasBannedMac()) {
|
||||
c.updateHWID(hwid);
|
||||
|
||||
if (c.hasBannedMac() || c.hasBannedHWID()) {
|
||||
c.getSession().close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Server.getInstance().unregisterLoginState(c);
|
||||
c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
|
||||
Server server = Server.getInstance();
|
||||
if(!server.haveCharacterid(c.getAccID(), charId)) {
|
||||
c.getSession().close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":");
|
||||
server.unregisterLoginState(c);
|
||||
c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
|
||||
server.setCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), charId);
|
||||
|
||||
String[] socket = server.getIP(c.getWorld(), c.getChannel()).split(":");
|
||||
try {
|
||||
c.announce(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));
|
||||
} catch (UnknownHostException | NumberFormatException e) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.server.handlers.login;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import net.AbstractMaplePacketHandler;
|
||||
@@ -13,7 +14,6 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
|
||||
|
||||
@Override
|
||||
public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
|
||||
|
||||
String pic = slea.readMapleAsciiString();
|
||||
int charId = slea.readInt();
|
||||
String macs = slea.readMapleAsciiString();
|
||||
@@ -25,11 +25,19 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
|
||||
c.getSession().close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Server server = Server.getInstance();
|
||||
if(!server.haveCharacterid(c.getAccID(), charId)) {
|
||||
c.getSession().close(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c.checkPic(pic)) {
|
||||
Server.getInstance().unregisterLoginState(c);
|
||||
server.unregisterLoginState(c);
|
||||
c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
|
||||
server.setCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), charId);
|
||||
|
||||
String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":");
|
||||
String[] socket = server.getIP(c.getWorld(), c.getChannel()).split(":");
|
||||
try {
|
||||
c.announce(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));
|
||||
} catch (UnknownHostException | NumberFormatException e) {
|
||||
|
||||
@@ -112,17 +112,21 @@ public final class CreateCharHandler extends AbstractMaplePacketHandler {
|
||||
}
|
||||
|
||||
MapleInventory equipped = newchar.getInventory(MapleInventoryType.EQUIPPED);
|
||||
|
||||
Item eq_top = MapleItemInformationProvider.getInstance().getEquipById(top);
|
||||
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
|
||||
|
||||
Item eq_top = ii.getEquipById(top);
|
||||
eq_top.setPosition((byte) -5);
|
||||
equipped.addFromDB(eq_top);
|
||||
Item eq_bottom = MapleItemInformationProvider.getInstance().getEquipById(bottom);
|
||||
|
||||
Item eq_bottom = ii.getEquipById(bottom);
|
||||
eq_bottom.setPosition((byte) -6);
|
||||
equipped.addFromDB(eq_bottom);
|
||||
Item eq_shoes = MapleItemInformationProvider.getInstance().getEquipById(shoes);
|
||||
|
||||
Item eq_shoes = ii.getEquipById(shoes);
|
||||
eq_shoes.setPosition((byte) -7);
|
||||
equipped.addFromDB(eq_shoes);
|
||||
Item eq_weapon = MapleItemInformationProvider.getInstance().getEquipById(weapon);
|
||||
|
||||
Item eq_weapon = ii.getEquipById(weapon);
|
||||
eq_weapon.setPosition((byte) -11);
|
||||
equipped.addFromDB(eq_weapon.copy());
|
||||
|
||||
@@ -131,6 +135,8 @@ public final class CreateCharHandler extends AbstractMaplePacketHandler {
|
||||
return;
|
||||
}
|
||||
c.announce(MaplePacketCreator.addNewCharEntry(newchar));
|
||||
Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.sendYellowTip("[NEW CHAR]: " + c.getAccountName() + " has created a new character with IGN " + name));
|
||||
|
||||
Server.getInstance().createCharacterid(newchar.getAccountID(), newchar.getId());
|
||||
Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.sendYellowTip("[NEW CHAR]: " + c.getAccountName() + " has created a new character with IGN " + name));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import java.security.Permission;
|
||||
import java.security.PermissionCollection;
|
||||
import java.util.Map;
|
||||
|
||||
public class AutoJCE{ // AutoJCE into server source thanks to Kradi-a
|
||||
public class AutoJCE{ // AutoJCE into server source thanks to Acernis dev team
|
||||
|
||||
/**
|
||||
* Credits: ntoskrnl of StackOverflow
|
||||
|
||||
@@ -63,7 +63,7 @@ public class MapleAESOFB {
|
||||
} catch (NoSuchPaddingException e) {
|
||||
System.out.println("ERROR " + e);
|
||||
} catch (InvalidKeyException e) {
|
||||
System.out.println("Error initalizing the encryption cipher. Make sure you're using the Unlimited Strength cryptography jar files.");
|
||||
System.out.println("Error initializing the encryption cipher. Make sure you're using the Unlimited Strength cryptography jar files.");
|
||||
}
|
||||
this.setIv(iv);
|
||||
this.mapleVersion = (short) (((mapleVersion >> 8) & 0xFF) | ((mapleVersion << 8) & 0xFF00));
|
||||
|
||||
@@ -27,16 +27,11 @@ import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
73
tools/MapleQuestlineFetcher/build.xml
Normal file
73
tools/MapleQuestlineFetcher/build.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- You may freely edit this file. See commented blocks below for -->
|
||||
<!-- some examples of how to customize the build. -->
|
||||
<!-- (If you delete it and reopen the project it will be recreated.) -->
|
||||
<!-- By default, only the Clean and Build commands use this build script. -->
|
||||
<!-- Commands such as Run, Debug, and Test only use this build script if -->
|
||||
<!-- the Compile on Save feature is turned off for the project. -->
|
||||
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
|
||||
<!-- in the project's Project Properties dialog box.-->
|
||||
<project name="MapleQuestlineFetcher" default="default" basedir=".">
|
||||
<description>Builds, tests, and runs the project MapleQuestlineFetcher.</description>
|
||||
<import file="nbproject/build-impl.xml"/>
|
||||
<!--
|
||||
|
||||
There exist several targets which are by default empty and which can be
|
||||
used for execution of your tasks. These targets are usually executed
|
||||
before and after some main targets. They are:
|
||||
|
||||
-pre-init: called before initialization of project properties
|
||||
-post-init: called after initialization of project properties
|
||||
-pre-compile: called before javac compilation
|
||||
-post-compile: called after javac compilation
|
||||
-pre-compile-single: called before javac compilation of single file
|
||||
-post-compile-single: called after javac compilation of single file
|
||||
-pre-compile-test: called before javac compilation of JUnit tests
|
||||
-post-compile-test: called after javac compilation of JUnit tests
|
||||
-pre-compile-test-single: called before javac compilation of single JUnit test
|
||||
-post-compile-test-single: called after javac compilation of single JUunit test
|
||||
-pre-jar: called before JAR building
|
||||
-post-jar: called after JAR building
|
||||
-post-clean: called after cleaning build products
|
||||
|
||||
(Targets beginning with '-' are not intended to be called on their own.)
|
||||
|
||||
Example of inserting an obfuscator after compilation could look like this:
|
||||
|
||||
<target name="-post-compile">
|
||||
<obfuscate>
|
||||
<fileset dir="${build.classes.dir}"/>
|
||||
</obfuscate>
|
||||
</target>
|
||||
|
||||
For list of available properties check the imported
|
||||
nbproject/build-impl.xml file.
|
||||
|
||||
|
||||
Another way to customize the build is by overriding existing main targets.
|
||||
The targets of interest are:
|
||||
|
||||
-init-macrodef-javac: defines macro for javac compilation
|
||||
-init-macrodef-junit: defines macro for junit execution
|
||||
-init-macrodef-debug: defines macro for class debugging
|
||||
-init-macrodef-java: defines macro for class execution
|
||||
-do-jar: JAR building
|
||||
run: execution of project
|
||||
-javadoc-build: Javadoc generation
|
||||
test-report: JUnit report generation
|
||||
|
||||
An example of overriding the target for project execution could look like this:
|
||||
|
||||
<target name="run" depends="MapleQuestlineFetcher-impl.jar">
|
||||
<exec dir="bin" executable="launcher.exe">
|
||||
<arg file="${dist.jar}"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
Notice that the overridden target depends on the jar target and not only on
|
||||
the compile target as the regular run target does. Again, for a list of available
|
||||
properties which you can use, check the target you are overriding in the
|
||||
nbproject/build-impl.xml file.
|
||||
|
||||
-->
|
||||
</project>
|
||||
373
tools/MapleQuestlineFetcher/lib/QuestReport.txt
Normal file
373
tools/MapleQuestlineFetcher/lib/QuestReport.txt
Normal file
@@ -0,0 +1,373 @@
|
||||
# Report File autogenerated from the MapleQuestlineFetcher feature by Ronan Lana.
|
||||
# Generated data takes into account several data info from the server-side WZ.xmls.
|
||||
|
||||
SKILL-RELATED NON-SCRIPTED QUESTS
|
||||
20500 EXPIRED
|
||||
20502 EXPIRED
|
||||
|
||||
|
||||
COMMON NON-SCRIPTED QUESTS
|
||||
1028
|
||||
1048 EXPIRED
|
||||
1049 EXPIRED
|
||||
1050 EXPIRED
|
||||
1051 EXPIRED
|
||||
1052 EXPIRED
|
||||
1053 EXPIRED
|
||||
1054 EXPIRED
|
||||
2147 EXPIRED
|
||||
2228
|
||||
2232
|
||||
2233
|
||||
2234
|
||||
2238
|
||||
2245
|
||||
2257
|
||||
2258
|
||||
2259
|
||||
2260
|
||||
2291
|
||||
2338
|
||||
3305 EXPIRED
|
||||
3306 EXPIRED
|
||||
3529
|
||||
3714
|
||||
4482 EXPIRED
|
||||
4483 EXPIRED
|
||||
4490 EXPIRED
|
||||
4676 EXPIRED
|
||||
6700 EXPIRED
|
||||
8247 EXPIRED
|
||||
9432 EXPIRED
|
||||
9633 EXPIRED
|
||||
9680 EXPIRED
|
||||
9682 EXPIRED
|
||||
9683 EXPIRED
|
||||
9684 EXPIRED
|
||||
9685 EXPIRED
|
||||
9690 EXPIRED
|
||||
9691 EXPIRED
|
||||
9692 EXPIRED
|
||||
9693 EXPIRED
|
||||
9694 EXPIRED
|
||||
9695 EXPIRED
|
||||
9696 EXPIRED
|
||||
9697 EXPIRED
|
||||
9698 EXPIRED
|
||||
9699 EXPIRED
|
||||
9700 EXPIRED
|
||||
9701 EXPIRED
|
||||
9702 EXPIRED
|
||||
9703 EXPIRED
|
||||
9730 EXPIRED
|
||||
9731 EXPIRED
|
||||
9732 EXPIRED
|
||||
9733 EXPIRED
|
||||
9734 EXPIRED
|
||||
9735 EXPIRED
|
||||
9745 EXPIRED
|
||||
9746 EXPIRED
|
||||
9747 EXPIRED
|
||||
9840 EXPIRED
|
||||
9841 EXPIRED
|
||||
9842 EXPIRED
|
||||
9844 EXPIRED
|
||||
9845 EXPIRED
|
||||
9846 EXPIRED
|
||||
9847 EXPIRED
|
||||
9849 EXPIRED
|
||||
9850 EXPIRED
|
||||
9851 EXPIRED
|
||||
9860 EXPIRED
|
||||
9875 EXPIRED
|
||||
9878 EXPIRED
|
||||
9880 EXPIRED
|
||||
9881 EXPIRED
|
||||
9882 EXPIRED
|
||||
9883 EXPIRED
|
||||
9884 EXPIRED
|
||||
9885 EXPIRED
|
||||
9887 EXPIRED
|
||||
9890 EXPIRED
|
||||
9891 EXPIRED
|
||||
9892 EXPIRED
|
||||
9893 EXPIRED
|
||||
9900 EXPIRED
|
||||
9901 EXPIRED
|
||||
9902 EXPIRED
|
||||
9903 EXPIRED
|
||||
9904 EXPIRED
|
||||
9905 EXPIRED
|
||||
9906 EXPIRED
|
||||
9907 EXPIRED
|
||||
9908 EXPIRED
|
||||
9909 EXPIRED
|
||||
9920 EXPIRED
|
||||
9922 EXPIRED
|
||||
9924 EXPIRED
|
||||
9925 EXPIRED
|
||||
9926 EXPIRED
|
||||
9927 EXPIRED
|
||||
9928 EXPIRED
|
||||
9929 EXPIRED
|
||||
9930 EXPIRED
|
||||
9931 EXPIRED
|
||||
9932 EXPIRED
|
||||
9933 EXPIRED
|
||||
9934 EXPIRED
|
||||
9935 EXPIRED
|
||||
9936 EXPIRED
|
||||
9937 EXPIRED
|
||||
9938 EXPIRED
|
||||
9939 EXPIRED
|
||||
9943 EXPIRED
|
||||
9947 EXPIRED
|
||||
9950 EXPIRED
|
||||
9951 EXPIRED
|
||||
9961 EXPIRED
|
||||
9965 EXPIRED
|
||||
9970 EXPIRED
|
||||
9980 EXPIRED
|
||||
9981 EXPIRED
|
||||
9982 EXPIRED
|
||||
9983 EXPIRED
|
||||
9984 EXPIRED
|
||||
9985 EXPIRED
|
||||
9990 EXPIRED
|
||||
9991 EXPIRED
|
||||
9997 EXPIRED
|
||||
10002 EXPIRED
|
||||
10008 EXPIRED
|
||||
10010 EXPIRED
|
||||
10011 EXPIRED
|
||||
10012 EXPIRED
|
||||
10014 EXPIRED
|
||||
10034 EXPIRED
|
||||
10036 EXPIRED
|
||||
10037 EXPIRED
|
||||
10039 EXPIRED
|
||||
10043 EXPIRED
|
||||
10046 EXPIRED
|
||||
10050 EXPIRED
|
||||
10052 EXPIRED
|
||||
10059 EXPIRED
|
||||
10066 EXPIRED
|
||||
10069 EXPIRED
|
||||
10074 EXPIRED
|
||||
10075 EXPIRED
|
||||
10076 EXPIRED
|
||||
10077 EXPIRED
|
||||
10078 EXPIRED
|
||||
10079 EXPIRED
|
||||
10082 EXPIRED
|
||||
10083 EXPIRED
|
||||
10084 EXPIRED
|
||||
10085 EXPIRED
|
||||
10086 EXPIRED
|
||||
10087 EXPIRED
|
||||
10088 EXPIRED
|
||||
10089 EXPIRED
|
||||
10090 EXPIRED
|
||||
10091 EXPIRED
|
||||
10092 EXPIRED
|
||||
10093 EXPIRED
|
||||
10094 EXPIRED
|
||||
10095 EXPIRED
|
||||
10096 EXPIRED
|
||||
10097 EXPIRED
|
||||
10098 EXPIRED
|
||||
10099 EXPIRED
|
||||
10100 EXPIRED
|
||||
10101 EXPIRED
|
||||
10102 EXPIRED
|
||||
10103 EXPIRED
|
||||
10104 EXPIRED
|
||||
10105 EXPIRED
|
||||
10106 EXPIRED
|
||||
10108 EXPIRED
|
||||
10110 EXPIRED
|
||||
10206 EXPIRED
|
||||
10207 EXPIRED
|
||||
10208 EXPIRED
|
||||
10209 EXPIRED
|
||||
10210 EXPIRED
|
||||
10211 EXPIRED
|
||||
10212 EXPIRED
|
||||
10213 EXPIRED
|
||||
10214 EXPIRED
|
||||
10215 EXPIRED
|
||||
10216 EXPIRED
|
||||
10217 EXPIRED
|
||||
10218 EXPIRED
|
||||
10219 EXPIRED
|
||||
10220 EXPIRED
|
||||
10222 EXPIRED
|
||||
10224 EXPIRED
|
||||
10231 EXPIRED
|
||||
10240 EXPIRED
|
||||
10241 EXPIRED
|
||||
10242 EXPIRED
|
||||
10243 EXPIRED
|
||||
10244 EXPIRED
|
||||
10245 EXPIRED
|
||||
10246 EXPIRED
|
||||
10247 EXPIRED
|
||||
10248 EXPIRED
|
||||
10249 EXPIRED
|
||||
10250 EXPIRED
|
||||
10251 EXPIRED
|
||||
10252 EXPIRED
|
||||
10253 EXPIRED
|
||||
10254 EXPIRED
|
||||
10255 EXPIRED
|
||||
10256 EXPIRED
|
||||
10260 EXPIRED
|
||||
10261 EXPIRED
|
||||
10262 EXPIRED
|
||||
10263 EXPIRED
|
||||
10264 EXPIRED
|
||||
10265 EXPIRED
|
||||
10266 EXPIRED
|
||||
10267 EXPIRED
|
||||
10268 EXPIRED
|
||||
10270 EXPIRED
|
||||
10271 EXPIRED
|
||||
10272 EXPIRED
|
||||
10274 EXPIRED
|
||||
10275 EXPIRED
|
||||
10276 EXPIRED
|
||||
10277 EXPIRED
|
||||
10278 EXPIRED
|
||||
10279 EXPIRED
|
||||
10280 EXPIRED
|
||||
10281 EXPIRED
|
||||
10282 EXPIRED
|
||||
10283 EXPIRED
|
||||
10284 EXPIRED
|
||||
10287 EXPIRED
|
||||
10300 EXPIRED
|
||||
10301 EXPIRED
|
||||
10302 EXPIRED
|
||||
10311 EXPIRED
|
||||
10312 EXPIRED
|
||||
10313 EXPIRED
|
||||
10314 EXPIRED
|
||||
10315 EXPIRED
|
||||
10316 EXPIRED
|
||||
10317 EXPIRED
|
||||
10318 EXPIRED
|
||||
10319 EXPIRED
|
||||
10320 EXPIRED
|
||||
10321 EXPIRED
|
||||
10324 EXPIRED
|
||||
10325 EXPIRED
|
||||
10326 EXPIRED
|
||||
10327 EXPIRED
|
||||
10329 EXPIRED
|
||||
10330 EXPIRED
|
||||
10331 EXPIRED
|
||||
10332 EXPIRED
|
||||
10333 EXPIRED
|
||||
10340 EXPIRED
|
||||
10341 EXPIRED
|
||||
10342 EXPIRED
|
||||
10344 EXPIRED
|
||||
10345 EXPIRED
|
||||
10346 EXPIRED
|
||||
10347 EXPIRED
|
||||
10348 EXPIRED
|
||||
10349 EXPIRED
|
||||
10350 EXPIRED
|
||||
10351 EXPIRED
|
||||
10352 EXPIRED
|
||||
10353 EXPIRED
|
||||
10354 EXPIRED
|
||||
10355 EXPIRED
|
||||
10356 EXPIRED
|
||||
10360 EXPIRED
|
||||
10370 EXPIRED
|
||||
10380 EXPIRED
|
||||
10394 EXPIRED
|
||||
10395 EXPIRED
|
||||
10396 EXPIRED
|
||||
10397 EXPIRED
|
||||
10398 EXPIRED
|
||||
10400 EXPIRED
|
||||
10401 EXPIRED
|
||||
10415 EXPIRED
|
||||
10417 EXPIRED
|
||||
10418 EXPIRED
|
||||
10419 EXPIRED
|
||||
10420 EXPIRED
|
||||
10450 EXPIRED
|
||||
10451 EXPIRED
|
||||
10452 EXPIRED
|
||||
10453 EXPIRED
|
||||
10454 EXPIRED
|
||||
10455 EXPIRED
|
||||
10456 EXPIRED
|
||||
10457 EXPIRED
|
||||
10470 EXPIRED
|
||||
19000
|
||||
19001
|
||||
19002
|
||||
19005 EXPIRED
|
||||
19006 EXPIRED
|
||||
20015
|
||||
20020
|
||||
20400
|
||||
20401
|
||||
20405
|
||||
20406
|
||||
20408
|
||||
20506 EXPIRED
|
||||
20507 EXPIRED
|
||||
20509 EXPIRED
|
||||
21303
|
||||
28004 EXPIRED
|
||||
28117 EXPIRED
|
||||
28118 EXPIRED
|
||||
28131 EXPIRED
|
||||
28137 EXPIRED
|
||||
28138 EXPIRED
|
||||
28139 EXPIRED
|
||||
28140 EXPIRED
|
||||
28141 EXPIRED
|
||||
28142 EXPIRED
|
||||
28143 EXPIRED
|
||||
28144 EXPIRED
|
||||
28145 EXPIRED
|
||||
28146 EXPIRED
|
||||
28147 EXPIRED
|
||||
28148 EXPIRED
|
||||
28149 EXPIRED
|
||||
28150 EXPIRED
|
||||
28151 EXPIRED
|
||||
28152 EXPIRED
|
||||
28153 EXPIRED
|
||||
28154 EXPIRED
|
||||
28155 EXPIRED
|
||||
28156 EXPIRED
|
||||
28157 EXPIRED
|
||||
28158 EXPIRED
|
||||
28159 EXPIRED
|
||||
28160 EXPIRED
|
||||
28161 EXPIRED
|
||||
28284 EXPIRED
|
||||
28285 EXPIRED
|
||||
28286 EXPIRED
|
||||
28304 EXPIRED
|
||||
28311 EXPIRED
|
||||
28318 EXPIRED
|
||||
28326 EXPIRED
|
||||
28327 EXPIRED
|
||||
28328 EXPIRED
|
||||
28333 EXPIRED
|
||||
28337 EXPIRED
|
||||
29500
|
||||
29501
|
||||
29502
|
||||
29503
|
||||
29505
|
||||
29506
|
||||
29508
|
||||
3
tools/MapleQuestlineFetcher/manifest.mf
Normal file
3
tools/MapleQuestlineFetcher/manifest.mf
Normal file
@@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
X-COMMENT: Main-Class will be added automatically by build
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server
|
||||
Copyleft (L) 2017 RonanLana
|
||||
|
||||
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 version 3 as published by
|
||||
the Free Software Foundation. You may not use, modify or distribute
|
||||
this program under any other version of the GNU Affero General Public
|
||||
License.
|
||||
|
||||
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 maplequestlinefetcher;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author RonanLana
|
||||
|
||||
This application parses the Quest.wz file inputted and generates a report showing
|
||||
all cases where quest script files have not been found for quests that requires a
|
||||
script file.
|
||||
As an extension, it highlights missing script files for questlines that hand over
|
||||
skills as rewards.
|
||||
|
||||
Running it should generate a report file under "lib" folder with the search results.
|
||||
|
||||
*/
|
||||
public class MapleQuestlineFetcher {
|
||||
static String actName = "../../wz/Quest.wz/Act.img.xml";
|
||||
static String checkName = "../../wz/Quest.wz/Check.img.xml";
|
||||
static String directoryName = "../..";
|
||||
static String newFile = "lib/QuestReport.txt";
|
||||
|
||||
static Connection con = null;
|
||||
static PrintWriter printWriter = null;
|
||||
static InputStreamReader fileReader = null;
|
||||
static BufferedReader bufferedReader = null;
|
||||
|
||||
static int initialLength = 200;
|
||||
static int initialStringLength = 50;
|
||||
|
||||
static byte status = 0;
|
||||
static int questId = -1;
|
||||
static int isCompleteState = 0;
|
||||
static boolean isScriptedQuest;
|
||||
static boolean isExpiredQuest;
|
||||
static List<Integer> questDependencyList;
|
||||
|
||||
static int curQuestId;
|
||||
static int curQuestState;
|
||||
|
||||
static Stack<Integer> skillObtainableQuests = new Stack<>();
|
||||
static Set<Integer> scriptedQuestFiles = new HashSet<>();
|
||||
static Set<Integer> expiredQuests = new HashSet<>();
|
||||
|
||||
static Map<Integer, List<Integer>> questDependencies = new HashMap<>();
|
||||
|
||||
static Set<Integer> nonScriptedQuests = new HashSet<>();
|
||||
static Set<Integer> skillObtainableNonScriptedQuests = new HashSet<>();
|
||||
|
||||
private static String getName(String token) {
|
||||
int i, j;
|
||||
char[] dest;
|
||||
String d;
|
||||
|
||||
i = token.lastIndexOf("name");
|
||||
i = token.indexOf("\"", i) + 1; //lower bound of the string
|
||||
j = token.indexOf("\"", i); //upper bound
|
||||
|
||||
dest = new char[initialStringLength];
|
||||
token.getChars(i, j, dest, 0);
|
||||
|
||||
d = new String(dest);
|
||||
return(d.trim());
|
||||
}
|
||||
|
||||
private static String getValue(String token) {
|
||||
int i, j;
|
||||
char[] dest;
|
||||
String d;
|
||||
|
||||
i = token.lastIndexOf("value");
|
||||
i = token.indexOf("\"", i) + 1; //lower bound of the string
|
||||
j = token.indexOf("\"", i); //upper bound
|
||||
|
||||
dest = new char[initialStringLength];
|
||||
token.getChars(i, j, dest, 0);
|
||||
|
||||
d = new String(dest);
|
||||
return(d.trim());
|
||||
}
|
||||
|
||||
private static void forwardCursor(int st) {
|
||||
String line = null;
|
||||
|
||||
try {
|
||||
while(status >= st && (line = bufferedReader.readLine()) != null) {
|
||||
simpleToken(line);
|
||||
}
|
||||
}
|
||||
catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void simpleToken(String token) {
|
||||
if(token.contains("/imgdir")) {
|
||||
status -= 1;
|
||||
}
|
||||
else if(token.contains("imgdir")) {
|
||||
status += 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void translateTokenCheck(String token) {
|
||||
String d;
|
||||
|
||||
if(token.contains("/imgdir")) {
|
||||
status -= 1;
|
||||
|
||||
if(status == 1) {
|
||||
evaluateCurrentQuest();
|
||||
}
|
||||
else if(status == 4) {
|
||||
evaluateCurrentQuestDependency();
|
||||
}
|
||||
}
|
||||
else if(token.contains("imgdir")) {
|
||||
if(status == 1) { //getting QuestId
|
||||
d = getName(token);
|
||||
questId = Integer.parseInt(d);
|
||||
|
||||
isScriptedQuest = false;
|
||||
isExpiredQuest = false;
|
||||
questDependencyList = new LinkedList<>();
|
||||
}
|
||||
else if(status == 2) { //start/complete
|
||||
d = getName(token);
|
||||
isCompleteState = Integer.parseInt(d);
|
||||
}
|
||||
else if(status == 3) {
|
||||
if(isCompleteState == 1 || !token.contains("quest")) {
|
||||
forwardCursor(status);
|
||||
}
|
||||
}
|
||||
|
||||
status += 1;
|
||||
}
|
||||
else {
|
||||
if(status == 3) {
|
||||
d = getName(token);
|
||||
|
||||
if(d.contains("script")) {
|
||||
isScriptedQuest = true;
|
||||
} else if(d.contains("end")) {
|
||||
isExpiredQuest = true;
|
||||
}
|
||||
}
|
||||
else if(status == 5) {
|
||||
readQuestLabel(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void translateTokenAct(String token) {
|
||||
String d;
|
||||
|
||||
if(token.contains("/imgdir")) {
|
||||
status -= 1;
|
||||
}
|
||||
else if(token.contains("imgdir")) {
|
||||
if(status == 1) { //getting QuestId
|
||||
d = getName(token);
|
||||
questId = Integer.parseInt(d);
|
||||
}
|
||||
else if(status == 2) { //start/complete
|
||||
d = getName(token);
|
||||
isCompleteState = Integer.parseInt(d);
|
||||
}
|
||||
else if(status == 3) {
|
||||
if(isCompleteState == 1 && token.contains("skill")) {
|
||||
skillObtainableQuests.add(questId);
|
||||
}
|
||||
|
||||
forwardCursor(status);
|
||||
}
|
||||
|
||||
status += 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void readQuestLabel(String token) {
|
||||
String name = getName(token);
|
||||
String value = getValue(token);
|
||||
|
||||
switch(name) {
|
||||
case "id":
|
||||
curQuestId = Integer.parseInt(value);
|
||||
break;
|
||||
|
||||
case "state":
|
||||
curQuestState = Integer.parseInt(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void evaluateCurrentQuestDependency() {
|
||||
if(curQuestState == 2) {
|
||||
questDependencyList.add(curQuestId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void evaluateCurrentQuest() {
|
||||
if(isScriptedQuest && !scriptedQuestFiles.contains(questId)) {
|
||||
nonScriptedQuests.add(questId);
|
||||
}
|
||||
if(isExpiredQuest) {
|
||||
expiredQuests.add(questId);
|
||||
}
|
||||
|
||||
questDependencies.put(questId, questDependencyList);
|
||||
}
|
||||
|
||||
private static void instantiateQuestScriptFiles(String directoryName) {
|
||||
File directory = new File(directoryName);
|
||||
|
||||
// get all the files from a directory
|
||||
File[] fList = directory.listFiles();
|
||||
for (File file : fList) {
|
||||
if (file.isFile()) {
|
||||
String fname = file.getName();
|
||||
|
||||
try {
|
||||
Integer questid = Integer.parseInt(fname.substring(0, fname.indexOf('.')));
|
||||
scriptedQuestFiles.add(questid);
|
||||
} catch(NumberFormatException nfe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void readQuestsWithMissingScripts() throws IOException {
|
||||
String line;
|
||||
|
||||
fileReader = new InputStreamReader(new FileInputStream(checkName), "UTF-8");
|
||||
bufferedReader = new BufferedReader(fileReader);
|
||||
|
||||
while((line = bufferedReader.readLine()) != null) {
|
||||
translateTokenCheck(line);
|
||||
}
|
||||
|
||||
bufferedReader.close();
|
||||
fileReader.close();
|
||||
}
|
||||
|
||||
private static void readQuestsWithSkillReward() throws IOException {
|
||||
String line;
|
||||
|
||||
fileReader = new InputStreamReader(new FileInputStream(actName), "UTF-8");
|
||||
bufferedReader = new BufferedReader(fileReader);
|
||||
|
||||
while((line = bufferedReader.readLine()) != null) {
|
||||
translateTokenAct(line);
|
||||
}
|
||||
|
||||
bufferedReader.close();
|
||||
fileReader.close();
|
||||
}
|
||||
|
||||
private static void calculateSkillRelatedMissingQuestScripts() {
|
||||
Stack<Integer> frontierQuests = skillObtainableQuests;
|
||||
Set<Integer> solvedQuests = new HashSet<>();
|
||||
|
||||
while(!frontierQuests.isEmpty()) {
|
||||
Integer questid = frontierQuests.pop();
|
||||
solvedQuests.add(questid);
|
||||
|
||||
if(nonScriptedQuests.contains(questid)) {
|
||||
skillObtainableNonScriptedQuests.add(questid);
|
||||
nonScriptedQuests.remove(questid);
|
||||
}
|
||||
|
||||
List<Integer> questDependency = questDependencies.get(questid);
|
||||
for(Integer i : questDependency) {
|
||||
if(!solvedQuests.contains(i)) {
|
||||
frontierQuests.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void printReportFileHeader() {
|
||||
printWriter.println(" # Report File autogenerated from the MapleQuestlineFetcher feature by Ronan Lana.");
|
||||
printWriter.println(" # Generated data takes into account several data info from the server-side WZ.xmls.");
|
||||
printWriter.println();
|
||||
}
|
||||
|
||||
private static List<Integer> getSortedListEntries(Set<Integer> set) {
|
||||
List<Integer> list = new ArrayList<>(set);
|
||||
Collections.sort(list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void printReportFileResults() {
|
||||
if(!skillObtainableNonScriptedQuests.isEmpty()) {
|
||||
printWriter.println("SKILL-RELATED NON-SCRIPTED QUESTS");
|
||||
for(Integer nsq : getSortedListEntries(skillObtainableNonScriptedQuests)) {
|
||||
printWriter.println(" " + nsq + (expiredQuests.contains(nsq) ? " EXPIRED" : ""));
|
||||
}
|
||||
|
||||
printWriter.println();
|
||||
}
|
||||
|
||||
printWriter.println("\nCOMMON NON-SCRIPTED QUESTS");
|
||||
for(Integer nsq : getSortedListEntries(nonScriptedQuests)) {
|
||||
printWriter.println(" " + nsq + (expiredQuests.contains(nsq) ? " EXPIRED" : ""));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReportQuestlineData() {
|
||||
// This will reference one line at a time
|
||||
|
||||
try {
|
||||
System.out.println("Reading quest scripts...");
|
||||
instantiateQuestScriptFiles(directoryName + "/scripts/quest");
|
||||
|
||||
System.out.println("Reading WZs...");
|
||||
readQuestsWithSkillReward();
|
||||
readQuestsWithMissingScripts();
|
||||
|
||||
System.out.println("Calculating skill related quests...");
|
||||
calculateSkillRelatedMissingQuestScripts();
|
||||
|
||||
System.out.println("Reporting results...");
|
||||
printWriter = new PrintWriter(newFile, "UTF-8");
|
||||
|
||||
printReportFileHeader();
|
||||
printReportFileResults();
|
||||
|
||||
printWriter.close();
|
||||
System.out.println("Done!");
|
||||
}
|
||||
|
||||
catch(FileNotFoundException ex) {
|
||||
System.out.println("Unable to open quest file.");
|
||||
}
|
||||
catch(IOException ex) {
|
||||
System.out.println("Error reading quest file.");
|
||||
}
|
||||
|
||||
catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static private List<Pair<Integer, List<Integer>>> getSortedMapEntries(Map<Integer, List<Integer>> map) {
|
||||
List<Pair<Integer, List<Integer>>> list = new ArrayList<>(map.size());
|
||||
for(Map.Entry<Integer, List<Integer>> e : map.entrySet()) {
|
||||
List<Integer> il = new ArrayList<>(2);
|
||||
for(Integer i : e.getValue()) {
|
||||
il.add(i);
|
||||
}
|
||||
|
||||
Collections.sort(il, new Comparator<Integer>() {
|
||||
@Override
|
||||
public int compare(Integer o1, Integer o2) {
|
||||
return o1 - o2;
|
||||
}
|
||||
});
|
||||
|
||||
list.add(new Pair<>(e.getKey(), il));
|
||||
}
|
||||
|
||||
Collections.sort(list, new Comparator<Pair<Integer, List<Integer>>>() {
|
||||
@Override
|
||||
public int compare(Pair<Integer, List<Integer>> o1, Pair<Integer, List<Integer>> o2) {
|
||||
return o1.getLeft() - o2.getLeft();
|
||||
}
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void DumpQuestlineData() {
|
||||
for(Pair<Integer, List<Integer>> questDependency : getSortedMapEntries(questDependencies)) {
|
||||
if(!questDependency.right.isEmpty()) {
|
||||
System.out.println(questDependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public static void main(String[] args) {
|
||||
ReportQuestlineData();
|
||||
}
|
||||
|
||||
}
|
||||
121
tools/MapleQuestlineFetcher/src/maplequestlinefetcher/Pair.java
Normal file
121
tools/MapleQuestlineFetcher/src/maplequestlinefetcher/Pair.java
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
This file is part of the OdinMS Maple Story Server
|
||||
Copyright (C) 2008 ~ 2010 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 version 3
|
||||
as published by the Free Software Foundation. You may not use, modify
|
||||
or distribute this program under any other version of the
|
||||
GNU Affero General Public License.
|
||||
|
||||
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 maplequestlinefetcher;
|
||||
|
||||
/**
|
||||
* Represents a pair of values.
|
||||
*
|
||||
* @author Frz
|
||||
* @since Revision 333
|
||||
* @version 1.0
|
||||
*
|
||||
* @param <E> The type of the left value.
|
||||
* @param <F> The type of the right value.
|
||||
*/
|
||||
public class Pair<E, F> {
|
||||
|
||||
public E left;
|
||||
public F right;
|
||||
|
||||
/**
|
||||
* Class constructor - pairs two objects together.
|
||||
*
|
||||
* @param left The left object.
|
||||
* @param right The right object.
|
||||
*/
|
||||
public Pair(E left, F right) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the left value.
|
||||
*
|
||||
* @return The left value.
|
||||
*/
|
||||
public E getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the right value.
|
||||
*
|
||||
* @return The right value.
|
||||
*/
|
||||
public F getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the pair into a string.
|
||||
*
|
||||
* @return Each value of the pair as a string joined by a colon.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return left.toString() + ":" + right.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hash code of this pair.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((left == null) ? 0 : left.hashCode());
|
||||
result = prime * result + ((right == null) ? 0 : right.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if two pairs are equal.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Pair other = (Pair) obj;
|
||||
if (left == null) {
|
||||
if (other.left != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!left.equals(other.left)) {
|
||||
return false;
|
||||
}
|
||||
if (right == null) {
|
||||
if (other.right != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!right.equals(other.right)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -22801,6 +22801,12 @@
|
||||
<int name="1" value="2111"/>
|
||||
<int name="2" value="2112"/>
|
||||
</imgdir>
|
||||
<imgdir name="quest">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="21749"/>
|
||||
<int name="state" value="2"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="1">
|
||||
<int name="npc" value="2131000"/>
|
||||
|
||||
Reference in New Issue
Block a user