code:
<?php
/*
 *	Github webhook In-game PR Announcer and Changelog Generator for /tg/Station13
 *	Author: MrStonedOne
 *	For documentation on the changelog generator see https://tgstation13.org/phpBB/viewtopic.php?f=5&t=5157
 *	To hide prs from being announced in game, place a [s] in front of the title
 *	All runtime errors are echo'ed to the webhook's logs in github
 */

/**CREDITS:
 * GitHub webhook handler template.
 *
 * @see  https://developer.github.com/webhooks/
 * @author  Miloslav Hula (https://github.com/milo)
 */

//CONFIG START (all defaults are random examples, do change them)
//Use single quotes for config options that are strings.

//Github lets you have it sign the message with a secret that you can validate. This prevents people from faking events.
//This var should match the secret you configured for this webhook on github.
//set to NULL (no quotes) to disable validation.
$hookSecret = '08ajh0qj93209qj90jfq932j32r';

//Api key for pushing changelogs.
$apiKey = '209ab8d879c0f987d06a09b9d879c0f987d06a09b9d8787d0a089c';

//servers to announce PRs to.
$servers = array();
/*
$servers[0] = array();
$servers[0]['address'] = 'game.tgstation13.org';
$servers[0]['port'] = '1337';
$servers[0]['comskey'] = '89aj90cq2fm0amc90832mn9rm90';
$servers[1] = array();
$servers[1]['address'] = 'game.tgstation13.org';
$servers[1]['port'] = '2337';
$servers[1]['comskey'] = '89aj90cq2fm0amc90832mn9rm90';
*/

//CONFIG END
set_error_handler(function($severity, $message, $file, $line) {
	throw new \ErrorException($message, 0, $severity, $file, $line);
});
set_exception_handler(function($e) {
	header('HTTP/1.1 500 Internal Server Error');
	echo "Error on line {$e->getLine()}: " . htmlSpecialChars($e->getMessage());
	die();
});
$rawPost = NULL;
if ($hookSecret !== NULL) {
	if (!isset($_SERVER['HTTP_X_HUB_SIGNATURE'])) {
		throw new \Exception("HTTP header 'X-Hub-Signature' is missing.");
	} elseif (!extension_loaded('hash')) {
		throw new \Exception("Missing 'hash' extension to check the secret code validity.");
	}
	list($algo, $hash) = explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE'], 2) + array('', '');
	if (!in_array($algo, hash_algos(), TRUE)) {
		throw new \Exception("Hash algorithm '$algo' is not supported.");
	}
	$rawPost = file_get_contents('php://input');
	if ($hash !== hash_hmac($algo, $rawPost, $hookSecret)) {
		throw new \Exception('Hook secret does not match.');
	}
}
$contenttype = null;
//apache and nginx/fastcgi/phpfpm call this two different things.
if (!isset($_SERVER['HTTP_CONTENT_TYPE'])) {
	if (!isset($_SERVER['CONTENT_TYPE']))
		throw new \Exception("Missing HTTP 'Content-Type' header.");
	else
		$contenttype = $_SERVER['CONTENT_TYPE'];
} else {
	$contenttype = $_SERVER['HTTP_CONTENT_TYPE'];
}
if (!isset($_SERVER['HTTP_X_GITHUB_EVENT'])) {
	throw new \Exception("Missing HTTP 'X-Github-Event' header.");
}
switch ($contenttype) {
	case 'application/json':
		$json = $rawPost ?: file_get_contents('php://input');
		break;
	case 'application/x-www-form-urlencoded':
		$json = $_POST['payload'];
		break;
	default:
		throw new \Exception("Unsupported content type: $contenttype");
}
# Payload structure depends on triggered event
# https://developer.github.com/v3/activity/events/types/
$payload = json_decode($json, true);

switch (strtolower($_SERVER['HTTP_X_GITHUB_EVENT'])) {
	case 'ping':
		echo 'pong';
		break;
	case 'pull_request':
		handle_pr($payload);
		break;
	default:
		header('HTTP/1.0 404 Not Found');
		echo "Event:$_SERVER[HTTP_X_GITHUB_EVENT] Payload:\n";
		print_r($payload); # For debug only. Can be found in GitHub hook log.
		die();
}
function handle_pr($payload) {
	$action = 'opened';
	switch ($payload["action"]) {
		case 'opened':
		case 'reopened':
			$action = $payload['action'];
			break;
		case 'closed':
			if (!$payload['pull_request']['merged']) {
				$action = 'closed';
			}
			else {
				$action = 'merged';
				checkchangelog($payload, true);
			}
			break;
		default:
			return;
	}

	if (strtolower(substr($payload['pull_request']['title'], 0, 3)) == '[s]') {
		echo "PR Announcement Halted; Secret tag detected.\n";
		return;
	}

	$msg = 'Pull Request '.$action.' by '.htmlSpecialChars($payload['sender']['login']).': '.htmlSpecialChars('#'.$payload['pull_request']['number'].' '.$payload['pull_request']['user']['login'].' - '.$payload['pull_request']['title']).'';
	sendtoallservers('?announce='.urlencode($msg));

}

function checkchangelog($payload, $merge = false) {
	global $apiKey;
	if (!$merge)
		return;
	if (!isset($payload['pull_request']) || !isset($payload['pull_request']['body'])) {
		return;
	}
	if (!isset($payload['pull_request']['user']) || !isset($payload['pull_request']['user']['login'])) {
		return;
	}
	$body = $payload['pull_request']['body'];
	$body = str_replace("\r\n", "\n", $body);
	$body = explode("\n", $body);
	$username = $payload['pull_request']['user']['login'];
	$incltag = false;
	$changelogbody = array();
	$currentchangelogblock = array();
	$foundcltag = false;
	foreach ($body as $line) {
		$line = trim($line);
		if (substr($line,0,4) == ':cl:') {
			$incltag = true;
			$foundcltag = true;
			$pos = strpos($line, " ");
			if ($pos)
				$username = substr($line, $pos+1);
			continue;
		} else if (substr($line,0,5) == '/:cl:' || substr($line,0,6) == '/ :cl:' || substr($line,0,5) == ':/cl:') {
			$incltag = false;
			$changelogbody = array_merge($changelogbody, $currentchangelogblock);
			continue;
		}
		if (!$incltag)
			continue;

		$firstword = explode(' ', $line)[0];
		$pos = strpos($line, " ");
		$item = '';
		if ($pos) {
			$firstword = trim(substr($line, 0, $pos));
			$item = trim(substr($line, $pos+1));
		} else {
			$firstword = $line;
		}

		if (!strlen($firstword)) {
			$currentchangelogblock[count($currentchangelogblock)-1]['body'] .= "\n";
			continue;
		}
		//not a prefix line.
		//so we add it to the last changelog entry as a separate line
		if (!strlen($firstword) || $firstword[strlen($firstword)-1] != ':') {
			if (count($currentchangelogblock) <= 0)
				continue;
			$currentchangelogblock[count($currentchangelogblock)-1]['body'] .= "\n".$line;
			continue;
		}
		$cltype = strtolower(substr($firstword, 0, -1));
		switch ($cltype) {
			case 'fix':
			case 'fixes':
			case 'bugfix':
				$currentchangelogblock[] = array('type' => 'bugfix', 'body' => $item);
				break;
			case 'wip':
				$currentchangelogblock[] = array('type' => 'wip', 'body' => $item);
				break;
			case 'rsctweak':
			case 'tweaks':
			case 'tweak':
				$currentchangelogblock[] = array('type' => 'tweak', 'body' => $item);
				break;
			case 'soundadd':
				$currentchangelogblock[] = array('type' => 'soundadd', 'body' => $item);
				break;
			case 'sounddel':
				$currentchangelogblock[] = array('type' => 'sounddel', 'body' => $item);
				break;
			case 'add':
			case 'adds':
			case 'rscadd':
				$currentchangelogblock[] = array('type' => 'rscadd', 'body' => $item);
				break;
			case 'del':
			case 'dels':
			case 'rscdel':
				$currentchangelogblock[] = array('type' => 'rscdel', 'body' => $item);
				break;
			case 'imageadd':
				$currentchangelogblock[] = array('type' => 'imageadd', 'body' => $item);
				break;
			case 'imagedel':
				$currentchangelogblock[] = array('type' => 'imagedel', 'body' => $item);
				break;
			case 'typo':
			case 'spellcheck':
				$currentchangelogblock[] = array('type' => 'spellcheck', 'body' => $item);
				break;
			case 'experimental':
			case 'experiment':
				$currentchangelogblock[] = array('type' => 'experiment', 'body' => $item);
				break;
			case 'tgs':
				$currentchangelogblock[] = array('type' => 'tgs', 'body' => $item);
				break;
			default:
				//we add it to the last changelog entry as a separate line
				if (count($currentchangelogblock) > 0)
					$currentchangelogblock[count($currentchangelogblock)-1]['body'] .= "\n".$line;
				break;
		}
	}

	if (!count($changelogbody))
		return;

	$file = 'author: '.$username."\n";
	$file .= "delete-after: True\n";
	$file .= "changes: \n";
	foreach ($changelogbody as $changelogitem) {
		$type = $changelogitem['type'];
		$body = trim(str_replace(array("\\", '"'), array("\\\\", "\\\""), $changelogitem['body']));
		$file .= '  - '.$type.': "'.$body.'"';
		$file .= "\n";
	}
	$content = array (
		'branch' 	=> $payload['pull_request']['base']['ref'],
		'message' 	=> 'Automatic changelog generation for PR #'.$payload['pull_request']['number'],
		'content' 	=> base64_encode($file)
	);
	$scontext = array('http' => array(
        'method'	=> 'PUT',
        'header'	=>
			"Content-type: application/json\r\n".
			'Authorization: token ' . $apiKey,
        'content'	=> json_encode($content),
		'ignore_errors' => true,
		'user_agent' 	=> 'tgstation13.org-Github-Automation-Tools'
    ));
	$filename = '/html/changelogs/AutoChangeLog-pr-'.$payload['pull_request']['number'].'.yml';
	echo file_get_contents($payload['pull_request']['base']['repo']['url'].'/contents'.$filename, false, stream_context_create($scontext));
}

function sendtoallservers($str) {
	global $servers;
	foreach ($servers as $serverid => $server) {
		if (isset($server['comskey']))
			$rtn = export($server['address'], $server['port'], $str.'&key='.$server['comskey']);
		else
			$rtn = export($server['address'], $server['port'], $str);

		echo "Server Number $serverid replied: $rtn\n";
	}
}

function export($addr, $port, $str) {
	global $error;
	// All queries must begin with a question mark (ie "?players")
	if($str{0} != '?') $str = ('?' . $str);

	/* --- Prepare a packet to send to the server (based on a reverse-engineered packet structure) --- */
	$query = "\x00\x83" . pack('n', strlen($str) + 6) . "\x00\x00\x00\x00\x00" . $str . "\x00";

	/* --- Create a socket and connect it to the server --- */
	$server = socket_create(AF_INET,SOCK_STREAM,SOL_TCP) or exit("ERROR");
	socket_set_option($server, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 2, 'usec' => 0)); //sets connect and send timeout to 2 seconds
	if(!socket_connect($server,$addr,$port)) {
		$error = true;
		return "ERROR";
	}

	/* --- Send bytes to the server. Loop until all bytes have been sent --- */
	$bytestosend = strlen($query);
	$bytessent = 0;
	while ($bytessent < $bytestosend) {
		//echo $bytessent.'
';
		$result = socket_write($server,substr($query,$bytessent),$bytestosend-$bytessent);
		//echo 'Sent '.$result.' bytes
';
		if ($result===FALSE) die(socket_strerror(socket_last_error()));
		$bytessent += $result;
	}

	/* --- Idle for a while until recieved bytes from game server --- */
	$result = socket_read($server, 10000, PHP_BINARY_READ);
	socket_close($server); // we don't need this anymore

	if($result != "") {
		if($result{0} == "\x00" || $result{1} == "\x83") { // make sure it's the right packet format

			// Actually begin reading the output:
			$sizebytes = unpack('n', $result{2} . $result{3}); // array size of the type identifier and content
			$size = $sizebytes[1] - 1; // size of the string/floating-point (minus the size of the identifier byte)

			if($result{4} == "\x2a") { // 4-byte big-endian floating-point
				$unpackint = unpack('f', $result{5} . $result{6} . $result{7} . $result{8}); // 4 possible bytes: add them up together, unpack them as a floating-point
				return $unpackint[1];
			}
			else if($result{4} == "\x06") { // ASCII string
				$unpackstr = ""; // result string
				$index = 5; // string index

				while($size > 0) { // loop through the entire ASCII string
					$size--;
					$unpackstr .= $result{$index}; // add the string position to return string
					$index++;
				}
				return $unpackstr;
			}
		}
	}
	//if we get to this point, something went wrong;
	$error = true;
	return "ERROR";
}
?>


code:
@echo off
rem Cheridan asked for this. - N3X
call python ss13_genchangelog.py ../html/changelog.html ../html/changelogs
pause

license: mit


code:
@echo off
cd ../../_maps/

for /R %%f in (*.dmm) do copy "%%f" "%%f.backup"

cls
echo All dmm files in _maps directories have been backed up
echo Now you can make your changes...
echo ---
echo Remember to run Run_Map_Merge.bat just before you commit your changes!
echo ---
pause

path: tools/mapmerge/Prepare Maps.bat


code:
@echo off
set MAPROOT="../../_maps/"
set TGM=0
python mapmerger.py %1 %MAPROOT% %TGM%
pause

path: tools/mapmerge/Run Map Merge - DMM.bat


code:
@echo off
set MAPROOT="../../_maps/"
set TGM=1
python mapmerger.py %1 %MAPROOT% %TGM%
pause

path: tools/mapmerge/Run Map Merge - TGM.bat


code:
import collections

maxx = 0
maxy = 0
key_length = 1

def reset_globals():
    global key_length
    global maxx
    global maxy
    key_length = 1
    maxx = 0
    maxy = 0

def merge_map(newfile, backupfile, tgm):
    reset_globals()

    shitmap = parse_map(newfile)
    shitDict = shitmap["dictionary"] #key to tile data dictionary
    shitGrid = shitmap["grid"] #x,y coords to tiles (keys) dictionary (the map's layout)

    originalmap = parse_map(backupfile)
    originalDict = originalmap["dictionary"]
    originalGrid = originalmap["grid"]

    mergeGrid = dict() #final map layout
    known_keys = dict() #mapping known keys to original keys
    tempGrid = dict() #saving tiles with newly generated keys for later processing
    temp_keys = dict() #mapping known keys to newly generated keys
    unused_keys = list(originalDict.keys()) #list with all existing keys that aren't being used
    tempDict = collections.OrderedDict() #mapping new keys to new data
    originalDict_size = len(originalDict)

    for y in range(1,maxy+1):
        for x in range(1,maxx+1):
            shitKey = shitGrid[x,y]

            #if this key was seen before, add it to the pile immediately
            if shitKey in known_keys:
                mergeGrid[x,y] = known_keys[shitKey]
                continue
            #if this key was seen before, add it to the pile immediately
            if shitKey in temp_keys:
                tempGrid[x,y] = temp_keys[shitKey]
                continue

            shitData = shitDict[shitKey]
            originalKey = originalGrid[x,y]
            originalData = originalDict[originalKey]

            #if new tile data at x,y is the same as original tile data at x,y, add to the pile
            if shitData == originalData:
                mergeGrid[x,y] = originalKey
                known_keys[shitKey] = originalKey
                unused_keys.remove(originalKey)
            else:
                #search for the new tile data in the original dictionary, if a key is found add it to the pile, else generate a new key
                newKey = search_key(originalDict, shitData)
                if newKey != None:
                    try:
                        unused_keys.remove(newKey)
                    except ValueError: #caused by a duplicate entry
                        print("WARNING: Correcting duplicate dictionary entry. ({})".format(shitKey))
                    mergeGrid[x,y] = newKey
                    known_keys[shitKey] = newKey
                #if data at original x,y no longer exists we reuse the key immediately
                elif search_key(shitDict, originalData) == None:
                    mergeGrid[x,y] = originalKey
                    originalDict[originalKey] = shitData
                    unused_keys.remove(originalKey)
                    known_keys[shitKey] = originalKey
                else:
                    if len(tempDict) == 0:
                        newKey = generate_new_key(originalDict)
                    else:
                        newKey = generate_new_key(tempDict)
                    if newKey == "OVERFLOW": #if this happens, merging is impossible
                        print("ERROR: Key overflow detected.")
                        return 0
                    tempGrid[x,y] = newKey
                    temp_keys[shitKey] = newKey
                    tempDict[newKey] = shitData

    sort = 0
    #find gaps in the dictionary keys sequence and add the missing keys to be recycled
    dict_list = list(originalDict.keys())
    for index in range(0, len(dict_list)):
        if index + 1 == len(dict_list):
            break

        key = dict_list[index]
        next_key = dict_list[index+1]

        difference = key_difference(key, next_key)
        if difference > 1:
            i = 1
            nextnew = key
            while i < difference:
                nextnew = get_next_key(nextnew)
                unused_keys.append(nextnew)
                i += 1
            sort = 1

    #Recycle outdated keys with any new tile data, starting from the bottom of the dictionary
    i = 0
    for key, value in reversed(tempDict.items()):
        recycled_key = key
        if len(unused_keys) > 0:
            recycled_key = unused_keys.pop()

        for coord, gridkey in tempGrid.items():
            if gridkey == None:
                continue
            if gridkey == key:
                mergeGrid[coord] = recycled_key
                tempGrid[coord] = None

        originalDict[recycled_key] = value

    #if gaps in the key sequence were found, sort the dictionary for cleanliness
    if sort == 1:
        sorted_dict = collections.OrderedDict()
        next_key = get_next_key("")
        while len(sorted_dict) < len(originalDict):
            try:
                sorted_dict[next_key] = originalDict[next_key]
            except KeyError:
                pass
            next_key = get_next_key(next_key)
        originalDict = sorted_dict

    if tgm:
        write_dictionary_tgm(newfile, originalDict)
        write_grid_coord_small(newfile, mergeGrid)
    else:
        write_dictionary(newfile, originalDict)
        write_grid(newfile, mergeGrid)
    return 1

#write dictionary in tgm format
def write_dictionary_tgm(filename, dictionary):
    with open(filename, "w") as output:
        output.write("//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE \n")
        for key, list_ in dictionary.items():
            output.write("\"{}\" = (\n".format(key))

            for thing in list_:
                buffer = ""
                in_quote_block = False
                in_varedit_block = False
                for char in thing:

                    if in_quote_block:
                        if char == "\"":
                            in_quote_block = False
                        buffer = buffer + char
                        continue
                    elif char == "\"":
                        in_quote_block = True
                        buffer = buffer + char
                        continue

                    if not in_varedit_block:
                        if char == "{":
                            in_varedit_block = True
                            buffer = buffer + "{\n\t"
                            continue
                    else:
                        if char == ";":
                            buffer = buffer + ";\n\t"
                            continue
                        elif char == "}":
                            buffer = buffer + "\n\t}"
                            in_varedit_block = False
                            continue

                    buffer = buffer + char

                if list_.index(thing) != len(list_) - 1:
                    buffer = buffer + ",\n"
                output.write(buffer)

            output.write(")\n")

#thanks to YotaXP for finding out about this one
def write_grid_coord_small(filename, grid):
    with open(filename, "a") as output:
        output.write("\n")

        for x in range(1, maxx+1):
            output.write("({},{},1) = {{\"\n".format(x, 1, 1))
            for y in range(1, maxy):
                output.write("{}\n".format(grid[x,y]))
            output.write("{}\n\"}}\n".format(grid[x,maxy-1]))

def search_key(dictionary, data):
    for key, value in dictionary.items():
        if value == data:
            return key
    return None

def generate_new_key(dictionary):
    last_key = next(reversed(dictionary))
    return get_next_key(last_key)

def get_next_key(key):
    if key == "":
        return "".join("a" for _ in range(key_length))

    length = len(key)
    new_key = ""
    carry = 1
    for char in key[::-1]:
        if carry <= 0:
            new_key = new_key + char
            continue
        if char == 'Z':
            new_key = new_key + 'a'
            carry += 1
            length -= 1
            if length <= 0:
                return "OVERFLOW"
        elif char == 'z':
            new_key = new_key + 'A'
        else:
            new_key = new_key + chr(ord(char) + 1)
        if carry > 0:
            carry -= 1
    return new_key[::-1]

#still does not support more than one z level per file, but should parse any format
def parse_map(map_file):
    with open(map_file, "r") as map_input:
        characters = map_input.read()

        in_quote_block = False
        in_key_block = False
        in_data_block = False
        in_varedit_block = False
        after_data_block = False
        escaping = False
        skip_whitespace = False

        dictionary = collections.OrderedDict()
        curr_key = ""
        curr_datum = ""
        curr_data = list()

        in_map_block = False
        in_coord_block = False
        in_map_string = False
        iter_x = 0
        adjust_y = True

        curr_num = ""
        reading_coord = "x"

        global key_length
        global maxx
        global maxy
        curr_x = 0
        curr_y = 0
        curr_z = 1
        grid = dict()

        for char in characters:

            if not in_map_block:

                if char == "\n" or char == "\t":
                    continue

                if in_data_block:

                    if in_varedit_block:

                        if in_quote_block:
                            if char == "\\":
                                curr_datum = curr_datum + char
                                escaping = True
                                continue

                            if escaping:
                                curr_datum = curr_datum + char
                                escaping = False
                                continue

                            if char == "\"":
                                curr_datum = curr_datum + char
                                in_quote_block = False
                                continue

                            curr_datum = curr_datum + char
                            continue

                        if skip_whitespace and char == " ":
                            skip_whitespace = False
                            continue
                        skip_whitespace = False

                        if char == "\"":
                            curr_datum = curr_datum + char
                            in_quote_block = True
                            continue

                        if char == ";":
                            skip_whitespace = True
                            curr_datum = curr_datum + char
                            continue

                        if char == "}":
                            curr_datum = curr_datum + char
                            in_varedit_block = False
                            continue

                        curr_datum = curr_datum + char
                        continue

                    if char == "{":
                        curr_datum = curr_datum + char
                        in_varedit_block = True
                        continue

                    if char == ",":
                        curr_data.append(curr_datum)
                        curr_datum = ""
                        continue

                    if char == ")":
                        curr_data.append(curr_datum)
                        dictionary[curr_key] = tuple(curr_data)
                        curr_data = list()
                        curr_datum = ""
                        curr_key = ""
                        in_data_block = False
                        after_data_block = True
                        continue

                    curr_datum = curr_datum + char
                    continue

                if in_key_block:
                    if char == "\"":
                        in_key_block = False
                        key_length = len(curr_key)
                    else:
                        curr_key = curr_key + char
                    continue
                #else we're looking for a key block, a data block or the map block

                if char == "\"":
                    in_key_block = True
                    after_data_block = False
                    continue

                if char == "(":
                    if after_data_block:
                        in_map_block = True
                        in_coord_block = True
                        after_data_block = False
                        curr_key = ""
                        continue
                    else:
                        in_data_block = True
                        after_data_block = False
                        continue

            else:

                if in_coord_block:
                    if char == ",":
                        if reading_coord == "x":
                            curr_x = string_to_num(curr_num)
                            if curr_x > maxx:
                                maxx = curr_x
                            iter_x = 0
                            curr_num = ""
                            reading_coord = "y"
                        elif reading_coord == "y":
                            curr_y = string_to_num(curr_num)
                            if curr_y > maxy:
                                maxy = curr_y
                            curr_num = ""
                            reading_coord = "z"
                        else:
                            pass
                        continue

                    if char == ")":
                        in_coord_block = False
                        reading_coord = "x"
                        curr_num = ""
                        #read z here if needed
                        continue

                    curr_num = curr_num + char
                    continue

                if in_map_string:

                    if char == "\"":
                        in_map_string = False
                        adjust_y = True
                        curr_y -= 1
                        continue

                    if char == "\n":
                        if adjust_y:
                            adjust_y = False
                        else:
                            curr_y += 1
                        if curr_x > maxx:
                            maxx = curr_x
                        if iter_x > 1:
                            curr_x = 1
                        iter_x = 0
                        continue

                    curr_key = curr_key + char
                    if len(curr_key) == key_length:
                        iter_x += 1
                        if iter_x > 1:
                            curr_x += 1

                        grid[curr_x, curr_y] = curr_key
                        curr_key = ""
                    continue

                #else look for coordinate block or a map string

                if char == "(":
                    in_coord_block = True
                    continue
                if char == "\"":
                    in_map_string = True
                    continue

        if curr_y > maxy:
            maxy = curr_y

        data = dict()
        data["dictionary"] = dictionary
        data["grid"] = grid
        return data

#subtract keyB from keyA
def key_difference(keyA, keyB):
    if len(keyA) != len(keyB):
        return "you fucked up"

    Ayek = keyA[::-1]
    Byek = keyB[::-1]

    result = 0
    for i in range(0, len(keyA)):
        base = 52**i
        A = 26 if Ayek[i].isupper() else 0
        B = 26 if Byek[i].isupper() else 0
        result += ( (ord(Byek[i].lower()) + B) - (ord(Ayek[i].lower()) + A) ) * base
    return result

def string_to_num(s):
    try:
        return int(s)
    except ValueError:
        return -1

#writes a tile data dictionary the same way Dreammaker does
def write_dictionary(filename, dictionary):
    with open(filename, "w") as output:
        for key, value in dictionary.items():
            output.write("\"{}\" = ({})\n".format(key, ",".join(value)))

#writes a map grid the same way Dreammaker does
def write_grid(filename, grid):
    with open(filename, "a") as output:
        output.write("\n")
        output.write("(1,1,1) = {\"\n")

        for y in range(1, maxy+1):
            for x in range(1, maxx+1):
                try:
                    output.write(grid[x,y])
                except KeyError:
                    print("Key error: ({},{})".format(x,y))
            output.write("\n")
        output.write("\"}")
        output.write("\n")

#inflated map grid; unused
def write_grid_coord(filename, grid):
    with open(filename, "a") as output:
        output.write("\n")
        for y in range(1, maxy+1):
            for x in range(1, maxx+1):
                output.write("({},{},1) = {{\"{}\"}}\n".format(x, y, grid[x,y]))

def key_compare(keyA, keyB): #thanks byond for not respecting ascii
    pos = 0
    for a in keyA:
        pos += 1
        count = pos
        for b in keyB:
            if(count > 1):
                count -= 1
                continue
            if a.islower() and b.islower():
                if(a < b):
                    return -1
                if(a > b):
                    return 1
                break
            if a.islower() and b.isupper():
                return -1
            if a.isupper() and b.islower():
                return 1
            if a.isupper() and b.isupper():
                if(a < b):
                    return -1
                if(a > b):
                    return 1
                break
    return 0


code:
import sys
import os
import pathlib
import map_helpers

#main("../../_maps/")
def main(map_folder, tgm=0):
    list_of_files = list()
    for root, directories, filenames in os.walk(map_folder):
        for filename in [f for f in filenames if f.endswith(".dmm")]:
            list_of_files.append(pathlib.Path(root, filename))

    for i in range(0, len(list_of_files)):
        to_print = "[{}]: {}".format(i, list_of_files[i])
        print(to_print)
        print("".join("-" for _ in range(len(to_print))))

    in_list = input("List the maps you want to merge (example: 1,2,3,4,5):\n")
    in_list = in_list.replace(" ", "")
    in_list = in_list.split(",")

    valid_indices = list()
    for m in in_list:
        index = string_to_num(m)
        if index >= 0 and index < len(list_of_files):
            valid_indices.append(index)

    if tgm == "1":
        print("\nMaps will be converted to tgm.")
        tgm = True
    else:
        print("\nMaps will not be converted to tgm.")
        tgm = False

    print("\nMerging these maps:")
    for i in valid_indices:
        print(list_of_files[i])
    merge = input("\nPress Enter to merge...")
    if merge == "abort":
        print("\nAborted map merge.")
        sys.exit()
    else:
        for i in valid_indices:
            path_str = str(list_of_files[i])
            try:
                if map_helpers.merge_map(path_str, path_str + ".backup", tgm) != 1:
                    print("ERROR MERGING: {}".format(list_of_files[i]))
                    continue
                print("MERGED: {}".format(path_str))
            except FileNotFoundError:
                print("\nERROR: File not found! Make sure you run 'Prepare Maps.bat' before merging.")
                print(path_str + " || " + path_str+".backup")

    print("\nFinished merging.")

def string_to_num(s):
    try:
        return int(s)
    except ValueError:
        return -1

main(sys.argv[1], sys.argv[2])


code:
@echo off
call java -jar MapMerge.jar "../../_maps/" /wait

pause


code:
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="src" path="src"/>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
	<classpathentry kind="lib" path="src/MapPatcher.jar"/>
	<classpathentry kind="output" path="bin"/>
</classpath>


code:
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
	<name>MapMerger</name>
	<comment></comment>
	<projects>
	</projects>
	<buildSpec>
		<buildCommand>
			<name>org.eclipse.jdt.core.javabuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
	</buildSpec>
	<natures>
		<nature>org.eclipse.jdt.core.javanature</nature>
	</natures>
</projectDescription>


code:
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;

public class FileFinder extends SimpleFileVisitor<Path> {
	private PathMatcher matcher;
	public ArrayList<Path> foundPaths = new ArrayList<>();
	public FileFinder(String pattern) {
		matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
	}

	@Override
	public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
		Path name = file.getFileName();
		if (matcher.matches(name)) {
			foundPaths.add(file);
		}
		return FileVisitResult.CONTINUE;
	}
}


code:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Scanner;

public class MapMerge {

	private static Scanner input = new Scanner(System.in);
	private static Path pathToMaps;

	public static void main(String[] mapPath) throws IOException {
		pathToMaps = Paths.get(mapPath[0]);
		FileFinder dmmFinder = new FileFinder("*.dmm");
		Files.walkFileTree(pathToMaps, dmmFinder);
		ArrayList<Path> foundFiles = dmmFinder.foundPaths;
		if (foundFiles.size() > 0) {
			try {
				MapMerge.merge(foundFiles);
			} catch (Exception e) {
				System.out.println("Something went wrong.");
				e.printStackTrace();
			}
		} else {
			System.out.println("No files were found in provided directory!");
			System.out.print("Path to maps folder: ");
			pathToMaps = Paths.get(input.nextLine());
			dmmFinder = new FileFinder("*.dmm");
			Files.walkFileTree(pathToMaps, dmmFinder);
			foundFiles = dmmFinder.foundPaths;
			try {
				MapMerge.merge(foundFiles);
			} catch (Exception e) {
				System.out.println("Something went wrong.");
				e.printStackTrace();
			}
		}
	}

	public static void merge(ArrayList<Path> foundFiles) throws IOException {

		System.out.println("How many files do you want to merge?");
		int selection1;
		inputCheck: while (true) {
			while (!input.hasNextInt()) {
				String temp = input.next();
				System.out.println(temp + " is not a valid int.");
			}
			selection1 = input.nextInt();
			if (selection1 < 0) {
				System.out.println("Use a number greater than 0!");
				continue inputCheck;
			} else {
				break inputCheck;
			}
		}

		for (int numOfFiles = selection1; numOfFiles != 0; numOfFiles--) {

			for (int num = 0; num < foundFiles.size(); num++) {
				System.out.println(num + ": " + foundFiles.get(num));
			}

			System.out.print("File to use: ");
			int selection2;
			inputCheck: while (true) {
				while (!input.hasNextInt()) {
					String temp = input.next();
					System.out.println(temp + " is not a valid int.");
				}
				selection2 = input.nextInt();
				if ((selection2 < 0) || (selection2 >= foundFiles.size())) {
					if (selection2 < 0) {
						System.out.println("Use a number greater than 0!");
					} else {
						System.out.println("Use a number less than " + foundFiles.size() + "!");
					}
					continue inputCheck;
				} else {
					break inputCheck;
				}
			}

			String selected_map = foundFiles.get(selection2) + "";
			String backup_map = selected_map + ".backup";
			String edited_map = selected_map;
			String to_save = selected_map;
			String[] passInto = { "-clean", backup_map, edited_map, to_save };
			MapPatcher.main(passInto);

			// Will try to fix when I have time ~CorruptComputer
			/*try{
				Process process = new ProcessBuilder("dmm2tgm\\dmm2tgm.exe", selected_map).start();
			}catch(Exception e1){
				System.out.println("You are not on a windows machine, trying the .py");
				try{
					Process process = new ProcessBuilder("dmm2tgm\\Source\\dmm2tgm.py", selected_map).start();
				}catch(Exception e2){
					System.out.println("You do not have python 2.7.x installed.");
					System.out.println("Downloads can be found here: https://www.python.org/downloads/");
				}
			}
			*/

		}
	}
}


code:
class Location
{
  int x;
  int y;
  int z;

  public Location()
  {
  }

  public Location(int paramInt1, int paramInt2, int paramInt3)
  {
    this.x = paramInt1;
    this.y = paramInt2;
    this.z = paramInt3;
  }

  public void set(int paramInt1, int paramInt2, int paramInt3)
  {
    this.x = paramInt1;
    this.y = paramInt2;
    this.z = paramInt3;
  }

  public boolean equals(Object paramObject)
  {
    if (!(paramObject instanceof Location)) return false;
    Location localLocation = (Location)paramObject;
    if ((this.x != localLocation.x) || (this.y != localLocation.y) || (this.z != localLocation.z)) return false;
    return true;
  }

  public int hashCode()
  {
    return (this.z * 256 + this.y) * 256 + this.x;
  }

  public String toString()
  {
    return "(" + this.x + "," + this.y + "," + this.z + ")";
  }
}


code:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

public class Map
{
  boolean sizeunknown;
  int minx;
  int miny;
  int minz;
  int maxx;
  int maxy;
  int maxz;
  HashMap<String, String> tile_types;
  HashMap<String, String> codes_by_value;
  HashMap<Location, String> tiles;

  public Map()
  {
    this.sizeunknown = true;
    this.tile_types = new HashMap();
    this.codes_by_value = new HashMap();
    this.tiles = new HashMap();
  }

  public Map(File paramFile)
  {
    this(paramFile, false);
  }

  public Map(File paramFile, boolean paramBoolean)
  {
    this.sizeunknown = true;
    try {
      BufferedReader localBufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(paramFile)));

      this.tile_types = new HashMap();
      this.codes_by_value = new HashMap();
      this.tiles = new HashMap();

      MapPatcher.Systemoutprintln(new StringBuilder().append("Loading map ").append(paramFile.getName()).toString());
      MapPatcher.Systemoutprint("Loading tiles");
      String str1 = "";
      int i = 0;
      while ((str1 = localBufferedReader.readLine()) != null)
      {
        if (str1.equals("")) break;
        if (str1.startsWith("\""))
        {
          if (i < 1)
          {
            int j = str1.indexOf("\"", 1);
            i = j - 1;
          }
          String str2 = str1.substring(1, 1 + i);
          String str3 = str1.substring(str1.indexOf("("));
          this.tile_types.put(str2, str3);
          this.codes_by_value.put(str3, str2);
        }
      }

      MapPatcher.Systemoutprintln(new StringBuilder().append(" ").append(this.tile_types.size()).toString());
      if (!paramBoolean)
      {
        MapPatcher.Systemoutprintln("Loading levels");
        while (true)
        {
          if ((str1 = localBufferedReader.readLine()) != null) { if (str1.startsWith("(")) break label270;  } else {
            label270: if (str1 == null)
            {
              break;
            }
            int k = str1.indexOf(",", 1);
            int m = Integer.parseInt(str1.substring(1, k));
            str1 = str1.substring(k);
            k = str1.indexOf(",", 1);
            int n = Integer.parseInt(str1.substring(1, k));
            str1 = str1.substring(k);
            k = str1.indexOf(")", 1);
            int i1 = Integer.parseInt(str1.substring(1, k));

            MapPatcher.Systemoutprintln(new StringBuilder().append("New map part from (").append(m).append(",").append(n).append(",").append(i1).append(")").toString());

            int i3 = n;
            if (this.sizeunknown)
            {
              this.minx = m; this.maxx = this.minx;
              this.miny = n; this.maxy = this.miny;
              this.minz = i1; this.maxz = this.minz;
              this.sizeunknown = false;
            }
            if (this.minz > i1) this.minz = i1;
            if (this.maxz < i1) this.maxz = i1;
            while (!(str1 = localBufferedReader.readLine()).startsWith("\"}"))
            {
              int i2 = m;
              if (this.miny > i3) this.miny = i3;
              if (this.maxy < i3) this.maxy = i3;
              while (str1.length() > 0)
              {
                String str4 = str1.substring(0, i);
                Location localLocation = new Location(i2, i3, i1);
                if (this.minx > i2) this.minx = i2;
                if (this.maxx < i2) this.maxx = i2;
                this.tiles.put(localLocation, this.tile_types.get(str4));
                str1 = str1.substring(i);
                i2++;
              }
              i3++;
            }
          }
        }
      }
      localBufferedReader.close();
    }
    catch (Exception localException)
    {
      localException.printStackTrace();
    }
  }

  public void mirrorY()
  {
    for (int i = this.minz; i <= this.maxz; i++)
      for (int j = this.minx; j <= this.maxx; j++)
        for (int k = this.miny; k < (this.miny + this.maxy) / 2; k++)
        {
          int m = this.maxy - (k - this.miny);
          String str = contentAt2(j, k, i);
          setAt(j, k, i, contentAt2(j, m, i));
          setAt(j, m, i, str);
        }
  }

  public String contentAt(int paramInt1, int paramInt2, int paramInt3)
  {
    Location localLocation = new Location(paramInt1, paramInt2, paramInt3);
    String str = (String)this.tiles.get(localLocation);
    if (str == null) System.err.println(new StringBuilder().append("Null at ").append(paramInt1).append(",").append(paramInt2).append(",").append(paramInt3).append(" Possible loading error").toString());
    return str == null ? "null" : str;
  }

  public String contentAt2(int paramInt1, int paramInt2, int paramInt3)
  {
    Location localLocation = new Location(paramInt1, paramInt2, paramInt3);
    return (String)this.tiles.get(localLocation);
  }

  public void setAt(int paramInt1, int paramInt2, int paramInt3, String paramString)
  {
    if (this.sizeunknown)
    {
      this.minx = (this.maxx = paramInt1);
      this.miny = (this.maxy = paramInt2);
      this.minz = (this.maxz = paramInt3);
      this.sizeunknown = false;
    }
    else
    {
      this.minx = Math.min(this.minx, paramInt1);
      this.miny = Math.min(this.miny, paramInt2);
      this.minz = Math.min(this.minz, paramInt3);
      this.maxx = Math.max(this.maxx, paramInt1);
      this.maxy = Math.max(this.maxy, paramInt2);
      this.maxz = Math.max(this.maxz, paramInt3);
    }
    Location localLocation = new Location(paramInt1, paramInt2, paramInt3);
    localLocation.set(paramInt1, paramInt2, paramInt3);
    this.tiles.put(localLocation, paramString);
  }

  public void save(File paramFile) throws Exception
  {
    saveReferencing(paramFile, null);
  }

  public void saveReferencing(File paramFile, Map paramMap) throws Exception
  {
    FileWriter localFileWriter = new FileWriter(paramFile);

    this.tile_types.clear();
    this.codes_by_value.clear();
    Vector localVector1 = new Vector();
    for (Object localObject1 = this.tiles.keySet().iterator(); ((Iterator)localObject1).hasNext(); ) { Location localLocation = (Location)((Iterator)localObject1).next();

      String str1 = (String)this.tiles.get(localLocation);
      if (!localVector1.contains(str1))
        localVector1.add(str1);
    }
    MapPatcher.Systemoutprintln(new StringBuilder().append("We have ").append(localVector1.size()).append(" different tiles").toString());
    localObject1 = new String[] { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };

    int i = 1;
    int j = localObject1.length;
    while (j < localVector1.size())
    {
      j *= localObject1.length;
      i++;
    }
    Vector localVector2;
    if (paramMap == null) {
      localVector2 = localVector1;
    }
    else {
      localVector2 = new Vector();
      for (Iterator localIterator = localVector1.iterator(); localIterator.hasNext(); ) { localObject2 = (String)localIterator.next();

        if (paramMap.codes_by_value.containsKey(localObject2))
        {
          localObject3 = paramMap.getIdFor((String)localObject2);
          this.tile_types.put(localObject3, localObject2);
          this.codes_by_value.put(localObject2, localObject3);
        }
        else {
          localVector2.add(localObject2);
        } }
      localVector1.clear();
    }

    int k = 0;
    for (Object localObject2 = localVector2.iterator(); ((Iterator)localObject2).hasNext(); ) { localObject3 = (String)((Iterator)localObject2).next();
      do
      {
        str2 = int2code((String[])localObject1, k, i);
        k++;
      }while (this.tile_types.containsKey(str2));
      this.tile_types.put(str2, localObject3);
      this.codes_by_value.put(localObject3, str2);
    }
    String str2;
    localVector2.clear();

    k = 0;
    for (int m = 0; m < this.tile_types.size(); m++)
    {
      do
      {
        localObject3 = int2code((String[])localObject1, k, i);
        k++;
      }while (!this.tile_types.containsKey(localObject3));
      str2 = (String)this.tile_types.get(localObject3);
      localFileWriter.write(new StringBuilder().append("\"").append((String)localObject3).append("\" = ").append(str2).append("\r\n").toString());
    }
    localVector2.clear();

    localFileWriter.write("\n");

    m = 1 + this.maxz - this.minz;
    Object localObject3 = new SavingThread[m];
    int n = (this.maxy - this.miny) * ((this.maxx - this.minx) * i + 2) + 32;

    for (k = 0; k < m; k++)
    {
      localObject3[k] = new SavingThread(this.minz + k, this, n);
      localObject3[k].start();
    }

    int i1 = 0;
    String str3 = "";
    while (i1 == 0) {
      try {
        Thread.sleep(100L); } catch (Exception localException) {
      }
      i1 = 1;

      str3 = "";
      for (k = 0; k < m; k++)
      {
        if (!localObject3[k].done)
          i1 = 0;
        if (str3.length() != 0) str3 = new StringBuilder().append(str3).append(" ").toString();
        str3 = new StringBuilder().append(str3).append(localObject3[k].done ? "Done" : new StringBuilder().append(localObject3[k].progress).append("%").toString()).toString();
      }
      MapPatcher.Systemoutprint(new StringBuilder().append(str3).append("\r").toString());
    }

    for (k = 0; k < m; k++) {
      localFileWriter.write(localObject3[k].result.toString());
    }
    localFileWriter.flush();
    localFileWriter.close();
  }

  public String getIdFor(String paramString)
  {
    if (this.codes_by_value.containsKey(paramString))
    {
      return (String)this.codes_by_value.get(paramString);
    }
    return "???";
  }

  public String int2code(String[] paramArrayOfString, int paramInt1, int paramInt2)
  {
    String str = "";
    while (paramInt1 >= paramArrayOfString.length)
    {
      int i = paramInt1 % paramArrayOfString.length;
      str = new StringBuilder().append(paramArrayOfString[i]).append(str).toString();
      paramInt1 -= i;
      paramInt1 /= paramArrayOfString.length;
    }
    str = new StringBuilder().append(paramArrayOfString[paramInt1]).append(str).toString();
    while (str.length() < paramInt2) str = new StringBuilder().append(paramArrayOfString[0]).append(str).toString();
    return str;
  }
}


code:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Arrays;

public class MapPatcher
{
  static boolean silent = false;

  public static void main(String[] paramArrayOfString)
  {
    String str1 = "usage: [me] -diff [old_map] [new_map] [diff_file]";
    String str2 = "usage: [me] -patch [old_map] [diff_file] [new_map]";
    String str3 = "usage: [me] -pack [unpacked] [packed.dmm]";
    String str4 = "usage: [me] -unpack [packed.dmm] [unpacked]";
    String str5 = "usage: [me] -clean [oldmap.dmm] [newmap.dmm] [cleaned.dmm]";
    String str6 = "usage: [me] -merge [original] [local] [remote] [output]";

    for (int i = 0; i < paramArrayOfString.length; i++)
      if (paramArrayOfString[i].equalsIgnoreCase("-silent"))
      {
        silent = true;
        for (; i < paramArrayOfString.length - 1; i++)
          paramArrayOfString[i] = paramArrayOfString[(i + 1)];
        paramArrayOfString = (String[])Arrays.copyOf(paramArrayOfString, paramArrayOfString.length - 1);
        break;
      }
    Object localObject;
    int i2;
    int i3;
    int i5;
    if ((paramArrayOfString.length > 0) && (paramArrayOfString[0].equalsIgnoreCase("-merge")))
    {
      if (paramArrayOfString.length < 5)
      {
        System.out.println(str6);
        try { System.in.read(); } catch (Exception localException1) {
        }return;
      }

      Map localMap1 = new Map(new File(paramArrayOfString[1]));
      localObject = new Map(new File(paramArrayOfString[2]));
      Map localMap8 = new Map(new File(paramArrayOfString[3]));
      Map localMap9 = new Map();

      if ((localMap1.minx != ((Map)localObject).minx) || (localMap1.minx != localMap8.minx) || (localMap1.maxx != ((Map)localObject).maxx) || (localMap1.maxx != localMap8.maxx) || (localMap1.miny != ((Map)localObject).miny) || (localMap1.miny != localMap8.miny) || (localMap1.maxy != ((Map)localObject).maxy) || (localMap1.maxy != localMap8.maxy) || (localMap1.minz != ((Map)localObject).minz) || (localMap1.minz != localMap8.minz) || (localMap1.maxz != ((Map)localObject).maxz) || (localMap1.maxz != localMap8.maxz))
      {
        Systemoutprintln("Map sizes differ");
        System.exit(1);
      }
      try
      {
        for (int n = localMap1.minz; n <= localMap1.maxz; n++)
          for (i2 = localMap1.miny; i2 <= localMap1.maxy; i2++)
            for (i3 = localMap1.minx; i3 <= localMap1.maxx; i3++)
            {
              boolean bool1 = localMap1.contentAt(i3, i2, n).equals(((Map)localObject).contentAt(i3, i2, n));
              boolean bool2 = localMap1.contentAt(i3, i2, n).equals(localMap8.contentAt(i3, i2, n));
              i5 = ((Map)localObject).contentAt(i3, i2, n).equals(localMap8.contentAt(i3, i2, n));
              if ((!bool1) && (!bool2))
              {
                if (i5 == 0)
                {
                  Systemoutprintln(i3 + "," + i2 + "," + n + " local and remote don't match original and differ");
                  System.exit(1);
                }
                else {
                  localMap9.setAt(i3, i2, n, ((Map)localObject).contentAt(i3, i2, n));
                }
              } else if (!bool1)
                localMap9.setAt(i3, i2, n, ((Map)localObject).contentAt(i3, i2, n));
              else if (!bool2)
                localMap9.setAt(i3, i2, n, localMap8.contentAt(i3, i2, n));
              else
                localMap9.setAt(i3, i2, n, localMap1.contentAt(i3, i2, n));
            }
        Systemoutprintln("Saving");
        localMap9.saveReferencing(new File(paramArrayOfString[4]), localMap1);
        Systemoutprintln("Done");
      }
      catch (Exception localException12)
      {
        localException12.printStackTrace();
      }
    }
    else
    {
      int m;
      int i1;
      if ((paramArrayOfString.length > 0) && (paramArrayOfString[0].equalsIgnoreCase("-diff")))
      {
        if (paramArrayOfString.length < 4)
        {
          System.out.println(str1);
          try { System.in.read(); } catch (Exception localException2) {
          }return;
        }

        Map localMap2 = new Map(new File(paramArrayOfString[1]));
        localObject = new Map(new File(paramArrayOfString[2]));

        int j = Math.max(localMap2.minx, ((Map)localObject).minx);
        m = Math.min(localMap2.maxx, ((Map)localObject).maxx);
        i1 = Math.max(localMap2.miny, ((Map)localObject).miny);
        i2 = Math.min(localMap2.maxy, ((Map)localObject).maxy);
        i3 = Math.max(localMap2.minz, ((Map)localObject).minz);
        int i4 = Math.min(localMap2.maxz, ((Map)localObject).maxz);
        Systemoutprintln("Comparing: x(" + j + "-" + m + ") y(" + i1 + "-" + i2 + ") z(" + i3 + "-" + i4 + ")");
        try
        {
          FileWriter localFileWriter2 = new FileWriter(paramArrayOfString[3]);
          i5 = 0;
          for (int i6 = i3; i6 <= i4; i6++)
          {
            Systemoutprintln("Z-level " + i6);
            for (int i7 = i1; i7 <= i2; i7++)
              for (int i8 = j; i8 <= m; i8++)
                if (!localMap2.contentAt(i8, i7, i6).equals(((Map)localObject).contentAt(i8, i7, i6)))
                {
                  localFileWriter2.write("(" + i8 + "," + (1 + ((Map)localObject).maxy - i7) + "," + i6 + ")=" + ((Map)localObject).contentAt(i8, i7, i6) + "\n");
                  i5++;
                }
          }
          localFileWriter2.flush();
          localFileWriter2.close();
          if (i5 == 0)
            Systemoutprintln("Files do match");
          else
            Systemoutprintln("Writed out " + i5 + " differences");
        }
        catch (Exception localException13)
        {
          localException13.printStackTrace();
        }

        Systemoutprintln("Done");
      }
      else
      {
        String str7;
        String str8;
        if ((paramArrayOfString.length > 0) && (paramArrayOfString[0].equalsIgnoreCase("-patch")))
        {
          if (paramArrayOfString.length < 4)
          {
            System.out.println(str2);
            try { System.in.read(); } catch (Exception localException3) {
            }return;
          }

          Map localMap3 = new Map(new File(paramArrayOfString[1]));
          try
          {
            localObject = new BufferedReader(new InputStreamReader(new FileInputStream(paramArrayOfString[2])));

            while ((str7 = ((BufferedReader)localObject).readLine()) != null)
            {
              str7 = str7.trim();
              if (str7.length() != 0)
              {
                m = str7.indexOf(",", 1);
                i1 = Integer.parseInt(str7.substring(1, m));
                str7 = str7.substring(m);
                m = str7.indexOf(",", 1);
                i2 = Integer.parseInt(str7.substring(1, m));
                str7 = str7.substring(m);
                m = str7.indexOf(")", 1);
                i3 = Integer.parseInt(str7.substring(1, m));
                str8 = str7.substring(str7.indexOf("=") + 1);
                localMap3.setAt(i1, 1 + localMap3.maxy - i2, i3, str8);
              }
            }
            localMap3.save(new File(paramArrayOfString[3]));
          }
          catch (Exception localException8)
          {
            localException8.printStackTrace();
          }

          Systemoutprintln("Done");
        }
        else if ((paramArrayOfString.length > 0) && (paramArrayOfString[0].equalsIgnoreCase("-pack")))
        {
          if (paramArrayOfString.length < 3)
          {
            System.out.println(str3);
            try { System.in.read(); } catch (Exception localException4) {
            }return;
          }

          Map localMap4 = new Map();
          try {
            BufferedReader localBufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(paramArrayOfString[1])));
            Systemoutprintln("Loading");

            while ((str7 = localBufferedReader.readLine()) != null)
            {
              str7 = str7.trim();
              if (str7.length() != 0)
              {
                m = str7.indexOf(",", 1);
                i1 = Integer.parseInt(str7.substring(1, m));
                str7 = str7.substring(m);
                m = str7.indexOf(",", 1);
                i2 = Integer.parseInt(str7.substring(1, m));
                str7 = str7.substring(m);
                m = str7.indexOf(")", 1);
                i3 = Integer.parseInt(str7.substring(1, m));
                str8 = str7.substring(str7.indexOf("=") + 1);
                localMap4.setAt(i1, i2, i3, str8);
              }
            }
            Systemoutprintln("Flipping");
            localMap4.mirrorY();
            Systemoutprintln("Saving, bounds: x{" + localMap4.minx + " - " + localMap4.maxx + "}, y{" + localMap4.miny + " - " + localMap4.maxy + "}, z{" + localMap4.minz + " - " + localMap4.maxz + "}");
            localMap4.save(new File(paramArrayOfString[2]));
            Systemoutprintln("Done");
          }
          catch (Exception localException9)
          {
            localException9.printStackTrace();
          }
        }
        else if ((paramArrayOfString.length > 0) && (paramArrayOfString[0].equalsIgnoreCase("-unpack")))
        {
          if (paramArrayOfString.length < 3)
          {
            System.out.println(str4);
            try { System.in.read(); } catch (Exception localException5) {
            }return;
          }

          Systemoutprintln("Loading");
          Map localMap5 = new Map(new File(paramArrayOfString[1]));
          try {
            FileWriter localFileWriter1 = new FileWriter(paramArrayOfString[2]);
            Systemoutprintln("Saving");
            for (int k = localMap5.minz; k <= localMap5.maxz; k++)
            {
              Systemoutprintln("Z-level " + k);
              for (m = localMap5.miny; m <= localMap5.maxy; m++)
                for (i1 = localMap5.minx; i1 <= localMap5.maxx; i1++)
                  localFileWriter1.write("(" + i1 + "," + (1 + localMap5.maxy - m) + "," + k + ")=" + localMap5.contentAt(i1, m, k) + "\n");
              localFileWriter1.write("\n");
            }
            localFileWriter1.flush();
            localFileWriter1.close();

            Systemoutprintln("Done");
          }
          catch (Exception localException10)
          {
            localException10.printStackTrace();
          }
        }
        else if ((paramArrayOfString.length > 0) && (paramArrayOfString[0].equalsIgnoreCase("-clean")))
        {
          if (paramArrayOfString.length < 4)
          {
            System.out.println(str5);
            try { System.in.read(); } catch (Exception localException6) {
            }return;
          }

          Map localMap6 = new Map(new File(paramArrayOfString[1]), true);
          Map localMap7 = new Map(new File(paramArrayOfString[2]));
          try
          {
            localMap7.saveReferencing(new File(paramArrayOfString[3]), localMap6);
            Systemoutprintln("Done");
          }
          catch (Exception localException11)
          {
            localException11.printStackTrace();
          }
        }
        else
        {
          System.out.println(str1);
          System.out.println(str2);
          System.out.println(str3);
          System.out.println(str4);
          System.out.println(str5);
          System.out.println(str6);
          try {
            System.in.read(); } catch (Exception localException7) {  }
        }
      }
    }
  }

  public static void Systemoutprintln(String paramString) { if (!silent)
      System.out.println(paramString); }

  public static void Systemoutprint(String paramString)
  {
    if (!silent)
      System.out.print(paramString);
  }
}


code:
class SavingThread extends Thread
{
  int z;
  Map mymap;
  boolean done;
  int progress;
  StringBuilder result;

  public SavingThread(int paramInt1, Map paramMap, int paramInt2)
  {
    this.z = paramInt1;
    this.mymap = paramMap;
    this.progress = 0;
    this.done = false;
    this.result = new StringBuilder(paramInt2);
  }

  public void run()
  {
    this.result.append("(" + this.mymap.minx + "," + this.mymap.miny + "," + this.z + ") = {\"\r\n");

    int i = (this.mymap.maxx - this.mymap.minx) * (this.mymap.maxy - this.mymap.miny) / 100;
    int j = 0;
    for (int k = this.mymap.miny; k <= this.mymap.maxy; k++)
    {
      for (int m = this.mymap.minx; m <= this.mymap.maxx; m++)
      {
        this.result.append(this.mymap.getIdFor(this.mymap.contentAt(m, k, this.z)));
        j++; if (j >= i) { j = 0; this.progress += 1; }
      }
      this.result.append("\r\n");
    }
    this.result.append("\"}\r\n");
    this.result.append("\r\n");
    this.done = true;
  }
}


code:
Microsoft Visual Studio Solution File, Format Version 11.00
# Visual C# Express 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "midi2piano", "midi2piano\midi2piano.csproj", "{68C84B61-F710-491C-BEE8-5E362C167897}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Debug|Mixed Platforms = Debug|Mixed Platforms
		Debug|x86 = Debug|x86
		Release|Any CPU = Release|Any CPU
		Release|Mixed Platforms = Release|Mixed Platforms
		Release|x86 = Release|x86
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|Any CPU.ActiveCfg = Debug|x86
		{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
		{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|Mixed Platforms.Build.0 = Debug|x86
		{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|x86.ActiveCfg = Debug|x86
		{68C84B61-F710-491C-BEE8-5E362C167897}.Debug|x86.Build.0 = Debug|x86
		{68C84B61-F710-491C-BEE8-5E362C167897}.Release|Any CPU.ActiveCfg = Release|x86
		{68C84B61-F710-491C-BEE8-5E362C167897}.Release|Mixed Platforms.ActiveCfg = Release|x86
		{68C84B61-F710-491C-BEE8-5E362C167897}.Release|Mixed Platforms.Build.0 = Release|x86
		{68C84B61-F710-491C-BEE8-5E362C167897}.Release|x86.ActiveCfg = Release|x86
		{68C84B61-F710-491C-BEE8-5E362C167897}.Release|x86.Build.0 = Release|x86
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
EndGlobal


code:
namespace midi2piano
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.OutputTxt = new System.Windows.Forms.TextBox();
            this.menuStrip1 = new System.Windows.Forms.MenuStrip();
            this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
            this.importMIDIToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
            this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
            this.copyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
            this.importDlg = new System.Windows.Forms.OpenFileDialog();
            this.halpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
            this.menuStrip1.SuspendLayout();
            this.SuspendLayout();
            //
            // OutputTxt
            //
            this.OutputTxt.Dock = System.Windows.Forms.DockStyle.Fill;
            this.OutputTxt.Location = new System.Drawing.Point(0, 24);
            this.OutputTxt.Multiline = true;
            this.OutputTxt.Name = "OutputTxt";
            this.OutputTxt.ReadOnly = true;
            this.OutputTxt.ScrollBars = System.Windows.Forms.ScrollBars.Both;
            this.OutputTxt.Size = new System.Drawing.Size(284, 240);
            this.OutputTxt.TabIndex = 0;
            //
            // menuStrip1
            //
            this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.fileToolStripMenuItem,
            this.copyToolStripMenuItem,
            this.halpToolStripMenuItem});
            this.menuStrip1.Location = new System.Drawing.Point(0, 0);
            this.menuStrip1.Name = "menuStrip1";
            this.menuStrip1.Size = new System.Drawing.Size(284, 24);
            this.menuStrip1.TabIndex = 1;
            this.menuStrip1.Text = "menuStrip1";
            //
            // fileToolStripMenuItem
            //
            this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.importMIDIToolStripMenuItem,
            this.exitToolStripMenuItem});
            this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
            this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20);
            this.fileToolStripMenuItem.Text = "&File";
            //
            // importMIDIToolStripMenuItem
            //
            this.importMIDIToolStripMenuItem.Name = "importMIDIToolStripMenuItem";
            this.importMIDIToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.I)));
            this.importMIDIToolStripMenuItem.Size = new System.Drawing.Size(184, 22);
            this.importMIDIToolStripMenuItem.Text = "&Import MIDI...";
            this.importMIDIToolStripMenuItem.Click += new System.EventHandler(this.importMIDIToolStripMenuItem_Click);
            //
            // exitToolStripMenuItem
            //
            this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
            this.exitToolStripMenuItem.Size = new System.Drawing.Size(184, 22);
            this.exitToolStripMenuItem.Text = "E&xit";
            this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click);
            //
            // copyToolStripMenuItem
            //
            this.copyToolStripMenuItem.Name = "copyToolStripMenuItem";
            this.copyToolStripMenuItem.Size = new System.Drawing.Size(47, 20);
            this.copyToolStripMenuItem.Text = "&Copy";
            this.copyToolStripMenuItem.Click += new System.EventHandler(this.copyToolStripMenuItem_Click);
            //
            // importDlg
            //
            this.importDlg.Filter = "MIDI File|*.midi;*.mid";
            //
            // halpToolStripMenuItem
            //
            this.halpToolStripMenuItem.Name = "halpToolStripMenuItem";
            this.halpToolStripMenuItem.Size = new System.Drawing.Size(44, 20);
            this.halpToolStripMenuItem.Text = "&Halp";
            this.halpToolStripMenuItem.Click += new System.EventHandler(this.halpToolStripMenuItem_Click);
            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 264);
            this.Controls.Add(this.OutputTxt);
            this.Controls.Add(this.menuStrip1);
            this.MainMenuStrip = this.menuStrip1;
            this.Name = "Form1";
            this.Text = "MIDI2Piano";
            this.menuStrip1.ResumeLayout(false);
            this.menuStrip1.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.TextBox OutputTxt;
        private System.Windows.Forms.MenuStrip menuStrip1;
        private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
        private System.Windows.Forms.ToolStripMenuItem importMIDIToolStripMenuItem;
        private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem;
        private System.Windows.Forms.OpenFileDialog importDlg;
        private System.Windows.Forms.ToolStripMenuItem copyToolStripMenuItem;
        private System.Windows.Forms.ToolStripMenuItem halpToolStripMenuItem;
    }
}


code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Sanford.Multimedia;
using Sanford.Multimedia.Midi;

namespace midi2piano
{
    public partial class Form1 : Form
    {
        [STAThread]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new Form1());
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        struct PNote
        {
            public float Length;
            public string Note;

            public PNote(float length, string note)
            {
                Length = length;
                Note = note;
            }

            public static readonly PNote Default = new PNote(0, "");
        }

        private void importMIDIToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (importDlg.ShowDialog(this)
                == System.Windows.Forms.DialogResult.Cancel)
                return;

            List<PNote> notes = new List<PNote>();
            PNote curNote = PNote.Default;
            float tempo = 1;
            float timeSig = 4f;

            // first, we pull midi data
            Sequence s = new Sequence(importDlg.FileName);

            // quickly see if there's a piano track first
            // and get the tempo as well
            int piano = -1;
            for (int it = 0; it < s.Count; it++)
            {
                Track t = s[it];
                foreach (MidiEvent me in t.Iterator())
                {
                    switch (me.MidiMessage.MessageType)
                    {
                        case MessageType.Channel:
                            {
                                ChannelMessage m = (ChannelMessage)me.MidiMessage;
                                if (m.Command == ChannelCommand.ProgramChange)
                                    if ((GeneralMidiInstrument)m.Data1 == GeneralMidiInstrument.AcousticGrandPiano)
                                    {
                                        piano = it;
                                    }
                            }
                            break;
                        case MessageType.Meta:
                            {
                                MetaMessage m = (MetaMessage)me.MidiMessage;
                                if (m.MetaType == MetaType.Tempo)
                                    tempo = (new TempoChangeBuilder(m)).Tempo;
                                else if (m.MetaType == MetaType.TimeSignature)
                                    timeSig = new TimeSignatureBuilder(m).Denominator;
                            }
                            break;
                    }
                    if (piano >= 0)
                        break;
                }
                if (piano >= 0)
                    break;
            }

            // didn't find one, so just try 0th track anyway
            if (piano == -1)
                piano = 0;

            // now, pull all notes (and tempo)
            // and make sure it's a channel that has content
            for (int it = piano; it < s.Count; it++)
            {
                Track t = s[it];

                int delta = 0;
                foreach (MidiEvent me in t.Iterator())
                {
                    delta += me.DeltaTicks;

                    switch (me.MidiMessage.MessageType)
                    {
                        case MessageType.Channel:
                            {
                                ChannelMessage m = (ChannelMessage)me.MidiMessage;
                                switch (m.Command)
                                {
                                    case ChannelCommand.NoteOn:
                                        if (curNote.Note != "")
                                        {
                                            curNote.Length = delta / 1000F;
                                            delta = 0;
                                            notes.Add(curNote);
                                        }

                                        curNote.Note = note2Piano(m.Data1);
                                        break;
                                }
                            }
                            break;
                        case MessageType.Meta:
                            {
                                MetaMessage m = (MetaMessage)me.MidiMessage;
                                if (m.MetaType == MetaType.Tempo)
                                    tempo = (new TempoChangeBuilder(m)).Tempo;
                            }
                            break;
                    }
                }

                // make sure we get last note
                if (curNote.Note != "")
                {
                    curNote.Length = delta / 1000F;
                    notes.Add(curNote);
                }

                // we found a track with content!
                if (notes.Count > 0)
                    break;
            }

            // compress redundant accidentals/octaves
            char[] notemods = new char[7];
            int[] noteocts = new int[7];
            for (int i = 0; i < 7; i++)
            {
                notemods[i] = 'n';
                noteocts[i] = 3;
            }
            for (int i = 0; i < notes.Count; i++)
            {
                string noteStr = notes[i].Note;
                int cur_note = noteStr[0] - 0x41;
                char mod = noteStr[1];
                int oct = int.Parse(noteStr.Substring(2));

                noteStr = noteStr.Substring(0, 1);
                if (mod != notemods[cur_note])
                {
                    noteStr += new string(mod, 1);
                    notemods[cur_note] = mod;
                }
                if (oct != noteocts[cur_note])
                {
                    noteStr += oct.ToString();
                    noteocts[cur_note] = oct;
                }

                notes[i] = new PNote(notes[i].Length, noteStr);
            }

            // now, we find what the "beat" length should be,
            // by counting numbers of times for each length, and finding statistical mode
            Dictionary<float, int> scores = new Dictionary<float, int>();
            foreach (PNote n in notes)
            {
                if (n.Length != 0)
                    if (scores.Keys.Contains(n.Length))
                        scores[n.Length]++;
                    else
                        scores.Add(n.Length, 1);
            }
            float winner = 1;
            int score = 0;
            foreach (KeyValuePair<float, int> kv in scores)
            {
                if (kv.Value > score)
                {
                    winner = kv.Key;
                    score = kv.Value;
                }
            }
            // realign all of them to match beat length
            for (int i = 0; i < notes.Count; i++)
            {
                notes[i] = new PNote(notes[i].Length / winner, notes[i].Note);
            }

            // compress chords down
            for (int i = 0; i < notes.Count; i++)
            {
                if (notes[i].Length == 0 && i < notes.Count - 1)
                {
                    notes[i + 1] = new PNote(notes[i + 1].Length, notes[i].Note + "-" + notes[i + 1].Note);
                    notes.RemoveAt(i);
                    i--;
                }
            }

            // add in time
            for (int i = 0; i < notes.Count; i++)
            {
                float len = notes[i].Length;
                notes[i] = new PNote(len, notes[i].Note + (len != 1 ? "/" + (1 / len).ToString("0.##") : ""));
            }

            // what is the bpm, anyway?
            int rpm = (int)(28800000 / tempo / winner); // 60 * 1,000,000 * .48  the .48 is because note lengths for some reason midi makes the beat note be .48 long

            // now, output!
            string line = "";
            string output = "";
            int lineCount = 1;
            foreach (PNote n in notes)
            {
                if (line.Length + n.Note.Length + 1 > 51)
                {
                    output += line.Substring(0, line.Length - 1) + "\r\n";
                    line = "";
                    if (lineCount == 50)
                        break;
                    lineCount++;
                }
                line += n.Note + ",";
            }
            if (line.Length > 0)
                output += line.Substring(0, line.Length - 1);
            OutputTxt.Text = "BPM: " + rpm.ToString() + "\r\n" + output;
            OutputTxt.SelectAll();
        }

        public enum NoteNames
        {
            C = 0,
            D = 2,
            E = 4,
            F = 5,
            G = 7,
            A = 9,
            B = 11
        }

        string note2Piano(int n)
        {
            string name, arg, octave;
            name = Enum.GetName(typeof(NoteNames), (NoteNames)(n % 12));
            if (name == null)
            {
                name = Enum.GetName(typeof(NoteNames), (NoteNames)((n + 1) % 12));
                arg = "b";
            }
            else
            {
                arg = "n";
            }
            octave = (n / 12 - 1).ToString();

            return name + arg + octave;
        }

        private void copyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OutputTxt.SelectAll();
            OutputTxt.Copy();
        }

        private void halpToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show(this,
                "This program prefers MIDIs that have a single track, otherwise it picks the first piano track it finds, else the first track. Songs with odd tempos may have their BPM's calculated wrong.",
                "Halp", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }
}


code:
<?xml version="1.0" encoding="utf-8"?>
<root>
  <!--
    Microsoft ResX Schema

    Version 2.0

    The primary goals of this format is to allow a simple XML format
    that is mostly human readable. The generation and parsing of the
    various data types are done through the TypeConverter classes
    associated with the data types.

    Example:

    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>

    There are any number of "resheader" rows that contain simple
    name/value pairs.

    Each data row contains a name, and value. The row also contains a
    type or mimetype. Type corresponds to a .NET class that support
    text/value conversion through the TypeConverter architecture.
    Classes that don't support this are serialized and stored with the
    mimetype set.

    The mimetype is used for serialized objects, and tells the
    ResXResourceReader how to depersist the object. This is currently not
    extensible. For a given mimetype the value must be set accordingly:

    Note - application/x-microsoft.net.object.binary.base64 is the format
    that the ResXResourceWriter will generate, however the reader can
    read any of the formats listed below.

    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
    <value>17, 17</value>
  </metadata>
  <metadata name="importDlg.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
    <value>132, 17</value>
  </metadata>
</root>


code:
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("midi2piano")]
[assembly: AssemblyProduct("midi2piano")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyCopyright("Copyright ©  2011")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type. Only Windows
// assemblies support COM.
[assembly: ComVisible(false)]

// On Windows, the following GUID is for the ID of the typelib if this
// project is exposed to COM. On other platforms, it unique identifies the
// title storage container when deploying this assembly to the device.
[assembly: Guid("9752c562-edc1-40da-8fa1-619df747e0f3")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
[assembly: AssemblyVersion("1.0.0.0")]


code:
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
    <OutputType>WinExe</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>midi2piano</RootNamespace>
    <AssemblyName>midi2piano</AssemblyName>
    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    <TargetFrameworkProfile>Client</TargetFrameworkProfile>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\x86\Debug</OutputPath>
    <DefineConstants>DEBUG;TRACE;WINDOWS</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <NoStdLib>true</NoStdLib>
    <UseVSHostingProcess>false</UseVSHostingProcess>
    <PlatformTarget>x86</PlatformTarget>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\x86\Release</OutputPath>
    <DefineConstants>TRACE;WINDOWS</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <NoStdLib>true</NoStdLib>
    <UseVSHostingProcess>false</UseVSHostingProcess>
    <PlatformTarget>x86</PlatformTarget>
  </PropertyGroup>
  <PropertyGroup>
    <StartupObject />
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="mscorlib">
      <Private>False</Private>
    </Reference>
    <Reference Include="Sanford.Multimedia.Midi, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>.\Sanford.Multimedia.Midi.dll</HintPath>
    </Reference>
    <Reference Include="System">
      <Private>False</Private>
    </Reference>
    <Reference Include="System.Data" />
    <Reference Include="System.Drawing" />
    <Reference Include="System.Windows.Forms" />
    <Reference Include="System.Xml">
      <Private>False</Private>
    </Reference>
    <Reference Include="System.Core">
      <RequiredTargetFramework>4.0</RequiredTargetFramework>
      <Private>False</Private>
    </Reference>
    <Reference Include="System.Xml.Linq">
      <RequiredTargetFramework>4.0</RequiredTargetFramework>
      <Private>False</Private>
    </Reference>
    <Reference Include="System.Net">
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Form1.cs">
      <SubType>Form</SubType>
    </Compile>
    <Compile Include="Form1.Designer.cs">
      <DependentUpon>Form1.cs</DependentUpon>
    </Compile>
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <ItemGroup>
    <EmbeddedResource Include="Form1.resx">
      <DependentUpon>Form1.cs</DependentUpon>
    </EmbeddedResource>
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>


code:
the compiled exe file for the Unstandardness text for DM program is in:
UnstandardnessTestForDM\bin\Debug\UnstandardnessTestForDM.exe
of
UnstandardnessTestForDM\bin\Release\UnstandardnessTestForDM.exe

You have to move it to the root folder (where the dme file is) and run it from there for it to work.


code:
'''
Usage:
    $ python ss13_genchangelog.py [--dry-run] html/changelog.html html/changelogs/

ss13_genchangelog.py - Generate changelog from YAML.

Copyright 2013 Rob "N3X15" Nelson <nexis@7chan.org>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
'''

from __future__ import print_function
import yaml, os, glob, sys, re, time, argparse
from datetime import datetime, date
from time import time

today = date.today()

dateformat = "%d %B %Y"

opt = argparse.ArgumentParser()
opt.add_argument('-d', '--dry-run', dest='dryRun', default=False, action='store_true', help='Only parse changelogs and, if needed, the targetFile. (A .dry_changelog.yml will be output for debugging purposes.)')
opt.add_argument('targetFile', help='The HTML changelog we wish to update.')
opt.add_argument('ymlDir', help='The directory of YAML changelogs we will use.')

args = opt.parse_args()

all_changelog_entries = {}

validPrefixes = [
    'bugfix',
    'wip',
    'tweak',
    'soundadd',
    'sounddel',
    'rscdel',
    'rscadd',
    'imageadd',
    'imagedel',
    'spellcheck',
    'experiment',
    'tgs'
]

def dictToTuples(inp):
    return [(k, v) for k, v in inp.items()]

changelog_cache = os.path.join(args.ymlDir, '.all_changelog.yml')

failed_cache_read = True
if os.path.isfile(changelog_cache):
    try:
        with open(changelog_cache) as f:
            (_, all_changelog_entries) = yaml.load_all(f)
            failed_cache_read = False

            # Convert old timestamps to newer format.
            new_entries = {}
            for _date in all_changelog_entries.keys():
                ty = type(_date).__name__
                # print(ty)
                if ty in ['str', 'unicode']:
                    temp_data = all_changelog_entries[_date]
                    _date = datetime.strptime(_date, dateformat).date()
                    new_entries[_date] = temp_data
                else:
                    new_entries[_date] = all_changelog_entries[_date]
            all_changelog_entries = new_entries
    except Exception as e:
        print("Failed to read cache:")
        print(e, file=sys.stderr)

if args.dryRun:
    changelog_cache = os.path.join(args.ymlDir, '.dry_changelog.yml')

if failed_cache_read and os.path.isfile(args.targetFile):
    from bs4 import BeautifulSoup
    from bs4.element import NavigableString
    print(' Generating cache...')
    with open(args.targetFile, 'r') as f:
        soup = BeautifulSoup(f)
        for e in soup.find_all('div', {'class':'commit'}):
            entry = {}
            date = datetime.strptime(e.h2.string.strip(), dateformat).date()  # key
            for authorT in e.find_all('h3', {'class':'author'}):
                author = authorT.string
                # Strip suffix
                if author.endswith('updated:'):
                    author = author[:-8]
                author = author.strip()

                # Find

                ulT = authorT.next_sibling
                while(ulT.name != 'ul'):
                    ulT = ulT.next_sibling
                changes = []

                for changeT in ulT.children:
                    if changeT.name != 'li': continue
                    val = changeT.decode_contents(formatter="html")
                    newdat = {changeT['class'][0] + '': val + ''}
                    if newdat not in changes:
                        changes += [newdat]

                if len(changes) > 0:
                    entry[author] = changes
            if date in all_changelog_entries:
                all_changelog_entries[date].update(entry)
            else:
                all_changelog_entries[date] = entry

del_after = []
print('Reading changelogs...')
for fileName in glob.glob(os.path.join(args.ymlDir, "*.yml")):
    name, ext = os.path.splitext(os.path.basename(fileName))
    if name.startswith('.'): continue
    if name == 'example': continue
    fileName = os.path.abspath(fileName)
    print(' Reading {}...'.format(fileName))
    cl = {}
    with open(fileName, 'r') as f:
        cl = yaml.load(f)
        f.close()
    if today not in all_changelog_entries:
        all_changelog_entries[today] = {}
    author_entries = all_changelog_entries[today].get(cl['author'], [])
    if len(cl['changes']):
        new = 0
        for change in cl['changes']:
            if change not in author_entries:
                (change_type, _) = dictToTuples(change)[0]
                if change_type not in validPrefixes:
                    print('  {0}: Invalid prefix {1}'.format(fileName, change_type), file=sys.stderr)
                author_entries += [change]
                new += 1
        all_changelog_entries[today][cl['author']] = author_entries
        if new > 0:
            print('  Added {0} new changelog entries.'.format(new))

    if cl.get('delete-after', False):
        if os.path.isfile(fileName):
            if args.dryRun:
                print('  Would delete {0} (delete-after set)...'.format(fileName))
            else:
                del_after += [fileName]

    if args.dryRun: continue

    cl['changes'] = []
    with open(fileName, 'w') as f:
        yaml.dump(cl, f, default_flow_style=False)

targetDir = os.path.dirname(args.targetFile)

with open(args.targetFile.replace('.htm', '.dry.htm') if args.dryRun else args.targetFile, 'w') as changelog:
    with open(os.path.join(targetDir, 'templates', 'header.html'), 'r') as h:
        for line in h:
            changelog.write(line)

    for _date in reversed(sorted(all_changelog_entries.keys())):
        entry_htm = '\n'
        entry_htm += '\t\t\t
{date}
\n'.format(date=_date.strftime(dateformat))
        write_entry = False
        for author in sorted(all_changelog_entries[_date].keys()):
            if len(all_changelog_entries[_date]) == 0: continue
            author_htm = '\t\t\t
{author} updated:
\n'.format(author=author)
            author_htm += '\t\t\t
\n'
            changes_added = []
            for (css_class, change) in (dictToTuples(e)[0] for e in all_changelog_entries[_date][author]):
                if change in changes_added: continue
                write_entry = True
                changes_added += [change]
                author_htm += '\t\t\t\t
{change}
\n'.format(css_class=css_class, change=change.strip())
            author_htm += '\t\t\t
\n'
            if len(changes_added) > 0:
                entry_htm += author_htm
        if write_entry:
            changelog.write(entry_htm)

    with open(os.path.join(targetDir, 'templates', 'footer.html'), 'r') as h:
        for line in h:
            changelog.write(line)

with open(changelog_cache, 'w') as f:
    cache_head = 'DO NOT EDIT THIS FILE BY HAND!  AUTOMATICALLY GENERATED BY ss13_genchangelog.py.'
    yaml.dump_all([cache_head, all_changelog_entries], f, default_flow_style=False)

if len(del_after):
    print('Cleaning up...')
    for fileName in del_after:
        if os.path.isfile(fileName):
            print(' Deleting {0} (delete-after set)...'.format(fileName))
            os.remove(fileName)