osumer-lib/src/main/java/com/github/mob41/osumer/exceptions/NoSuchBuildNumberException.java
public class NoSuchBuildNumberException extends WithDumpException {

    /**
     * 
     */
    private static final long serialVersionUID = 4508589028039107867L;
    private static final String MSG = "No such build number/index defined in the versions' JSON";

    public NoSuchBuildNumberException(String json, String last_op, String this_op, String next_op, int buildnum) {
        super(json, last_op, this_op, next_op, MSG + ": " + buildnum, false);
    }

    public NoSuchBuildNumberException(String json, String last_op, String this_op, String next_op, String arg0,
            int buildnum) {
        super(json, last_op, this_op, next_op, MSG + ": " + buildnum, false, arg0);
    }

    public NoSuchBuildNumberException(String json, String last_op, String this_op, String next_op, Throwable arg0,
            int buildnum) {
        super(json, last_op, this_op, next_op, MSG + ": " + buildnum, false, arg0);
    }

    public NoSuchBuildNumberException(String json, String last_op, String this_op, String next_op, String arg0,
            Throwable arg1, int buildnum) {
        super(json, last_op, this_op, next_op, MSG + ": " + buildnum, false, arg0, arg1);
    }

    public NoSuchBuildNumberException(String json, String last_op, String this_op, String next_op, String arg0,
            Throwable arg1, boolean arg2, boolean arg3, int buildnum) {
        super(json, last_op, this_op, next_op, MSG + ": " + buildnum, false, arg0, arg1, arg2, arg3);
    }

}
osumer-lib/src/main/java/com/github/mob41/osumer/rmi/IDaemon.java
public interface IDaemon extends Remote{
	
	public void test() throws RemoteException;
    
    public MethodResult<Integer> addQueue(String url) throws RemoteException;
    
    public MethodResult<Integer> addQueue(String url, int downloadAction, String targetFileOrFolder) throws RemoteException;
    
    public boolean removeQueue(String name) throws RemoteException;
    
    public QueueStatus[] getQueues() throws RemoteException;
    
    public void registerUi(IUI ui) throws RemoteException;
    
    public void unregisterUi(IUI ui) throws RemoteException;
    
    public void setOverlayAgreement(boolean agreed) throws RemoteException;
    
    public boolean isOverlayAgreement() throws RemoteException;
    
    public void startOsuWithOverlay() throws RemoteException;
    
    public void trayIconDisplayMessage(String caption, String text, MessageType msgType) throws RemoteException;
    
    public void reloadConfiguration() throws RemoteException, IOException;
    
    public void shutdown() throws RemoteException;
    
}
osumer-lib/src/main/java/com/github/mob41/osumer/exceptions/NoSuchVersionException.java
public class NoSuchVersionException extends WithDumpException {

    /**
     * 
     */
    private static final long serialVersionUID = 4508589028039107867L;
    private static final String MSG = "No such version are defined in the source JSON";

    public NoSuchVersionException(String json, String last_op, String this_op, String next_op, String source,
            String version) {
        super(json, last_op, this_op, next_op, MSG + ": " + source + "/" + version, false);
    }

    public NoSuchVersionException(String json, String last_op, String this_op, String next_op, String arg0,
            String source, String version) {
        super(json, last_op, this_op, next_op, MSG + ": " + source + "/" + version, false, arg0);
    }

    public NoSuchVersionException(String json, String last_op, String this_op, String next_op, Throwable arg0,
            String source, String version) {
        super(json, last_op, this_op, next_op, MSG + ": " + source + "/" + version, false, arg0);
    }

    public NoSuchVersionException(String json, String last_op, String this_op, String next_op, String arg0,
            Throwable arg1, String source, String version) {
        super(json, last_op, this_op, next_op, MSG + ": " + source + "/" + version, false, arg0, arg1);
    }

    public NoSuchVersionException(String json, String last_op, String this_op, String next_op, String arg0,
            Throwable arg1, boolean arg2, boolean arg3, String source, String version) {
        super(json, last_op, this_op, next_op, MSG + ": " + source + "/" + version, false, arg0, arg1, arg2, arg3);
    }

}
osumer-lib/src/main/java/com/github/mob41/osumer/queue/QueueStatus.java
public class QueueStatus implements Serializable{

    private String title;
    
    private String fileName;
    
    private String thumbUrl;
    
    private int progress;
    
    private long eta;
    
    private long elapsed;
    
    private int status;

    public QueueStatus(String title, String fileName, String thumbUrl, int progress, long eta, long elapsed, int status) {
        super();
        this.title = title;
        this.fileName = fileName;
        this.thumbUrl = thumbUrl;
        this.progress = progress;
        this.eta = eta;
        this.elapsed = elapsed;
        this.status = status;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getThumbUrl() {
        return thumbUrl;
    }

    public void setThumbUrl(String thumbUrl) {
        this.thumbUrl = thumbUrl;
    }

    public int getProgress() {
        return progress;
    }

    public void setProgress(int progress) {
        this.progress = progress;
    }

    public long getEta() {
        return eta;
    }

    public void setEta(int eta) {
        this.eta = eta;
    }

    public long getElapsed() {
        return elapsed;
    }

    public void setElapsed(int elapsed) {
        this.elapsed = elapsed;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }
    
    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

}
osumer-lib/src/main/java/com/github/mob41/osumer/io/URLDownloader.java
public class URLDownloader extends Downloader {

    private static final int MAX_BUFFER_SIZE = 1024;

    private final URL url;

    private final String folder;

    private final String fileName;

    private int size = -1;

    private int downloaded = 0;

    private int status = -1;

	private DebugDump dump;

    public URLDownloader(String downloadFolder, String fileName, URL downloadUrl) {
        this.url = downloadUrl;
        this.folder = downloadFolder;
        this.fileName = fileName;
    }

    public String getDownloadFolder() {
        return folder;
    }

    public String getFileName() {
        return fileName;
    }

    public String getUrl() {
        return url.toString();
    }

    public int getDownloaded() {
        return downloaded;
    }

    public int getSize() {
        return size;
    }

    public float getProgress() {
        return ((float) downloaded / size) * 100;
    }

    public int getStatus() {
        return status;
    }

    public void pause() {
        status = PAUSED;
        reportState();
    }

    public void resume() {
        status = DOWNLOADING;
        reportState();
        download();
    }

    public void cancel() {
        status = CANCELLED;
        reportState();

        File file = new File(folder + "\\" + fileName + ".osz");
        file.delete();

        downloaded = 0;
    }

    private void error() {
        status = ERROR;
        reportState();
    }

    public void download() {
        if (status != DOWNLOADING && status != COMPLETED) {
            status = DOWNLOADING;
            Thread thread = new Thread(this);
            thread.start();
        }
    }

    @Override
    public void run() {
        RandomAccessFile file = null;
        InputStream in = null;

        try {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            conn.setRequestProperty("Range", "bytes=" + downloaded + "-");

            conn.connect();

            if (conn.getResponseCode() / 100 != 2) {
                error();
            }

            int len = conn.getContentLength();

            if (len < 1) {
                error();
            }

            if (size == -1) {
                size = len;
                reportState();
            }

            file = new RandomAccessFile(folder + "\\" + fileName, "rw");
            file.seek(downloaded);

            in = conn.getInputStream();

            while (status == DOWNLOADING) {
                if (size == downloaded) {
                    break;
                }

                byte[] buffer = size - downloaded > MAX_BUFFER_SIZE ? new byte[MAX_BUFFER_SIZE]
                        : new byte[size - downloaded];
                int read = in.read(buffer);
                if (read == -1) {
                    break;
                }

                file.write(buffer, 0, read);
                downloaded += read;
                reportState();
            }

            if (status == DOWNLOADING) {
                if (file != null) {
                    try {
                        file.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                
                status = COMPLETED;
                
                reportState();
            }
        } catch (IOException e) {
            DumpManager.addDump(dump = new DebugDump(null, "(Try&catch try)", "Error reporting and debug dump",
                    "(Try&catch finally)", "Error when downloading", false, e));
            error();
        } finally {
            if (file != null) {
                try {
                    file.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void reportState() {
        setChanged();
        notifyObservers();
    }

	@Override
	public DebugDump getErrorDump() {
		return dump;
	}

}
osumer-lib/src/main/java/com/github/mob41/osumer/io/OsuDownloader.java
public class OsuDownloader extends Downloader {

    private static final int MAX_BUFFER_SIZE = 1024;

    private final URL url;

    private final Osums osu;

    private final String folder;

    private final String fileName;

    private int size = -1;

    private int downloaded = 0;

    private int status = -1;

	private DebugDump dump;

    public OsuDownloader(String downloadFolder, String fileName, Osums osu, URL downloadUrl) {
        this.url = downloadUrl;
        this.osu = osu;
        this.folder = downloadFolder;
        this.fileName = fileName;
    }

    public String getDownloadFolder() {
        return folder;
    }

    public String getFileName() {
        return fileName;
    }

    public String getUrl() {
        return url.toString();
    }

    public int getDownloaded() {
        return downloaded;
    }

    public int getSize() {
        return size;
    }

    public float getProgress() {
        return ((float) downloaded / size) * 100;
    }

    public int getStatus() {
        return status;
    }

    public void pause() {
        status = PAUSED;
        reportState();
    }

    public void resume() {
        status = DOWNLOADING;
        reportState();
        download();
    }

    public void cancel() {
        status = CANCELLED;
        reportState();

        File file = new File(folder + "\\" + fileName + ".osz");
        file.delete();

        downloaded = 0;
    }

    private void error() {
    	System.out.println("Errored!!");
        status = ERROR;
        reportState();
    }

    public void download() {
        if (status != DOWNLOADING && status != COMPLETED) {
            status = DOWNLOADING;
            Thread thread = new Thread(this);
            thread.start();
            reportState();
        }
    }

    @Override
    public void run() {
        RandomAccessFile file = null;
        InputStream in = null;

        try {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            conn.setRequestProperty("Range", "bytes=" + downloaded + "-");

            osu.setCookies(conn);

            conn.connect();
            
            int code = conn.getResponseCode() / 100;
            if (code < 2 && code > 3) {
                error();
            }

            int len = conn.getContentLength();

            if (len < 1) {
                error();
            }

            if (size == -1) {
                size = len;
                reportState();
            }

            file = new RandomAccessFile(folder + "\\" + fileName + ".osz", "rw");
            file.seek(downloaded);

            in = conn.getInputStream();

            while (status == DOWNLOADING) {
                byte[] buffer = size - downloaded > MAX_BUFFER_SIZE ? new byte[MAX_BUFFER_SIZE]
                        : new byte[size - downloaded];
                int read = in.read(buffer);
                if (read == -1) {
                    break;
                }

                file.write(buffer, 0, read);
                downloaded += read;
                reportState();
            }

            if (status == DOWNLOADING) {
                status = COMPLETED;
                reportState();
            }
        } catch (IOException e) {
        	e.printStackTrace();
            DumpManager.addDump(dump = new DebugDump(null, "(Try&catch try)", "Error reporting and debug dump",
                    "(Try&catch finally)", "Error when downloading", false, e));
            error();
        } finally {
            if (file != null) {
                try {
                    file.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void reportState() {
        setChanged();
        notifyObservers();
    }

    private static String join(String separator, List<HttpCookie> objs) {
        String out = "";

        String str;
        for (int i = 0; i < objs.size(); i++) {
            str = objs.get(i).toString();

            out += str + separator;

            if (i != objs.size() - 1) {
                out += " ";
            }
        }

        return out;
    }

	@Override
	public DebugDump getErrorDump() {
		return dump;
	}

}
osumer-lib/src/main/java/com/github/mob41/osumer/exceptions/NoBuildsForVersionException.java
public class NoBuildsForVersionException extends WithDumpException {

    /**
     * 
     */
    private static final long serialVersionUID = 4508589028039107867L;
    private static final String MSG = "No builds are defined in the version JSON.";

    public NoBuildsForVersionException(String json, String last_op, String this_op, String next_op) {
        super(json, last_op, this_op, next_op, MSG, false);
    }

    public NoBuildsForVersionException(String json, String last_op, String this_op, String next_op, String arg0) {
        super(json, last_op, this_op, next_op, MSG, false, arg0);
    }

    public NoBuildsForVersionException(String json, String last_op, String this_op, String next_op, Throwable arg0) {
        super(json, last_op, this_op, next_op, MSG, false, arg0);
    }

    public NoBuildsForVersionException(String json, String last_op, String this_op, String next_op, String arg0,
            Throwable arg1) {
        super(json, last_op, this_op, next_op, MSG, false, arg0, arg1);
    }

    public NoBuildsForVersionException(String json, String last_op, String this_op, String next_op, String arg0,
            Throwable arg1, boolean arg2, boolean arg3) {
        super(json, last_op, this_op, next_op, MSG, false, arg0, arg1, arg2, arg3);
    }

}
osumer-lib/src/main/java/com/github/mob41/osumer/Osumer.java
public class Osumer {

    // TODO: Hard-code version?

    public static final String OSUMER_VERSION = "2.0.0";

    public static final String OSUMER_BRANCH = "snapshot";

    public static final int OSUMER_BUILD_NUM = 10;
    
    public static String getVersionString() {
    	return OSUMER_VERSION + "-" + OSUMER_BRANCH + "-b" + OSUMER_BUILD_NUM;
    }

    private Osumer() {
    
    }

    public static boolean isWindows() {
        return System.getProperty("os.name").contains("Windows");
    }

    public static boolean isWindowsElevated() {
        if (!isWindows()) {
            return false;
        }

        final String programfiles = System.getenv("PROGRAMFILES");

        if (programfiles == null || programfiles.length() < 1) {
            throw new IllegalStateException("OS mismatch. Program Files directory not detected");
        }

        File testPriv = new File(programfiles);
        if (!testPriv.canWrite()) {
            return false;
        }
        File fileTest = null;

        try {
            fileTest = File.createTempFile("testsu", ".dll", testPriv);
        } catch (IOException e) {
            return false;
        } finally {
            if (fileTest != null) {
                fileTest.delete();
            }
        }
        return true;
    }

    public static int updateSourceStrToInt(String branchStr) {
        if (branchStr.equals("snapshot")) {
            return 2;
        } else if (branchStr.equals("beta")) {
            return 1;
        } else if (branchStr.equals("stable")) {
            return 0;
        }
        return -1;
    }

}
osumer-lib/src/main/java/com/github/mob41/osumer/updater/Announcement.java
public class Announcement {
	
	private final Calendar time;
	
	private final String text;
	
	public Announcement(Calendar time, String text) {
		this.time = time;
		this.text = text;
	}

	public Calendar getTime() {
		return time;
	}

	public String getText() {
		return text;
	}

}
osumer-debugdump/src/main/java/com/github/mob41/osumer/debug/DebugDump.java
public class DebugDump {
	
	private final String os;
	
	private final String osumerVersion;
	
	private final String debuggerVersion;
	
	private final String message;
	
	private final String uid;
	
	private final long generated;
	
	private final boolean containsPrivateData;
	
	private final String generated_human;
	
	private final String stacktrace;
	
	private final String specified_data;
	
	private final String exceptionClass;
	
	private final String lastoperation;
	
	private final String thisoperation;
	
	private final String nextoperation;
	
	public DebugDump(String specified_data, String last_op, String this_op, String next_op, boolean containsPrivateData, String message){
		this(specified_data, last_op, this_op, next_op, message, containsPrivateData, null);
	}
	
	public DebugDump(String specified_data, String last_op, String this_op, String next_op, String message, boolean containsPrivateData, Throwable e) {
		os = System.getProperty("os.name");
		osumerVersion = DumpManager.getOsumerVersion();
		debuggerVersion = DumpManager.getDebuggerVersion();
		
		Calendar cal = Calendar.getInstance();
		
		byte[] randuid = new byte[128/8];
		new SecureRandom().nextBytes(randuid);
		uid = Hex.encodeHexString(randuid);
		
		generated = cal.getTimeInMillis();
		generated_human = cal.getTime().toString();
		
		if (e != null){
			StringWriter sw = new StringWriter();
			PrintWriter pw = new PrintWriter(sw);
			e.printStackTrace(pw);
			pw.close();
			
			stacktrace = sw.toString();	
			exceptionClass = e.getClass().getCanonicalName();
		} else {
			stacktrace = null;
			exceptionClass = null;
		}
		
		this.message = message;
		
		this.containsPrivateData = containsPrivateData;
		this.specified_data = specified_data;
		this.lastoperation = last_op;
		this.thisoperation = this_op;
		this.nextoperation = next_op;
		
		try {
			DumpManager.writeDump(this);
		} catch (IOException e1) {
			e1.printStackTrace();
			System.err.println("DumpManager: Error writing dump");
		}
	}
	
	public String getOs(){
		return os;
	}
	
	public long getGenerated(){
		return generated;
	}
	
	@Override
	public String toString(){
		return 	"===!===!===!===!-osumer-debugger-dump-!===!===!===!===\n" +
				"Debug information generated on " + generated_human + " (" + generated + " ms)\n" +
				"osumerDebugger v." + debuggerVersion + "\n" +
				"osumer v." + osumerVersion + "\n" +
				"\n" +
				"O/S Name: " + os + "\n" +
				"\n" +
				"Current operation: " + thisoperation + "\n" +
				"Returned message: " + message + "\n" +
				"\n" +
				"Last operation: " + lastoperation + "\n" +
				"Next operation: " + nextoperation + "\n" +
				"\n" +
				"Data specified: " + (containsPrivateData ? "(Contains private data)" : specified_data) + "\n" +
				"\n" +
				"Exception stacktrace: " + (stacktrace == null ? "(Does not contain exception)" : "\n" + stacktrace) + "\n" +
				"===!===!===!===!-dump-end-!===!===!===!===\n";
	}

	public String getOsumerVersion() {
		return osumerVersion;
	}

	public String getDebuggerVersion() {
		return debuggerVersion;
	}

	public String getMessage() {
		return message;
	}

	public String getUid() {
		return uid;
	}

	public boolean isContainsPrivateData() {
		return containsPrivateData;
	}

	public String getGeneratedHuman() {
		return generated_human;
	}

	public String getStacktrace() {
		return stacktrace;
	}

	public String getSpecifiedData() {
		return specified_data;
	}

	public String getLastOperation() {
		return lastoperation;
	}

	public String getThisOperation() {
		return thisoperation;
	}

	public String getNextOperation() {
		return nextoperation;
	}

	public String getExceptionClass() {
		return exceptionClass;
	}

}
osumer-lib/src/main/java/com/github/mob41/osumer/updater/AnnouncementChecker.java
public class AnnouncementChecker {
	
    private static final String ANNOUNCEMENT_LIST = "https://mob41.github.io/osumer-updater/announcements.json";
    
    public Announcement[] getAnnouncements() throws WithDumpException{
        URL url = null;
        
        try {
            url = new URL(ANNOUNCEMENT_LIST + "?update=" + Calendar.getInstance().getTimeInMillis());
        } catch (MalformedURLException e){
            throw new WithDumpException(
            		ANNOUNCEMENT_LIST,
                    "URL url = null;",
                    "new URL(VERSION_LIST);",
                    "URLConnection conn = url.openConnection();",
                    "",
                    false, e);
        }
        
        String data = "";
        try {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Connection", "close");
            conn.setUseCaches(false);
            conn.setConnectTimeout(10000);
            conn.setReadTimeout(10000);
            
            InputStream in = conn.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            
            String line;
            while ((line = reader.readLine()) != null){
                data += line;
            }
        } catch (IOException e) {
            throw new WithDumpException(
            		ANNOUNCEMENT_LIST,
                    "URL url = new URL(VERSION_LIST);",
                    "(lots of code) -- Connecting and fetch data",
                    "Data validating (isEmpty / null)",
                    "",
                    false, e);
        }
        
        if (data == null || data.isEmpty()){
            throw new WithDumpException(
            		ANNOUNCEMENT_LIST,
                    "(lots of code) -- Connecting and fetch data",
                    "Data validating (isEmpty / null)",
                    "Create JSONObject",
                    "No data fetched. \"data\" is null/isEmpty",
                    false);
        }
        
        JSONObject json = null;
        try {
            json = new JSONObject(data);
        } catch (JSONException e){
            throw new WithDumpException(
            		ANNOUNCEMENT_LIST,
                    "Data validating (isEmpty / null)",
                    "Create JSONObject",
                    "JSONObject validating \"announcements\" parameter",
                    "Structure invalid",
                    false, e);
        }
        
        if (json.isNull("announcements")){
            throw new WithDumpException(
            		ANNOUNCEMENT_LIST,
                    "Create JSONObject",
                    "JSONObject validating \"sources\" parameter",
                    "Parsing announcement json to objects",
                    "Structure invalid, missing \"announcements\" parameter",
                    false);
        }
        
        JSONArray arr = json.getJSONArray("announcements");
        
        Calendar cal;
        JSONObject obj;
        Announcement[] out = new Announcement[arr.length()];
        for (int i = 0; i < out.length; i++) {
        	obj = arr.getJSONObject(i);
        	if (obj.isNull("time") || obj.isNull("text")) {
                throw new WithDumpException(
                		data,
                        "JSONObject validating \"sources\" parameter",
                        "Parsing announcement json to objects",
                        "Output array",
                        "Structure invalid, announcement json missing some parameters",
                        false);
        	}
        	cal = Calendar.getInstance();
        	cal.setTimeInMillis(obj.getLong("time"));
        	out[i] = new Announcement(cal, obj.getString("text"));
        }
        
        return out;
    }

}
osumer-lib/src/main/java/com/github/mob41/osumer/updater/UpdateInfo.java
public class UpdateInfo {

    private final String version;

    private final String webLink;

    private final String exeLink;

    private final String jarLink;

    private final String description;

    private final int branch;

    private final int buildNum;

    private final boolean isThisVersion;

    private final boolean upgradedVersion;

    public UpdateInfo(String description, String version, int branch, int buildNum, String webLink, String exeLink,
            String jarLink, boolean isThisVersion, boolean upgradedVersion) {
        this.description = description;
        this.version = version;
        this.webLink = webLink;
        this.exeLink = exeLink;
        this.jarLink = jarLink;
        this.branch = branch;
        this.buildNum = buildNum;
        this.isThisVersion = isThisVersion;
        this.upgradedVersion = upgradedVersion;
    }

    public String getVersion() {
        return version;
    }

    public int getBranch() {
        return branch;
    }

    public int getBuildNum() {
        return buildNum;
    }

    public String getDescription() {
        return description;
    }

    public boolean isThisVersion() {
        return isThisVersion;
    }

    public boolean isUpgradedVersion() {
        return upgradedVersion;
    }

    public String getExeLink() {
        return exeLink;
    }

    public String getJarLink() {
        return jarLink;
    }

    public String getWebLink() {
        return webLink;
    }

}
osumer-lib/src/main/java/com/github/mob41/osumer/updater/Updater.java
public class Updater {

    public static final int UPDATE_SOURCE_STABLE = 0;

    public static final int UPDATE_SOURCE_BETA = 1;

    public static final int UPDATE_SOURCE_SNAPSHOT = 2;

    public static final int DEFAULT_UPDATE_SOURCE = UPDATE_SOURCE_SNAPSHOT; // Only
                                                                            // applies
                                                                            // on
                                                                            // snapshot
                                                                            // branch

    // This checksum is to prevent if my account is hacked and the updater
    // executable is changed, the updater won't run.
    // private static final String LEGACY_UPDATER_MD5_CHECKSUM = "";

    private static final String VERSION_LIST = "https://mob41.github.io/osumer-updater/versions.json";

    public static final String LEGACY_UPDATER_JAR = "https://github.com/mob41/osumer-updater/releases/download/legacy/osumer-updater.exe";

    private static final String SOURCE_SNAPSHOT = "snapshot";

    private static final String SOURCE_BETA = "beta";

    private static final String SOURCE_STABLE = "stable";

    public static final int CHECKSUM_FAILED = -3;

    public static final int NO_DATA = -2;

    public static final int CONN_FAILED = -1;

    public static final int NO_UPDATE = 0;

    public static final int UPDATE_NEEDED = 1;

    public static final int TOO_OLD = 2;

    // private OsuDownloader dwn = null;

    private Configuration config;

    public Updater(Configuration config) {
        this.config = config;
    }

    public String getThisVersion() {
        return Osumer.OSUMER_VERSION;
    }
    
    public JSONObject getVersions() throws WithDumpException{
        URL url = null;
        
        try {
            url = new URL(VERSION_LIST + "?update=" + Calendar.getInstance().getTimeInMillis());
        } catch (MalformedURLException e){
            throw new WithDumpException(
                    VERSION_LIST,
                    "URL url = null;",
                    "new URL(VERSION_LIST);",
                    "URLConnection conn = url.openConnection();",
                    "",
                    false, e);
        }
        
        String data = "";
        try {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            
            conn.setRequestMethod("GET");
            conn.setRequestProperty("Connection", "close");
            conn.setUseCaches(false);
            conn.setConnectTimeout(10000);
            conn.setReadTimeout(10000);
            
            InputStream in = conn.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            
            String line;
            while ((line = reader.readLine()) != null){
                data += line;
            }
        } catch (IOException e) {
            throw new WithDumpException(
                    VERSION_LIST,
                    "URL url = new URL(VERSION_LIST);",
                    "(lots of code) -- Connecting and fetch data",
                    "Data validating (isEmpty / null)",
                    "",
                    false, e);
        }
        
        if (data == null || data.isEmpty()){
            throw new WithDumpException(
                    VERSION_LIST,
                    "(lots of code) -- Connecting and fetch data",
                    "Data validating (isEmpty / null)",
                    "Create JSONObject",
                    "No data fetched. \"data\" is null/isEmpty",
                    false);
        }
        
        JSONObject json = null;
        try {
            json = new JSONObject(data);
        } catch (JSONException e){
            throw new WithDumpException(
                    VERSION_LIST,
                    "Data validating (isEmpty / null)",
                    "Create JSONObject",
                    "JSONObject validating \"sources\" parameter",
                    "Structure invalid",
                    false, e);
        }
        return json;
    }
    
    public UpdateInfo getLatestVersion() throws WithDumpException{
        final int updateSource = config.getUpdateSource();
        JSONObject json = getVersions();
        
        if (json.isNull("sources")){
            throw new WithDumpException(
                    VERSION_LIST,
                    "Create JSONObject",
                    "JSONObject validating \"sources\" parameter",
                    "Convert source integer to string",
                    "Structure invalid, missing \"sources\" parameter",
                    false);
        }
        
        // sources -> snapshot (updateSource) -> 1.0.0 (version) -> (array: index0) 1 (build-number)
        JSONObject sourcesJson = json.getJSONObject("sources");
        JSONArray buildsArr;
        
        String sourceKey = null;
        switch (updateSource){
        case UPDATE_SOURCE_SNAPSHOT:
            sourceKey = SOURCE_SNAPSHOT;
            break;
        case UPDATE_SOURCE_BETA:
            sourceKey = SOURCE_BETA;
            break;
        case UPDATE_SOURCE_STABLE:
            sourceKey = SOURCE_STABLE;
            break;
        default:
            throw new InvalidSourceIntegerException(
                    json.toString(5),
                    "JSONObject validating \"sources\" parameter",
                    "Convert source integer to string",
                    "Validate sources' JSONObject and set variable",
                    updateSource);
        }
        
        if (sourcesJson.isNull(sourceKey)){
            throw new NoSuchSourceException(
                    json.toString(5),
                    "Convert source integer to string",
                    "Validate sources' JSONObject and set variable",
                    "Validate versions' JSONObject and set variable",
                    sourceKey);
        }
        
        JSONObject versionsJson = sourcesJson.getJSONObject(sourceKey);
        
        VersionInfo thisInfo = new VersionInfo(Osumer.OSUMER_VERSION, Osumer.OSUMER_BRANCH, Osumer.OSUMER_BUILD_NUM);
        
        Iterator<String> it = versionsJson.keys();
        String last = null;
        String key;
        while (it.hasNext()){
            key = it.next();
            
            if (last != null && compareVersion(key, last) != 1){
                continue;
            } else {
                last = key;
            }
        }
        
        if (last == null){
            return null;
        }
        
        buildsArr = versionsJson.getJSONArray(last);
        
        int latest = buildsArr.length();
        
        JSONObject verJson = buildsArr.getJSONObject(latest - 1);
        
        String webLink = verJson.isNull("web_link") ? null : verJson.getString("web_link");
        String exeLink = verJson.isNull("exe_link") ? null : verJson.getString("exe_link");
        String jarLink = verJson.isNull("jar_link") ? null : verJson.getString("jar_link");

        String desc = verJson.isNull("desc") ? null : verJson.getString("desc");
        
        boolean isThisVersion = thisInfo != null && last.equals(thisInfo.getVersion()) &&
                latest == thisInfo.getBuildNum() && getBranchStr(updateSource).equals(thisInfo.getBranch());
        if (thisInfo != null) {
            System.out.println(last + " to " + thisInfo.getVersion());
            System.out.println(latest + " to " + thisInfo.getBuildNum());
            System.out.println(getBranchStr(updateSource) + " to " + thisInfo.getBranch());
        } else {
            System.out.println("ThisInfoNull");
        }
        System.out.println("IsThisVer: " + isThisVersion);
        return new UpdateInfo(desc, last, updateSource, latest, webLink, exeLink, jarLink, isThisVersion, !isThisVersion);
    }

    public UpdateInfo getPerVersionPerBranchLatestVersion() throws WithDumpException {
        final String thisVersion = Osumer.OSUMER_VERSION;
        final String buildBranch = Osumer.OSUMER_BRANCH;
        final int buildNum = Osumer.OSUMER_BUILD_NUM;
        final int updateSource = config.getUpdateSource();
        
        JSONObject json = getVersions();

        if (json.isNull("sources")) {
            throw new WithDumpException(VERSION_LIST, "Create JSONObject",
                    "JSONObject validating \"sources\" parameter", "Convert source integer to string",
                    "Structure invalid, missing \"sources\" parameter", false);
        }

        // sources -> snapshot (updateSource) -> 1.0.0 (version) -> (array:
        // index0) 1 (build-number)
        JSONObject sourcesJson = json.getJSONObject("sources");
        JSONArray buildsArr;

        String sourceKey = null;
        switch (updateSource) {
        case UPDATE_SOURCE_SNAPSHOT:
            sourceKey = SOURCE_SNAPSHOT;
            break;
        case UPDATE_SOURCE_BETA:
            sourceKey = SOURCE_BETA;
            break;
        case UPDATE_SOURCE_STABLE:
            sourceKey = SOURCE_STABLE;
            break;
        default:
            throw new InvalidSourceIntegerException(json.toString(5), "JSONObject validating \"sources\" parameter",
                    "Convert source integer to string", "Validate sources' JSONObject and set variable", updateSource);
        }

        if (sourcesJson.isNull(sourceKey)) {
            throw new NoSuchSourceException(json.toString(5), "Convert source integer to string",
                    "Validate sources' JSONObject and set variable", "Validate versions' JSONObject and set variable",
                    sourceKey);
        }

        JSONObject versionsJson = sourcesJson.getJSONObject(sourceKey);

        if (versionsJson.isNull(thisVersion)) {
            throw new NoSuchVersionException(json.toString(5), "Validate sources' JSONObject and set variable",
                    "Validate versions' JSONObject and set variable", "Validate builds JSONArray and set variable",
                    sourceKey, thisVersion);
        }

        buildsArr = versionsJson.getJSONArray(thisVersion);

        if (buildsArr.length() < buildNum) {
            throw new NoSuchBuildNumberException(json.toString(5), "Validate versions' JSONObject and set variable",
                    "Validate builds JSONArray and set variable", "Get latest build number from JSONArray", buildNum);
        }

        int latest = buildsArr.length();

        JSONObject verJson = buildsArr.getJSONObject(latest - 1);

        if ((verJson.isNull("ended") || !verJson.getBoolean("ended"))
                && (!buildBranch.equals(sourceKey) || latest != buildNum)) {
            String webLink = verJson.isNull("web_link") ? null : verJson.getString("web_link");
            String exeLink = verJson.isNull("exe_link") ? null : verJson.getString("exe_link");
            String jarLink = verJson.isNull("jar_link") ? null : verJson.getString("jar_link");
            String desc = verJson.isNull("desc") ? null : verJson.getString("desc");
            return new UpdateInfo(desc, thisVersion, updateSource, latest, webLink, exeLink, jarLink, false, false);
        }

        // As the version is ended, we are finding a new version here
        Iterator<String> it = versionsJson.keys();
        String key;
        String upgradeNode = null;
        while (it.hasNext()) {
            key = it.next();
            switch (compareVersion(thisVersion, key)) {
            case -2:
                break;
            case -1:
                if ((upgradeNode != null && compareVersion(upgradeNode, key) == -1) || upgradeNode == null) {
                    upgradeNode = key;
                }
                break;
            case 0:
                break;
            case 1:
                break;
            }
        }

        JSONArray upgradeNodeArr = null;
        int upgradedBuildNum = -1;

        if (upgradeNode != null) {
            upgradeNodeArr = versionsJson.getJSONArray(upgradeNode);
            upgradedBuildNum = upgradeNodeArr.length();
        }

        if (upgradedBuildNum == 0) {
            throw new NoBuildsForVersionException(json.toString(5), "Get Upgrade Node", "Validate build info JSON",
                    "Return VersionInfo");
        }

        if (upgradeNode != null && upgradedBuildNum != -1) {
            JSONObject upgradedVerJson = upgradeNodeArr.getJSONObject(upgradedBuildNum - 1);

            String webLink = upgradedVerJson.isNull("web_link") ? null : upgradedVerJson.getString("web_link");
            String exeLink = upgradedVerJson.isNull("exe_link") ? null : upgradedVerJson.getString("exe_link");
            String jarLink = upgradedVerJson.isNull("jar_link") ? null : upgradedVerJson.getString("jar_link");

            String desc = upgradedVerJson.isNull("desc") ? null : upgradedVerJson.getString("desc");
            return new UpdateInfo(desc, upgradeNode, updateSource, upgradedBuildNum, webLink, exeLink, jarLink, false,
                    true);
        } else {
            String webLink = verJson.isNull("web_link") ? null : verJson.getString("web_link");
            String exeLink = verJson.isNull("exe_link") ? null : verJson.getString("exe_link");
            String jarLink = verJson.isNull("jar_link") ? null : verJson.getString("jar_link");

            String desc = verJson.isNull("desc") ? null : verJson.getString("desc");
            return new UpdateInfo(desc, thisVersion, updateSource, buildNum, webLink, exeLink, jarLink, true, false);
        }
    }

    public boolean isUpdateAvailable() throws WithDumpException {
        UpdateInfo latestVer = getLatestVersion();
        return latestVer != null && !latestVer.isThisVersion();
    }

    public static String getUpdaterLink() throws WithDumpException {
        URL url = null;

        try {
            url = new URL(VERSION_LIST + "?update=" + Calendar.getInstance().getTimeInMillis());
        } catch (MalformedURLException e) {
            throw new WithDumpException(VERSION_LIST, "URL url = null;", "new URL(VERSION_LIST);",
                    "URLConnection conn = url.openConnection();", "", false, e);
        }

        String data = "";
        try {
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            conn.setRequestMethod("GET");
            conn.setRequestProperty("Connection", "close");
            conn.setUseCaches(false);
            conn.setConnectTimeout(10000);
            conn.setReadTimeout(10000);

            InputStream in = conn.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));

            String line;
            while ((line = reader.readLine()) != null) {
                data += line;
            }
        } catch (IOException e) {
            throw new WithDumpException(VERSION_LIST, "URL url = new URL(VERSION_LIST);",
                    "(lots of code) -- Connecting and fetch data", "Data validating (isEmpty / null)", "", false, e);
        }

        if (data == null || data.isEmpty()) {
            throw new WithDumpException(VERSION_LIST, "(lots of code) -- Connecting and fetch data",
                    "Data validating (isEmpty / null)", "Create JSONObject",
                    "No data fetched. \"data\" is null/isEmpty", false);
        }

        JSONObject json = null;
        try {
            json = new JSONObject(data);
        } catch (JSONException e) {
            throw new WithDumpException(VERSION_LIST, "Data validating (isEmpty / null)", "Create JSONObject",
                    "JSON stuff", "Structure invalid", false, e);
        }

        return json.isNull("updater_exe_link") ? null : json.getString("updater_exe_link");
    }

    // TODO: Implement updater
    public int getUpdate(UpdateInfo verInfo) {
        return NO_DATA;
    }

    /**
     * Returns 1 if ver0 newer than ver1<br>
     * Returns -1 if ver0 older than ver1<br>
     * Returns -2 if ver0 or ver1 are not a versionNode<br>
     * Returns 0 if they are the same<br>
     */
    public static int compareVersion(String ver0, String ver1) {
        int[] ver0sub = separateVersion(ver0);
        int[] ver1sub = separateVersion(ver1);

        if (ver0sub == null || ver1sub == null) {
            return -2;
        }

        return compareVersion(ver0sub[0], ver0sub[1], ver0sub[2], ver1sub[0], ver1sub[1], ver1sub[2]);
    }

    public static String getBranchStr(int updateSource) {
        switch (updateSource) {
        case UPDATE_SOURCE_SNAPSHOT:
            return SOURCE_SNAPSHOT;
        case UPDATE_SOURCE_BETA:
            return SOURCE_BETA;
        case UPDATE_SOURCE_STABLE:
            return SOURCE_STABLE;
        default:
            return null;
        }
    }

    /**
     * Returns 1 if ver0 newer than ver1<br>
     * Returns -1 if ver0 older than ver1<br>
     * Returns 0 if they are the same<br>
     */
    public static int compareVersion(int ver0sub0, int ver0sub1, int ver0sub2, int ver1sub0, int ver1sub1,
            int ver1sub2) {
        if (ver0sub0 > ver1sub0) {
            return 1;
        } else if (ver0sub0 < ver1sub0) {
            return -1;
        }

        if (ver0sub1 > ver1sub1) {
            return 1;
        } else if (ver0sub1 < ver1sub1) {
            return -1;
        }

        if (ver0sub2 > ver1sub2) {
            return 1;
        } else if (ver0sub2 < ver1sub2) {
            return -1;
        }

        return 0;
    }

    public static int[] separateVersion(String versionNode) {
        int[] out = new int[3];

        String str;
        int outIndex = 0;
        int tmp = 0;
        for (int i = 0; i <= versionNode.length(); i++) {
            if (outIndex >= 3) {
                return null;
            }

            if (versionNode.length() == i || versionNode.charAt(i) == '.') {
                str = versionNode.substring(tmp, i);
                tmp = i + 1;

                try {
                    out[outIndex] = Integer.parseInt(str);
                } catch (NumberFormatException e) {
                    return null;
                }

                outIndex++;
            }
        }
        return out;
    }

}
osumer-lib/src/main/java/com/github/mob41/osumer/Configuration.java
public class Configuration {
    
    public static final String CHECK_UPDATE_FREQ_EVERY_STARTUP = "everyStartup";
    
    public static final String CHECK_UPDATE_FREQ_EVERY_ACT = "everyAct";
    
    public static final String CHECK_UPDATE_FREQ_NEVER = "never";
    
    public static final String CHECK_UPDATE_ALGO_PER_VER_PER_BRANCH = "perVerPerBranch";
            
    public static final String CHECK_UPDATE_ALGO_LATEST_VER_PER_BRANCH = "latestVerPerBranch";
    
    public static final String CHECK_UPDATE_ALGO_LATEST_VER_OVERALL = "latestVerOverall";
    
    public static final String CHECK_UPDATE_ALGO_STABLITY = "stablity";

    public static final String DEFAULT_DATA_FILE_NAME = "osumer_configuration.json";

    private static final String KEY_UPDATE_SOURCE = "updateSource";

    private static final int KEY_UPDATE_SOURCE_DEFAULT_VALUE = Osumer.updateSourceStrToInt(Osumer.OSUMER_BRANCH);
    
    private static final String KEY_UI_SKIN = "uiSkin";
    
    private static final String KEY_UI_SKIN_DEFAULT_VALUE = "dark";

    private static final String KEY_DEFAULT_BROWSER = "defaultBrowser";

    private static final String KEY_DEFAULT_BROWSER_DEFAULT_VALUE = "IEXPLORE.EXE";

    private static final String KEY_USE_CUSTOM_BROWSER = "useCustomBrowser";

    private static final boolean KEY_USE_CUSTOM_BROWSER_DEFAULT_VALUE = false;

    private static final String KEY_CUSTOM_BROWSER = "customBrowser";

    private static final String KEY_CUSTOM_BROWSER_DEFAULT_VALUE = "";
    
    private static final String KEY_DEFAULT_OPEN_BEATMAP_ACTION = "defaultOpenBeatmapAction";
    
    private static final int KEY_DEFAULT_OPEN_BEATMAP_ACTION_DEFAULT_VALUE = 0;
    
    private static final String KEY_DEFAULT_BEATMAP_SAVE_LOCATION = "defaultBeatmapSaveLocation";
    
    private static final String KEY_DEFAULT_BEATMAP_SAVE_LOCATION_DEFAULT_VALUE = System.getenv("USERPROFILE") + "\\osumerDownloads";

    private static final String KEY_OE_ENABLED = "oeenabled";

    private static final boolean KEY_OE_ENABLED_DEFAULT_VALUE = true;

    private static final String KEY_NO_UI_ARG_SWITCH_BROWSER = "switchToBrowserIfWithoutUiArg";

    private static final boolean KEY_NO_UI_ARG_SWITCH_BROWSER_DEFAULT_VALUE = false;
    
    private static final String KEY_OVERLAY_ENABLED = "overlayEnabled";
    
    private static final boolean KEY_OVERLAY_ENABLED_DEFAULT_VALUE = true;
    
    private static final String KEY_OVERLAY_AGREEMENT = "overlayAgreement";
    
    private static final boolean KEY_OVERLAY_AGREEMENT_DEFAULT_VALUE = false;

    private static final String KEY_AUTO_SWITCH_BROWSER = "autoSwitchBrowser";

    private static final boolean KEY_AUTO_SWITCH_BROWSER_DEFAULT_VALUE = true;

    private static final String KEY_GETTING_STARTED_STARTUP = "gettingStartedOnStartup_Version" + Osumer.OSUMER_VERSION
            + "-" + Osumer.OSUMER_BRANCH + "-" + Osumer.OSUMER_BUILD_NUM;

    private static final boolean KEY_GETTING_STARTED_STARTUP_DEFAULT_VALUE = true;

    private static final String KEY_SERVER_PRORITY = "serverPrority";

    private static final String[] KEY_SERVER_PRORITY_DEFAULT_VALUE = { "osu! forum" }; // bloodcat
                                                                                       // not
                                                                                       // available

    private static final String KEY_ENC_ASK_ON_STARTUP = "usrPwdEncAskOnStartup";

    private static final boolean KEY_ENC_ASK_ON_STARTUP_DEFAULT_VALUE = true;

    private static final String KEY_USER = "user";

    private static final String KEY_PASS = "pass";

    private static final String KEY_ENC = "usrPwdEnc";

    private static final boolean KEY_ENC_DEFAULT_VALUE = false;

    private static final String KEY_ENC_SALT = "usrPwdEncSalt";

    private static final String KEY_ENC_IV = "usrPwdEncIv";

    private static final String KEY_RUN_DAEMON_ON_WIN_STARTUP = "runDaemonOnWinStartup";

    private static final boolean KEY_RUN_DAEMON_ON_WIN_STARTUP_DEFAULT_VALUE = true;

    private static final String KEY_DAEMON_DISABLED = "daemonDisabled";

    private static final boolean KEY_DAEMON_DISABLED_DEFAULT_VALUE = false;

    private static final String KEY_QUEUE_MAX_THREADS = "queuingMaxThreads";

    private static final int KEY_QUEUE_MAX_THREADS_DEFAULT_VALUE = 4;

    private static final String KEY_QUEUE_NEXT_CHECK_DELAY = "queueingNextCheckDelay";

    private static final int KEY_QUEUE_NEXT_CHECK_DELAY_DEFAULT_VALUE = 2000;

    private static final String KEY_QUEUE_MASSIVE_DOWNLOADING_THREADS = "queuingRemoveLimit";

    private static final boolean KEY_QUEUE_MASSIVE_DOWNLOADING_THREADS_DEFAULT_VALUE = false;

    private static final String KEY_CHECK_UPDATE_FREQ = "updateFreq";

    private static final String KEY_CHECK_UPDATE_FREQ_DEFAULT_VALUE = "everyAct";

    private static final String KEY_CHECK_UPDATE_ALGO = "updateCompareAlgo";

    private static final String KEY_CHECK_UPDATE_ALGO_DEFAULT_VALUE = "latestVerPerBranch";

    private static final String KEY_AUTO_ACCEPT_CRITICAL_UPDATES = "updateCriticalAuto";

    private static final boolean KEY_AUTO_ACCEPT_CRITICAL_UPDATES_DEFAULT_VALUE = true;

    private static final String KEY_AUTO_DOWNLOAD_APPLY_PATCHES = "updatePatchesAuto";

    private static final boolean KEY_AUTO_DOWNLOAD_APPLY_PATCHES_DEFAULT_VALUE = true;

    private static final String KEY_PLUGIN_SUMS = "plugSums";

    private static final String KEY_ENABLE_TONE_BEFORE_DOWNLOAD = "toneBeforeDownload";

    private static final boolean KEY_ENABLE_TONE_BEFORE_DOWNLOAD_DEFAULT_VALUE = true;
    
    private static final String KEY_SELECTED_TONE_BEFORE_DOWNLOAD = "toneBeforeDownloadPath";
    
    private static final String KEY_SELECTED_TONE_BEFORE_DOWNLOAD_DEFAULT_VALUE = "C:\\Windows\\Media\\Windows Logon.wav";

    private static final String KEY_ENABLE_TONE_AFTER_DOWNLOAD = "toneAfterDownload";

    private static final boolean KEY_ENABLE_TONE_AFTER_DOWNLOAD_DEFAULT_VALUE = true;
    
    private static final String KEY_SELECTED_TONE_AFTER_DOWNLOAD = "toneAfterDownloadPath";
    
    private static final String KEY_SELECTED_TONE_AFTER_DOWNLOAD_DEFAULT_VALUE = "C:\\Windows\\Media\\Windows Background.wav";
    
    private static final String KEY_USE_OLD_PARSER = "useOldParser";
    
    private static final boolean KEY_USE_OLD_PARSER_DEFAULT_VALUE = true;
    
    private static final String KEY_METRICS_ENABLED = "metricsEnabled";
    
    private static final boolean KEY_METRICS_ENABLED_DEFAULT_VALUE = true;

    private JSONObject json;

    private final String dataFilePath;

    private final String dataFileName;

    private String unlockKey = null;

    public Configuration(String dataFilePath, String dataFileName) {
        this.dataFilePath = dataFilePath;
        this.dataFileName = dataFileName;
    }
    
    public void setUpdateSource(int source){
        json.put(KEY_UPDATE_SOURCE, source);
    }

    public int getUpdateSource() {
        return json.getInt(KEY_UPDATE_SOURCE);
    }
    
    public void setDefaultBrowser(String browser){
        json.put(KEY_DEFAULT_BROWSER, browser);
    }

    public String getDefaultBrowser() {
        return json.getString(KEY_DEFAULT_BROWSER);
    }
    
    public void setDefaultOpenBeatmapAction(int action) {
        json.put(KEY_DEFAULT_OPEN_BEATMAP_ACTION, action);
    }
    
    public int getDefaultOpenBeatmapAction() {
        return json.getInt(KEY_DEFAULT_OPEN_BEATMAP_ACTION);
    }
    
    public void setDefaultBeatmapSaveLocation(String location) {
        json.put(KEY_DEFAULT_BEATMAP_SAVE_LOCATION, location);
    }
    
    public String getDefaultBeatmapSaveLocation() {
        return json.getString(KEY_DEFAULT_BEATMAP_SAVE_LOCATION);
    }
    
    public void setOEEnabled(boolean enabled){
        json.put(KEY_OE_ENABLED, enabled);
    }

    public boolean isOEEnabled() {
        return json.getBoolean(KEY_OE_ENABLED);
    }
    
    public void setSwitchToBrowserIfWithoutUiArg(boolean enabled){
        json.put(KEY_NO_UI_ARG_SWITCH_BROWSER, enabled);
    }

    public boolean isSwitchToBrowserIfWithoutUiArg() {
        return json.getBoolean(KEY_NO_UI_ARG_SWITCH_BROWSER);
    }
    
    public void setAutoSwitchBrowser(boolean enabled){
        json.put(KEY_AUTO_SWITCH_BROWSER, enabled);
    }

    public boolean isAutoSwitchBrowser() {
        return json.getBoolean(KEY_AUTO_SWITCH_BROWSER);
    }
    
    public void setShowGettingStartedOnStartup(boolean enabled){
        json.put(KEY_GETTING_STARTED_STARTUP, enabled);
    }

    public boolean isShowGettingStartedOnStartup() {
        return json.getBoolean(KEY_GETTING_STARTED_STARTUP);
    }
    
    public void setServerPrority(String[] strs){
        json.put(KEY_SERVER_PRORITY, strs);
    }

    public String[] getServerPrority() {
        JSONArray arr = json.getJSONArray(KEY_SERVER_PRORITY);
        String[] out = new String[arr.length()];
        for (int i = 0; i < arr.length(); i++) {
            out[i] = arr.getString(i);
        }
        return out;
    }
    
    public void setEncPassAskOnStartup(boolean enabled){
        json.put(KEY_ENC_ASK_ON_STARTUP, enabled);
    }

    public boolean isEncPassAskOnStartup() {
        return json.getBoolean(KEY_ENC_ASK_ON_STARTUP);
    }
    
    public void setUser(String user){
        if (isUserPassEncrypted()){
            //Do enc stuff
        } else {
            json.put(KEY_USER, user);
        }
    }

    public String getUser() {
        String raw = json.getString(KEY_USER);
        if (isUserPassEncrypted()) {
            // do enc stuff
            return null;
        } else {
            return new String(Base64.decodeBase64(raw), StandardCharsets.UTF_8);
        }
    }
    
    public void setPass(String pass){
        if (isUserPassEncrypted()){
            //Do enc stuff
        } else {
            json.put(KEY_PASS, pass);
        }
    }

    public String getPass() {
        String raw = json.getString(KEY_PASS);
        if (isUserPassEncrypted()) {
            // do enc stuff
            return null;
        } else {
            return new String(Base64.decodeBase64(raw), StandardCharsets.UTF_8);
        }
    }
    
    public void setUserPassEncrypted(boolean enabled){
        json.put(KEY_ENC, enabled);
    }

    public boolean isUserPassEncrypted() {
        return json.getBoolean(KEY_ENC);
    }
    
    private void setSalt(String salt){
        json.put(KEY_ENC_SALT, salt);
    }
    
    private String getSalt(){
        return json.getString(KEY_ENC_SALT);
    }
    
    private void setIv(String iv){
        json.put(KEY_ENC_IV, iv);
    }
    
    private String getIv(){
        return json.getString(KEY_ENC_IV);
    }
    
    public void setRunDaemonOnWinStartup(boolean enabled){
        json.put(KEY_RUN_DAEMON_ON_WIN_STARTUP, enabled);
    }
    
    public boolean isRunDaemonOnWinStartup(){
        return json.getBoolean(KEY_RUN_DAEMON_ON_WIN_STARTUP);
    }
    
    public void setDaemonDisabled(boolean disabled){
        json.put(KEY_DAEMON_DISABLED, disabled);
    }
    
    public boolean isDaemonDisabled(){
        return json.getBoolean(KEY_DAEMON_DISABLED);
    }
    
    public void setMaxThreads(int threads){
        json.put(KEY_QUEUE_MAX_THREADS, threads);
    }
    
    public int getMaxThreads(){
        int x = json.getInt(KEY_QUEUE_MAX_THREADS);
        if (x > 8 && !isMassiveDownloadingThreads()){
            return 8;
        } else {
            return x;
        }
    }
    
    public void setNextCheckDelay(int ms){
        json.put(KEY_QUEUE_NEXT_CHECK_DELAY, ms);
    }
    
    public int getNextCheckDelay(){
        return json.getInt(KEY_QUEUE_NEXT_CHECK_DELAY);
    }
    
    public void setMassiveDownloadingThreads(boolean enabled){
        json.put(KEY_QUEUE_MASSIVE_DOWNLOADING_THREADS, enabled);
    }
    
    public boolean isMassiveDownloadingThreads(){
        return json.getBoolean(KEY_QUEUE_MASSIVE_DOWNLOADING_THREADS);
    }
    
    public void setCheckUpdateFreq(String freqStr){
        json.put(KEY_CHECK_UPDATE_FREQ, freqStr);
    }
    
    public String getCheckUpdateFreq(){
        return json.getString(KEY_CHECK_UPDATE_FREQ);
    }
    
    public void setCheckUpdateAlgo(String algoStr){
        json.put(KEY_CHECK_UPDATE_ALGO, algoStr);
    }
    
    public String getCheckUpdateAlgo(){
        return json.getString(KEY_CHECK_UPDATE_ALGO);
    }
    
    public void setAutoAcceptCriticalUpdates(boolean enabled){
        json.put(KEY_AUTO_ACCEPT_CRITICAL_UPDATES, enabled);
    }
    
    public boolean isAutoAcceptCriticalUpdates(){
        return json.getBoolean(KEY_AUTO_ACCEPT_CRITICAL_UPDATES);
    }
    
    public void setAutoDownloadApplyPatches(boolean enabled){
        json.put(KEY_AUTO_DOWNLOAD_APPLY_PATCHES, enabled);
    }
    
    public boolean isAutoDownloadApplyPatches(){
        return json.getBoolean(KEY_AUTO_DOWNLOAD_APPLY_PATCHES);
    }
    
    public void setPluginSums(String[] sums){
        json.put(KEY_PLUGIN_SUMS, sums);
    }
    
    public String[] getPluginSums(){
        JSONArray arr = json.getJSONArray(KEY_PLUGIN_SUMS);
        String[] out = new String[arr.length()];
        for (int i = 0; i < arr.length(); i++) {
            out[i] = arr.getString(i);
        }
        return out;
    }
    
    public void setEnableToneBeforeDownload(boolean enabled){
        json.put(KEY_ENABLE_TONE_BEFORE_DOWNLOAD, enabled);
    }
    
    public boolean isEnableToneBeforeDownload(){
        return json.getBoolean(KEY_ENABLE_TONE_BEFORE_DOWNLOAD);
    }
    public String getToneBeforeDownloadPath() {
        return json.getString(KEY_SELECTED_TONE_BEFORE_DOWNLOAD);
    }
    
    public void setToneBeforeDownloadPath(String path) {
        json.put(KEY_SELECTED_TONE_BEFORE_DOWNLOAD, path);
    }
    
    public void setEnableToneAfterDownload(boolean enabled){
        json.put(KEY_ENABLE_TONE_AFTER_DOWNLOAD, enabled);
    }
    
    public String getToneAfterDownloadPath() {
        return json.getString(KEY_SELECTED_TONE_AFTER_DOWNLOAD);
    }
    
    public void setToneAfterDownloadPath(String path) {
        json.put(KEY_SELECTED_TONE_AFTER_DOWNLOAD, path);
    }
    
    public boolean isEnableToneAfterDownload(){
        return json.getBoolean(KEY_ENABLE_TONE_AFTER_DOWNLOAD);
    }
    
    public void setUseOldParser(boolean enabled){
        json.put(KEY_USE_OLD_PARSER, enabled);
    }
    
    public boolean isUseOldParser() {
    	//TODO Force use old
    	return true;
        //return json.getBoolean(KEY_USE_OLD_PARSER);
    }
    
    public void setUiSkin(String skin) {
    	json.put(KEY_UI_SKIN, skin);
    }
    
    public String getUiSkin() {
    	return json.getString(KEY_UI_SKIN);
    }
    
    public void setOverlayEnabled(boolean enabled) {
    	json.put(KEY_OVERLAY_ENABLED, enabled);
    }
    
    public boolean isOverlayEnabled() {
    	return json.getBoolean(KEY_OVERLAY_ENABLED);
    }
    
    public void setOverlayAgreement(boolean agreed) {
    	json.put(KEY_OVERLAY_AGREEMENT, agreed);
    }
    
    public boolean isOverlayAgreement() {
    	return json.getBoolean(KEY_OVERLAY_AGREEMENT);
    }
    
    public void setMetricsEnabled(boolean enabled) {
    	json.put(KEY_METRICS_ENABLED, enabled);
    }
    
    public boolean isMetricsEnabled() {
    	return json.getBoolean(KEY_METRICS_ENABLED);
    }

    private static boolean fillJson(JSONObject json) {
        boolean modified = false;
        modified |= ifNullPutValue(json, KEY_UPDATE_SOURCE, KEY_UPDATE_SOURCE_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_UI_SKIN, KEY_UI_SKIN_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_DEFAULT_BROWSER, KEY_DEFAULT_BROWSER_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_DEFAULT_OPEN_BEATMAP_ACTION, KEY_DEFAULT_OPEN_BEATMAP_ACTION_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_DEFAULT_BEATMAP_SAVE_LOCATION, KEY_DEFAULT_BEATMAP_SAVE_LOCATION_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_OE_ENABLED, KEY_OE_ENABLED_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_OVERLAY_ENABLED, KEY_OVERLAY_ENABLED_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_OVERLAY_AGREEMENT, KEY_OVERLAY_AGREEMENT_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_NO_UI_ARG_SWITCH_BROWSER, KEY_NO_UI_ARG_SWITCH_BROWSER_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_AUTO_SWITCH_BROWSER, KEY_AUTO_SWITCH_BROWSER_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_GETTING_STARTED_STARTUP, KEY_GETTING_STARTED_STARTUP_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_SERVER_PRORITY, KEY_SERVER_PRORITY_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_ENC_ASK_ON_STARTUP, KEY_ENC_ASK_ON_STARTUP_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_USER, "");
        modified |= ifNullPutValue(json, KEY_PASS, "");
        modified |= ifNullPutValue(json, KEY_ENC, KEY_ENC_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_ENC_SALT, "");
        modified |= ifNullPutValue(json, KEY_ENC_IV, "");
        modified |= ifNullPutValue(json, KEY_RUN_DAEMON_ON_WIN_STARTUP, KEY_RUN_DAEMON_ON_WIN_STARTUP_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_DAEMON_DISABLED, KEY_DAEMON_DISABLED_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_QUEUE_MAX_THREADS, KEY_QUEUE_MAX_THREADS_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_QUEUE_NEXT_CHECK_DELAY, KEY_QUEUE_NEXT_CHECK_DELAY_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_QUEUE_MASSIVE_DOWNLOADING_THREADS,
                KEY_QUEUE_MASSIVE_DOWNLOADING_THREADS_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_CHECK_UPDATE_FREQ, KEY_CHECK_UPDATE_FREQ_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_CHECK_UPDATE_ALGO, KEY_CHECK_UPDATE_ALGO_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_AUTO_ACCEPT_CRITICAL_UPDATES,
                KEY_AUTO_ACCEPT_CRITICAL_UPDATES_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_AUTO_DOWNLOAD_APPLY_PATCHES,
                KEY_AUTO_DOWNLOAD_APPLY_PATCHES_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_PLUGIN_SUMS, new JSONArray());
        modified |= ifNullPutValue(json, KEY_ENABLE_TONE_BEFORE_DOWNLOAD,
                KEY_ENABLE_TONE_BEFORE_DOWNLOAD_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_SELECTED_TONE_BEFORE_DOWNLOAD,
                KEY_SELECTED_TONE_BEFORE_DOWNLOAD_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_ENABLE_TONE_AFTER_DOWNLOAD, KEY_ENABLE_TONE_AFTER_DOWNLOAD_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_SELECTED_TONE_AFTER_DOWNLOAD,
                KEY_SELECTED_TONE_AFTER_DOWNLOAD_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_USE_OLD_PARSER, KEY_USE_OLD_PARSER_DEFAULT_VALUE);
        modified |= ifNullPutValue(json, KEY_METRICS_ENABLED, KEY_METRICS_ENABLED_DEFAULT_VALUE);
        return modified;
    }

    private static boolean ifNullPutValue(JSONObject json, String key, Object val) {
        if (json.isNull(key)) {
            json.put(key, val);
            return true;
        } else {
            return false;
        }
    }

    public void load() throws IOException {
        File file = new File(dataFilePath + "/" + dataFileName);
        if (!file.exists()) {
            write();
            return;
        }
        FileInputStream in = new FileInputStream(file);

        String data = "";
        String line;
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        while ((line = reader.readLine()) != null) {
            data += line;
        }
        reader.close();

        /* 
         * Preferences prefs = Preferences.userRoot();
         * 
         * String data = prefs.get("com.github.mob41.osumer.configuration",
         * null);
         * 
         * if (data == null){ write(); return; }
         */

        try {
            json = new JSONObject(data);
            if (fillJson(json)) {
                write();
            }
        } catch (JSONException e) {
            throw new IOException("Invalid configuration data structure", e);
        }
    }

    public void write() throws IOException {

        File folder = new File(dataFilePath);
        if (!folder.exists()) {
            folder.mkdirs();
        }

        File file = new File(dataFilePath + "/" + dataFileName);
        if (!file.exists()) {
            file.createNewFile();

            if (json == null) {
                json = new JSONObject();
            }
            fillJson(json);
        }
        FileOutputStream out = new FileOutputStream(file);
        PrintWriter writer = new PrintWriter(out, true);
        writer.println(json.toString(5));
        writer.close();
        out.flush();
        out.close();

        /*
         * Preferences prefs = Preferences.userRoot();
         * 
         * if (json == null){ json = new JSONObject(); }
         * 
         * prefs.put("com.github.mob41.osumer.configuration", json.toString());
         * 
         * try { prefs.flush(); } catch (BackingStoreException e) { throw new
         * IOException("Error occurred", e); }
         */
    }

}
osumer-debugdump/src/main/java/com/github/mob41/osumer/debug/DumpManager.java
public class DumpManager {
	
	private static MetricRegistry metrics;

	private static String osumerVersion;
	
	private static String debuggerVersion;
	
	private static Map<Long, String> dumps;
	
	private static boolean init = false;

	private static String macAddr;

	private static Graphite graphite;

	private static GraphiteReporter reporter;
	
	public static void init(String osumerVersion, String debuggerVersion) throws IOException {
		if (init) {
			return;
		}
		DumpManager.osumerVersion = osumerVersion;
		DumpManager.debuggerVersion = debuggerVersion;
		
		metrics = new MetricRegistry();
		
		graphite = new Graphite(new InetSocketAddress("graphite1.osumer.tk", 2003));
		reporter = GraphiteReporter.forRegistry(metrics)
								.prefixedWith("osumer")
								.convertRatesTo(TimeUnit.SECONDS)
								.convertDurationsTo(TimeUnit.MILLISECONDS)
								.filter(new NotReportedFilter())
								.build(graphite);
		reporter.start(1, TimeUnit.MINUTES);
		
		metrics.register("jvm.gc", new GarbageCollectorMetricSet());
        metrics.register("jvm.threads", new CachedThreadStatesGaugeSet(10, TimeUnit.SECONDS));
        metrics.register("jvm.memory", new MemoryUsageGaugeSet());
		
		readDumps();
		init = true;
		
		reportEvent("debugManagerInit");
		
		if (osumerVersion != null) {
			reportEvent("version", osumerVersion.replaceAll("\\.", "-"));
		}
		
		String mac = obtainMac();
		
		if (mac != null) {
			reportEvent("activeUsers", mac);
		}
	}

    private static boolean isVMMac(byte[] mac) {
        if(null == mac) return false;
        byte invalidMacs[][] = {
                {0x00, 0x05, 0x69},             //VMWare
                {0x00, 0x1C, 0x14},             //VMWare
                {0x00, 0x0C, 0x29},             //VMWare
                {0x00, 0x50, 0x56},             //VMWare
                {0x08, 0x00, 0x27},             //Virtualbox
                {0x0A, 0x00, 0x27},             //Virtualbox
                {0x00, 0x03, (byte)0xFF},       //Virtual-PC
                {0x00, 0x15, 0x5D}              //Hyper-V
        };

        for (byte[] invalid: invalidMacs){
            if (invalid[0] == mac[0] && invalid[1] == mac[1] && invalid[2] == mac[2]) return true;
        }

        return false;
    }
	
	private static String obtainMac() {
		try {
			Enumeration<NetworkInterface> net = NetworkInterface.getNetworkInterfaces();

	        while (net.hasMoreElements()) {
	            NetworkInterface element = net.nextElement();
	            Enumeration<InetAddress> addresses = element.getInetAddresses();
	            
	            byte[] mac = element.getHardwareAddress();
	            if (mac == null || mac.length == 0 || isVMMac(mac)) {
	            	continue;
	            }
	            
	            while (addresses.hasMoreElements()) {
	                InetAddress ip = addresses.nextElement();
	                if (ip.isSiteLocalAddress()) {
	                	StringBuilder sb = new StringBuilder();
	                    for (int i = 0; i < mac.length; i++) {
	                        sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? "-" : ""));
	                    }
	                    return sb.toString();
	                }
	            }
	        }
		} catch (Exception e) {
			e.printStackTrace();
			DumpManager.addDump(new DebugDump(null, "(Method Start)", "Obtain MAC", "(Method End)", "Could not obtain MAC", false, e));
			System.err.println("Could not obtain MAC");
		}
        return null;
	}
	
	public static void forceMetricsReport() {
		if (!init) {
			throw new IllegalStateException("DumpManager was not initialized before getting metrics registry!");
		}
		reporter.report();
	}
	
	public static void reportEvent(String name, String... names) {
		if (!init) {
			throw new IllegalStateException("DumpManager was not initialized before getting metrics registry!");
		}
		String key = MetricRegistry.name(name, names);
		
		@SuppressWarnings("rawtypes")
		Map<String, Gauge> map = metrics.getGauges();

		Counter c = metrics.counter(key + "_not_reported");
		
		if (!map.containsKey(key)) {
			metrics.register(key, new ResettableGauge(c));
		}
		
		c.inc();
	}

	public static String getDebuggerVersion() {
		return debuggerVersion;
	}

	public static void setDebuggerVersion(String debuggerVersion) {
		DumpManager.debuggerVersion = debuggerVersion;
	}

	public static String getOsumerVersion() {
		return osumerVersion;
	}

	public static void setOsumerVersion(String osumerVersion) {
		DumpManager.osumerVersion = osumerVersion;
	}
	
	public static Map<Long, String> getDumps(){
		return dumps;
	}
	
	public static boolean readDumps() throws IOException{
        String path = System.getenv("localappdata") + "\\osumerExpress\\dumps";
        File folder = new File(path);
        
        if (!folder.exists()) {
        	return false;
        }
        
        dumps = new HashMap<Long, String>();
        
        long time;
        String name;
        int extDot = -1;
        File[] files = folder.listFiles();
        for (File file : files) {
        	name = file.getName();
        	extDot = name.lastIndexOf('.');
        	if (extDot != -1) {
        		name = name.substring(0, extDot);
        	}
        	try {
        		time = Long.parseLong(name);
        	} catch (NumberFormatException e) {
        		System.err.println("DumpManager: Could not load \"" + file.getName() + "\" as dump. Not a timestamp.");
        		continue;
        	}
        	
        	try {
        		FileInputStream out = new FileInputStream(file);
        		BufferedReader reader = new BufferedReader(new InputStreamReader(out));
        		String line;
        		String data = "";
        		while ((line = reader.readLine()) != null) {
        			data += line;
        		}
        		reader.close();
        		out.close();
            	
            	dumps.put(time, data);
        	} catch (IOException e) {
        		e.printStackTrace();
        		System.err.println("DumpManager: Error reading old dumps at " + time + " ms");
        		continue;
        	}
        }
        
        return true;
	}
	
	public static void addDump(DebugDump dump) {
		if (!init) {
			throw new IllegalStateException("DumpManager was not initialized before adding this dump!");
		}
		try {
			writeDump(dump);
		} catch (IOException e) {
			e.printStackTrace();
			System.err.println("DumpManager: Error writing dump");
		}
	}
	
	public static void writeDump(DebugDump dump) throws IOException {
		if (!init) {
			throw new IllegalStateException("DumpManager was not initialized before writing this dump!");
		}
		
		String name = dump.getExceptionClass();
		
		if (name == null) {
			name = "Typed-Dump";
		}
		
		reportEvent("exceptions", name.replaceAll("\\.", "-"));
		
        String path = System.getenv("localappdata") + "\\osumerExpress\\dumps";
        File folder = new File(path);
        folder.mkdirs();
        
        File file = new File(path + "\\" + dump.getGenerated() + ".txt");
        FileOutputStream out = new FileOutputStream(file);
        PrintWriter writer = new PrintWriter(out, true);
        writer.println(dump.toString());
        writer.flush();
        writer.close();
        out.close();
	}

}
osumer-lib/src/main/java/com/github/mob41/osumer/method/ErrorCode.java
public class ErrorCode {
	
	public static final int RESULT_OK = 0;
	
	public static final int RESULT_NO_CREDENTIALS = 1;
	
	public static final int RESULT_LOGIN_FAILED = 2;
	
	public static final int RESULT_GET_BEATMAP_INFO_FAILED = 3;

	public static final int RESULT_VALIDATE_DOWNLOAD_URL_FAILED = 4;

	public static final Integer RESULT_DOWNLOAD_URL_TOO_SHORT = 5;
	
}
osums-api/src/main/java/com/github/mob41/osums/beatmap/OsuSong.java
public class OsuSong {

	private final String originalUrl;
	
	private final String name;
	
	//private final String[] difficulties;
	
	private final String title;
	
	private final String artist;
	
	private final String creator;
	
	private final String source;
	
	private final String genre;
	
	private final String dwnUrl;
	
	private final String thumbUrl;
	
	private final int bad_rating;
	
	private final int good_rating;
	
	private final float bpm;
	
	private final boolean pageBeatmap;

	public OsuSong(String originalUrl, String name, String title,  //... String name, String[] difficulties ...
			String artist, String creator, String source, String genre, String dwnUrl,
			String thumbUrl, int badRating, int goodRating,
			float bpm, boolean pageBeatmap) {
		this.originalUrl = originalUrl;
		this.name = name;
		//this.difficulties = difficulties;
		this.title = title;
		this.artist = artist;
		this.creator = creator;
		this.source = source;
		this.genre = genre;
		this.dwnUrl = dwnUrl;
		this.thumbUrl = thumbUrl;
		this.bad_rating = badRating;
		this.good_rating = goodRating;
		this.bpm = bpm;
		this.pageBeatmap = pageBeatmap;
	}
	
	public String getName(){
		return name;
	}
	
	//TODO Unnecessary for a beatmap URL instead of song URL
	/*
	public String[] getDifficulties(){
		return difficulties;
	}
	*/
	
	public String getTitle(){
		return title;
	}
	
	public String getArtist() {
		return artist;
	}
	
	public String getCreator() {
		return creator;
	}

	public String getSource() {
		return source;
	}

	public String getGenre() {
		return genre;
	}

	public String getDwnUrl() {
		return dwnUrl;
	}

	public String getThumbUrl() {
		return thumbUrl;
	}

	public int getBadRating() {
		return bad_rating;
	}

	public int getGoodRating() {
		return good_rating;
	}
	
	public float getRating(){
		return (float) good_rating / (bad_rating + good_rating) * 100;
	}

	public float getBpm() {
		return bpm;
	}

    public String getOriginalUrl() {
        return originalUrl;
    }
	
	public static boolean equalsArr(String[] arg0, String[] arg1){
		if (arg0 == null && arg1 == null){
			return true;
		} else if (arg0 == null || arg1 == null) {
			return false;
		}
		
		if (arg0.length != arg1.length){
			return false;
		}
		
		for (int i = 0; i < arg0.length; i++){
			if (!arg0[i].equals(arg1[i])){
				return false;
			}
		}
		return true;
	}
	
	public boolean equals(OsuSong map){
		return getArtist().equals(map.getArtist()) &&
				getBadRating() == map.getBadRating() &&
				getBpm() == map.getBpm() &&
				getCreator().equals(map.getCreator()) &&
				//equalsArr(getDifficulties(), map.getDifficulties()) &&
				getDwnUrl().equals(map.getDwnUrl()) &&
				getGenre().equals(map.getGenre()) &&
				getGoodRating() == map.getGoodRating() &&
				getName().equals(map.getName()) &&
				getRating() == map.getRating() &&
				getSource().equals(map.getSource()) &&
				getThumbUrl().equals(map.getThumbUrl()) &&
				getTitle().equals(map.getTitle());
	}

	public boolean isPageBeatmap() {
		return pageBeatmap;
	}
    
	/*
    public JSONObject toJson(){
        return mapToJson(this);
    }
    
    public static JSONObject mapToJson(OsuBeatmap map){
        JSONObject json = new JSONObject();
        json.put("orgUrl", map.getOriginalUrl());
        json.put("artist", map.getArtist());
        json.put("badRating", map.getBadRating());
        json.put("bpm", map.getBpm());
        json.put("creator", map.getCreator());
        //json.put("diff", map.getDifficulties());
        json.put("dwnUrl", map.getDwnUrl());
        json.put("genre", map.getGenre());
        json.put("goodRating", map.getGoodRating());
        json.put("name", map.getName());
        json.put("rating", map.getRating());
        json.put("source", map.getSource());
        json.put("circleSize", map.getCircleSize());
        json.put("approachRate", map.getApproachRate());
        json.put("hpDrain", map.getHpDrain());
        json.put("accuracy", map.getAccuracy());
        json.put("starDiff", map.getStarDifficulty());
        json.put("successRate", map.getSuccessRate());
        json.put("thumbUrl", map.getThumbUrl());
        json.put("title", map.getTitle());
        return json;
    }
    
    public static OsuBeatmap fromJson(JSONObject json){
        return new OsuBeatmap(
                json.getString("orgUrl"), json.getString("name"), json.getString("title"),
                json.getString("artist"), json.getString("creator"), json.getString("source"),
                json.getString("genre"), json.getString("dwnUrl"), json.getString("thumbUrl"),
                (float) json.getDouble("circleSize"), (float) json.getDouble("approachRate"), (float) json.getDouble("hpDrain"),
                (float) json.getDouble("accuracy"), (float) json.getDouble("starDiff"), json.getInt("badRating"),
                json.getInt("goodRating"), (float) json.getDouble("bpm"), (float) json.getDouble("successRate"),
                true);
    }
    */

}
osumer-lib/src/main/java/com/github/mob41/osumer/method/MethodResult.java
public class MethodResult<T> implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -5828470383744187539L;

	private T result;
	
	private DebugDump dump;
	
	public MethodResult(T result) {
		this(result, null);
	}
	
	public MethodResult(T result, DebugDump dump) {
		this.result = result;
		this.dump = dump;
	}
	
	public T getResult() {
		return result;
	}
	
	public DebugDump getDump() {
		return dump;
	}
	
}
osumer-debugdump/src/main/java/com/github/mob41/osumer/debug/WithDumpException.java
public class WithDumpException extends Exception {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -4350229047576754645L;
	private final DebugDump dump;
	
	public WithDumpException(String specified_data, String last_op, String this_op, String next_op, String message, boolean containsPrivateData) {
		dump = new DebugDump(specified_data, last_op, this_op, next_op, message, containsPrivateData, this);
	}

	public WithDumpException(String specified_data, String last_op, String this_op, String next_op, String message, boolean containsPrivateData, String arg0) {
		super(arg0);
		dump = new DebugDump(specified_data, last_op, this_op, next_op, message, containsPrivateData, this);
	}

	public WithDumpException(String specified_data, String last_op, String this_op, String next_op, String message, boolean containsPrivateData, Throwable arg0) {
		super(arg0);
		dump = new DebugDump(specified_data, last_op, this_op, next_op, message, containsPrivateData, this);
	}

	public WithDumpException(String specified_data, String last_op, String this_op, String next_op, String message, boolean containsPrivateData, String arg0, Throwable arg1) {
		super(arg0, arg1);
		dump = new DebugDump(specified_data, last_op, this_op, next_op, message, containsPrivateData, this);
	}

	public WithDumpException(String specified_data, String last_op, String this_op, String next_op, String message, boolean containsPrivateData, String arg0, Throwable arg1, boolean arg2, boolean arg3) {
		super(arg0, arg1, arg2, arg3);
		dump = new DebugDump(specified_data, last_op, this_op, next_op, message, containsPrivateData, this);
	}
	
	public DebugDump getDump(){
		return dump;
	}

}
osums-api/src/main/java/com/github/mob41/osums/Osums.java
public abstract class Osums {
	
	public static final String RANK_ANY = "any";
	
	public static final String RANK_RANKED = "ranked";
	
	public static final String RANK_PENDING = "pending";
	
	public static final String RANK_QUALIFIED = "qualified";
	
	public static final String RANK_LOVED = "loved";

    public static final int SUCCESS = 0;

    public static final int ALREADY_LOGGED_IN = 1;

    public static final int INVALID_USERNAME_PASSWORD = -1;

    public static final int ERROR = -2;

    private final CookieManager cookieMgr;
    
    public Osums() {
    	this.cookieMgr = new CookieManager();
    }
    
    public final CookieManager getCookieManager() {
    	return cookieMgr;
    }
    
    public final void setCookies(URLConnection conn) {
        if (cookieMgr.getCookieStore().getCookies().size() > 0) {
            // While joining the Cookies, use ',' or ';' as needed. Most of
            // the servers are using ';'
            conn.setRequestProperty("Cookie", join(";", cookieMgr.getCookieStore().getCookies()));
        }
    }
    
    public final void retrieveCookies(Map<String, List<String>> headerFields) {
    	List<String> cookiesHeader = new ArrayList<String>();
        Iterator<String> it = headerFields.keySet().iterator();
        while (it.hasNext()) {
        	String key = it.next();
            if (key != null && key.toLowerCase().equals("set-cookie")) {
            	cookiesHeader.addAll(headerFields.get(key));
            }
        }

        if (cookiesHeader.size() > 0) {
            for (String cookie : cookiesHeader) {
            	cookieMgr.getCookieStore().add(null, HttpCookie.parse(cookie).get(0));
            }
        }
    }
    
    protected final HttpURLConnection prepareUrlConnectionGet(URL url) throws IOException {
    	HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        setCookies(conn);

        conn.setUseCaches(false);
        conn.setDoOutput(false);
        conn.setDoInput(true);
        conn.setAllowUserInteraction(false);
        conn.setInstanceFollowRedirects(false);
        conn.setRequestMethod("GET");

        fakeEnvHeaders(conn);
        
        return conn;
    }
    
    protected final HttpURLConnection prepareUrlConnectionPost(URL url) throws IOException {
    	HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        setCookies(conn);

        conn.setUseCaches(false);
        conn.setDoOutput(true);
        conn.setAllowUserInteraction(false);
        conn.setInstanceFollowRedirects(false);
        conn.setRequestMethod("POST");
        
        fakeEnvHeaders(conn);
        
        return conn;
    }
    
    protected final void fakeEnvHeaders(URLConnection conn) {
        // Fake environment from Chrome
        conn.setRequestProperty("Connection", "Keep-alive");
        conn.setRequestProperty("Cache-Control", "max-age=0");
        conn.setRequestProperty("Origin", "https://osu.ppy.sh");
        conn.setRequestProperty("Upgrade-Insecure-Requests", "0");
        conn.setRequestProperty("User-Agent",
                "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36");
        conn.setRequestProperty("Accept",
                "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
        conn.setRequestProperty("DNT", "1");
        conn.setRequestProperty("Accept-Encoding", "gzip"); //Accept gzip encoding
        conn.setRequestProperty("Accept-Language", "zh-TW,zh;q=0.8,en;q=0.6");
    }
    
    protected final String readAllData(InputStream in) throws IOException {
    	String data = "";
        String line;
        
        BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));

        while ((line = reader.readLine()) != null) {
            data += line;
        }
        
        reader.close();
        
        return data;
    }
    
    protected final String getHttpCookiedContent(String link) throws IOException {
    	URL url = new URL(link);

        HttpURLConnection conn = prepareUrlConnectionGet(url);
        
        InputStream in = prepareInputStream(conn);
        String data = readAllData(in);
        
        Map<String, List<String>> headerFields = conn.getHeaderFields();

        retrieveCookies(headerFields);
        
        return data;
    }
    
    protected final InputStream prepareInputStream(URLConnection conn) throws IOException {
    	String encoding = conn.getHeaderField("Content-Encoding");
        InputStream in;
        if (encoding != null && encoding.equals("gzip")){
            in = new GZIPInputStream(conn.getInputStream());
        } else {
            in = conn.getInputStream();
        }
        return in;
    }

    private final static String join(String separator, List<HttpCookie> objs) {
        String out = "";

        String str;
        for (int i = 0; i < objs.size(); i++) {
            str = objs.get(i).toString();

            out += str + separator;

            if (i != objs.size() - 1) {
                out += " ";
            }
        }

        return out;
    }
    
    public final SearchResult searchOsumsMaps(String keywords) throws WithDumpException {
    	return searchOsumsMaps(keywords, null, 1);
    }
    
    public final SearchResult searchOsumsMaps(String keywords, SearchFilter[] filters) throws WithDumpException {
    	return searchOsumsMaps(keywords, filters, 1);
    }
    
    public final SearchResult searchOsumsMaps(String keywords, SearchFilter[] filters, int page) throws WithDumpException {
    	return null;
    }
    
    public SearchResult searchOnlineMaps(String keywords) throws WithDumpException {
    	return searchOnlineMaps(keywords, null, 1);
    }
    
    public SearchResult searchOnlineMaps(String keywords, SearchFilter[] filters) throws WithDumpException {
    	return searchOnlineMaps(keywords, filters, 1);
    }
    
    public abstract boolean isVaildBeatmapUrl(String url);
    
    public abstract SearchResult searchOnlineMaps(String keywords, SearchFilter[] filters, int page) throws WithDumpException;
    
    public abstract int login(String username, String password) throws WithDumpException;
    
    public abstract boolean logout() throws WithDumpException;
    
    public abstract boolean isLoggedIn();
    
    public abstract boolean checkLoggedIn() throws IOException;
    
    public abstract OsuSong getSongInfo(String link) throws WithDumpException;
    
    public abstract OsuBeatmap getBeatmapInfo(String link) throws WithDumpException;
	
	public abstract SongResult[] parseSearch(String category, Document doc) throws WithDumpException;
	
	public abstract OsuSong parseSong(String originalUrl, Document doc) throws WithDumpException;
    
}
osums-api/src/main/java/com/github/mob41/osums/beatmap/OsuBeatmap.java
public class OsuBeatmap extends OsuSong{
	
	private final float circleSize;
	
    private final float approachRate;
    
    private final float hpDrain;
    
    private final float accuracy;
	
	private final float starDifficulty;
	
	private final float success_rate;

	public OsuBeatmap(String originalUrl, String name, String title,
			String artist, String creator, String source, String genre, String dwnUrl,
			String thumbUrl, int badRating, int goodRating,
			float bpm, float circleSize, float approachRate, float hpDrain,
			float acurracy, float starDifficulty, float successRate, boolean pageBeatmap) {
		super(originalUrl, name, title, artist, creator, source, genre, dwnUrl, thumbUrl, badRating, goodRating, bpm, pageBeatmap);
		this.circleSize = circleSize;
		this.approachRate = approachRate;
		this.hpDrain = hpDrain;
		this.accuracy = acurracy;
		this.starDifficulty = starDifficulty;
		this.success_rate = successRate;
	}

	public float getStarDifficulty() {
		return starDifficulty;
	}

	public float getSuccessRate() {
		return success_rate;
	}

    public float getCircleSize() {
        return circleSize;
    }

    public float getApproachRate() {
        return approachRate;
    }

    public float getHpDrain() {
        return hpDrain;
    }

    public float getAccuracy() {
        return accuracy;
    }
	
	public static boolean equalsArr(String[] arg0, String[] arg1){
		if (arg0 == null && arg1 == null){
			return true;
		} else if (arg0 == null || arg1 == null) {
			return false;
		}
		
		if (arg0.length != arg1.length){
			return false;
		}
		
		for (int i = 0; i < arg0.length; i++){
			if (!arg0[i].equals(arg1[i])){
				return false;
			}
		}
		return true;
	}
	
	public boolean equals(OsuBeatmap map){
		return getArtist().equals(map.getArtist()) &&
				getBadRating() == map.getBadRating() &&
				getBpm() == map.getBpm() &&
				getCreator().equals(map.getCreator()) &&
				//equalsArr(getDifficulties(), map.getDifficulties()) &&
				getDwnUrl().equals(map.getDwnUrl()) &&
				getGenre().equals(map.getGenre()) &&
				getGoodRating() == map.getGoodRating() &&
				getName().equals(map.getName()) &&
				getRating() == map.getRating() &&
				getSource().equals(map.getSource()) &&
				getCircleSize() == map.getCircleSize() &&
				getApproachRate() == map.getApproachRate() &&
				getHpDrain() == map.getHpDrain() &&
				getAccuracy() == map.getAccuracy() &&
				getStarDifficulty() == map.getStarDifficulty() &&
				getSuccessRate() == map.getSuccessRate() &&
				getThumbUrl().equals(map.getThumbUrl()) &&
				getTitle().equals(map.getTitle());
	}
    
	/*
    public JSONObject toJson(){
        return mapToJson(this);
    }
    
    public static JSONObject mapToJson(OsuBeatmap map){
        JSONObject json = new JSONObject();
        json.put("orgUrl", map.getOriginalUrl());
        json.put("artist", map.getArtist());
        json.put("badRating", map.getBadRating());
        json.put("bpm", map.getBpm());
        json.put("creator", map.getCreator());
        //json.put("diff", map.getDifficulties());
        json.put("dwnUrl", map.getDwnUrl());
        json.put("genre", map.getGenre());
        json.put("goodRating", map.getGoodRating());
        json.put("name", map.getName());
        json.put("rating", map.getRating());
        json.put("source", map.getSource());
        json.put("circleSize", map.getCircleSize());
        json.put("approachRate", map.getApproachRate());
        json.put("hpDrain", map.getHpDrain());
        json.put("accuracy", map.getAccuracy());
        json.put("starDiff", map.getStarDifficulty());
        json.put("successRate", map.getSuccessRate());
        json.put("thumbUrl", map.getThumbUrl());
        json.put("title", map.getTitle());
        return json;
    }
    
    public static OsuBeatmap fromJson(JSONObject json){
        return new OsuBeatmap(
                json.getString("orgUrl"), json.getString("name"), json.getString("title"),
                json.getString("artist"), json.getString("creator"), json.getString("source"),
                json.getString("genre"), json.getString("dwnUrl"), json.getString("thumbUrl"),
                (float) json.getDouble("circleSize"), (float) json.getDouble("approachRate"), (float) json.getDouble("hpDrain"),
                (float) json.getDouble("accuracy"), (float) json.getDouble("starDiff"), json.getInt("badRating"),
                json.getInt("goodRating"), (float) json.getDouble("bpm"), (float) json.getDouble("successRate"),
                true);
    }
    */

}


Question: import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.ResourceBundle;
import com.github.mob41.osumer.Configuration;
import com.github.mob41.osumer.Osumer;
import com.github.mob41.osumer.debug.DebugDump;
import com.github.mob41.osumer.debug.DumpManager;
import com.github.mob41.osumer.debug.WithDumpException;
import com.github.mob41.osumer.exceptions.NoBuildsForVersionException;
import com.github.mob41.osumer.exceptions.NoSuchBuildNumberException;
import com.github.mob41.osumer.exceptions.NoSuchVersionException;
import com.github.mob41.osumer.io.OsuDownloader;
import com.github.mob41.osumer.io.URLDownloader;
import com.github.mob41.osumer.method.ErrorCode;
import com.github.mob41.osumer.method.MethodResult;
import com.github.mob41.osumer.queue.QueueStatus;
import com.github.mob41.osumer.rmi.IDaemon;
import com.github.mob41.osumer.updater.Announcement;
import com.github.mob41.osumer.updater.AnnouncementChecker;
import com.github.mob41.osumer.updater.UpdateInfo;
import com.github.mob41.osumer.updater.Updater;
import com.github.mob41.osums.Osums;
import com.github.mob41.osums.beatmap.OsuBeatmap;
import com.github.mob41.osums.beatmap.OsuSong;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioButton;
import javafx.scene.control.SingleSelectionModel;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import jfxtras.styles.jmetro8.JMetro;
                    OsuSong map = null;
            		Platform.runLater(new Runnable() {
						@Override
						public void run() {
		                    progressController.getStatusText().setText("Status: Obtaining song/beatmap information...");
						}
					});
                    try {
                    	if (_isBeatmap) {
                            map = osums.getBeatmapInfo(_url);
                    	} else {
                    		map = osums.getSongInfo(_url);
                    	}
                    } catch (WithDumpException e) {
                        e.printStackTrace();
                        Platform.runLater(new Runnable() {
							@Override
							public void run() {
		                		Alert alert = new Alert(AlertType.INFORMATION, "Error getting song/beatmap info:\n" + e.getDump().getStacktrace(), ButtonType.OK);
		                		alert.showAndWait();
		                    	progressDialog.close();
							}
						});
                        return;
                    }
                    
                    final OsuSong _map = map;
                    Platform.runLater(new Runnable() {
            			@Override
            			public void run() {
            				progressDialog.hide();
            				
            				FXMLLoader loader = new FXMLLoader();
                            loader.setLocation(AppMain.class.getResource("/view/BeatmapPreviewDialogLayout.fxml"));
                            DialogPane dialogPane = null;;
                            try {
                    			dialogPane = (DialogPane) loader.load();
                    		} catch (IOException e) {
                    			e.printStackTrace();
                    		}
                            
                            BeatmapPreviewDialogController controller = loader.getController();
                            controller.preview(_map);
                            
            				Alert previewDialog = new Alert(AlertType.NONE);
                            previewDialog.initStyle(StageStyle.UTILITY);
                            previewDialog.initModality(Modality.APPLICATION_MODAL);
                            previewDialog.setTitle("Beatmap Preview");
                            previewDialog.setDialogPane(dialogPane);
                            
                            List<ButtonType> btnTypes = previewDialog.getButtonTypes();
                            btnTypes.add(ButtonType.YES);
                            btnTypes.add(ButtonType.NO);
                            previewDialog.showAndWait();
                            
                            ButtonType result = previewDialog.getResult();
                            
                            if (result != ButtonType.YES) {
                            	progressDialog.close();
                            	return;
                            }
                            
                            progressDialog.show();
                        	uiRequestQueue(progressDialog, progressController, _url, changeTab);
            			}
            		});
            	}
            };
            thread.start();
        } else {
        	uiRequestQueue(progressDialog, progressController, url, changeTab);
        }        
	}
	
	private void uiRequestQueue(Alert progressDialog, ProgressDialogController progressController, final String url, boolean changeTab) {
		progressController.getStatusText().setText("Status: Requesting daemon...");
        new Thread() {
        	public void run() {
        		requestQueue(url);
        		Platform.runLater(new Runnable() {
					
					@Override
					public void run() {
	                    progressDialog.close();
	                    
	                    if (changeTab) {
	                    	SingleSelectionModel<Tab> selectionModel = tabPane.getSelectionModel();
	                        selectionModel.clearAndSelect(1);
	                    }
					}
				});
        	}
        }.start();
	}
	
	private boolean requestQueue(String url) {
		int downloadAction = -1;
        String targetFileOrFolder = null;
        
        if (rdBtnUseDefault.isSelected()) {
            downloadAction = -1;
        } else if (rdBtnDwnImport.isSelected()) {
            downloadAction = 0;
        } else if (rdBtnDwnOsuSong.isSelected()) {
            downloadAction = 1;
        } else if (rdBtnDwnFile.isSelected()) {
            downloadAction = 2;
            targetFileOrFolder = "";
        } else if (rdBtnDwnFolder.isSelected()) {
            downloadAction = 3;
            targetFileOrFolder = "";
        }


		MethodResult<Integer> result = null;
        try {
            result = d.addQueue(url, downloadAction, targetFileOrFolder);
        } catch (RemoteException e) {
            e.printStackTrace();
            
Answer:             DumpManager.addDump(new DebugDump(null, "Process download action", "Request daemon to add queue", "(Method End)", "Unable to request daemon to add queue", false, e));