From b4ab8b3592b8c26514b923eacc6aa44f17371ac8 Mon Sep 17 00:00:00 2001 From: PyroSamurai Date: Wed, 1 May 2024 22:45:56 -0400 Subject: [PATCH] 1.6.0 release, adds further Create support & user comfort, closes #7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit long awaited and funded 1.6.0 release, made possible by 3 donaters: Daniel Fernandes, 林瑋誠, github.com/grawlinson, Forever immortalized in this git commit. Thank you. --- README.md | 17 +- VERSION | 1 + compile-jar.bat | 13 + compile-jar.sh | 12 + docs/NORI_format.md | 8 +- docs/Unsupported.txt | 6 + src/Analyze.java | 480 +++++++++++++++----------- src/Analyzer.java | 729 ++++++++++++++++++++++----------------- src/Create.java | 716 +++++++++++++++++---------------------- src/Extract.java | 222 ++++++------ src/GetCfgData.java | 367 ++++++++++++++++++++ src/JBL.java | 788 +++++++++++++++++++++++-------------------- src/Main.java | 367 +++++++++----------- src/NORI.java | 324 ++++++++++++++---- 14 files changed, 2362 insertions(+), 1688 deletions(-) create mode 100644 VERSION create mode 100755 compile-jar.bat create mode 100755 compile-jar.sh create mode 100644 docs/Unsupported.txt create mode 100644 src/GetCfgData.java diff --git a/README.md b/README.md index bff44e4..89dca36 100755 --- a/README.md +++ b/README.md @@ -15,21 +15,10 @@ Non-updated forks are annoying. BTW, Followers > Stars > Watchers > Forks ------------------------------------ -How to compile and package TNT ----------------------------------- +__Compiling and Packaging__: -Install the Java JDK, links: [here](http://jdk.java.net) or [here](https://github.com/ojdkbuild/ojdkbuild) -Access the `src` folder from the command prompt or terminal. - -Then run the following command: -```bash -javac *.java -``` - -Then to package TNT into a `.jar` file, run this command in the same directory: -```bash -jar cfe TNT.jar Main *.class -``` +1. Install the Java JDK, links: [here](http://jdk.java.net) or [here](https://github.com/ojdkbuild/ojdkbuild) +2. Double-click `compile-jar.bat` or `compile-jar.sh` to make `TNT.jar` Now you can copy & paste TNT.jar anywhere you like and use it from there. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..18e9285 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.6.00 diff --git a/compile-jar.bat b/compile-jar.bat new file mode 100755 index 0000000..0767395 --- /dev/null +++ b/compile-jar.bat @@ -0,0 +1,13 @@ +@ECHO OFF +SET "ReadMe=TNT\README.md" +ATTRIB -r TNT.jar +DEL TNT.jar +CD .. +jar cf TNT\src\TNT.jar TNT\src\*.java TNT\docs TNT\LICENSE TNT\VERSION %ReadMe% +CD TNT\src +javac *.java +jar ufe TNT.jar Main *.class +CD .. +DEL src\*.class +MOVE src\TNT.jar TNT.jar +ATTRIB +r TNT.jar diff --git a/compile-jar.sh b/compile-jar.sh new file mode 100755 index 0000000..4a07840 --- /dev/null +++ b/compile-jar.sh @@ -0,0 +1,12 @@ +#!/bin/bash +src="TNT/src" +rm -f TNT.jar +cd .. +jar cf $src/TNT.jar $src/*.java TNT/docs TNT/LICENSE TNT/VERSION TNT/README.md +cd $src +javac *.java +jar ufe TNT.jar Main *.class +cd .. +rm src/*.class +mv src/TNT.jar TNT.jar +chmod 705 TNT.jar diff --git a/docs/NORI_format.md b/docs/NORI_format.md index 7f864f8..7b282dd 100644 --- a/docs/NORI_format.md +++ b/docs/NORI_format.md @@ -1,7 +1,7 @@ # NORI Format Specification
-Copyright (C) 2014-2020 Libre Trickster Team
+Copyright (C) 2014-2024 Libre Trickster Team
 
 Note: All sizes throughout the specification are measured in bytes.
 
@@ -45,7 +45,7 @@ NORI file as it is suppose to look:
 +--------------+-----+---------------------------------------------------------+
 | nParam2      |  4  | Unidentified data (ex: majority of mapbgeffect NORI)    |
 +--------------+-----+---------------------------------------------------------+
-| nParam3      |  4  | negative var of some sort (ex: most of the mapbgeffect) |
+| nParam3      |  4  | Unidentified data (ex: majority of mapbgeffect NORI)    |
 +--------------+-----+---------------------------------------------------------+
 | nParam4      |  4  | Unidentified data (ex: majority of mapbgeffect NORI)    |
 +--------------+-----+---------------------------------------------------------+
@@ -138,7 +138,7 @@ NORI file as it is suppose to look:
 | Name         |Bytes| Description                                             |
 +--------------+-----+---------------------------------------------------------+
 | bmp_count    |  4  | When >1, img subset exists, subs lack a bmpOffset value |
-|              |     | When =0, it skips the rest of BitmapData                |
+|              |     | When =0, the BitmapData section & this var do not exist |
 +--------------+-----+---------------------------------------------------------+
 +--------------+-----+---------------------------------------------------------+
 | data_length  |  4  | data size(=sod)                                         |
@@ -260,7 +260,7 @@ They are structured visually like this:
 +--------------+-----+---------------------------------------------------------+
 | mcParam6     |  4  | Unidentified data (could be a length param)             |
 +--------------+-----+---------------------------------------------------------+
-| mcParam7     |  A  | Unidentified, A=(mcParam1 x mcParam2), stored base64RLE |
+| mcParam7     |  A  | Unidentified, A=(mcParam1 x mcParam2), stored base64    |
 +--------------+-----+---------------------------------------------------------+
 | mcParam8     | 20  | Unidentified data, currently XML-stored as base64       |
 +--------------+-----+---------------------------------------------------------+
diff --git a/docs/Unsupported.txt b/docs/Unsupported.txt
new file mode 100644
index 0000000..65e9e71
--- /dev/null
+++ b/docs/Unsupported.txt
@@ -0,0 +1,6 @@
+NORI Features Not Supported In Create
+-------------------------------------
+
+RLE compression, b/c zlib's DEFLATE is better if you need compression
+Palettes for non-8bit NORI, b/c they are not used
+Creation of 0 bmpCount NORI files
diff --git a/src/Analyze.java b/src/Analyze.java
index 4a07f48..d6ccfe7 100644
--- a/src/Analyze.java
+++ b/src/Analyze.java
@@ -1,7 +1,7 @@
 /*
 Analyze.java: this file is part of the TNT program.
 
-Copyright (C) 2014-2020 Libre Trickster Team
+Copyright (C) 2014-2024 Libre Trickster Team
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -16,6 +16,7 @@
 import java.io.*;
 import java.nio.*;
 import java.nio.file.*;
+import java.util.*;
 import javax.xml.parsers.*;
 import javax.xml.transform.*;
 import javax.xml.transform.dom.DOMSource;
@@ -36,242 +37,319 @@
 */
 public class Analyze
 {
-    // class variables
+// class variables
+private static NORI nf;
+private static Document cfg;
+private static int specsIdx=0,numFrames,numPlanes;
 
-    // constructor for Analyze class
-    public Analyze(byte[] ba, NORI nf, boolean createConfig)
+// constructor for Analyze class
+public Analyze(byte[] ba, File nFile, boolean createConfig)
+{
+    nf = new NORI();
+    nf.setNORI(nFile);
+    try
     {
-        try
-        {
-            // Wraps byte array in litte-endian bytebuffer
-            ByteBuffer bb = ByteBuffer.wrap(ba).order(ByteOrder.LITTLE_ENDIAN);
-            // Analyze the file
-            Analyzer a = new Analyzer(bb,nf);
+        // Wraps byte array in litte-endian bytebuffer
+        ByteBuffer bb = ByteBuffer.wrap(ba).order(ByteOrder.LITTLE_ENDIAN);
+        // Analyze the file
+        Analyzer a = new Analyzer(bb,nf,false);
 
-            // make NORI config file
-            if(createConfig)
+        // make NORI config file
+        if(createConfig)
+        {
+            nf.fixNORI(false);
+            writeCfg();
+            if(nf.hasPalette==1)
             {
-                writeCfg(nf);
-                File xfbFile = new File(nf.dir+"xfb"+nf.noriVer+".bin");
-                Files.write(xfbFile.toPath(),nf.xfb);
-                if(nf.hasPalette==1)
-                {
-                    File palFile = new File(nf.dir+nf.name+"_pal.bin");
-                    Files.write(palFile.toPath(),nf.pb);
-                }
+                File palFile = new File(nf.dir+nf.name+"_pal.bin");
+                Files.write(palFile.toPath(),nf.palBytes);
             }
         }
-        catch(Exception ex)
-        {
-            out.println("Error in (OptA):\n"+ex);
-        }
     }
+    catch(Exception ex)
+    {
+        out.println("Error in (OptA):");
+        ex.printStackTrace(System.out);
+    }
+}
 
-    // Prepare and write NORI config file
-    private static void writeCfg(NORI nf)
+// Prepare and write NORI config file
+private static void writeCfg()
+{
+    try
     {
-        try
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        DocumentBuilder docBuilder = dbf.newDocumentBuilder();
+        // Set xml config document to global static var: cfg
+        cfg = docBuilder.newDocument();
+        // Root Element
+        Element root = cfg.createElement("NORI");
+        root.setAttribute("name",nf.name);
+        cfg.appendChild(root);
+        // NORI Header Elements
+        Element noriHdr = cfg.createElement("NORI_HDR");
+        root.appendChild(noriHdr);
+        // NORI Header SubElements
+        setNoriHdrVars(noriHdr);
+        // GAWI Elements
+        Element gawi = cfg.createElement("GAWI");
+        root.appendChild(gawi);
+        // GAWI Header Elements
+        Element gawiHdr = cfg.createElement("GAWI_HDR");
+        gawi.appendChild(gawiHdr);
+        // NORI Header SubElements
+        setGawiHdrVars(gawiHdr);
+        // Palette Elements
+        if(nf.hasPalette==1)
         {
-            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
-            DocumentBuilder docBuilder = dbf.newDocumentBuilder();
-            Document cfg = docBuilder.newDocument();
-            // Root Element
-            Element root = cfg.createElement("NORI");
-            root.setAttribute("name",nf.name);
-            cfg.appendChild(root);
-            // NORI Header Elements
-            Element noriHdr = cfg.createElement("NORI_HDR");
-            root.appendChild(noriHdr);
-            // NORI Header SubElements
-            setNoriHdrVars(cfg,noriHdr,nf);
-            // GAWI Elements
-            Element gawi = cfg.createElement("GAWI");
-            root.appendChild(gawi);
-            // GAWI Header Elements
-            Element gawiHdr = cfg.createElement("GAWI_HDR");
-            gawi.appendChild(gawiHdr);
-            // NORI Header SubElements
-            setGawiHdrVars(cfg,gawiHdr,nf);
-            // Palette Elements
-            if(nf.hasPalette==1)
-            {
-                Element pal = cfg.createElement("PAL");
-                gawi.appendChild(pal);
-                setPaletteVars(cfg,pal,nf);
-            }
-            // BMP Offset Elements
-            for(int i=0; i < nf.numBMP; i++)
-            {
-                Element bmpOff = cfg.createElement("bmpOffset");
-                bmpOff.setAttribute("id",""+i);
-                bmpOff.appendChild(cfg.createTextNode(""+nf.bmpOffsets[i]));
-                gawi.appendChild(bmpOff);
-            }
-            // BMP Data Elements
-            for(int i=0; i < nf.numBMP; i++)
-            {
-                Element bmp = cfg.createElement("BMP");
-                bmp.setAttribute("id",""+i);
-                bmp.setAttribute("offset",""+nf.bmpOffsets[i]+"+"+nf.bpos);
-                gawi.appendChild(bmp);
-                // BMP SubElements
-                setBmpSpecs(cfg,bmp,i,nf);
-                Element bmpData = cfg.createElement("RGB"+nf.bpp+"DATA");
-                bmp.appendChild(bmpData);
-            }
-            // Animation Offset Elements
-            for(int i=0; i < nf.anims; i++)
-            {
-                Element animOff = cfg.createElement("animOffset");
-                animOff.setAttribute("id",""+i);
-                animOff.appendChild(cfg.createTextNode(""+nf.animOffsets[i]));
-                root.appendChild(animOff);
-            }
-            // Animation Data Elements
-            for(int i=0; i < nf.anims; i++)
-            {
-                Element anim = cfg.createElement("ANIM");
-                anim.setAttribute("id",""+i);
-                anim.setAttribute("offset",""+nf.animOffsets[i]+"+"+nf.apos);
-                root.appendChild(anim);
-                // Anim SubElements
-                Element name = cfg.createElement("name");
-                name.appendChild(cfg.createTextNode(nf.animName[i]));
-                anim.appendChild(name);
-                mkSubElement(cfg,anim,"frames",nf.frames[i]);
-                setFrameOffsets(cfg,anim,i,nf);
-                // Frame Data and SubElements
-                setFrames(cfg,anim,i,nf);
-            }
-
-            // Prep xml data
-            TransformerFactory tf = TransformerFactory.newInstance();
-            Transformer t = tf.newTransformer();
-            t.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
-            t.setOutputProperty(OutputKeys.INDENT, "yes");
-            String indentAmount = "{http://xml.apache.org/xslt}indent-amount";
-            t.setOutputProperty(indentAmount,"2");
-            // Output xml config file
-            DOMSource src = new DOMSource(cfg);
-            File config = new File(nf.dir+nf.name+".cfg");
-            StreamResult file = new StreamResult(config);
-            t.transform(src, file);
+            Element pal = cfg.createElement("PAL");
+            gawi.appendChild(pal);
+            setPaletteVars(pal);
         }
-        catch(Exception ex)
+        // BMP Offset Elements
+        for(int i=0; i < nf.bmpStructs; i++)
         {
-            out.println("Error in (mkCfg):\n"+ex);
+            Element bmpOff = cfg.createElement(nf.xml_tag[31]);
+            bmpOff.setAttribute("id",""+i);
+            bmpOff.appendChild(cfg.createTextNode(""+nf.bmpOffsets[i]));
+            gawi.appendChild(bmpOff);
         }
-    }
+        // BMP Data Elements
+        for(int i=0; i < nf.bmpStructs; i++)
+        {
+            Element bmp = cfg.createElement("BMP");
+            bmp.setAttribute("id",""+i);
+            bmp.setAttribute("offset",""+nf.bpos+"+"+nf.bmpOffsets[i]);
+            gawi.appendChild(bmp);
+            // BMP SubElements
+            setBmpSpecs(bmp,i);
+        }
+        // Animation Offset Elements
+        for(int a=0; a < nf.anims; a++)
+        {
+            Element animOff = cfg.createElement(nf.xml_tag[39]);
+            animOff.setAttribute("id",""+a);
+            animOff.appendChild(cfg.createTextNode(""+nf.animOffsets[a]));
+            root.appendChild(animOff);
+        }
+        // Animation Data Elements
+        for(int a=0; a < nf.anims; a++)
+        {
+            Element anim = cfg.createElement("ANIM");
+            anim.setAttribute("id",""+a);
+            anim.setAttribute("offset",""+nf.apos+"+"+nf.animOffsets[a]);
+            root.appendChild(anim);
+            // Anim SubElements
+            Element name = cfg.createElement(nf.xml_tag[40]);
+            name.appendChild(cfg.createTextNode(nf.title[a]));
+            anim.appendChild(name);
+            numFrames = nf.numFrames[a];
+            mkSubE(anim, nf.xml_tag[41], numFrames);
+            setFrameOffsets(anim,a);
+            // Frame Data and SubElements
+            setFrames(anim,a);
+        }
+
+        // Prep xml data
+        TransformerFactory tf = TransformerFactory.newInstance();
+        Transformer t = tf.newTransformer();
+        t.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+        t.setOutputProperty(OutputKeys.INDENT, "yes");
+        String indentAmount = "{http://xml.apache.org/xslt}indent-amount";
+        t.setOutputProperty(indentAmount,"2");
+        // Output xml config file
+        DOMSource src = new DOMSource(cfg);
+        File config = new File(nf.dir+nf.name+".cfg");
+        StreamResult file = new StreamResult(config);
+        t.transform(src, file);
+
+        }catch(Exception ex)
+        {
+            out.println("Error in (mkCfg):");
+            ex.printStackTrace(System.out);
+        }
+}
 
-    private static void setNoriHdrVars(Document cfg, Element e, NORI nf)
+private static void setNoriHdrVars(Element e)
+{
+    mkSubE(e, nf.xml_tag[0], nf.fsig);
+    mkSubE(e, nf.xml_tag[1], nf.noriVer);
+    mkSubE(e, nf.xml_tag[2], nf.nParam1);
+    mkSubE(e, nf.xml_tag[3], nf.nParam2);
+    mkSubE(e, nf.xml_tag[4], nf.nParam3);
+    mkSubE(e, nf.xml_tag[5], nf.nParam4);
+    mkSubE(e, nf.xml_tag[6], nf.nParam5);
+    mkSubE(e, nf.xml_tag[7], nf.anims);
+    mkSubE(e, nf.xml_tag[8], nf.woGawi);
+    mkSubE(e, nf.xml_tag[9], nf.fsize);
+}
+
+private static void setGawiHdrVars(Element e)
+{
+    mkSubE(e, nf.xml_tag[10], nf.gsig);
+    mkSubE(e, nf.xml_tag[11], nf.gawiVer);
+    mkSubE(e, nf.xml_tag[12], nf.bpp);
+    mkSubE(e, nf.xml_tag[13], nf.compressed);
+    mkSubE(e, nf.xml_tag[14], nf.hasPalette);
+    mkSubE(e, nf.xml_tag[15], nf.gParam4);
+    mkSubE(e, nf.xml_tag[16], nf.gParam5);
+    mkSubE(e, nf.xml_tag[17], nf.gParam6);
+    mkSubE(e, nf.xml_tag[18], nf.gParam7);
+    mkSubE(e, nf.xml_tag[19], nf.bmpStructs);
+    mkSubE(e, nf.xml_tag[20], nf.gsize);
+}
+
+private static void setPaletteVars(Element e)
+{
+    mkSubE(e, nf.xml_tag[21], nf.psig);
+    mkSubE(e, nf.xml_tag[22], nf.palVer);
+    mkSubE(e, nf.xml_tag[23], nf.pParam1);
+    mkSubE(e, nf.xml_tag[24], nf.pParam2);
+    mkSubE(e, nf.xml_tag[25], nf.pParam3);
+    mkSubE(e, nf.xml_tag[26], nf.pParam4);
+    mkSubE(e, nf.xml_tag[27], nf.divided);
+    mkSubE(e, nf.xml_tag[28], nf.psize);
+    mkSubE(e, "RGB24DATA", "");
+    if(nf.psize==808)
     {
-        mkSubElement(cfg, e, "fsig", nf.fsig);
-        mkSubElement(cfg, e, "noriver", nf.noriVer);
-        mkSubElement(cfg, e, "nparam1", nf.nParam1);
-        mkSubElement(cfg, e, "nparam2", nf.nParam2);
-        mkSubElement(cfg, e, "nparam3", nf.nParam3);
-        mkSubElement(cfg, e, "nparam4", nf.nParam4);
-        mkSubElement(cfg, e, "nparam5", nf.nParam5);
-        mkSubElement(cfg, e, "anims", nf.anims);
-        mkSubElement(cfg, e, "woGawi", nf.woGawi);
-        mkSubElement(cfg, e, "fsize", nf.fsize);
+        mkSubE(e, nf.xml_tag[29], nf.mainS);
+        mkSubE(e, nf.xml_tag[30], nf.mainE);
     }
+}
 
-    private static void setGawiHdrVars(Document cfg, Element e, NORI nf)
+private static void setBmpSpecs(Element bmp, int i)
+{
+    mkSubE(bmp, nf.xml_tag[32], nf.bmpCount[i]);
+    boolean subBMP = (nf.bmpCount[i] > 1);
+    for(int x=0; x < nf.bmpCount[i]; x++)
     {
-        mkSubElement(cfg, e, "gsig", nf.gsig);
-        mkSubElement(cfg, e, "gawiver", nf.gawiVer);
-        mkSubElement(cfg, e, "bpp", nf.bpp);
-        mkSubElement(cfg, e, "compressed", nf.compressed);
-        mkSubElement(cfg, e, "hasPalette", nf.hasPalette);
-        mkSubElement(cfg, e, "gparam4", nf.gParam4);
-        mkSubElement(cfg, e, "gparam5", nf.gParam5);
-        mkSubElement(cfg, e, "gparam6", nf.gParam6);
-        mkSubElement(cfg, e, "gparam7", nf.gParam7);
-        mkSubElement(cfg, e, "numBMP", nf.numBMP);
-        mkSubElement(cfg, e, "gsize", nf.gsize);
+        if(subBMP) mkSubE(bmp, String.format("SubBMP_%02d",x+1), "");
+        mkSubE(bmp, nf.xml_tag[33], nf.bmpSpecs[specsIdx][0]);
+        mkSubE(bmp, nf.xml_tag[34], nf.bmpSpecs[specsIdx][1]);
+        mkSubE(bmp, nf.xml_tag[35], nf.bmpSpecs[specsIdx][2]);
+        mkSubE(bmp, nf.xml_tag[36], nf.bmpSpecs[specsIdx][3]);
+        mkSubE(bmp, nf.xml_tag[37], nf.bmpSpecs[specsIdx][4]);
+        mkSubE(bmp, nf.xml_tag[38], nf.bmpSpecs[specsIdx][5]);
+        mkSubE(bmp, "RGB"+nf.bpp+"DATA", "");
+        specsIdx++;
     }
+}
 
-    private static void setPaletteVars(Document cfg, Element e, NORI nf)
+private static void setFrameOffsets(Element e, int a)
+{
+    // Frame Offset Elements
+    for(int f=0; f < numFrames; f++)
     {
-        mkSubElement(cfg, e, "psig", nf.psig);
-        mkSubElement(cfg, e, "palver", nf.palVer);
-        mkSubElement(cfg, e, "pparam1", nf.pParam1);
-        mkSubElement(cfg, e, "pparam2", nf.pParam2);
-        mkSubElement(cfg, e, "pparam3", nf.pParam3);
-        mkSubElement(cfg, e, "pparam4", nf.pParam4);
-        mkSubElement(cfg, e, "divided", nf.divided);
-        mkSubElement(cfg, e, "psize", nf.psize);
-        Element palData = cfg.createElement("RGB24DATA");
-        e.appendChild(palData);
-        if(nf.psize==808)
-        {
-            mkSubElement(cfg,e,"mainS",nf.mainS);
-            mkSubElement(cfg,e,"mainE",nf.mainE);
-        }
+        Element frameOff = cfg.createElement(nf.xml_tag[42]);
+        frameOff.setAttribute("id",""+f);
+        frameOff.appendChild(cfg.createTextNode(""+nf.frameOffsets[a][f]));
+        e.appendChild(frameOff);
     }
+}
 
-    private static void setBmpSpecs(Document cfg, Element e, int i, NORI nf)
+private static void setFrames(Element e, int a)
+{
+    for(int f=0; f < numFrames; f++)
     {
-        mkSubElement(cfg, e, "dcount", nf.bmpSpecs[i][0]);
-        mkSubElement(cfg, e, "dlen", nf.bmpSpecs[i][1]);
-        mkSubElement(cfg, e, "w", nf.bmpSpecs[i][2]);
-        mkSubElement(cfg, e, "h", nf.bmpSpecs[i][3]);
-        mkSubElement(cfg, e, "bparam4", nf.bmpSpecs[i][4]);
-        mkSubElement(cfg, e, "pos_x", nf.bmpSpecs[i][5]);
-        mkSubElement(cfg, e, "pos_y", nf.bmpSpecs[i][6]);
+        // Frame element
+        Element frame = cfg.createElement("FRAME");
+        frame.setAttribute("id",""+f);
+        frame.setAttribute("offset",""+nf.frameOffsets[a][f]);
+        e.appendChild(frame);
+        // FrameDataTop (duration,numPlanes)
+        mkSubE(frame, nf.xml_tag[43], nf.frameDataTop[a][f][0]);
+        mkSubE(frame, nf.xml_tag[44], nf.frameDataTop[a][f][1]);
+        numPlanes = nf.frameDataTop[a][f][1];
+        // PlaneData (bmp_id,x,y,opacity,flip,blend_mode,flag_param)
+        setPlanes(frame,a,f);
+        // FrameDataBottom
+        setFrameDataBottom(frame,a,f);
     }
+}
 
-    private static void setFrameOffsets(Document cfg,Element e,int a,NORI nf)
+private static void setPlanes(Element e, int a, int f)
+{
+    for(int p=0; p < numPlanes; p++)
     {
-        // Frame Offset Elements
-        for(int i=0; i < nf.frames[a]; i++)
-        {
-            Element frameOff = cfg.createElement("frameOffset");
-            frameOff.setAttribute("id",""+i);
-            frameOff.appendChild(cfg.createTextNode(""+nf.frameOffsets[a][i]));
-            e.appendChild(frameOff);
-        }
+        Element plane = cfg.createElement("PLANE");
+        plane.setAttribute("id",""+p);
+        e.appendChild(plane);
+        mkSubE(plane, nf.xml_tag[45], nf.planeData[a][f][p][0]);
+        mkSubE(plane, nf.xml_tag[46], nf.planeData[a][f][p][1]);
+        mkSubE(plane, nf.xml_tag[47], nf.planeData[a][f][p][2]);
+        mkSubE(plane, nf.xml_tag[48], nf.planeData[a][f][p][3]);
+        mkSubE(plane, nf.xml_tag[49], nf.planeData[a][f][p][4]);
+        mkSubE(plane, nf.xml_tag[50], nf.planeData[a][f][p][5]);
+        mkSubE(plane, nf.xml_tag[51], nf.planeData[a][f][p][6]);
     }
+}
 
-    private static void setFrames(Document cfg,Element e,int a,NORI nf)
+// Much of the FrameDataBottom information is unknown. Therefore, as a temporary
+// measure, much of it has been encoded & stored as base64 data.
+// Once we know more about this section the base64 encoding can be replaced with
+// proper data type vars.
+private static void setFrameDataBottom(Element frame, int a, int f)
+{
+    if(nf.notV300)
     {
-        // Frame Offset Elements
-        for(int i=0; i < nf.frames[a]; i++)
+        mkSubE(frame, nf.xml_tag[52], nf.numCoordSets[a][f]);
+        for(int i=0; i < nf.numCoordSets[a][f]; i++)
         {
-            Element frame = cfg.createElement("frame");
-            frame.setAttribute("id",""+i);
-            frame.setAttribute("offset",""+nf.frameOffsets[a][i]);
-            e.appendChild(frame);
-            mkSubElement(cfg,frame,"delay",nf.frameData[a][i][0]);
-            mkSubElement(cfg,frame,"planes",nf.frameData[a][i][1]);
-            setPlanes(cfg,frame,a,i,nf);
-            mkSubElement(cfg,frame,"xfb",nf.xtraFrameBytes);
+            mkSubE(frame, nf.xml_tag[53], nf.coordSets[a][f][i][0]);
+            mkSubE(frame, nf.xml_tag[54], nf.coordSets[a][f][i][1]);
         }
     }
-
-    private static void setPlanes(Document cfg,Element e,int a,int f, NORI nf)
+    mkSubE(frame, nf.xml_tag[55], nf.cdBlockSize);
+    if(nf.hasEB)
     {
-        for(int i=0; i < nf.frameData[a][f][1]; i++)
+        mkSubE(frame, nf.xml_tag[56], b64Enc(nf.entryBlocks[a][f][0]));
+        mkSubE(frame, nf.xml_tag[56], b64Enc(nf.entryBlocks[a][f][1]));
+        mkSubE(frame, nf.xml_tag[56], b64Enc(nf.entryBlocks[a][f][2]));
+        mkSubE(frame, nf.xml_tag[56], b64Enc(nf.entryBlocks[a][f][3]));
+        mkSubE(frame, nf.xml_tag[56], b64Enc(nf.entryBlocks[a][f][4]));
+        mkSubE(frame, nf.xml_tag[56], b64Enc(nf.entryBlocks[a][f][5]));
+    }
+    mkSubE(frame, nf.xml_tag[57], b64Enc(nf.unknownData1[a][f][0]));
+    mkSubE(frame, nf.xml_tag[57], b64Enc(nf.unknownData1[a][f][1]));
+    mkSubE(frame, nf.xml_tag[58], nf.soundEffect[a][f]);
+    mkSubE(frame, nf.xml_tag[59], b64Enc(nf.unknownData2[a][f]));
+    if(nf.maybeMCV)
+    {
+        mkSubE(frame, nf.xml_tag[60], nf.hasMCValues[a][f]);
+        if(nf.hasMCValues[a][f]==1)
         {
-            Element plane = cfg.createElement("plane");
-            plane.setAttribute("id",""+i);
-            e.appendChild(plane);
-            mkSubElement(cfg,plane,"bmp_id", nf.planeData[a][f][i][0]);
-            mkSubElement(cfg,plane,"point_x", nf.planeData[a][f][i][1]);
-            mkSubElement(cfg,plane,"point_y", nf.planeData[a][f][i][2]);
-            mkSubElement(cfg,plane,"opacity", nf.planeData[a][f][i][3]);
-            mkSubElement(cfg,plane,"flip_axis", nf.planeData[a][f][i][4]);
-            mkSubElement(cfg,plane,"blend_mode", nf.planeData[a][f][i][5]);
-            mkSubElement(cfg,plane,"flag_param", nf.planeData[a][f][i][6]);
+            mkSubE(frame, nf.xml_tag[61], nf.mcValues[a][f][0]);
+            mkSubE(frame, nf.xml_tag[62], nf.mcValues[a][f][1]);
+            mkSubE(frame, nf.xml_tag[63], nf.mcValues[a][f][2]);
+            mkSubE(frame, nf.xml_tag[64], nf.mcValues[a][f][3]);
+            mkSubE(frame, nf.xml_tag[65], nf.mcValues[a][f][4]);
+            mkSubE(frame, nf.xml_tag[66], nf.mcValues[a][f][5]);
+            mkSubE(frame, nf.xml_tag[67], nf.mcValues[a][f][6]);
+            mkSubE(frame, nf.xml_tag[68], nf.mcParam7[a][f]);
+            mkSubE(frame, nf.xml_tag[69], b64Enc(nf.mcParam8[a][f]));
         }
     }
+}
 
-    // Make Element child (Element's Element)
-    private static void mkSubElement(Document cfg,Element e,String name,int val)
-    {
-        Element subE = cfg.createElement(name);
-        subE.appendChild(cfg.createTextNode(""+val));
-        e.appendChild(subE);
-    }
+// Make Element child (Element's Element)
+private static void mkSubE(Element e, String name, String value)
+{
+    Element subE = cfg.createElement(name);
+    subE.appendChild(cfg.createTextNode(""+value));
+    e.appendChild(subE);
+}
+
+// Make Element child (Element's Element)
+private static void mkSubE(Element e, String name, int value)
+{
+    Element subE = cfg.createElement(name);
+    subE.appendChild(cfg.createTextNode(""+value));
+    e.appendChild(subE);
+}
+
+// Shorten the new base64 encoded string from byte array command
+private static String b64Enc(byte[] ba)
+{
+    return Base64.getEncoder().encodeToString(ba);
+}
 }
diff --git a/src/Analyzer.java b/src/Analyzer.java
index 6bec9d8..ed15771 100644
--- a/src/Analyzer.java
+++ b/src/Analyzer.java
@@ -1,7 +1,7 @@
 /*
 Analyzer.java: this file is part of the TNT program.
 
-Copyright (C) 2014-2020 Libre Trickster Team
+Copyright (C) 2014-2024 Libre Trickster Team
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -15,11 +15,10 @@
 */
 import java.io.*;
 import java.nio.*;
+import java.nio.charset.*;
 import java.nio.file.*;
 import java.util.*;
-import static java.lang.System.in;
 import static java.lang.System.out;
-import static java.lang.System.err;
 /**
 Class Description:
 The Analyzer class contains all functions that TNT uses to find the information
@@ -30,387 +29,497 @@
 the variables and variable assignments are not commented. Descriptive names are
 used for most things regardless. Fair warning has been given.
 
-Development Priority: HIGH
+Development Priority: HIGHEST
 */
 public class Analyzer
 {
-    // class variables
-    public static int pos=0,rem=0,bpos=0,apos=0,fpos=0,bmpNxt=0,animNxt=0;
-    // special GAWI variables
-    public static int gStart=0,gEnd=0;
-    public static boolean compressed=false, hasPalette=false;
-    // special BMP data variables
-    public static int[] bmpOffsets;
-    // special animation variables
-    public static int asize=0, numFrames=0, numPlanes=0;
-    public static int[] animOffsets;
-    public static byte[] animName = new byte[32];
-    public static int[][] frameOffsets;
-
-    // constructor for Analyzer class
-    public Analyzer(ByteBuffer bb, NORI nf)
+// class variables
+private static NORI nf;
+private static ByteBuffer bb;
+static Charset UTF8=StandardCharsets.UTF_8,EUC_KR=Charset.forName("EUC-KR");
+private static int pos,rem,animNxt;
+static String bxc ="[^\u0020-\uD7FF\uE000-\uFFFD\ud800\udbff-\udc00\udfff]";
+private static String badXmlChars = bxc;
+// OffsetCheck arrays
+private static int[] bmpOffsets,animOffsets;
+private static int[][] frameOffsets;
+// Special animation variables
+private static int numFrames,fpos,numPlanes,subtractNum,areaSize;
+
+// constructor for Analyzer class
+public Analyzer(ByteBuffer BB, NORI NF, boolean extract_mode)
+{
+    // Save coding space by making BB and NF static global vars
+    bb = BB;
+    nf = NF;
+    // Start Analyzer output
+    out.println("Filename: "+nf.name);
+    try
     {
-        out.println("========================================================");
-        out.println("Filename: "+nf.name);
-        try
-        {
-            // Read and Assign info about the noriFile
-            setNoriHeader(bb,nf);
-            setGawiHeader(bb,nf);
-            if(hasPalette) setPaletteData(bb,nf);
-            setBmpOffsets(bb,nf);
-            dryExtract(bb,nf);// Skip through bmpData, assign bmpSpecs data
-            if(nf.gsize==0) gawiSizeFixes(nf);
-            prepAnimVars(nf);
-            setAnimOffsets(bb,nf);
-            setAnimInfo(bb,nf);
-            offsetCheck(nf);
-            //bbStatus(bb);// rem!=0 if noriVer is wrong (ex: Mini_mapd01a.nri)
-            // Reset bytebuffer for extraction
-            bb.position(bpos);
-        }
-        catch(Exception ex)
+        // Read and Assign info about the noriFile
+        setNoriHeaderData();
+        setGawiHeaderData();
+        if(nf.hasPalette==1) setPaletteData();
+        setBmpOffsets();
+        if(!extract_mode)
         {
-            out.println("Error in (AM):\n"+ex);
+            dryExtract();// Skip through bmpData, assign bmpSpecs data
+            prepAnimVars();
+            setAnimOffsets();
+            // Set Animation Data
+            for(int a=0; a < nf.anims; a++)
+            {
+                setAnimData(a);
+            }
         }
     }
-
-    private static void setNoriHeader(ByteBuffer bb, NORI nf)
+    catch(Exception ex)
     {
-        nf.fsig = bb.getInt();
-        noriCheck(nf.fsig);
-        nf.noriVer = bb.getInt();
-        noriVerCheck(nf);
-        nf.nParam1 = bb.getInt();
-        nf.nParam2 = bb.getInt();
-        nf.nParam3 = bb.getInt();
-        nf.nParam4 = bb.getInt();
-        nf.nParam5 = bb.getInt();
-        nf.anims = bb.getInt();
-        out.println("# of animations: "+nf.anims);
-        nf.woGawi = bb.getInt();
-        out.println("fsize w/o GAWI: "+nf.woGawi);
-        nf.fsize = bb.getInt();
-        out.println("fsize: "+nf.fsize);
-        if(nf.fsize==0) nf.fsize = bb.capacity();// Fix Ntree*'s mistake
-        out.println();
+        out.println("Error in (AM):");
+        ex.printStackTrace(System.out);
     }
+}
 
-    private static void setGawiHeader(ByteBuffer bb, NORI nf)
+private static void setNoriHeaderData()
+{
+    nf.fsig = bb.getInt();
+    noriCheck();
+    nf.noriVer = bb.getInt();
+    noriVerCheck();
+    nf.nParam1 = bb.getInt();
+    nf.nParam2 = bb.getInt();
+    nf.nParam3 = bb.getInt();
+    nf.nParam4 = bb.getInt();
+    nf.nParam5 = bb.getInt();
+    nf.anims   = bb.getInt();
+    out.println("# of animations: "+nf.anims);
+    nf.woGawi  = bb.getInt();
+    out.println("File size w/o GAWI: "+nf.woGawi);
+    nf.fsize   = bb.getInt();
+    out.println("File size: "+nf.fsize);
+    // Fix fsize here, even if not needed, b/c it's the best place to do so
+    nf.fsize   = bb.capacity();
+    out.println();
+}
+
+private static void setGawiHeaderData()
+{
+    nf.gsig = bb.getInt();
+    gawiCheck();
+    nf.gawiVer = bb.getInt();
+    gawiVerCheck();
+    nf.bpp = bb.getInt();
+    nf.Bpp = nf.bpp/8;
+    out.println("BitsPerPixel: "+nf.bpp);
+    nf.compressed = bb.getInt();
+    out.println("Compressed: "+(nf.compressed==1));
+    nf.hasPalette = bb.getInt();
+    out.println("hasPalette: "+(nf.hasPalette==1));
+    nf.gParam4 = bb.getInt();
+    nf.gParam5 = bb.getInt();
+    nf.gParam6 = bb.getInt();
+    nf.gParam7 = bb.getInt();
+    nf.bmpStructs  = bb.getInt();
+    out.println("# of BMP Structures: "+nf.bmpStructs);
+    nf.nLen  = String.valueOf(nf.bmpStructs).length();
+    nf.gsize = bb.getInt();
+    out.println("gsize: "+nf.gsize);
+    out.println();
+}
+
+private static void setPaletteData() throws Exception
+{
+    nf.psig = bb.getInt();
+    palCheck();
+    nf.palVer = bb.getInt();
+    palVerCheck();
+    nf.pParam1 = bb.getInt();
+    nf.pParam2 = bb.getInt();
+    nf.pParam3 = bb.getInt();
+    nf.pParam4 = bb.getInt();
+    nf.divided = bb.getInt();
+    nf.psize = bb.getInt();
+    out.println("psize: "+nf.psize);
+    nf.palette = setPalette();
+    if(nf.psize==808)
     {
-        gStart = bb.position();
-        nf.gsig = bb.getInt();
-        gawiCheck(nf.gsig);
-        nf.gawiVer = bb.getInt();
-        gawiVerCheck(nf.gawiVer);
-        nf.bpp = bb.getInt();
-        out.println("BitsPerPixel: "+nf.bpp);
-        nf.compressed = bb.getInt();
-        compressed = (nf.compressed==1);
-        out.println("Compressed: "+compressed);
-        nf.hasPalette = bb.getInt();
-        hasPalette = (nf.hasPalette==1);
-        out.println("hasPalette: "+hasPalette);
-        nf.gParam4 = bb.getInt();
-        nf.gParam5 = bb.getInt();
-        nf.gParam6 = bb.getInt();
-        nf.gParam7 = bb.getInt();
-        nf.numBMP = bb.getInt();
-        out.println("# of images: "+nf.numBMP);
-        nf.gsize = bb.getInt();
-        out.println("gsize: "+nf.gsize);
-        out.println();
+        nf.mainS = bb.getInt();
+        nf.mainE = bb.getInt();
     }
+    out.println();
+}
 
-    private static void setPaletteData(ByteBuffer bb, NORI nf)
+// Make BMP color palette from raw palette data. Okay, one of the harder to
+// follow parts here. Colors are stored in BGR order. Take it in stride.
+private static byte[][] setPalette() throws Exception
+{
+    nf.palBytes = new byte[768];
+    byte[][] colors = new byte[256][3];
+    // gets/puts the palette bytes into the pb array
+    bb.get(nf.palBytes,0,768);
+    // standardize the bg to neon pink
+    nf.palBytes[0] = (byte)255;
+    nf.palBytes[1] = (byte)0;
+    nf.palBytes[2] = (byte)255;
+    // Place the bytes in the dual array 'colors' that groups the rgb
+    // bytes according to the color/palette index they represent
+    for(int i=0; i < 256; i++)
     {
-        nf.psig = bb.getInt();
-        palCheck(nf.psig);
-        nf.palVer = bb.getInt();
-        palVerCheck(nf.palVer);
-        nf.pParam1 = bb.getInt();
-        nf.pParam2 = bb.getInt();
-        nf.pParam3 = bb.getInt();
-        nf.pParam4 = bb.getInt();
-        nf.divided = bb.getInt();
-        nf.psize = bb.getInt();
-        out.println("psize: "+nf.psize);
-        nf.palette = setPalette(bb,nf);
-        if(nf.psize==808)
-        {
-            nf.mainS = bb.getInt();
-            nf.mainE = bb.getInt();
-        }
-        out.println();
+        int x=i*3, b=x+0, g=x+1, r=x+2;
+        colors[i][0] = nf.palBytes[r];
+        colors[i][1] = nf.palBytes[g];
+        colors[i][2] = nf.palBytes[b];
     }
+    return colors;
+}
 
-    // Make BMP color palette from raw palette data. Okay, one of the harder to
-    // follow parts here. Colors are stored in BGR order. Take it in stride.
-    private static byte[][] setPalette(ByteBuffer bb, NORI nf)
+// Load bmp offsets into the bmpOffsets array for global use
+private static void setBmpOffsets()
+{
+    bmpOffsets = new int[nf.bmpStructs];
+    nf.bmpOffsets = new int[nf.bmpStructs];
+    switch(nf.compressed)
     {
-        nf.pb = new byte[768];
-        byte[] newBG = {(byte)255,(byte)0,(byte)255};
-        byte[][] colors = new byte[256][3];
-        try
+    case 1:
+        for(int i=0; i < nf.bmpStructs; i++)
         {
-            // gets/puts the palette bytes into the pb array
-            bb.get(nf.pb,0,768);
-            ByteBuffer pbb = mkLEBB(nf.pb);
-            // standardize the bg to neon pink
-            pbb.put(newBG,0,3);
-            // Place the bytes in the dual array 'colors' that groups the rgb
-            // bytes according to the color/palette index they represent
-            for(int i = 0; i < 256; i++)
-            {
-                int x = i*3, b=x+0, g=x+1, r=x+2;
-                colors[i][0] = nf.pb[r];
-                colors[i][1] = nf.pb[g];
-                colors[i][2] = nf.pb[b];
-            }
+            nf.bmpOffsets[i] = bb.getInt();
+            nf.bmpOffsets[i] += i*28;
         }
-        catch(Exception ex)
+        break;
+    default:
+        for(int i=0; i < nf.bmpStructs; i++)
         {
-            out.println("Error in (setPal):\n"+ex);
+            nf.bmpOffsets[i] = bb.getInt();
         }
-        return colors;
+        break;
     }
+    // get buffer position at end of offsets
+    nf.bpos = bb.position();
+}
 
-    // Load bmp offsets into the bmpOffsets array for global use
-    private static void setBmpOffsets(ByteBuffer bb, NORI nf)
+// Load the bmpCount & bmpSpecs array and simulate extraction for the bytebuffer
+private static void dryExtract()
+{
+    nf.bmpCount = new int[nf.bmpStructs];
+    for(int i=0,dataLength=0; i < nf.bmpStructs; i++)
     {
-        bmpOffsets = new int[nf.numBMP+1];
-        nf.bmpOffsets = new int[nf.numBMP+1];
-        for(int i=0; i < nf.numBMP; i++)
+        bmpOffsets[i] = bb.position()-nf.bpos;// Set bmpOffsetCheck() value
+        // Get & Set bmpCount data
+        nf.bmpCount[i] = bb.getInt();
+        for(int x=0; x < nf.bmpCount[i]; x++)
         {
-            nf.bmpOffsets[i] = bb.getInt();
-            if(compressed) nf.bmpOffsets[i] += i*28;
+            dataLength = bb.getInt();
+            movePosFwd(20+dataLength);
         }
-        // get buffer position at end of offsets
-        bpos = bb.position();
-        nf.bpos = bpos;
+        nf.totalBMP += nf.bmpCount[i];// Update total BMP
     }
-
-    // Load the bmpSpecs array and simulate extraction for the bytebuffer
-    private static void dryExtract(ByteBuffer bb, NORI nf)
+    bmpOffsetCheck();// Test the BMP offsets before we go any further (for dbg)
+    bb.position(nf.bpos);
+    nf.bmpSpecs = new int[nf.totalBMP][6];
+    for(int i=0,specsIdx=0; i < nf.bmpStructs; i++)
     {
-        JBL bl = new JBL();
-        nf.bmpSpecs = new int[nf.numBMP][7];
-        int offsetDiff = nf.bmpOffsets[nf.numBMP-1]-nf.bmpOffsets[0];
-        boolean offDiff = (offsetDiff > 0);
-        for(int i=0; i < nf.numBMP; i++)
+        // Get & Set BMP Specs
+        if(nf.bmpCount[i]!=0) bb.getInt();// Skip bmpCount, it is already known
+        for(int x=0; x < nf.bmpCount[i]; x++)
         {
-            bmpOffsets[i] = bb.position() - bpos;// Set offsetCheck() value
-            if(nf.bmpOffsets[i+1]!=0)
-                bmpNxt=nf.bmpOffsets[i+1] + bpos;
-            else
-                bmpNxt=nf.bmpOffsets[i+1];
-            nf.bmpSpecs[i][0] = bb.getInt();
-            nf.bmpSpecs[i][1] = bb.getInt();
-            nf.bmpSpecs[i][2] = bb.getInt();
-            nf.bmpSpecs[i][3] = bb.getInt();
-            nf.bmpSpecs[i][4] = bb.getInt();
-            nf.bmpSpecs[i][5] = bb.getInt();
-            nf.bmpSpecs[i][6] = bb.getInt();
-            bl.setBmpVars(nf.bmpSpecs[i][2],nf.bmpSpecs[i][3],nf.bpp);
-            byte[] rawBytes = bl.getImgBytes(bb,nf.bmpSpecs[i][1]);
-            // Ensure the buffer is in the right position for the next bmp
-            if(offDiff && bpos!=bmpNxt && bmpNxt!=0) bb.position(bmpNxt);
+            nf.bmpSpecs[specsIdx][0] = bb.getInt();//dataLength
+            nf.bmpSpecs[specsIdx][1] = bb.getInt();//w
+            nf.bmpSpecs[specsIdx][2] = bb.getInt();//h
+            nf.bmpSpecs[specsIdx][3] = bb.getInt();//bParam4
+            nf.bmpSpecs[specsIdx][4] = bb.getInt();//bmp_x
+            nf.bmpSpecs[specsIdx][5] = bb.getInt();//bmp_y
+            movePosFwd(nf.bmpSpecs[specsIdx][0]);
+            specsIdx++;
         }
-        gEnd = bb.position();
-        asize = bb.remaining();
     }
+    nf.asize = bb.remaining();
+}
 
-    // One of many data fixes I've implemented to prevent Ntree* mistakes from
-    // being carried over to the config files. This fixes woGawi and gsize.
-    private static void gawiSizeFixes(NORI nf)
+// Prepare the animation-related arrays
+private static void prepAnimVars()
+{
+    // large array sizes needed for dealing with unknown input
+    int frames = 220;//largest found:216 (pet_cm_387.nri)
+    int planes = 120;//largest found:114 (map_sq07.bac)
+    nf.animOffsets  = new int[nf.anims+1];
+    nf.titleBytes   = new byte[nf.anims][32];//stored as an array for debugging
+    nf.title        = new String[nf.anims];
+    nf.numFrames    = new int[nf.anims];
+    frameOffsets    = new int[nf.anims][frames];
+    nf.frameOffsets = new int[nf.anims][frames];
+    nf.frameDataTop = new int[nf.anims][frames][2];
+    // planeData is the largest array, consuming approx 472MB for itm_cm_shop000
+    nf.planeData    = new int[nf.anims][frames][planes][7];
+    if(nf.notV300)
+    {
+        nf.numCoordSets = new int[nf.anims][frames];//record:14 (map_uw03.bac)
+        nf.coordSets    = new int[nf.anims][frames][20][2];
+    }
+    if(nf.hasEB) nf.entryBlocks = new byte[nf.anims][frames][6][28];
+    nf.unknownData1 = new byte[nf.anims][frames][2][22];
+    nf.soundEffect  = new String[nf.anims][frames];
+    nf.unknownData2 = new byte[nf.anims][frames][18];
+    if(nf.maybeMCV)
     {
-        nf.gsize = gEnd - gStart;
-        if(nf.woGawi==0) nf.woGawi = 40 + asize;
+        nf.hasMCValues = new int[nf.anims][frames];
+        nf.mcValues    = new int[nf.anims][frames][7];
+        nf.mcParam7    = new String[nf.anims][frames];
+        nf.mcParam8    = new byte[nf.anims][frames][20];
     }
+}
 
-    // Prepare the animation-related arrays
-    private static void prepAnimVars(NORI nf)
+private static void setAnimOffsets()
+{
+    for(int a=0; a < nf.anims; a++)
     {
-        // large array sizes needed for dealing with unknown input
-        animOffsets = new int[nf.anims+1];
-        nf.animOffsets = new int[nf.anims+1];
-        nf.animName = new String[nf.anims];
-        nf.frames = new int[nf.anims];
-        frameOffsets = new int[nf.anims][200];
-        nf.frameOffsets = new int[nf.anims][200];
-        nf.frameData = new int[nf.anims][200][2];
-        nf.planeData = new int[nf.anims][200][100][7];
-        nf.xfb = new byte[nf.xtraFrameBytes];
+        nf.animOffsets[a] = bb.getInt();
     }
+    nf.apos = bb.position();
+}
 
-    private static void setAnimOffsets(ByteBuffer bb, NORI nf)
+// Set the data for all the animations
+private static void setAnimData(int a)
+{
+    if(nf.animOffsets[a+1]!=0)
+        animNxt=nf.animOffsets[a+1]+nf.apos;
+    else
+        animNxt=nf.animOffsets[a+1];
+    // Get & Set ANIM data
+    bb.get(nf.titleBytes[a],0,32);
+    nf.title[a] = newXmlStr(nf.titleBytes[a],EUC_KR);
+    nf.numFrames[a] = bb.getInt();
+    numFrames = nf.numFrames[a];
+    //if(numFrames>220) out.printf("Ole!numFrames:%d @%d\n",numFrames,getPos());
+    nf.totalFrames += numFrames;//Fixer var
+    // Set Frame Offsets
+    for(int f=0; f < numFrames; f++)
     {
-        for(int i=0; i < nf.anims; i++)
-        {
-            nf.animOffsets[i] = bb.getInt();
-        }
-        apos = bb.position();
-        nf.apos = apos;
+        nf.frameOffsets[a][f] = bb.getInt();
+    }
+    fpos = bb.position();//End of frame offsets
+    // Set Frame Data
+    for(int f=0; f < numFrames; f++)
+    {
+        frameOffsets[a][f] = bb.position()-fpos;// Set frameOffsetCheck() value
+        setFrameDataTop(a,f);
+        setPlaneData(a,f);
+        setFrameDataBottom(a,f);
     }
+    frameOffsetCheck(a,numFrames,fpos);// Test the frameOffsets (for dbg)
+    pos = bb.position();
+    if(pos!=animNxt && animNxt!=0)
+    {
+        out.printf("Ole! Anim:%d @%d, animNxt:%d\n",a,pos,animNxt);
+        bb.position(animNxt);
+    }
+}
 
-    // Set the info for all the animations
-    private static void setAnimInfo(ByteBuffer bb, NORI nf)
+private static void setFrameDataTop(int a, int f)
+{
+    nf.frameDataTop[a][f][0] = bb.getInt();//duration
+    nf.frameDataTop[a][f][1] = bb.getInt();//numPlanes
+    numPlanes = nf.frameDataTop[a][f][1];
+    //if(numPlanes>120) out.printf("Ole!numPlanes:%d @%d\n",numPlanes,getPos());
+    nf.totalPlanes += numPlanes;//Fixer var
+}
+
+private static void setPlaneData(int a, int f)
+{
+    for(int p=0; p < numPlanes; p++)
+    {
+        nf.planeData[a][f][p][0] = bb.getInt();//bmp_id
+        nf.planeData[a][f][p][1] = bb.getInt();//plane_x
+        nf.planeData[a][f][p][2] = bb.getInt();//plane_y
+        nf.planeData[a][f][p][3] = bb.getInt();//opacity
+        nf.planeData[a][f][p][4] = bb.getInt();//flip
+        nf.planeData[a][f][p][5] = bb.getInt();//blend_mode
+        nf.planeData[a][f][p][6] = bb.getInt();//flag_param
+    }
+}
+
+private static void setFrameDataBottom(int a, int f)
+{
+    try
     {
-        try
+        if(nf.notV300)
         {
-            int offsetDiff = nf.animOffsets[nf.anims-1] - nf.animOffsets[0];
-            boolean offDiff = (offsetDiff > 0);
-            for(int i=0; i < nf.anims; i++)
+            nf.numCoordSets[a][f] = bb.getInt();
+            for(int i=0; i < nf.numCoordSets[a][f]; i++)
             {
-                animOffsets[i] = bb.position() - apos;// Set offsetCheck() value
-                if(nf.animOffsets[i+1]!=0)
-                    animNxt=nf.animOffsets[i+1] + apos;
-                else
-                    animNxt=nf.animOffsets[i+1];
-                bb.get(animName,0,32);
-                nf.animName[i] = (new String(animName,"EUC-KR")).trim();
-                nf.frames[i] = bb.getInt();
-                numFrames = nf.frames[i];
-                setFrameOffsets(bb,i,nf);
-                setFrameData(bb,i,nf);
-                pos = bb.position();
-                if(offDiff && pos!=animNxt && animNxt!=0) bb.position(animNxt);
+                nf.coordSets[a][f][i][0] = bb.getInt();
+                nf.coordSets[a][f][i][1] = bb.getInt();
             }
         }
-        catch(Exception ex)
+        movePosFwd(nf.cdBlockSize);
+        if(nf.hasEB)
         {
-            out.println("Error in (setAnimInfo):\n"+ex);
+            bb.get(nf.entryBlocks[a][f][0]);
+            bb.get(nf.entryBlocks[a][f][1]);
+            bb.get(nf.entryBlocks[a][f][2]);
+            bb.get(nf.entryBlocks[a][f][3]);
+            bb.get(nf.entryBlocks[a][f][4]);
+            bb.get(nf.entryBlocks[a][f][5]);
         }
-    }
-
-    private static void setFrameOffsets(ByteBuffer bb, int a, NORI nf)
-    {
-        for(int i=0; i < numFrames; i++)
+        bb.get(nf.unknownData1[a][f][0]);
+        bb.get(nf.unknownData1[a][f][1]);
+        bb.get(nf.sfx);
+        nf.soundEffect[a][f] = newXmlStr(nf.sfx,UTF8);
+        bb.get(nf.unknownData2[a][f]);
+        if(nf.maybeMCV)
         {
-            nf.frameOffsets[a][i] = bb.getInt();
+            nf.hasMCValues[a][f] = bb.getInt();
+            if(nf.hasMCValues[a][f]==1)
+            {
+                nf.mcValues[a][f][0] = bb.getInt();
+                nf.mcValues[a][f][1] = bb.getInt();
+                nf.mcValues[a][f][2] = bb.getInt();
+                nf.mcValues[a][f][3] = bb.getInt();
+                nf.mcValues[a][f][4] = bb.getInt();
+                nf.mcValues[a][f][5] = bb.getInt();
+                nf.mcValues[a][f][6] = bb.getInt();
+                areaSize = nf.mcValues[a][f][1]*nf.mcValues[a][f][2];
+                byte[] area = new byte[areaSize];
+                bb.get(area);
+                //nf.mcParam7[a][f] = toBase64RLE(b64Enc(area));
+                nf.mcParam7[a][f] = b64Enc(area);
+                bb.get(nf.mcParam8[a][f]);
+            }
         }
-        fpos = bb.position();
     }
-
-    // Set actual data for frames (and planes)
-    private static void setFrameData(ByteBuffer bb, int a, NORI nf)
+    catch(Exception ex)
     {
-        for(int i=0; i < numFrames; i++)
-        {
-            frameOffsets[a][i] = bb.position() - fpos;// Set offsetCheck() value
-            nf.frameData[a][i][0] = bb.getInt();
-            nf.frameData[a][i][1] = bb.getInt();
-            numPlanes = nf.frameData[a][i][1];
-            setPlaneData(bb,a,i,nf);
-        }
+        out.println("Error in (FrameDataBottom):");
+        ex.printStackTrace(System.out);
     }
+}
 
-    private static void setPlaneData(ByteBuffer bb, int a, int f, NORI nf)
+// Check if nf offset arrays = local arrays, fix nf arrays if not equal
+private static void bmpOffsetCheck()
+{
+    if(!Arrays.equals(nf.bmpOffsets,bmpOffsets))
     {
-        for(int i=0; i < numPlanes; i++)
-        {
-            nf.planeData[a][f][i][0] = bb.getInt();
-            nf.planeData[a][f][i][1] = bb.getInt();
-            nf.planeData[a][f][i][2] = bb.getInt();
-            nf.planeData[a][f][i][3] = bb.getInt();
-            nf.planeData[a][f][i][4] = bb.getInt();
-            nf.planeData[a][f][i][5] = bb.getInt();
-            nf.planeData[a][f][i][6] = bb.getInt();
-        }
-        bb.get(nf.xfb,0,nf.xtraFrameBytes);
+        out.println("BMP Offset Check Failed!");
+        out.println("Original BMP Offsets:");
+        printIntArr(nf.bmpOffsets,""+nf.bpos+"+","",nf.bmpStructs);
+        out.println("New BMP Offsets:");
+        printIntArr(bmpOffsets,""+nf.bpos+"+","",nf.bmpStructs);
+        nf.bmpOffsets = bmpOffsets;
     }
+}
 
-    // Check if nf offset arrays = local arrays, fix nf arrays if not equal
-    private static void offsetCheck(NORI nf)
+private static void frameOffsetCheck(int a, int frames, int frameOffsetOrigin)
+{
+    if(!Arrays.equals(nf.frameOffsets[a],frameOffsets[a]))
     {
-        if(nf.bmpOffsets!=bmpOffsets) nf.bmpOffsets = bmpOffsets;
-        if(nf.animOffsets!=animOffsets) nf.animOffsets = animOffsets;
-        if(nf.frameOffsets!=frameOffsets) nf.frameOffsets = frameOffsets;
+        out.println("Frame Offset Check Failed!");
+        out.println("Original Frame Offsets for AnimID["+a+"]:");
+        printIntArr(nf.frameOffsets[a],""+frameOffsetOrigin+"+","",frames);
+        out.println("New Frame Offsets for AnimID["+a+"]:");
+        printIntArr(frameOffsets[a],""+frameOffsetOrigin+"+","",frames);
+        nf.frameOffsets[a] = frameOffsets[a];
     }
+}
 
-    private static void noriCheck(int signature)
-    {
-        out.print("NORI Signature Check: ");
-        intCheck(1230131022, signature);
-    }
+private static void noriCheck()
+{
+    out.print("NORI Signature Check: ");
+    intCheck(1230131022, nf.fsig);
+}
 
-    // Checks NORI version and sets the appropriate # of extra bytes
-    private static void noriVerCheck(NORI nf)
-    {
-        out.print("NORI Version: ");
-        switch(nf.noriVer)
-        {
-        case 300:
-            nf.xtraFrameBytes = 224;
-            break;
-        case 301:
-            nf.xtraFrameBytes = 228;
-            break;
-        case 302:
-            nf.xtraFrameBytes = 348;
-            break;
-        case 303:
-            nf.xtraFrameBytes = 352;
-            break;
-        default:
-            out.println("Unknown type! File a bug report.");
-            System.exit(1);
-            break;
-        }
-        out.println(nf.noriVer);
-    }
+// Checks NORI version and sets the version-specific variables
+private static void noriVerCheck()
+{
+    out.print("NORI Version: ");
+    nf.setVerSpecific();
+    out.println(nf.noriVer);
+}
 
-    private static void gawiCheck(int signature)
-    {
-        out.print("GAWI Signature Check: ");
-        intCheck(1230455111, signature);
-    }
+private static void gawiCheck()
+{
+    out.print("GAWI Signature Check: ");
+    intCheck(1230455111, nf.gsig);
+}
 
-    private static void gawiVerCheck(int verNum)
-    {
-        out.print("GAWI Version: ");
-        intCheck(300,verNum);
-    }
+private static void gawiVerCheck()
+{
+    out.print("GAWI Version: ");
+    intCheck(300, nf.gawiVer);
+}
+
+private static void palCheck()
+{
+    out.print("PAL_ Signature Check: ");
+    intCheck(1598832976, nf.psig);
+}
+
+private static void palVerCheck()
+{
+    out.print("PAL_ Version: ");
+    intCheck(100, nf.palVer);
+}
 
-    private static void palCheck(int signature)
+// Reusable int check, b/c we do this often
+private static void intCheck(int ref, int input)
+{
+    if(input == ref)
     {
-        out.print("PAL_ Signature Check: ");
-        intCheck(1598832976, signature);
+        out.println("Passed.");
     }
-
-    private static void palVerCheck(int verNum)
+    else
     {
-        out.print("PAL_ Version: ");
-        intCheck(100,verNum);
+        out.println("Failed!");
+        System.exit(1);
     }
+}
 
-    // Reusable int check, b/c we do this often
-    private static void intCheck(int ref, int input)
+// Moves the ByteBuffer position forward by int param value
+private static void movePosFwd(int incrementNum)
+{
+    pos = bb.position()+incrementNum;
+    bb.position(pos);
+}
+
+// A space saver + better readability function
+private static int getPos()
+{
+    return bb.position();
+}
+
+// Shorten the new base64 encoded string from byte array command
+private static String b64Enc(byte[] ba)
+{
+    return Base64.getEncoder().encodeToString(ba);
+}
+
+// Exception catching for new XML String creation
+private static String newXmlStr(byte[] ba, Charset charSet)
+{
+    String newXmlStr="";
+    try
     {
-        if(input == ref)
-        {
-            out.println("Passed.");
-        }
-        else
-        {
-            out.println("Failed!");
-            System.exit(1);
-        }
+        newXmlStr = new String(ba,charSet);
+        newXmlStr = newXmlStr.replaceAll(badXmlChars," ").trim();
     }
-
-    // (Dbg) Prints the buffer's current status info
-    private static void bbStatus(ByteBuffer bb)
+    catch(Exception ex)
     {
-        rem = bb.remaining();
-        pos = bb.position();
-        out.println("\nRemaining bytes: "+rem);
-        out.println("position: "+pos);
-        out.println("========================================");
+        out.println("Error in (newXmlStr):");
+        ex.printStackTrace(System.out);
     }
+    return newXmlStr;
+}
 
-    // Shorthand function to wrap a byte array in a little-endian bytebuffer
-    private static ByteBuffer mkLEBB(byte[] ba)
+private static void printIntArr(int[] arr, String prfx, String sffx, int limit)
+{
+    for(int x=0; x < limit; x++)
     {
-        return ByteBuffer.wrap(ba).order(ByteOrder.LITTLE_ENDIAN);
+        out.println(prfx+arr[x]+sffx);
     }
 }
+
+// Shorthand function to wrap a byte array in a little-endian bytebuffer
+private static ByteBuffer mkLEBB(byte[] ba)
+{
+    return ByteBuffer.wrap(ba).order(ByteOrder.LITTLE_ENDIAN);
+}
+}
diff --git a/src/Create.java b/src/Create.java
index 2d3aa04..4c64aff 100644
--- a/src/Create.java
+++ b/src/Create.java
@@ -1,7 +1,7 @@
 /*
 Create.java: this file is part of the TNT program.
 
-Copyright (C) 2014-2020 Libre Trickster Team
+Copyright (C) 2014-2024 Libre Trickster Team
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -15,489 +15,395 @@
 */
 import java.io.*;
 import java.nio.*;
+import java.nio.charset.*;
 import java.nio.file.*;
 import java.util.*;
-import javax.xml.parsers.*;
-import org.w3c.dom.*;
+import java.util.zip.*;
 import static java.lang.System.out;
 /**
 Class Description:
-For all intents and purposes, the Create class is meant to be the interface for
-creating new NORI files from suitable bitmap images. It will rely heavily on
-config files which will be specified by user input.
+The Create class is the interface for creating new NORI files from suitable BMP
+images. It relies heavily on a config file that is specified by user input.
 
 Dev Notes:
 Creating new NORI files is honestly of little use to the Libre Trickster project
-and this part of the program is little more than a curiosity.
+so this part of the program is little more than a curiosity. Nonetheless, it has
+a high demand among the userbase.
 
 Development Priority: LOW
 */
 public class Create
 {
-    // class variables
-    public static int maxNoF=0,maxNoP=0,pos=0;
-    public static int pdex0=0,pdex1=0,pdex2=0,pdex3=0,pdex4=0,pdex5=0,pdex6=0;
-    public static byte[] nfba, palette, imgData, fba;
-    public static int[] bmp_id, point_x, point_y, opacity, flip_axis;
-    public static int[] blend_mode, flag_param;
-    // constructor for Create class
-    public Create(File config, String bmpDir, NORI nf)
+// class variables
+private static NORI nf;
+private static ByteBuffer bb;
+static Charset UTF8=StandardCharsets.UTF_8,EUC_KR=Charset.forName("EUC-KR");
+private static byte xCD = (byte)0xCD;
+private static int pos, zlibSize;
+private static byte[] nfba, palette, tmpData, finalBA;
+// constructor for Create class
+public Create(File config, String bmpDir, boolean zlibCompress)
+{
+    nf = new NORI();
+    if(!bmpDir.endsWith(nf.fs)) bmpDir += nf.fs;
+    try
     {
-        try
+        out.println("\nGathering data from config file...");
+        // Get XML Data
+        GetCfgData gcd = new GetCfgData(config,nf);
+        out.println("NORI filename: "+nf.name);
+        // Get image data from BMP files
+        getImgData(bmpDir);
+        out.println("Total image data bytes: "+nf.bmpData.length);
+        // Run size fixes & disable uncouth features
+        nf.fixNORI(true);
+        // Setup NORI file byte array and bytebuffer
+        nfba = new byte[nf.fsize];
+        out.println("NORI file size: "+nfba.length);
+        bb = mkLEBB(nfba);
+        // Add NORI header to the nfba
+        add_NORI_HDR();
+        // Add GAWI header to the nfba
+        add_GAWI_HDR();
+        // Add Palette section if it exists & bpp=8
+        if(nf.hasPalette==1 && nf.bpp==8) add_PAL();
+        // Add BMP Offsets
+        add_bmpOffsets();
+        // Add BMP specs and data
+        add_BMP();
+        // Add Animation Offsets
+        add_animOffsets();
+        // Add Animations, FrameDataTops, Plane Data, & FrameDataBottoms
+        add_ANIM();
+        out.println("Finalizing file...");
+        if(zlibCompress)
         {
-            out.println("\nGathering data from config file...");
-            // Set the NORI file vars
-            nf.setNFileVars(config,1);
-            nf.checkDir();
-            out.println("NORI filename: "+nf.name);
-            // Get XML Data
-            getConfigData(config,nf);
-            // Setup NORI file byte array and bytebuffer
-            nfba = new byte[nf.fsize];
-            ByteBuffer nbb = mkLEBB(nfba);
-            // Add NORI header to the nfba
-            addNoriHdr(nbb,nf);
-            // Add GAWI header to the nfba
-            addGawiHdr(nbb,nf);
-            // Add Palette section if it exists; it won't, future-proofing a bit
-            if(nf.hasPalette==1) addPalSection(nbb,nf);
-            // Add BMP Offsets
-            addBmpOffsets(nbb,nf);
-            // Get image data from BMP files
-            imgData = getImgData(bmpDir,nf);
-            ByteBuffer dbb = mkLEBB(imgData);
-            // Add BMP specs and data
-            addBmpSection(nbb,dbb,nf);
-            // Add Animation Offsets
-            addAnimOffsets(nbb,nf);
-            // Get XFB
-            nf.xfb = file2BA(nf.dir+"xfb"+nf.noriVer+".bin");
-            // Add Anims, Frames, Plane Data, & xfb
-            addAnims(nbb,nf);
-            out.println("Finalizing file...");
-
-            // Set BMP name and location, then write BMP to file
-            File nori = new File(nf.dir+nf.name);
-            Files.write(nori.toPath(),nfba);
-            out.println("NORI File Creation Complete.\n");
+            Deflater zlib = new Deflater();
+            zlib.setInput(nfba);
+            zlib.finish();
+            byte[] tmpBA = new byte[nf.fsize];
+            zlibSize = zlib.deflate(tmpBA);
+            out.println("Compressed Size: "+(zlibSize+12));
+            zlib.end();
+            finalBA = new byte[12+zlibSize];
+            ByteBuffer finalBB = mkLEBB(finalBA);
+            finalBB.putInt(41136);// xB0A00000
+            finalBB.putInt(nfba.length);// Actual Size
+            finalBB.putInt(zlibSize);// Compressed Size
+            finalBB.put(tmpBA,0,zlibSize);// Compressed Data w/ zlib header
         }
-        catch(Exception ex)
+        else
         {
-            out.println("Error in (CM):\n"+ex);
+            finalBA = nfba;
         }
+        // Set NORI file location and name
+        File nori = new File(nf.dir+nf.name);
+        File nori_orig = new File(nf.dir+nf.name+".orig");
+        // Make backup file if it doesn't already exist
+        if(nori.exists() && !nori_orig.exists()) nori.renameTo(nori_orig);
+        // Write NORI to file
+        Files.write(nori.toPath(),finalBA);
+        out.println("NORI File Creation Complete.\n");
     }
-
-    private static void addNoriHdr(ByteBuffer bb, NORI nf)
+    catch(Exception ex)
     {
-        bb.putInt(nf.fsig);
-        bb.putInt(nf.noriVer);
-        bb.putInt(nf.nParam1);
-        bb.putInt(nf.nParam2);
-        bb.putInt(nf.nParam3);
-        bb.putInt(nf.nParam4);
-        bb.putInt(nf.nParam5);
-        bb.putInt(nf.anims);
-        bb.putInt(nf.woGawi);
-        bb.putInt(nf.fsize);
-    }
-
-    private static void addGawiHdr(ByteBuffer bb, NORI nf)
-    {
-        bb.putInt(nf.gsig);
-        bb.putInt(nf.gawiVer);
-        bb.putInt(nf.bpp);
-        bb.putInt(nf.compressed);
-        bb.putInt(nf.hasPalette);
-        bb.putInt(nf.gParam4);
-        bb.putInt(nf.gParam5);
-        bb.putInt(nf.gParam6);
-        bb.putInt(nf.gParam7);
-        bb.putInt(nf.numBMP);
-        bb.putInt(nf.gsize);
-    }
-
-    private static void addPalSection(ByteBuffer bb, NORI nf)
-    {
-
-        bb.putInt(nf.psig);
-        bb.putInt(nf.palVer);
-        bb.putInt(nf.pParam1);
-        bb.putInt(nf.pParam2);
-        bb.putInt(nf.pParam3);
-        bb.putInt(nf.pParam4);
-        bb.putInt(nf.divided);
-        bb.putInt(nf.psize);
-        nf.pb = file2BA(nf.dir+nf.name+"_pal.bin");
-        bb.put(nf.pb);
-        if(nf.psize==808)
-        {
-            bb.putInt(nf.mainS);
-            bb.putInt(nf.mainE);
-        }
+        out.println("Error in (CM):");
+        ex.printStackTrace(System.out);
     }
+}
 
-    private static void addBmpOffsets(ByteBuffer bb, NORI nf)
+private static void getImgData(String bmpDir)
+{
+    try
     {
-        for(int i=0; i < nf.numBMP; i++)
+        JBL bl = new JBL();
+        // Gather the list of bmp files
+        File dataDir = new File(bmpDir);
+        String[] tmpFL = dataDir.list();
+        String[] fl = cleanFL(tmpFL);
+        if(fl.length!=nf.totalBMP)
         {
-            bb.putInt(nf.bmpOffsets[i]);
+            out.println("Check BMPs and the config file.");
+            out.println("The sum of all "+nf.xml_tag[32]+" values is wrong!");
+            System.exit(1);
         }
-    }
-
-    private static byte[] getImgData(String bmpDir, NORI nf)
-    {
-        JBL bl = new JBL();
-        // Setup byte array where pixel data will go
-        int ids=0;
-        for(int i=0; i < nf.numBMP; i++)
+        // Alphabetic ordering
+        Arrays.sort(fl);
+        // Pull the file contents into a byte array for later use
+        out.println("Absorbing BMP files:");
+        // Prep bmpData array
+        for(int i=0; i < nf.totalBMP; i++)
         {
-            ids += nf.bmpSpecs[i][1];
+            nf.bmpDataSize += nf.bmpSpecs[i][0];
         }
-        byte[] ba = new byte[ids];
-        ByteBuffer bb = mkLEBB(ba);
-        try
+        nf.bmpData = new byte[nf.bmpDataSize];
+        ByteBuffer bmpData = mkLEBB(nf.bmpData);
+        String end;
+        // Fill bmpData array
+        for(int i=0; i < fl.length; i++)
         {
-            // Gather the list of bmp files
-            File dataDir = new File(bmpDir);
-            String[] tmpFL = dataDir.list();
-            String[] fl = cleanFL(tmpFL,nf);
-            // Alphabetic ordering
-            Arrays.sort(fl);
-            // Pull the file contents into a byte array for later use
-            out.println("Absorbing BMP files:");
-            for(int i=0; i < fl.length; i++)
+            // output full file name
+            out.printf(bmpDir+fl[i]);
+            end = "";
+            // Read BMP into a byte array & wrap in ByteBuffer
+            byte[] bmp = file2BA(bmpDir+fl[i]);
+            ByteBuffer img = mkLEBB(bmp);
+            // Set JBL BMP variables
+            bl.getBitmapVars(bmp);
+            // Strip the header off the image
+            img.position(bl.dataStart);
+            byte[] hdrless = bl.getImgBytes(img,0);
+            // NORI format uses top-down scanlines
+            byte[] revData = bl.reverseRows(hdrless);
+            // Strip any padding off the pixels
+            byte[] rawData = bl.stripPadding(revData);
+            // PhotoSh*p BMP fix
+            if(bl.w!=2 && nf.bmpSpecs[i][0]==(rawData.length-2))
             {
-                // output full file name
-                out.println(bmpDir+fl[i]);
-                // read bmp into a byte array, then wrap in a bytebuffer
-                byte[] bmp = file2BA(bmpDir+fl[i]);
-                ByteBuffer bbb = mkLEBB(bmp);
-                // Strip the header off the image
-                bbb.position(10);
-                int pxStart = bbb.getInt();
-                //out.println("pxStart: "+pxStart);
-                int pxLen = bbb.capacity() - pxStart;
-                //out.println("pxLength: "+pxLen);
-                bbb.position(pxStart);
-                byte[] hdrless = new byte[pxLen];
-                bbb.get(hdrless,0,pxLen);
-                // Set BMP header vars from xml data
-                bl.setBmpVars(nf.bmpSpecs[i][2],nf.bmpSpecs[i][3],nf.bpp);
-                // NORI format uses top-down scanlines
-                byte[] revData = bl.reverseRows(hdrless);
-                // Strip any padding on the pixels
-                byte[] rawData = bl.stripPadding(revData);
-                // Add raw data to ba byte array
-                bb.put(rawData);
+                tmpData = new byte[nf.bmpSpecs[i][0]];
+                for(int x=0; x < nf.bmpSpecs[i][0]; x++)
+                {
+                    tmpData[x] = rawData[x];
+                }
+                rawData = tmpData;
+                end = " (PS BMP Fixed)";
             }
-        }
-        catch(Exception ex)
-        {
-            out.println("Error in (getImgData):\n"+ex);
-        }
-        return ba;
-    }
-
-    private static void addBmpSection(ByteBuffer bb, ByteBuffer dbb, NORI nf)
-    {
-        String dcErr,manualFix;
-        dcErr="Error: dcount not 1, space was added for BMP id: ";
-        manualFix="To solve, manually fix: fsize, gsize, bmpOffsets, & dcount";
-        for(int i=0; i < nf.numBMP; i++)
-        {
-            int addSpace=0;
-            bb.putInt(nf.bmpSpecs[i][0]);
-            bb.putInt(nf.bmpSpecs[i][1]);
-            bb.putInt(nf.bmpSpecs[i][2]);
-            bb.putInt(nf.bmpSpecs[i][3]);
-            bb.putInt(nf.bmpSpecs[i][4]);
-            bb.putInt(nf.bmpSpecs[i][5]);
-            bb.putInt(nf.bmpSpecs[i][6]);
-            byte[] data = new byte[nf.bmpSpecs[i][1]];
-            dbb.get(data,0,nf.bmpSpecs[i][1]);
-            bb.put(data);
-            pos = bb.position();
-            if(nf.bmpSpecs[i][0]!=1)
+            out.println(end);
+            // Crash if image size doesn't match w*h*(bpp/8) calculation
+            if(nf.bmpSpecs[i][0]!=rawData.length)
             {
-                if(i!=(nf.numBMP-1))
-                    addSpace = nf.bmpOffsets[i+1]-nf.bmpOffsets[i]-data.length;
-                else
-                    addSpace = (nf.gsize+40) - pos;
-                out.println(dcErr+i+"\n"+manualFix);
-                bb.position(pos+addSpace);
+                out.println("BMP #"+i+"'s pixel data size does not match!");
+                out.println("Expected: "+nf.bmpSpecs[i][0]);
+                out.println("Received: "+rawData.length);
+                out.println("Causes: incorrect bpp, w, h, &/or input BMP");
+                System.exit(1);
             }
+            // Add raw data to bmpData array
+            bmpData.put(rawData);
         }
-    }
-
-    private static void addAnimOffsets(ByteBuffer bb, NORI nf)
+    }catch(Exception ex)
     {
-        for(int i=0; i < nf.anims; i++)
-        {
-            bb.putInt(nf.animOffsets[i]);
-        }
+        out.println("Error in (getImgData):");
+        ex.printStackTrace(System.out);
     }
+}
 
-    private static void addAnims(ByteBuffer bb, NORI nf)
-    {
-        for(int i=0; i < nf.anims; i++)
-        {
-            byte[] animName = new byte[32];
-            animName = (nf.animName[i]).getBytes();
-            pos = bb.position();
-            bb.put(animName);
-            bb.position(pos+32);
-            bb.putInt(nf.frames[i]);
-            addFrameOffsets(bb,i,nf);
-            addFrameData(bb,i,nf);
-        }
-    }
+private static void add_NORI_HDR()
+{
+    bb.putInt(nf.fsig);
+    bb.putInt(nf.noriVer);
+    bb.putInt(nf.nParam1);
+    bb.putInt(nf.nParam2);
+    bb.putInt(nf.nParam3);
+    bb.putInt(nf.nParam4);
+    bb.putInt(nf.nParam5);
+    bb.putInt(nf.anims);
+    bb.putInt(nf.woGawi);
+    bb.putInt(nf.fsize);
+}
+
+private static void add_GAWI_HDR()
+{
+    bb.putInt(nf.gsig);
+    bb.putInt(nf.gawiVer);
+    bb.putInt(nf.bpp);
+    bb.putInt(nf.compressed);
+    bb.putInt(nf.hasPalette);
+    bb.putInt(nf.gParam4);
+    bb.putInt(nf.gParam5);
+    bb.putInt(nf.gParam6);
+    bb.putInt(nf.gParam7);
+    bb.putInt(nf.bmpStructs);
+    bb.putInt(nf.gsize);
+}
 
-    private static void addFrameOffsets(ByteBuffer bb, int a, NORI nf)
+private static void add_PAL()
+{
+    bb.putInt(nf.psig);
+    bb.putInt(nf.palVer);
+    bb.putInt(nf.pParam1);
+    bb.putInt(nf.pParam2);
+    bb.putInt(nf.pParam3);
+    bb.putInt(nf.pParam4);
+    bb.putInt(nf.divided);
+    bb.putInt(nf.psize);
+    nf.palBytes = file2BA(nf.dir+nf.name+"_pal.bin");
+    bb.put(nf.palBytes);
+    if(nf.psize==808)
     {
-        for(int i=0; i < nf.frames[a]; i++)
-        {
-            bb.putInt(nf.frameOffsets[a][i]);
-        }
+        bb.putInt(nf.mainS);
+        bb.putInt(nf.mainE);
     }
+}
 
-    // Set actual data for frames (and planes)
-    private static void addFrameData(ByteBuffer bb, int a, NORI nf)
+private static void add_bmpOffsets()
+{
+    for(int i=0; i < nf.bmpStructs; i++)
     {
-        for(int i=0; i < nf.frames[a]; i++)
-        {
-            bb.putInt(nf.frameData[a][i][0]);
-            bb.putInt(nf.frameData[a][i][1]);
-            addPlaneData(bb,a,i,nf);
-        }
+        bb.putInt(nf.bmpOffsets[i]);
     }
+}
 
-    private static void addPlaneData(ByteBuffer bb, int a, int f, NORI nf)
+private static void add_BMP()
+{
+    for(int i=0,offset=0,bmpIdx=0; i < nf.bmpStructs; i++)
     {
-        for(int i=0; i < nf.frameData[a][f][1]; i++)
+        bb.putInt(nf.bmpCount[i]);
+        for(int x=0; x < nf.bmpCount[i]; x++)
         {
-            bb.putInt(nf.planeData[a][f][i][0]);
-            bb.putInt(nf.planeData[a][f][i][1]);
-            bb.putInt(nf.planeData[a][f][i][2]);
-            bb.putInt(nf.planeData[a][f][i][3]);
-            bb.putInt(nf.planeData[a][f][i][4]);
-            bb.putInt(nf.planeData[a][f][i][5]);
-            bb.putInt(nf.planeData[a][f][i][6]);
+            bb.putInt(nf.bmpSpecs[bmpIdx][0]);
+            bb.putInt(nf.bmpSpecs[bmpIdx][1]);
+            bb.putInt(nf.bmpSpecs[bmpIdx][2]);
+            bb.putInt(nf.bmpSpecs[bmpIdx][3]);
+            bb.putInt(nf.bmpSpecs[bmpIdx][4]);
+            bb.putInt(nf.bmpSpecs[bmpIdx][5]);
+            bb.put(nf.bmpData, offset, nf.bmpSpecs[bmpIdx][0]);
+            offset += nf.bmpSpecs[bmpIdx][0];
+            bmpIdx++;
         }
-        // Skip through xtraFrameBytes
-        bb.put(nf.xfb);
     }
+}
 
-    // Cleans the file list, if user is stupid, to make sure only bmp get in
-    private static String[] cleanFL(String[] tmp, NORI nf)
+private static void add_animOffsets()
+{
+    for(int a=0; a < nf.anims; a++)
     {
-        String[] cfl = new String[nf.numBMP];
-        int x=0;
-        for(int i=0; i < tmp.length; i++)
-        {
-            if((tmp[i].toLowerCase()).endsWith(".bmp")) cfl[x++]=tmp[i];
-        }
-        return cfl;
+        bb.putInt(nf.animOffsets[a]);
+        out.println("AnimOffset["+a+"]: "+nf.animOffsets[a]);
     }
+}
 
-    private static void getConfigData(File config, NORI nf)
+private static void add_ANIM() throws UnsupportedEncodingException
+{
+    for(int a=0; a < nf.anims; a++)
     {
-        try
+        pos = bb.position();
+        bb.put(nf.title[a].getBytes(EUC_KR));
+        bb.position(pos+32);//ensure title uses only 32 bytes
+        bb.putInt(nf.numFrames[a]);
+        // Add Frame Offsets
+        for(int f=0; f < nf.numFrames[a]; f++)
         {
-            // Make document object from config file
-            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
-            DocumentBuilder dBuilder = dbf.newDocumentBuilder();
-            Document cfg = dBuilder.parse(config);
-            cfg.getDocumentElement().normalize();
-            // Set NORI Header and GAWI Header Elements
-            Element noriHdr = getElementByTagName(cfg,"NORI_HDR");
-            Element gawiHdr = getElementByTagName(cfg,"GAWI_HDR");
-            // Get NORI Header Data
-            nf.fsig    = getIntVal(noriHdr,"fsig");
-            nf.noriVer = getIntVal(noriHdr,"noriver");
-            nf.nParam1 = getIntVal(noriHdr,"nparam1");
-            nf.nParam2 = getIntVal(noriHdr,"nparam2");
-            nf.nParam3 = getIntVal(noriHdr,"nparam3");
-            nf.nParam4 = getIntVal(noriHdr,"nparam4");
-            nf.nParam5 = getIntVal(noriHdr,"nparam5");
-            nf.anims   = getIntVal(noriHdr,"anims");
-            nf.woGawi  = getIntVal(noriHdr,"woGawi");
-            nf.fsize   = getIntVal(noriHdr,"fsize");
-            // Get GAWI Header Data
-            nf.gsig       = getIntVal(gawiHdr,"gsig");
-            nf.gawiVer    = getIntVal(gawiHdr,"gawiver");
-            nf.bpp        = getIntVal(gawiHdr,"bpp");
-            nf.compressed = getIntVal(gawiHdr,"compressed");
-            nf.hasPalette = getIntVal(gawiHdr,"hasPalette");
-            nf.gParam4    = getIntVal(gawiHdr,"gparam4");
-            nf.gParam5    = getIntVal(gawiHdr,"gparam5");
-            nf.gParam6    = getIntVal(gawiHdr,"gparam6");
-            nf.gParam7    = getIntVal(gawiHdr,"gparam7");
-            nf.numBMP     = getIntVal(gawiHdr,"numBMP");
-            nf.gsize      = getIntVal(gawiHdr,"gsize");
-            // Get BMP Offsets
-            nf.bmpOffsets = getIntArrByTag(cfg,"bmpOffset");
-            // Get BMP Specs
-            nf.bmpSpecs = new int[nf.numBMP][7];
-            int[] dcount  = getIntArrByTag(cfg,"dcount");
-            int[] dlen    = getIntArrByTag(cfg,"dlen");
-            int[] w       = getIntArrByTag(cfg,"w");
-            int[] h       = getIntArrByTag(cfg,"h");
-            int[] bparam4 = getIntArrByTag(cfg,"bparam4");
-            int[] pos_x   = getIntArrByTag(cfg,"pos_x");
-            int[] pos_y   = getIntArrByTag(cfg,"pos_y");
-            for(int bmp=0; bmp < nf.numBMP; bmp++)
-            {
-                nf.bmpSpecs[bmp][0] = dcount[bmp];
-                nf.bmpSpecs[bmp][1] = dlen[bmp];
-                nf.bmpSpecs[bmp][2] = w[bmp];
-                nf.bmpSpecs[bmp][3] = h[bmp];
-                nf.bmpSpecs[bmp][4] = bparam4[bmp];
-                nf.bmpSpecs[bmp][5] = pos_x[bmp];
-                nf.bmpSpecs[bmp][6] = pos_y[bmp];
-            }
-            // Get Animation Offsets
-            nf.animOffsets = getIntArrByTag(cfg,"animOffset");
-            // Get Animation Data
-            nf.animName = getStrArrByTag(cfg,"name");
-            nf.frames   = getIntArrByTag(cfg,"frames");
-            // Prep Frame Data arrays
-            maxNoF = getMax(nf.frames);
-            nf.frameOffsets = new int[nf.anims][maxNoF];
-            nf.frameData = new int[nf.anims][maxNoF][2];
-            // Get Frame Data Arrays
-            int[] frameOff = getIntArrByTag(cfg,"frameOffset");
-            int[] delays   = getIntArrByTag(cfg,"delay");
-            int[] planes   = getIntArrByTag(cfg,"planes");
-            // Prep Plane Data arrays
-            maxNoP = getMax(planes);
-            nf.planeData = new int[nf.anims][maxNoF][maxNoP][7];
-            // Get Plane Data Arrays
-            bmp_id     = getIntArrByTag(cfg,"bmp_id");
-            point_x    = getIntArrByTag(cfg,"point_x");
-            point_y    = getIntArrByTag(cfg,"point_y");
-            opacity    = getIntArrByTag(cfg,"opacity");
-            flip_axis  = getIntArrByTag(cfg,"flip_axis");
-            blend_mode = getIntArrByTag(cfg,"blend_mode");
-            flag_param = getIntArrByTag(cfg,"flag_param");
-            // Get Frame Data
-            int dex0=0,dex1=0,dex2=0;
-            for(int a=0; a < nf.anims; a++)
-            {
-                for(int f=0; f < nf.frames[a]; f++)
-                {
-                    nf.frameOffsets[a][f] = frameOff[dex0++];
-                    nf.frameData[a][f][0] = delays[dex1++];
-                    nf.frameData[a][f][1] = planes[dex2++];
-                    // Get Plane Data
-                    getPlaneData(cfg,a,f,nf);
-                }
-            }
+            bb.putInt(nf.frameOffsets[a][f]);
         }
-        catch(Exception ex)
+        // Add Frame Data
+        for(int f=0; f < nf.numFrames[a]; f++)
         {
-            out.println("Error in (getConfigData):\n"+ex);
+            add_FrameDataTop(a,f);
+            add_PlaneData(a,f);
+            add_FrameDataBottom(a,f);
         }
     }
+}
+
+private static void add_FrameDataTop(int a, int f)
+{
+    bb.putInt(nf.frameDataTop[a][f][0]);
+    bb.putInt(nf.frameDataTop[a][f][1]);
+}
 
-    private static void getPlaneData(Document cfg, int a, int f, NORI nf)
+private static void add_PlaneData(int a, int f)
+{
+    for(int p=0; p < nf.frameDataTop[a][f][1]; p++)
     {
-        for(int p=0; p < nf.frameData[a][f][1]; p++)
-        {
-            nf.planeData[a][f][p][0] = bmp_id[pdex0++];
-            nf.planeData[a][f][p][1] = point_x[pdex1++];
-            nf.planeData[a][f][p][2] = point_y[pdex2++];
-            nf.planeData[a][f][p][3] = opacity[pdex3++];
-            nf.planeData[a][f][p][4] = flip_axis[pdex4++];
-            nf.planeData[a][f][p][5] = blend_mode[pdex5++];
-            nf.planeData[a][f][p][6] = flag_param[pdex6++];
-        }
+        bb.putInt(nf.planeData[a][f][p][0]);
+        bb.putInt(nf.planeData[a][f][p][1]);
+        bb.putInt(nf.planeData[a][f][p][2]);
+        bb.putInt(nf.planeData[a][f][p][3]);
+        bb.putInt(nf.planeData[a][f][p][4]);
+        bb.putInt(nf.planeData[a][f][p][5]);
+        bb.putInt(nf.planeData[a][f][p][6]);
     }
+}
 
-    // An anti-duplication + better readability function
-    private static int getMax(int[] array)
+private static void add_FrameDataBottom(int a, int f)
+{
+    if(nf.notV300)
     {
-        int max=0;
-        for(int x : array)
+        bb.putInt(nf.numCoordSets[a][f]);
+        for(int i=0; i < nf.numCoordSets[a][f]; i++)
         {
-            if(x > max) max=x;
+            bb.putInt(nf.coordSets[a][f][i][0]);
+            bb.putInt(nf.coordSets[a][f][i][1]);
         }
-        return max;
     }
-
-    // Get single int array (int[]) by tagName
-    private static int[] getIntArrByTag(Document cfg, String tag)
+    for(int i=0; i < nf.cdBlockSize; i++)
     {
-        NodeList nl = cfg.getElementsByTagName(tag);
-        int max = nl.getLength();
-        int[] tmp = new int[max];
-        for(int i = 0; i < max; i++)
-        {
-            Node n = nl.item(i);
-            tmp[i] = toInt((n.getTextContent()).trim());
-        }
-        return tmp;
+        bb.put(xCD);
     }
-
-    // Get single string array (String[]) by tagName
-    private static String[] getStrArrByTag(Document cfg, String tag)
+    if(nf.hasEB)
+    {
+        bb.put(nf.entryBlocks[a][f][0]);
+        bb.put(nf.entryBlocks[a][f][1]);
+        bb.put(nf.entryBlocks[a][f][2]);
+        bb.put(nf.entryBlocks[a][f][3]);
+        bb.put(nf.entryBlocks[a][f][4]);
+        bb.put(nf.entryBlocks[a][f][5]);
+    }
+    bb.put(nf.unknownData1[a][f][0]);
+    bb.put(nf.unknownData1[a][f][1]);
+    pos = bb.position();
+    bb.put(nf.soundEffect[a][f].getBytes(UTF8));
+    bb.position(pos+18);//ensure soundEffect uses 18 bytes
+    bb.put(nf.unknownData2[a][f]);
+    if(nf.maybeMCV)
     {
-        NodeList nl = cfg.getElementsByTagName(tag);
-        int max = nl.getLength();
-        String[] tmp = new String[max];
-        for(int i = 0; i < max; i++)
+        bb.putInt(nf.hasMCValues[a][f]);
+        if(nf.hasMCValues[a][f]==1)
         {
-            Node n = nl.item(i);
-            tmp[i] = (n.getTextContent()).trim();
+            bb.putInt(nf.mcValues[a][f][0]);
+            bb.putInt(nf.mcValues[a][f][1]);
+            bb.putInt(nf.mcValues[a][f][2]);
+            bb.putInt(nf.mcValues[a][f][3]);
+            bb.putInt(nf.mcValues[a][f][4]);
+            bb.putInt(nf.mcValues[a][f][5]);
+            bb.putInt(nf.mcValues[a][f][6]);
+            bb.put(b64Dec(nf.mcParam7[a][f]));
+            bb.put(nf.mcParam8[a][f]);
         }
-        return tmp;
     }
+}
 
-    // An anti-duplication + better readability function
-    private static Element getElementByTagName(Document cfg,String tagName)
-    {
-        Node node0 = cfg.getElementsByTagName(tagName).item(0);
-        return (Element) node0;
-    }
+// Shorten the byte array from base64 encoded string command
+private static byte[] b64Dec(String s)
+{
+    return Base64.getDecoder().decode(s.getBytes());
+}
 
-    // An anti-duplication + better readability function
-    private static int getIntVal(Element parentE,String tagName)
+// Cleans the file list to make sure only bmp get in, b/c users are careless
+private static String[] cleanFL(String[] tmp)
+{
+    String[] cfl = new String[nf.totalBMP];
+    for(int i=0,x=0; i < tmp.length; i++)
     {
-        Node node0 = parentE.getElementsByTagName(tagName).item(0);
-        return toInt((node0.getTextContent()).trim());
+        if((tmp[i].toLowerCase()).endsWith(".bmp"))
+            cfl[x++]=tmp[i];
+        else
+            out.println(tmp[i]+" is not a BMP file. Remove from BMP folder.");
     }
+    return cfl;
+}
 
-    // Shorthand function to wrap a byte array in a little-endian bytebuffer
-    private static ByteBuffer mkLEBB(byte[] ba)
-    {
-        return ByteBuffer.wrap(ba).order(ByteOrder.LITTLE_ENDIAN);
-    }
+// Shorthand function to wrap a byte array in a little-endian bytebuffer
+private static ByteBuffer mkLEBB(byte[] ba)
+{
+    return ByteBuffer.wrap(ba).order(ByteOrder.LITTLE_ENDIAN);
+}
 
-    // An anti-duplication + better readability function
-    private static byte[] file2BA(String fStr)
+// An anti-duplication + better readability function
+private static byte[] file2BA(String fStr)
+{
+    File file = new File(fStr);
+    byte[] ba = new byte[(int)file.length()];
+    try
     {
-        try
-        {
-            fba = Files.readAllBytes((new File(fStr)).toPath());
-        }
-        catch(Exception ex)
-        {
-            out.println("Error in (file2BA):\n"+ex);
-        }
-        return fba;
+        ba = Files.readAllBytes(file.toPath());
     }
-
-    // An anti-duplication + better readability function
-    private static int toInt(String str)
+    catch(Exception ex)
     {
-        int i=0;
-        try
-        {
-            i = Integer.parseInt(str);
-        }
-        catch(NumberFormatException e)
-        {
-            i = 0;
-        }
-        return i;
+        out.println("Error in (file2BA):");
+        ex.printStackTrace(System.out);
     }
+    return ba;
+}
 }
diff --git a/src/Extract.java b/src/Extract.java
index 135c728..ff711c9 100644
--- a/src/Extract.java
+++ b/src/Extract.java
@@ -1,7 +1,7 @@
 /*
 Extract.java: this file is part of the TNT program.
 
-Copyright (C) 2014-2020 Libre Trickster Team
+Copyright (C) 2014-2024 Libre Trickster Team
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -17,7 +17,6 @@
 import java.nio.*;
 import java.nio.file.*;
 import static java.lang.System.out;
-import static java.lang.System.err;
 /**
 Class Description:
 The Extract class contains the functions which do the actual extraction of the
@@ -32,142 +31,127 @@
 */
 public class Extract
 {
-    // class variables
-    public static int pos=0,dlen=0,w=0,h=0,nlen=0,dcount=0,bmpNxt=0;
-    public static boolean compressed=false, dcBool=false;
-    public static byte x00=(byte)0,xFF=(byte)255,x1F=(byte)31,x7C=(byte)124;
+// class variables
+private static NORI nf;
+private static ByteBuffer bb;
+private static int pos=0,w=0,h=0,bmpCount=0;
+private static byte x00=(byte)0,xFF=(byte)255,x1F=(byte)31,x7C=(byte)124;
+private static byte[] rawBytes,pixels,bmp;
 
-    // constructor for Extract class
-    public Extract(byte[] ba, NORI nf, boolean subs)
+// constructor for Extract class
+public Extract(byte[] ba, File nFile)
+{
+    nf = new NORI();
+    nf.setNORI(nFile);
+    bb = mkLEBB(ba);
+    try
     {
-        try
-        {
-            ByteBuffer bb = mkLEBB(ba);
-            // Analyze and assign class vars
-            Analyzer a = new Analyzer(bb,nf);
-            compressed = a.compressed;
+        // Analyze and assign NORI vars
+        Analyzer a = new Analyzer(bb,nf,true);
 
-            // Make the directory where we will extract the bmp to
-            File d = new File(nf.exdir);
-            Files.createDirectories(d.toPath());
+        // Make the directory where we will extract the bmp to
+        Files.createDirectories((new File(nf.exdir)).toPath());
 
-            // Initialize Java Bitmap Library
-            JBL bl = new JBL();
-            bl.setFileVars(nf.exdir,nf.name);
-            bl.set16BitFmtIn("RGB555");
-            nlen = bl.setImgSetSize(nf.numBMP);
-            bl.setPalette(nf.palette);
+        // Initialize Java Bitmap Library
+        JBL bl = new JBL();
+        bl.setFileVars(nf.exdir,nf.name);
+        bl.set16BitFmtIn("RGB555");
+        bl.setNumLength(nf.nLen);
+        bl.setPalette(nf.palette);
 
-            // Extract the images
-            out.println("Extracting Bitmaps...");
-            for(int i=0; i < nf.numBMP; i++)
+        // Extract the images
+        out.println("Extracting Bitmaps...");
+        boolean isSub=false;
+        for(int i=0; i < nf.bmpStructs; i++)
+        {
+            // get/set bmp count (if larger than 1, subset exists)
+            bmpCount = bb.getInt();
+            isSub = (bmpCount >1);
+            for(int x=1,dataLength; x <= bmpCount; x++)
             {
-                if(nf.bmpOffsets[i+1]!=0)
-                    bmpNxt=nf.bmpOffsets[i+1] + nf.bpos;
+                // get/set the standard info about the bmp
+                dataLength = bb.getInt();
+                w = bb.getInt();
+                h = bb.getInt();
+                bb.position(bb.position()+12);//skip bParam4,bmp_x,bmp_y
+                bl.setBitmapVars(w,h,nf.bpp);
+                // Get image data & turn data into proper scanlines
+                rawBytes = bl.getImgBytes(bb,dataLength);
+                pixels   = bl.toStdRGB(decompressor(rawBytes));
+                // Ntree* uses top-down bmp scanlines in the NORI format
+                bmp = bl.setBMP(bl.reverseRows(pixels),false);
+                // Write the new BMP into existence
+                if(isSub)
+                    bl.makeBMP(bmp,i,String.format("_%02d",x));
                 else
-                    bmpNxt=nf.bmpOffsets[i+1];
-                // get data count (if larger than 1, subset exists)
-                dcount = bb.getInt();
-                dcBool = (dcount >1);
-                for(int x=1; x <= dcount; x++)
-                {
-                    // get/set the standard info about the bmp
-                    setBitmapData(bb);
-                    bl.setBmpVars(w,h,nf.bpp);
-                    // Next 4 lines: get img data, prep pixel data, & prep BMP
-                    byte[] rawBytes = bl.getImgBytes(bb,dlen);
-                    byte[] bytes = decompressor(rawBytes,nf);
-                    byte[] pixels = bl.toStdRGB(bytes);
-                    // Ntree* uses top-down bmp scanlines in the NORI format
-                    byte[] bmp = bl.setBMP(bl.reverseRows(pixels),false);
-                    // Write the new BMP into existence
-                    if(dcBool && subs)
-                        bl.makeBMP(bmp,i+1,String.format("_%02d",x));
-                    else
-                        bl.makeBMP(bmp,i+1,"");
-                }
-                pos = bb.position();
-                // Ensure the buffer is in the right position for the next bmp
-                if(pos!=bmpNxt && bmpNxt!=0) bb.position(bmpNxt);
+                    bl.makeBMP(bmp,i,"");
             }
-            out.println("Extraction Complete.\n");
-        }
-        catch(Exception ex)
-        {
-            out.println("Error in (EM):\n"+ex);
         }
+        out.println("Extraction Complete.\n");
     }
-
-    // Assign the BitmapData header info
-    private static void setBitmapData(ByteBuffer bb)
+    catch(Exception ex)
     {
-        // assign: data length, width, height; then temporarily assign unknowns
-        dlen = bb.getInt();
-        w = bb.getInt();
-        h = bb.getInt();
-        int bParam4 = bb.getInt();
-        int pos_x = bb.getInt();
-        int pos_y = bb.getInt();
+        out.println("Error in (EM):");
+        ex.printStackTrace(System.out);
     }
+}
 
-    // Minor interface for decompress() to make code cleaner
-    private static byte[] decompressor(byte[] rawBytes, NORI nf)
-    {
-        if(compressed)
-            return decompress(rawBytes,nf);
-        else
-            return rawBytes;
-    }
+// Minor interface for decompress() to make code cleaner
+private static byte[] decompressor(byte[] bmpData)
+{
+    if(nf.compressed==1)
+        return decompress(bmpData);
+    else
+        return bmpData;
+}
 
-    // Custom Run-length Encoding Decompression function.
-    // Each scanline is defined by a encodedSize, then a cycle of background and
-    // foreground pixel data that is repeated until the encodedSize is met.
-    private static byte[] decompress(byte[] input, NORI nf)
-    {
-        // Initialize vars: encodedSize, bg pixels, fg pixels, Bytes/px, fg*Bpp
-        int encodedSize=0, bg=0, fg=0, Bpp=(nf.bpp/8), fgxBpp=0;
-        byte[] output = new byte[w*h*Bpp], bg1= {x1F,x7C}, bg2= {xFF,x00,xFF};
-        // Create bytebuffers for the input and output arrays
-        ByteBuffer bi = mkLEBB(input), bo = mkLEBB(output);
+// Custom Run-length Encoding Decompression function.
+// Each scanline is defined by a encodedSize, then a cycle of background and
+// foreground pixel data that is repeated until the encodedSize is met.
+private static byte[] decompress(byte[] input)
+{
+    byte[] output = new byte[w*h*nf.Bpp], bg1= {x1F,x7C}, bg2= {xFF,x00,xFF};
+    // Create bytebuffers for the input and output arrays
+    ByteBuffer bi = mkLEBB(input), bo = mkLEBB(output);
 
-        for(int i=0; i < h; i++)
+    for(int i=0,encodedSize,bg,fg,fgxBpp; i < h; i++)
+    {
+        // set the encodedSize, then subtract 2, since it includes itself
+        encodedSize = (int)bi.getShort()-2;
+        while(encodedSize > 0)
         {
-            // set the encodedSize, then subtract 2, since it includes itself
-            encodedSize = (int)bi.getShort()-2;
-            while(encodedSize > 0)
+            // Get the encoded scanline internal parameters
+            bg =(int)bi.getShort();
+            fg =(int)bi.getShort();
+            fgxBpp = fg*nf.Bpp;
+            // Get foreground pixel data for the scanline
+            byte[] fgData = new byte[fgxBpp];
+            bi.get(fgData,0,fgxBpp);
+            // Set background pixels for scanline
+            for(int x=0; x < bg; x++)
+            {
+                if(nf.Bpp==2)
+                    bo.put(bg1,0,2);
+                else if(nf.Bpp==3)
+                    bo.put(bg2,0,3);
+                else
+                    bo.put(x00);
+            }
+            // Set foreground pixels for scanline
+            for(int y=0; y < fgxBpp; y++)
             {
-                // Get the encoded scanline internal parameters
-                bg =(int)bi.getShort();
-                fg =(int)bi.getShort();
-                fgxBpp = fg*Bpp;
-                // Get foreground pixel data for the scanline
-                byte[] fgData = new byte[fgxBpp];
-                bi.get(fgData,0,fgxBpp);
-                // Set background pixels for scanline
-                for(int x=0; x < bg; x++)
-                {
-                    if(Bpp==2)
-                        bo.put(bg1,0,2);
-                    else if(Bpp==3)
-                        bo.put(bg2,0,3);
-                    else
-                        bo.put(x00);
-                }
-                // Set foreground pixels for scanline
-                for(int y=0; y < fgxBpp; y++)
-                {
-                    bo.put(fgData[y]);
-                }
-                // Subtract the bytes for the fg & bg vars, and fgData
-                encodedSize -= 4+fgxBpp;
+                bo.put(fgData[y]);
             }
+            // Subtract the bytes for the fg & bg vars, and fgData
+            encodedSize -= 4+fgxBpp;
         }
-        return output;
     }
+    return output;
+}
 
-    // Shorthand function to wrap a byte array in a little-endian bytebuffer
-    private static ByteBuffer mkLEBB(byte[] ba)
-    {
-        return ByteBuffer.wrap(ba).order(ByteOrder.LITTLE_ENDIAN);
-    }
+// Shorthand function to wrap a byte array in a little-endian bytebuffer
+private static ByteBuffer mkLEBB(byte[] ba)
+{
+    return ByteBuffer.wrap(ba).order(ByteOrder.LITTLE_ENDIAN);
+}
 }
diff --git a/src/GetCfgData.java b/src/GetCfgData.java
new file mode 100644
index 0000000..a2277e2
--- /dev/null
+++ b/src/GetCfgData.java
@@ -0,0 +1,367 @@
+/*
+GetCfgData.java: this file is part of the TNT program.
+
+Copyright (C) 2014-2024 Libre Trickster Team
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+*/
+import java.io.*;
+import java.nio.*;
+import java.nio.charset.*;
+import java.nio.file.*;
+import java.util.*;
+import javax.xml.parsers.*;
+import org.w3c.dom.*;
+import static java.lang.System.out;
+/**
+Class Description:
+
+Dev Notes:
+
+Development Priority: MEDIUM
+*/
+public class GetCfgData
+{
+// class variables
+private static NORI nf;
+private static Document cfg;
+static Charset UTF8=StandardCharsets.UTF_8,EUC_KR=Charset.forName("EUC-KR");
+private static int pos,max,maxNoF,NoF,maxNoP,NoP;
+private static int animOffDiff,frameOffDiff,frameOffTotal;
+private static int[] ftDex,pdex,fbDex,coordSets,coordX,coordY,hasMCV;
+private static int[][] mcVals;
+private static String[] eBlocks,uData1,sfx,uData2,mcParam7,mcParam8;
+
+// constructor for GetCfgData class
+public GetCfgData(File config, NORI NF)
+{
+    nf = NF;
+    try
+    {
+        // Set NORI file directory
+        nf.setDir(config);
+        // Make document object from config file
+        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+        DocumentBuilder dBuilder = dbf.newDocumentBuilder();
+        // Set xml config document to global var: cfg
+        cfg = dBuilder.parse(config);
+        cfg.getDocumentElement().normalize();
+        // Set NORI element, get & set NORI file name
+        Element root = cfg.getDocumentElement();
+        nf.name = root.getAttribute("name");
+
+        // Set NORI Header and GAWI Header Elements
+        Element noriHdr = getElementByTagName("NORI_HDR");
+        Element gawiHdr = getElementByTagName("GAWI_HDR");
+        // Get & Set NORI Header Data
+        nf.noriVer = getIntVal(noriHdr,nf.xml_tag[1]);
+        nf.nParam1 = getIntVal(noriHdr,nf.xml_tag[2]);
+        nf.nParam2 = getIntVal(noriHdr,nf.xml_tag[3]);
+        nf.nParam3 = getIntVal(noriHdr,nf.xml_tag[4]);
+        nf.nParam4 = getIntVal(noriHdr,nf.xml_tag[5]);
+        nf.nParam5 = getIntVal(noriHdr,nf.xml_tag[6]);
+        nf.anims   = getIntVal(noriHdr,nf.xml_tag[7]);
+        // Set NORI version-specific variables
+        nf.setVerSpecific();
+        // Get & Set GAWI Header Data
+        nf.bpp        = getIntVal(gawiHdr,nf.xml_tag[12]);
+        nf.hasPalette = getIntVal(gawiHdr,nf.xml_tag[14]);
+        nf.gParam4    = getIntVal(gawiHdr,nf.xml_tag[15]);
+        nf.gParam5    = getIntVal(gawiHdr,nf.xml_tag[16]);
+        nf.gParam6    = getIntVal(gawiHdr,nf.xml_tag[17]);
+        nf.gParam7    = getIntVal(gawiHdr,nf.xml_tag[18]);
+        nf.bmpStructs = getIntVal(gawiHdr,nf.xml_tag[19]);
+        // Get & Set Palette Header Data, if palette exists
+        if(nf.bpp==8)
+        {
+            Element pal = getElementByTagName("PAL");
+            nf.pParam1 = getIntVal(pal,nf.xml_tag[23]);
+            nf.pParam2 = getIntVal(pal,nf.xml_tag[24]);
+            nf.pParam3 = getIntVal(pal,nf.xml_tag[25]);
+            nf.pParam4 = getIntVal(pal,nf.xml_tag[26]);
+            nf.psize   = getIntVal(pal,nf.xml_tag[28]);
+            if(nf.psize==808)
+            {
+                nf.mainS = getIntVal(pal,nf.xml_tag[29]);
+                nf.mainE = getIntVal(pal,nf.xml_tag[30]);
+            }
+        }
+        // Prep BMP Offsets & BMP Specs
+        nf.bmpOffsets = new int[nf.bmpStructs];
+        nf.bmpOffsets = getIntArrByTag(nf.xml_tag[31]);
+        nf.bmpCount   = new int[nf.bmpStructs];
+        nf.bmpCount   = getIntArrByTag(nf.xml_tag[32]);
+        nf.totalBMP   = getIntArrSum(nf.bmpCount);
+        nf.bmpSpecs   = new int[nf.totalBMP][6];
+        // Get & Set BMP Specs data arrays
+        int[] width   = getIntArrByTag(nf.xml_tag[34]);
+        int[] height  = getIntArrByTag(nf.xml_tag[35]);
+        int[] bParam4 = getIntArrByTag(nf.xml_tag[36]);
+        int[] bmpX    = getIntArrByTag(nf.xml_tag[37]);
+        int[] bmpY    = getIntArrByTag(nf.xml_tag[38]);
+        // Set BMP Specs
+        for(int bmp=0; bmp < nf.totalBMP; bmp++)
+        {
+            nf.bmpSpecs[bmp][0] = width[bmp]*height[bmp]*(nf.bpp/8);
+            nf.bmpSpecs[bmp][1] = width[bmp];
+            nf.bmpSpecs[bmp][2] = height[bmp];
+            nf.bmpSpecs[bmp][3] = bParam4[bmp];
+            nf.bmpSpecs[bmp][4] = bmpX[bmp];
+            nf.bmpSpecs[bmp][5] = bmpY[bmp];
+        }
+        // Prep Animation Offsets
+        nf.animOffsets = new int[nf.anims];
+        // Get & Set Animation Data
+        nf.title  = getStrArrByTag(nf.xml_tag[40]);
+        nf.numFrames = getIntArrByTag(nf.xml_tag[41]);
+        nf.totalFrames = getIntArrSum(nf.numFrames);
+        // Prep FrameDataTop arrays
+        maxNoF = getMax(nf.numFrames);
+        nf.frameOffsets = new int[nf.anims][maxNoF];
+        nf.frameDataTop = new int[nf.anims][maxNoF][2];
+        ftDex = new int[2];
+        // Get & Set FrameDataTop Data Arrays
+        int[] durations = getIntArrByTag(nf.xml_tag[43]);
+        int[] numPlanes = getIntArrByTag(nf.xml_tag[44]);
+        nf.totalPlanes = getIntArrSum(numPlanes);
+        // Prep PlaneData array
+        maxNoP = getMax(numPlanes);
+        nf.planeData = new int[nf.anims][maxNoF][maxNoP][7];
+        pdex = new int[7];
+        // Get & Set PlaneData Data Arrays
+        int[] bmpID      = getIntArrByTag(nf.xml_tag[45]);
+        int[] planeX     = getIntArrByTag(nf.xml_tag[46]);
+        int[] planeY     = getIntArrByTag(nf.xml_tag[47]);
+        int[] opacity    = getIntArrByTag(nf.xml_tag[48]);
+        int[] flip       = getIntArrByTag(nf.xml_tag[49]);
+        int[] blend_mode = getIntArrByTag(nf.xml_tag[50]);
+        int[] flag_param = getIntArrByTag(nf.xml_tag[51]);
+        // Prep FrameDataBottom + Get & Set FDB Data arrays
+        fbDex = new int[11];
+        if(nf.notV300)
+        {
+            // Get & Set Coordinate Data Arrays
+            coordSets = getIntArrByTag(nf.xml_tag[52]);
+            coordX    = getIntArrByTag(nf.xml_tag[53]);
+            coordY    = getIntArrByTag(nf.xml_tag[54]);
+            nf.totalCoordSetsBytes = (coordSets.length+(coordX.length*2))*4;
+            // Prep Coordinate arrays
+            int maxNoCS = getMax(coordSets);
+            nf.numCoordSets = new int[nf.anims][maxNoF];
+            nf.coordSets    = new int[nf.anims][maxNoF][maxNoCS][2];
+        }
+        if(nf.hasEB)
+        {
+            nf.entryBlocks = new byte[nf.anims][maxNoF][6][28];
+            eBlocks = getStrArrByTag(nf.xml_tag[56]);
+        }
+        nf.unknownData1 = new byte[nf.anims][maxNoF][2][22];
+        nf.soundEffect  = new String[nf.anims][maxNoF];
+        nf.unknownData2 = new byte[nf.anims][maxNoF][18];
+        uData1 = getStrArrByTag(nf.xml_tag[57]);
+        sfx    = getStrArrByTag(nf.xml_tag[58]);
+        uData2 = getStrArrByTag(nf.xml_tag[59]);
+        if(nf.maybeMCV)
+        {
+            nf.hasMCValues = new int[nf.anims][maxNoF];
+            nf.mcValues    = new int[nf.anims][maxNoF][7];
+            nf.mcParam7    = new String[nf.anims][maxNoF];
+            nf.mcParam8    = new byte[nf.anims][maxNoF][20];
+            hasMCV = getIntArrByTag(nf.xml_tag[60]);
+            mcVals = new int[7][nf.totalFrames];
+            mcVals[0] = getIntArrByTag(nf.xml_tag[61]);
+            mcVals[1] = getIntArrByTag(nf.xml_tag[62]);
+            mcVals[2] = getIntArrByTag(nf.xml_tag[63]);
+            mcVals[3] = getIntArrByTag(nf.xml_tag[64]);
+            mcVals[4] = getIntArrByTag(nf.xml_tag[65]);
+            mcVals[5] = getIntArrByTag(nf.xml_tag[66]);
+            mcVals[6] = getIntArrByTag(nf.xml_tag[67]);
+            mcParam7  = getStrArrByTag(nf.xml_tag[68]);
+            mcParam8  = getStrArrByTag(nf.xml_tag[69]);
+        }
+        // Set Frame Data & animOffsets & frameOffsets
+        for(int a=0,animOffTotal=0; a < nf.anims; a++)
+        {
+            nf.animOffsets[a] = animOffTotal;
+            animOffDiff=0;
+            frameOffTotal=0;
+            NoF = nf.numFrames[a];
+            animOffDiff = 36+(4*NoF);
+            for(int f=0; f < NoF; f++)
+            {
+                nf.frameOffsets[a][f] = frameOffTotal;
+                frameOffDiff=0;
+                // Set FrameDataTop values
+                nf.frameDataTop[a][f][0] = durations[ftDex[0]++];
+                nf.frameDataTop[a][f][1] = numPlanes[ftDex[1]++];
+                NoP = nf.frameDataTop[a][f][1];
+                frameOffDiff = 8+(28*NoP);
+                // Set PlaneData array
+                for(int p=0; p < NoP; p++)
+                {
+                    nf.planeData[a][f][p][0] = bmpID[pdex[0]++];
+                    nf.planeData[a][f][p][1] = planeX[pdex[1]++];
+                    nf.planeData[a][f][p][2] = planeY[pdex[2]++];
+                    nf.planeData[a][f][p][3] = opacity[pdex[3]++];
+                    nf.planeData[a][f][p][4] = flip[pdex[4]++];
+                    nf.planeData[a][f][p][5] = blend_mode[pdex[5]++];
+                    nf.planeData[a][f][p][6] = flag_param[pdex[6]++];
+                }
+                // Set FrameDataBottom values
+                fillFrameDataBottom(a,f);
+                frameOffTotal += frameOffDiff;
+            }
+            animOffDiff += frameOffTotal;
+            animOffTotal += animOffDiff;
+        }
+    }catch(Exception ex)
+    {
+        out.println("Error in (getConfigData):");
+        ex.printStackTrace(System.out);
+    }
+}
+
+private static void fillFrameDataBottom(int a, int f)
+{
+    if(nf.notV300)
+    {
+        nf.numCoordSets[a][f] = coordSets[fbDex[0]++];
+        for(int i=0; i < nf.numCoordSets[a][f]; i++)
+        {
+            nf.coordSets[a][f][i][0] = coordX[fbDex[1]++];
+            nf.coordSets[a][f][i][1] = coordY[fbDex[2]++];
+        }
+        frameOffDiff += 4+(nf.numCoordSets[a][f]*8);
+    }
+    frameOffDiff += nf.cdBlockSize;
+    if(nf.hasEB)
+    {
+        nf.entryBlocks[a][f][0] = b64Dec(eBlocks[fbDex[3]++]);
+        nf.entryBlocks[a][f][1] = b64Dec(eBlocks[fbDex[3]++]);
+        nf.entryBlocks[a][f][2] = b64Dec(eBlocks[fbDex[3]++]);
+        nf.entryBlocks[a][f][3] = b64Dec(eBlocks[fbDex[3]++]);
+        nf.entryBlocks[a][f][4] = b64Dec(eBlocks[fbDex[3]++]);
+        nf.entryBlocks[a][f][5] = b64Dec(eBlocks[fbDex[3]++]);
+        frameOffDiff += 168;
+    }
+    nf.unknownData1[a][f][0] = b64Dec(uData1[fbDex[4]++]);
+    nf.unknownData1[a][f][1] = b64Dec(uData1[fbDex[4]++]);
+    nf.soundEffect[a][f]     = sfx[fbDex[5]++];
+    nf.unknownData2[a][f]    = b64Dec(uData2[fbDex[6]++]);
+    frameOffDiff += 44+18+18;
+    if(nf.maybeMCV)
+    {
+        nf.hasMCValues[a][f] = hasMCV[fbDex[7]++];
+        frameOffDiff += 4;
+        if(nf.hasMCValues[a][f]==1)
+        {
+            nf.mcSizeSum += 28;
+            frameOffDiff += 28;
+            nf.mcValues[a][f][0] = mcVals[0][fbDex[8]];
+            nf.mcValues[a][f][1] = mcVals[1][fbDex[8]];
+            nf.mcValues[a][f][2] = mcVals[2][fbDex[8]];
+            nf.mcValues[a][f][3] = mcVals[3][fbDex[8]];
+            nf.mcValues[a][f][4] = mcVals[4][fbDex[8]];
+            nf.mcValues[a][f][5] = mcVals[5][fbDex[8]];
+            nf.mcValues[a][f][6] = mcVals[6][fbDex[8]++];
+            int A = nf.mcValues[a][f][1]*nf.mcValues[a][f][2];
+            nf.mcSizeSum += A;
+            frameOffDiff += A;
+            nf.mcParam7[a][f] = mcParam7[fbDex[9]++];
+            nf.mcParam8[a][f] = b64Dec(mcParam8[fbDex[10]++]);
+            nf.mcSizeSum += 20;
+            frameOffDiff += 20;
+        }
+    }
+}
+
+// An anti-duplication + better readability function
+private static int getMax(int[] array)
+{
+    max=0;
+    for(int x : array)
+    {
+        if(x > max) max=x;
+    }
+    return max;
+}
+
+private static int getIntArrSum(int[] array)
+{
+    int sum=0;
+    for(int x : array)
+    {
+        sum += x;
+    }
+    return sum;
+}
+
+// Shorten the byte array from base64 encoded string command
+private static byte[] b64Dec(String s)
+{
+    return Base64.getDecoder().decode(s.getBytes());
+}
+
+// Get single int array (int[]) by tagName
+private static int[] getIntArrByTag(String tag)
+{
+    NodeList nl = cfg.getElementsByTagName(tag);
+    max = nl.getLength();
+    int[] tmp = new int[max];
+    for(int i = 0; i < max; i++)
+    {
+        Node n = nl.item(i);
+        tmp[i] = toInt((n.getTextContent()).trim());
+    }
+    return tmp;
+}
+
+// Get single string array (String[]) by tagName
+private static String[] getStrArrByTag(String tag)
+{
+    NodeList nl = cfg.getElementsByTagName(tag);
+    max = nl.getLength();
+    String[] tmp = new String[max];
+    for(int i = 0; i < max; i++)
+    {
+        Node n = nl.item(i);
+        tmp[i] = (n.getTextContent()).trim();
+    }
+    return tmp;
+}
+
+// An anti-duplication + better readability function
+private static Element getElementByTagName(String tagName)
+{
+    Node node0 = cfg.getElementsByTagName(tagName).item(0);
+    return (Element) node0;
+}
+
+// An anti-duplication + better readability function
+private static int getIntVal(Element parentE,String tagName)
+{
+    Node node0 = parentE.getElementsByTagName(tagName).item(0);
+    return toInt((node0.getTextContent()).trim());
+}
+
+// An anti-duplication + better readability function
+private static int toInt(String str)
+{
+    try
+    {
+        return Integer.parseInt(str);
+    }
+    catch(NumberFormatException e)
+    {
+        return 0;
+    }
+}
+}
diff --git a/src/JBL.java b/src/JBL.java
index 95309ef..475b5fc 100644
--- a/src/JBL.java
+++ b/src/JBL.java
@@ -3,7 +3,7 @@ Java Bitmap Library (JBL)
 A fairly spontaneous Java library that contains functions to deal with bitmaps
 both standard and abnormal. Useful for any bitmap not just BMP images.
 
-Copyright (C) 2018-2020 Sean Stafford (a.k.a. PyroSamurai)
+Copyright (C) 2018-2021 Sean Stafford (a.k.a. PyroSamurai)
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -22,7 +22,7 @@ Java Bitmap Library (JBL)
 /**
 Class Description:
 This class/library was created to deal with the task of extracting bitmaps from
-proprietary image archive formats to standard RGB24 bitmaps. It can help with
+proprietary image archive formats to standard RGB bitmaps. It can help with
 normal bitmap creation too, since Java lacks proper byte support for bitmaps.
 
 Dev Notes:
@@ -31,443 +31,509 @@ Java Bitmap Library (JBL)
 All data is little-endian format. Don't think too hard about the actual code.
 It is a huge headache to understand these bit formats.
 
-Version: 1.0.0
+For the sake of versatility all members of the class are public & rely as little
+as possible on each other for data, relying mostly on params & class vars.
+This means you are responsible for using them in the right order, though.
+Just keep that in mind.
+
+For your benefit, the functions are actually in the order you should use them,
+with the exception of stripPadding & reverseRows whose location/existence in
+your program can vary a lot with your use-case.
+
+Version: 1.1.2
 */
 public class JBL
 {
-    // class variables
-    public static int bpp=0,Bpp=0,w=0,h=0,dataSize=0,bppOut=0,pixels=0,nLen=0;
-    public static String name, dir, bitFmtIn, bitFmtOut, RGB24="RGB24";
-    public static String RGB555="RGB555",RGB565="RGB565",ARGB16="ARGB16";
-    public static byte[][] palette = new byte[256][3];
-    public static boolean bitFmtOutSet=false;
+// class variables
+public int bmpSize,dataStart,dibSize,w,h,planes=1,bpp,compMethod,dataSize;
+public int Bpp,bppOut,pixels,pxLen,nLen,bitmaskR,bitmaskG,bitmaskB,bitmaskA;
+public String name,dir,bitFmtIn,bitFmtOut,RGB8="RGB8";
+public String RGB24="RGB24",RGB555="RGB555",RGB565="RGB565",ARGB16="ARGB16";
+public byte[][] palette = new byte[256][3];
+public boolean bitFmtOutSet=false;
 
-    // constructor for JBL class
-    public JBL() {}
+// constructor for JBL class
+public JBL(){}
 
-    // ######################## class mutators: begin ########################
-    // The mutators need to be run before getImgBytes(), to have any effect
+// ######################## class mutators: begin ########################
+// The mutators need to be run before non-mutators, to have any effect
 
-    // Sets the file-related variables, required for all
-    public static void setFileVars(String fileDir,String bmpRootName)
-    {
-        // File Directory String (should include the File.separator)
-        dir = fileDir;
-        // The name that will serve as the base for all bitmap output
-        name = bmpRootName;
-    }
+// Sets the file-related vars, required for makeBMP()
+public void setFileVars(String fileDir,String bmpRootName)
+{
+    // File Directory String (should include the File.separator)
+    dir = fileDir;
+    // If it doesn't have File.separator, add it
+    if(!dir.endsWith(File.separator)) dir += File.separator;
+    // The name that will serve as the base for all bitmap output
+    name = bmpRootName;
+}
 
-    // Sets the pixel-related variables, required for all
-    public static void setBmpVars(int W,int H,int bitDepth)
-    {
-        // Bitmap width in pixels
-        w = W;
-        // Bitmap height in pixels
-        h = H;
-        // Bits Per Pixel
-        bpp = bitDepth;
-        // Set bpp output
-        if(bpp==8 && bitFmtOutSet==false) bppOut=24;
-        if(bpp!=8 && bitFmtOutSet==false) bppOut=bpp;
-        // Calculated # of pixels in bitmap
-        pixels = w*h;
-        // Data Size, calculated size of the input bitmap data
-        dataSize = pixels*(bpp/8);
-    }
+// Sets the pixel-related vars, must use this or getBitmapVars for makeBMP()
+public void setBitmapVars(int width,int height,int bitDepth)
+{
+    // Bitmap width & height in pixels
+    w = width;
+    h = height;
+    // Bits Per Pixel
+    bpp = bitDepth;
+    // Set bpp output
+    if(bpp==8 && bitFmtOutSet==false) bppOut=24;
+    if(bpp!=8 && bitFmtOutSet==false) bppOut=bpp;
+    // Calculated # of pixels in bitmap
+    pixels = w*h;
+    // Pixel Length, calculated size of the input bitmap data
+    pxLen = pixels*(bpp/8);
+}
 
-    // Sets the palette array, required for 8-bit conversions
-    public static void setPalette(byte[][] pal)
+// Sets the pixel-related vars, useful for pre-existing BMP
+public void getBitmapVars(byte[] bitmap)
+{
+    ByteBuffer bmp = ByteBuffer.wrap(bitmap).order(ByteOrder.LITTLE_ENDIAN);
+    bmp.getChar();//skip file signature
+    bmpSize = bmp.getInt();
+    bmp.getInt();//skip reserved bytes
+    dataStart = bmp.getInt();
+    dibSize = bmp.getInt();
+    switch(dibSize)
     {
-        palette = pal;
+    case 12:
+        w = (int)bmp.getChar();
+        h = (int)bmp.getChar();
+        bmp.getChar();//planes
+        bpp = (int)bmp.getChar();
+        break;
+    case 16:
+    case 52:
+    case 56:
+    case 64:
+    case 108:
+    case 124:
+    default://40
+        w = bmp.getInt();
+        h = bmp.getInt();
+        bmp.getChar();//planes
+        bpp = (int)bmp.getChar();
+        compMethod = bmp.getInt();
+        dataSize = bmp.getInt();
+        break;
     }
+    // Calculated # of pixels in bitmap
+    pixels = w*h;
+    // Pixel Length, calculated size of the input bitmap data
+    pxLen = pixels*(bpp/8);
+}
 
-    // Sets the input bit format, required for 16-bit conversions
-    public static void set16BitFmtIn(String bitFormat)
-    {
-        bitFmtIn = bitFormat;
-        bitFmtOut = bitFormat;
-    }
+// Sets the palette array, required for 8-bit conversions
+public void setPalette(byte[][] pal)
+{
+    palette = pal;
+}
 
-    // Sets bit format output, required for a bppOut != bpp
-    // 8bit input will default to 24bit output if this is not set
-    public static void setBitFmtOut(String bitFormat)
-    {
-        bitFmtOut = bitFormat;
-        if(bitFmtOut.equals(RGB24)) bppOut=24;
-        if(!bitFmtOut.equals(RGB24)) bppOut=16;
-        bitFmtOutSet = true;
-    }
+// Sets the input bit format, required for 16-bit conversions
+public void set16BitFmtIn(String bitFormat)
+{
+    bitFmtIn = bitFormat;
+    bitFmtOut = bitFormat;
+}
 
-    // Sets length of largest # & returns it, required for makeBMP(byte[],int)
-    public static int setImgSetSize(int numberOfImages)
-    {
-        return nLen = String.valueOf(numberOfImages).length();
-    }
+// Sets bit format output, required for a bppOut != bpp
+// 8bit input will default to 24bit output if this is not set
+public void setBitFmtOut(String bitFormat)
+{
+    bitFmtOut = bitFormat;
+    if(bitFmtOut.equals(RGB24)) bppOut=24;
+    if(!bitFmtOut.equals(RGB24)) bppOut=16;
+    bitFmtOutSet = true;
+}
 
-    // ######################### class mutators: end #########################
-    // #######################################################################
+// Use of 1 of the following is required for makeBMP(byte[],int,String)
+//##########################################################################
 
-    // Place bitmap bytes in a byte array
-    public static byte[] getImgBytes(ByteBuffer bb, int dataLength)
-    {
-        // dataLength is for grabbing compressed data, just set to 0 if unneeded
-        if(dataLength!=0) dataSize = dataLength;
-        byte[] rawBitmap = new byte[dataSize];
-        // Read the raw bitmap data into the array that was just made.
-        bb.get(rawBitmap,0,dataSize);
-        return rawBitmap;
-    }
+// Sets nLen to the value of the int parameter
+public void setNumLength(int lengthOfLargestNumber)
+{
+    nLen = lengthOfLargestNumber;
+}
 
-    // An interface to convert pixels to a standard RGB format
-    public static byte[] toStdRGB(byte[] rawPixels)
-    {
-        byte[] temp24 = toRGB24(rawPixels);
-        if(bppOut==16)
-            return addPadding((toRGB16(temp24)), 2);
-        else
-            return addPadding(temp24, 3);
-    }
+// Sets nLen to length of largest number
+public void setImgsetSize(int numberOfImages)
+{
+    nLen = String.valueOf(numberOfImages).length();
+}
 
-    // Converts other BMP formats to the uncompressed 24-bit format
-    public static byte[] toRGB24(byte[] rawBytes)
+// ######################### class mutators: end #########################
+/*########################################################################*/
+
+// Get bitmap bytes from a bytebuffer-wrapped byte array
+public byte[] getImgBytes(ByteBuffer bb, int dataLength)
+{
+    // dataLength is for grabbing compressed data, just set to 0 if unneeded
+    if(dataLength!=0) dataSize = dataLength;
+    if(dataSize==0) dataSize = pxLen;
+    byte[] rawBMP = new byte[dataSize];
+    // Read the raw bitmap data into the array that was just made.
+    bb.get(rawBMP,0,dataSize);
+    return rawBMP;
+}
+
+// An interface to convert pixels to a standard RGB format
+public byte[] toStdRGB(byte[] rawPixels)
+{
+    byte[] temp24 = toRGB24(rawPixels);
+    if(bppOut==16)
+        return addPadding((toRGB16(temp24)), 2);
+    else
+        return addPadding(temp24, 3);
+}
+
+// Converts other BMP formats to the uncompressed 24-bit format
+public byte[] toRGB24(byte[] rawBytes)
+{
+    byte[] px = new byte[pixels*3];
+    // convert 8-bit bmp data to 24-bit data
+    if(bpp==8)
     {
-        byte[] px = new byte[pixels*3];
-        // convert 8-bit bmp data to 24-bit data
-        if(bpp==8)
+        for(int i = 0; i < pixels; i++)
         {
-            for(int i = 0; i < pixels; i++)
-            {
-                int x = i*3, r=0, g=1, b=2;
-                // get the color index from the 8bit bmp array
-                int c = rawBytes[i];
-                // bytes are always signed, deal with the negative half
-                if(c < 0) c = (c & 0xFF);
-                // add/assign the rgb bytes from the palette based on the index
-                px[x+0] = palette[c][r];
-                px[x+1] = palette[c][g];
-                px[x+2] = palette[c][b];
-            }
-        }
-        // ######################## 16-bit Conversions ########################
-        else if(bpp==16 && bitFmtIn.equals(RGB555))
-        {
-            // RGB555 (5 bits per color) stored in 2 bytes
-            for(int i = 0; i < pixels; i++)
-            {
-                // A shoutout to OrigamiGuy for insight on this conversion
-                int x=i*2, y=i*3;
-                byte b1=rawBytes[x], b2=rawBytes[x+1];
-                // assign the bits inside the 2 bytes to r, g, b vars
-                int b = (b1 & 0x1F) << 3;
-                int g = ((b2 & 0x03) << 6) | ((b1 & 0xE0) >> 2);
-                int r = (b2 & 0x7C) << 1;
-                // mirror the 5 bits to 3 empty ones to get the right 8bit vals
-                r = r | r >> 5;
-                g = g | g >> 5;
-                b = b | b >> 5;
-                // change the int vars to bytes vars & add them to px array add
-                // them in reverse order b/c that is the way format is, ugh
-                px[y+0] = (byte)b;
-                px[y+1] = (byte)g;
-                px[y+2] = (byte)r;
-            }
+            int x = i*3, r=0, g=1, b=2;
+            // get the color index from the 8bit bmp array
+            int c = rawBytes[i];
+            // bytes are always signed, deal with the negative half
+            if(c < 0) c = (c & 0xFF);
+            // add/assign the rgb bytes from the palette based on the index
+            px[x+0] = palette[c][r];
+            px[x+1] = palette[c][g];
+            px[x+2] = palette[c][b];
         }
-        else if(bpp==16 && bitFmtIn.equals(RGB565))
-        {
-            // RGB565 stored in 2 bytes (as bgr)
-            for(int i = 0; i < pixels; i++)
-            {
-                int x=i*2, y=i*3;
-                byte b1=rawBytes[x], b2=rawBytes[x+1];
-                // assign the bits inside the 2 bytes to r, g, b vars
-                int b = (b1 & 0x1F) << 3;
-                int g = ((b2 & 0x07) << 5) | ((b1 & 0xE0) >> 3);
-                int r = (b2 & 0xF8);
-                // mirror the color bits to the empty bits for correct 8bit vals
-                r = r | r >> 5;
-                g = g | g >> 6;
-                b = b | b >> 5;
-                // change the int vars to bytes vars & add them to px array add
-                // them in reverse order b/c that is the way format is, ugh
-                px[y+0] = (byte)b;
-                px[y+1] = (byte)g;
-                px[y+2] = (byte)r;
-            }
-        }
-        else if(bpp==16 && bitFmtIn.equals(ARGB16))
-        {
-            // ARGB16 (ARGB4444) (4 bits per color) stored in 2 bytes
-            // bitmaps don't support transparency, so even though this format is
-            // mainly used to support it in 16-bit, I'm going to ignore it, ftb.
-            for(int i = 0; i < pixels; i++)
-            {
-                int x=i*2, y=i*3;
-                byte b1=rawBytes[x], b2=rawBytes[x+1];
-                // assign the bits inside the 2 bytes to a, r, g, b vars
-                int a = (b2 & 0xF0);
-                int r = (b2 & 0x0F) << 4;
-                int g = (b1 & 0xF0);
-                int b = (b1 & 0x0F) << 4;
-                // mirror the 4 bits to 4 empty ones to get the right 8bit vals
-                a = a | a >> 4;
-                r = r | r >> 4;
-                g = g | g >> 4;
-                b = b | b >> 4;
-                // change the int vars to bytes vars & add them to px array add
-                // them in reverse order b/c that is the way format is, ugh
-                px[y+0] = (byte)b;
-                px[y+1] = (byte)g;
-                px[y+2] = (byte)r;
-            }
-        }
-        // pass 24-bit bitmap data right through untouched
-        else
-        {
-            px = rawBytes;
-        }
-        return px;
     }
-
-    // Converts standard 24-bit BMP pixels to 16-bit
-    public static byte[] toRGB16(byte[] rgb24)
+    // ######################## 16-bit Conversions ########################
+    else if(bpp==16 && bitFmtIn.equals(RGB555))
     {
-        byte[] rgb16 = new byte[pixels*2];
-        if(bitFmtOut.equals(RGB555))
+        // RGB555 (5 bits per color) stored in 2 bytes
+        for(int i = 0; i < pixels; i++)
         {
-            // RGB24 to RGB555 (5 bits per color) stored in 2 bytes
-            for(int i=0; i < pixels; i++)
-            {
-                int x=i*2, y=i*3;
-                byte b=rgb24[y],g=rgb24[y+1],r=rgb24[y+2];
-                int b1 = ((g<<2) & 0xE0) | ((b>>3) & 0x1F);
-                int b2 = ((r>>1) & 0x7C) | ((g>>6) & 0x03);
-                // change the ints to byte vars and add them to rgb16 array
-                rgb16[x+0] = (byte)b1;
-                rgb16[x+1] = (byte)b2;
-            }
+            // A shoutout to OrigamiGuy for insight on this conversion
+            int x=i*2, y=i*3;
+            byte b1=rawBytes[x], b2=rawBytes[x+1];
+            // assign the bits inside the 2 bytes to r, g, b vars
+            int b = (b1 & 0x1F) << 3;
+            int g = ((b2 & 0x03) << 6) | ((b1 & 0xE0) >> 2);
+            int r = (b2 & 0x7C) << 1;
+            // mirror the 5 bits to 3 empty ones to get the right 8bit vals
+            r = r | r >> 5;
+            g = g | g >> 5;
+            b = b | b >> 5;
+            // change the int vars to bytes vars & add them to px array add
+            // them in reverse order b/c that is the way format is, ugh
+            px[y+0] = (byte)b;
+            px[y+1] = (byte)g;
+            px[y+2] = (byte)r;
         }
-        else if(bitFmtOut.equals(ARGB16))
+    }
+    else if(bpp==16 && bitFmtIn.equals(RGB565))
+    {
+        // RGB565 stored in 2 bytes (as bgr)
+        for(int i = 0; i < pixels; i++)
         {
-            // RGB24 to ARGB16 stored in 2 bytes
-            for(int i = 0; i < pixels; i++)
-            {
-                int x=i*2, y=i*3;
-                byte b=rgb24[y],g=rgb24[y+1],r=rgb24[y+2];
-                // assign the a, r, g, b vars to 2 bytes
-                int b1 = (g & 0xF0) | (b & 0x0F);
-                int b2 = (r & 0x0F);
-                // change the ints to byte vars and add them to rgb16 array
-                rgb16[x+0] = (byte)b1;
-                rgb16[x+1] = (byte)b2;
-            }
+            int x=i*2, y=i*3;
+            byte b1=rawBytes[x], b2=rawBytes[x+1];
+            // assign the bits inside the 2 bytes to r, g, b vars
+            int b = (b1 & 0x1F) << 3;
+            int g = ((b2 & 0x07) << 5) | ((b1 & 0xE0) >> 3);
+            int r = (b2 & 0xF8);
+            // mirror the color bits to the empty bits for correct 8bit vals
+            r = r | r >> 5;
+            g = g | g >> 6;
+            b = b | b >> 5;
+            // change the int vars to bytes vars & add them to px array add
+            // them in reverse order b/c that is the way format is, ugh
+            px[y+0] = (byte)b;
+            px[y+1] = (byte)g;
+            px[y+2] = (byte)r;
         }
-        else
+    }
+    else if(bpp==16 && bitFmtIn.equals(ARGB16))
+    {
+        // ARGB16 (ARGB4444) (4 bits per color) stored in 2 bytes
+        // bitmaps don't support transparency, so even though this format is
+        // mainly used to support it in 16-bit, I'm going to ignore it, ftb.
+        for(int i = 0; i < pixels; i++)
         {
-            // RGB24 to RGB565 Standard 16bit Format for bitmaps
-            for(int i=0; i < pixels; i++)
-            {
-                int x=i*2, y=i*3;
-                byte b=rgb24[y],g=rgb24[y+1],r=rgb24[y+2];
-                int b1 = ((g<<3) & 0xE0) | ((b>>3) & 0x1F);
-                int b2 = (r & 0xF8) | ((g>>5) & 0x07);
-                // change the ints to byte vars and add them to rgb16 array
-                rgb16[x+0] = (byte)b1;
-                rgb16[x+1] = (byte)b2;
-            }
+            int x=i*2, y=i*3;
+            byte b1=rawBytes[x], b2=rawBytes[x+1];
+            // assign the bits inside the 2 bytes to a, r, g, b vars
+            int a = (b2 & 0xF0);
+            int r = (b2 & 0x0F) << 4;
+            int g = (b1 & 0xF0);
+            int b = (b1 & 0x0F) << 4;
+            // mirror the 4 bits to 4 empty ones to get the right 8bit vals
+            a = a | a >> 4;
+            r = r | r >> 4;
+            g = g | g >> 4;
+            b = b | b >> 4;
+            // change the int vars to bytes vars & add them to px array add
+            // them in reverse order b/c that is the way the format is, ugh
+            px[y+0] = (byte)b;
+            px[y+1] = (byte)g;
+            px[y+2] = (byte)r;
         }
-        return rgb16;
     }
+    // pass 24-bit bitmap data right through untouched
+    else
+    {
+        px = rawBytes;
+    }
+    return px;
+}
 
-    // add the necessary scanline byte padding required by bitmaps
-    public static byte[] addPadding(byte[] rgb, int BppOut)
+// Converts standard 24-bit BMP pixels to 16-bit
+public byte[] toRGB16(byte[] rgb24)
+{
+    byte[] rgb16 = new byte[pixels*2];
+    if(bitFmtOut.equals(RGB555))
     {
-        int colorBytes=w*BppOut, padBytes=(4-(w*BppOut%4))%4;
-        int scanline=colorBytes+padBytes, size=scanline*h, dex1=0, dex2=0;
-        byte[] scanlines = new byte[size];
-        byte padByte = 0x00;
-        if(padBytes!=0)
+        // RGB24 to RGB555 (5 bits per color) stored in 2 bytes
+        for(int i=0; i < pixels; i++)
         {
-            for(int i=0; i < h; i++)
-            {
-                for(int x=0; x < scanline; x++)
-                {
-                    if(x < colorBytes)
-                        scanlines[dex1++] = rgb[dex2++];
-                    else
-                        scanlines[dex1++] = padByte;
-                }
-            }
-        }
-        else
-        {
-            scanlines = rgb;
+            int x=i*2, y=i*3;
+            byte b=rgb24[y],g=rgb24[y+1],r=rgb24[y+2];
+            int b1 = ((g<<2) & 0xE0) | ((b>>3) & 0x1F);
+            int b2 = ((r>>1) & 0x7C) | ((g>>6) & 0x03);
+            // change the ints to byte vars and add them to rgb16 array
+            rgb16[x+0] = (byte)b1;
+            rgb16[x+1] = (byte)b2;
         }
-        return scanlines;
+        // A good location to set rgb555-specific bitmask info
+        bitmaskR = 31744;
+        bitmaskG = 992;
+        bitmaskB = 31;
     }
-
-    // remove the scanline byte padding required by bitmaps
-    public static byte[] stripPadding(byte[] scanlines)
+    else if(bitFmtOut.equals(ARGB16))
     {
-        int colorBytes=w*(bpp/8), padBytes=(4-(w*(bpp/8)%4))%4;
-        int scanline=colorBytes+padBytes, size=colorBytes*h, dex1=0, dex2=0;
-        byte[] rgb = new byte[size];
-        if(padBytes!=0)
+        // RGB24 to ARGB16 stored in 2 bytes
+        for(int i = 0; i < pixels; i++)
         {
-            for(int i=0; i < h; i++)
-            {
-                for(int x=0; x < scanline; x++)
-                {
-                    if(x>3) & 0x1F);
+            int b2 = (r & 0xF8) | ((g>>5) & 0x07);
+            // change the ints to byte vars and add them to rgb16 array
+            rgb16[x+0] = (byte)b1;
+            rgb16[x+1] = (byte)b2;
         }
-        return rgb;
+        // A good location to set rgb565-specific bitmask info
+        bitmaskR = 63488;
+        bitmaskG = 2016;
+        bitmaskB = 31;
     }
+    return rgb16;
+}
 
-    // For BMP's convoluted format to work well the data needs to written
-    // bottom-up, with the last scanline at the top and vice versa.
-    public static byte[] reverseRows(byte[] topDownLines)
+// add the necessary scanline byte padding required by bitmaps
+public byte[] addPadding(byte[] rgb, int BppOut)
+{
+    int colorBytes=w*BppOut, padBytes=(4-(w*BppOut%4))%4;
+    int scanline=colorBytes+padBytes, size=scanline*h, dex1=0, dex2=0;
+    byte[] scanlines = new byte[size];
+    byte padByte = 0x00;
+    if(padBytes!=0)
     {
-        int scanline=(topDownLines.length / h), dex1=0, dex2=0;
-        byte[] trueScanlines = new byte[topDownLines.length];
-        byte[][] scanlines = new byte[h][scanline];
         for(int i=0; i < h; i++)
         {
             for(int x=0; x < scanline; x++)
             {
-                scanlines[i][x] = topDownLines[dex1++];
+                if(x < colorBytes)
+                    scanlines[dex1++] = rgb[dex2++];
+                else
+                    scanlines[dex1++] = padByte;
             }
         }
-        int lastLine = h - 1;
+    }
+    else
+    {
+        scanlines = rgb;
+    }
+    return scanlines;
+}
+
+// remove the scanline byte padding required by bitmaps
+public byte[] stripPadding(byte[] scanlines)
+{
+    int colorBytes=w*(bpp/8), padBytes=(4-(w*(bpp/8)%4))%4;
+    int scanline=colorBytes+padBytes, size=colorBytes*h, dex1=0, dex2=0;
+    byte[] rgb = new byte[size];
+    if(padBytes!=0)
+    {
         for(int i=0; i < h; i++)
         {
             for(int x=0; x < scanline; x++)
             {
-                trueScanlines[dex2++] = scanlines[lastLine-i][x];
+                if(x>>8), (byte)(v>>>16), (byte)(v>>>24)};
-        return ba;
+        // Set BMP name and location, then write BMP to file
+        String sNum = String.format("%0"+nLen+"d", currentNum);
+        File img = new File(dir+name+"_"+sNum+suffix+".bmp");
+        Files.write(img.toPath(),BMP);
     }
+    catch(Exception ex)
+    {
+        out.println("Error in (makeBMPSet):");
+        ex.printStackTrace(System.out);
+    }
+}
 }
diff --git a/src/Main.java b/src/Main.java
index 00eeaff..be61e81 100644
--- a/src/Main.java
+++ b/src/Main.java
@@ -2,7 +2,7 @@
 The NORI Tool (TNT), is a program designed to extract & create NORI files for
 the Libre Trickster project.
 
-Copyright (C) 2014-2020 Libre Trickster Team
+Copyright (C) 2014-2024 Libre Trickster Team
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -18,9 +18,7 @@ The NORI Tool (TNT), is a program designed to extract & create NORI files for
 import java.nio.*;
 import java.nio.file.*;
 import java.util.zip.*;
-import static java.lang.System.in;
 import static java.lang.System.out;
-import static java.lang.System.err;
 /**
 Class Description:
 The Main class is the 'main' class of the TNT program (this is obvious).
@@ -37,236 +35,205 @@ The NORI Tool (TNT), is a program designed to extract & create NORI files for
 */
 public class Main
 {
-    // Functions ordered by importance to TNT (& thus more likely to be edited)
-    // class variables
-    public static char mode;
-    public static boolean argsBool=false;
-    public static int argsLen=0;
-    public static File noriFile, cfg;
-    public static byte[] fba; // fba: file byte array
-
-    // Main function (keep clean)
-    public static void main(String[] args)
+// class variables
+private static int argsLen=0;
+private static char mode;
+private static boolean noError,create_mode;
+private static String aAe="aAe",cC="cC",RTFM="",dLn="";
+private static File nFile, cfg;
+
+// Main function (keep clean)
+public static void main(String[] args)
+{
+    RTFM="See 'NORI_format.md' for information on the XML tags!";
+    dLn="=====================================================================";
+    argsLen = args.length;
+    argCheck(args);
+    if(create_mode)
     {
-        NORI nf = new NORI();
-        argsLen = args.length;
-        argsBool = argCheck(args);
-        if(argsBool)
+        out.println(dLn);
+        switch(mode)
         {
-            if(mode!='c')
-            {
-                for(int i=1; i < argsLen; i++)
-                {
-                    noriFile = new File(args[i]);
-                    if(noriFile.exists())
-                    {
-                        nf.setNFileVars(noriFile,0);
-                        nf.checkDir();
-                        fba = byteLoader(noriFile);
-                        runMode(inflateIfNeeded(fba),nf);
-                    }
-                    else
-                    {
-                        argErrors(3);
-                    }
-                }
-            }
-            else
+        case 'C':
+            Create opt_C = new Create(cfg,args[2],true);
+            break;
+        default:
+            Create opt_c = new Create(cfg,args[2],false);
+            break;
+        }
+    }
+    else
+    {
+        for(int i=1; i < argsLen; i++)
+        {
+            nFile = new File(args[i]);
+            if(nFile.exists()==false) argErrors(3);
+            out.println(dLn);
+            byte[] nFileBA = zInflate(file2BA(nFile));
+            switch(mode)
             {
-                Create optC = new Create(cfg,args[2],nf);
+            case 'e':
+                Extract opt_e = new Extract(nFileBA,nFile);
+                break;
+            case 'A':
+                Analyze opt_A = new Analyze(nFileBA,nFile,true);
+                break;
+            default:
+                Analyze opt_a = new Analyze(nFileBA,nFile,false);
+                break;
             }
         }
+        if(mode=='A') out.println(RTFM);
     }
+}
 
-    // This function loads the file into a byte array, so other functions can
-    // access it. Does nothing else. No need to name the file after it ;)
-    private static byte[] byteLoader(File file)
+// Checks for and decompresses zlib compression if found
+private static byte[] zInflate(byte[] in)
+{
+    ByteBuffer bb = ByteBuffer.wrap(in).order(ByteOrder.LITTLE_ENDIAN);
+    int sig = bb.getInt();
+    int sizeExpected = bb.getInt();
+    if(sig!=1230131022 && in[12]==0x78)
     {
-        // Max byte array size is ~2GB, which is much longer than any existing
-        // NORI file so we don't have to check for a file that is too big.
-        byte[] bytes = new byte[(int)file.length()];
+        byte[] tmp = new byte[sizeExpected];
         try
         {
-            // Loads file into byte array
-            bytes = Files.readAllBytes(file.toPath());
+            // Inflater() expects the zlib header to be included
+            Inflater dcmp = new Inflater();
+            // Loads input byte array, start offset, compressed data size
+            dcmp.setInput(in,12,bb.getInt());
+            // Takes in a byte array & loads it with the decompressed result
+            int size = dcmp.inflate(tmp);// returns decompressed size
+            dcmp.end();
+            if(sizeExpected==size) out.println("Decompression successful!\n");
         }
         catch(Exception ex)
         {
-            out.println("Error in (FBA):\n"+ex);
+            out.println("Error in (INFLATE):");
+            ex.printStackTrace(System.out);
         }
-        return bytes;
+        return tmp;
     }
-
-    // Checks for and decompresses zlib compression if found
-    private static byte[] inflateIfNeeded(byte[] in)
+    else
     {
-        int fsig=0, decompsz=0, cmpdatasz=0;
-        boolean fsb = false;
-        ByteBuffer bb = ByteBuffer.wrap(in).order(ByteOrder.LITTLE_ENDIAN);
-        fsig = bb.getInt();
-        fsb = (fsig!=1230131022);
-        decompsz = bb.getInt();
-        cmpdatasz = bb.getInt();
-        if(fsb && in[12]==0x78 && (in[13]==0x01||in[13]==0x9C||in[13]==0xDA))
-        {
-            byte[] tmp = new byte[decompsz];
-            try
-            {
-                // Inflater class expects the zlib header to be included unless
-                // initialized to Inflater(true) instead of Inflater()
-                Inflater dcmp = new Inflater();
-                // Loads input byte array, start offset, compressed data size
-                dcmp.setInput(in,12,cmpdatasz);
-                // inflate funct takes in the recipient array and loads it with
-                // the result. Also has a return value: the decompressed size
-                int realsz = dcmp.inflate(tmp);
-                dcmp.end();
-                if(decompsz==realsz) out.println("Decompression successful!");
-            }
-            catch(Exception ex)
-            {
-                out.println("Error in (INFLATE):\n"+ex);
-            }
-            return tmp;
-        }
-        else
-        {
-            return in;
-        }
+        return in;
     }
+}
 
-    // Runs the selected mode (side bonus: removes code duplication)
-    private static void runMode(byte[] ba, NORI nri)
+// Determines validity of cmd-line args & prevents main() from being ugly
+private static void argCheck(String[] args)
+{
+    // check for existence of mode argument of the correct length
+    if(args.length!=0 && args[0].length()==1)
     {
-        switch(mode)
+        // assign the first argument's first character to mode
+        mode = args[0].charAt(0);
+        // This 'if' tree checks for valid mode arg and correct # of args
+        // for the given mode. Invokes helpful error messages on failure.
+        if(aAe.indexOf(mode)>=0)
         {
-        case 'e':
-            Extract opte = new Extract(ba,nri,false);
-            break;
-        case 'E':
-            Extract optE = new Extract(ba,nri,true);
-            break;
-        case 'A':
-            Analyze optA = new Analyze(ba,nri,true);
-            break;
-        default:
-            Analyze opta = new Analyze(ba,nri,false);
-            break;
+            if(argsLen < 2) argErrors(2);
         }
-    }
-
-    // Determines validity of cmd-line args & returns the resulting case number
-    // This exists as a function because it would make main() ugly if it didn't.
-    private static boolean argCheck(String[] args)
-    {
-        boolean argResult = false;
-        // check for existence of mode argument of the correct length
-        if(argsLen!=0 && args[0].length()==1)
+        else if(cC.indexOf(mode)>=0)
         {
-            // assign first argument to mode
-            mode = args[0].charAt(0);
-            // This 'if' tree checks for valid mode arg and correct # of args
-            // for the given mode. Invokes helpful error messages on failure.
-            if((mode =='a'||mode =='A') && argsLen >=2)
-                argResult = true;
-            else if((mode =='e'||mode =='E') && argsLen >=2)
-                argResult = true;
-            else if(mode =='c' && argsLen ==3)
-                argResult = cmArgCheck(args);
-            else if((mode =='a'||mode =='A') && argsLen < 2)
-                argErrors(2);
-            else if((mode =='e'||mode =='E') && argsLen < 2)
-                argErrors(2);
-            else if(mode =='c' && argsLen !=3)
-                argErrors(2);
-            else
-                argErrors(1);
+            if(argsLen !=3) argErrors(2);
+            cmCheck(args);
         }
         else
         {
-            argErrors(0);
+            argErrors(1);
         }
-        return argResult;
     }
-
-    // Verifies the existence of the Create mode parameters
-    private static boolean cmArgCheck(String[] args)
+    else
     {
-        boolean result = false;
-        cfg = new File(args[1]);
-        if(cfg.exists())
-        {
-            if(Files.isDirectory(Paths.get(args[2])))
-                result = true;
-            else
-                argErrors(5);
-        }
-        else
-        {
-            argErrors(4);
-        }
-        return result;
+        argErrors(0);
     }
+}
 
-    // Invalid command-line arguments responses
-    private static void argErrors(int argErrorNum)
-    {
-        int errNum = argErrorNum;
-        String errMsg0,errMsg1,errMsg2,errMsg3,errMsg4,errMsg5,errMsg6;
-        errMsg0 ="Error: Mode argument is too long or missing";
-        errMsg1 ="Error: Invalid Mode argument";
-        errMsg2 ="Error: Incorrect number of arguments for this Mode";
-        errMsg3 ="Error: NORI file does not exist. Nice try.";
-        errMsg4 ="Error: Config file does not exist. Tragic.";
-        errMsg5 ="Error: Specified BMP directory does not exist.";
-        switch(errNum)
-        {
-        case 0:
-            out.println(errMsg0);
-            break;
-        case 1:
-            out.println(errMsg1);
-            break;
-        case 2:
-            out.println(errMsg2);
-            break;
-        case 3:
-            out.println(errMsg3);
-            break;
-        case 4:
-            out.println(errMsg4);
-            break;
-        case 5:
-            out.println(errMsg5);
-            break;
-        default:
-            out.println("Unknown Error");
-            break;
-        }
-        usage();
-    }
+// Verifies the existence of the Create mode parameters
+private static void cmCheck(String[] args)
+{
+    create_mode = true;
+    cfg = new File(args[1]);
+    if(cfg.exists()==false) argErrors(4);
+    if(Files.isDirectory(Paths.get(args[2]))==false) argErrors(5);
+}
 
-    // Standard usage output, explaining available modes and required arguments
-    private static void usage()
+// Invalid command-line arguments responses
+private static void argErrors(int errorNum)
+{
+    String errMsg0,errMsg1,errMsg2,errMsg3,errMsg4,errMsg5,errMsg6;
+    errMsg0 ="Error: Mode argument is too long or missing";
+    errMsg1 ="Error: Invalid Mode argument";
+    errMsg2 ="Error: Incorrect number of arguments for this MODE";
+    errMsg3 ="Error: NORI file does not exist. Nice try.";
+    errMsg4 ="Error: Config file does not exist. Tragic.";
+    errMsg5 ="Error: Specified BMP directory does not exist";
+    switch(errorNum)
     {
-        String cr, use, col, bdr, opa, opA, ope, opE, opc, ex;
-        cr = "The NORI Tool (TNT)\n"+
-             "Copyright (C) 2014-2020 Libre Trickster Team\n"+
-             "License: GPLv3+\n\n";
-
-        use="Usage: java -jar TNT.jar {mode} {/path/file.nri} {etc}\n";
-        col="|Mode|        Arguments         | Description                 |\n";
-        bdr="===============================================================\n";
-        opa="| a  | [filename(s)]            | Analyze NORI files          |\n";
-        opA="| A  | [filename(s)]            | Analyze w/ config output    |\n";
-        ope="| e  | [filename(s)]            | Extract NORI files          |\n";
-        opE="| E  | [filename(s)]            | Extract w/ img subsets      |\n";
-        opc="| c  | [example.cfg] [/imgDir/] | Create NORI file            |\n";
+    case 0:
+        out.println(errMsg0);
+        break;
+    case 1:
+        out.println(errMsg1);
+        break;
+    case 2:
+        out.println(errMsg2);
+        break;
+    case 3:
+        out.println(errMsg3);
+        break;
+    case 4:
+        out.println(errMsg4);
+        break;
+    case 5:
+        out.println(errMsg5);
+        break;
+    default:
+        out.println("Unknown Error");
+        break;
+    }
+    usage();
+    System.exit(1);
+}
 
-        ex ="Example: java -jar TNT.jar a ../ex/path/ntf/all.nri\n";
+// Standard usage output, explaining available modes & required arguments
+private static void usage()
+{
+    String cr, use, col, bdr, opa, opA, ope, opc, opC, ex;
+    // You are not allowed to remove this copyright notice or its output
+    cr ="The NORI Tool (TNT) - https://github.com/TricksterOnline/TNT\n"+
+        "Copyright (C) 2014-2024 Libre Trickster Team\n"+
+        "License: GPLv3+\n\n";
+
+    use="Usage: java -jar TNT.jar {mode} {/path/file.nri} {etc}\n";
+    col="|Mode|        Arguments         | Description                     |\n";
+    bdr="===================================================================\n";
+    opa="| a  | [filename(s)]            | Analyze NORI files              |\n";
+    opA="| A  | [filename(s)]            | Analyze w/ config file output   |\n";
+    ope="| e  | [filename(s)]            | Extract BMPs from NORI files    |\n";
+    opc="| c  | [example.cfg] [/imgDir/] | Create NORI file                |\n";
+    opC="| C  | [example.cfg] [/imgDir/] | Create w/ zlib-compression      |\n";
+
+    ex ="Example: java -jar TNT.jar a ../ex/path/ntf/all.bac\n";
+
+    // Actual output function
+    out.println("\n"+cr+use+bdr+col+bdr+opa+opA+ope+opc+opC+bdr+ex);
+}
 
-        // Actual output function
-        out.println("\n"+cr+use+bdr+col+bdr+opa+opA+ope+opE+opc+bdr+ex);
+// An anti-duplication + better readability function
+private static byte[] file2BA(File file)
+{
+    byte[] ba = new byte[(int)file.length()];
+    try
+    {
+        ba = Files.readAllBytes(file.toPath());
     }
+    catch(Exception ex)
+    {
+        out.println("Error in (file2BA):");
+        ex.printStackTrace(System.out);
+    }
+    return ba;
+}
 }
-
diff --git a/src/NORI.java b/src/NORI.java
index 29a3d8d..0b66da5 100644
--- a/src/NORI.java
+++ b/src/NORI.java
@@ -1,7 +1,7 @@
 /*
 NORI.java: this file is part of the TNT program.
 
-Copyright (C) 2014-2020 Libre Trickster Team
+Copyright (C) 2014-2024 Libre Trickster Team
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 import static java.lang.System.out;
 /**
 Class Description:
-A structure-like class for storing a NORI file's data neatly.
+A structure-like class that represents and stores a NORI file's data neatly.
 
 Dev Notes:
 Since this class is essentially a structure, I'm going to attempt to keep it as
@@ -27,85 +27,261 @@
 */
 public class NORI
 {
-    // class variables
-    public static File nf;
-    public static String name,dname,dir,exdir,fs=File.separator;
-    // special NORI variables
-    public static int fsig = 0;
-    public static int noriVer = 0;
-    public static int nParam1 = 0;
-    public static int nParam2 = 0;
-    public static int nParam3 = 0;
-    public static int nParam4 = 0;
-    public static int nParam5 = 0;
-    public static int anims = 0;
-    public static int woGawi = 0;
-    public static int fsize = 0;
-    // special GAWI variables
-    public static int gsig = 0;
-    public static int gawiVer = 0;
-    public static int bpp = 0;
-    public static int compressed = 0;
-    public static int hasPalette = 0;
-    public static int gParam4 = 0;
-    public static int gParam5 = 0;
-    public static int gParam6 = 0;
-    public static int gParam7 = 0;
-    public static int numBMP = 0;
-    public static int gsize = 0;
-    // special palette variables
-    public static int psig = 0;
-    public static int palVer = 0;
-    public static int pParam1 = 0;
-    public static int pParam2 = 0;
-    public static int pParam3 = 0;
-    public static int pParam4 = 0;
-    public static int divided = 0;
-    public static int psize = 0;
-    public static byte[] pb;
-    public static byte[][] palette;
-    public static int mainS = 111;
-    public static int mainE = 254;
-    // special BMP data variables
-    public static int[] bmpOffsets;
-    public static int bpos = 0;
-    public static int[][] bmpSpecs;
-    public static byte[] bmpData;
-    // special animation variables
-    public static int[] animOffsets;
-    public static int apos = 0;
-    public static String[] animName;
-    public static int[] frames;
-    public static int[][] frameOffsets;
-    public static int[][][] frameData;
-    public static int[][][][] planeData;
-    public static int xtraFrameBytes = 0;
-    public static byte[] xfb;
+// class variables
+public String name,dname,dir,exdir;
+public static String fs=File.separator;
+public static String[] xml_tag;
 
-    // constructor for NORI class
-    public NORI() {}
+public static byte[] sfx = new byte[18];
+// Special variables for modifying NORI data
+public int totalBMP,totalFrames,totalPlanes;
+public int asize;//# of animOffsets+animations bytes
+public int fdbSizeSum;
+public int ebSize;//# of entryblocks bytes per frame
+public int totalCoordSetsBytes;
+public int mcSizeSum;
+// Special NORI variables
+public int fsig=1230131022;//file signature
+public int noriVer;
+public int nParam1;
+public int nParam2;
+public int nParam3;
+public int nParam4;
+public int nParam5;
+public int anims;
+public int woGawi;
+public int fsize;//file size
+// Special GAWI variables
+public int gsig=1230455111;//GAWI section signature
+public int gawiVer=300;
+public int bpp,Bpp;
+public int compressed;
+public int hasPalette;
+public int gParam4;
+public int gParam5;
+public int gParam6;
+public int gParam7;
+public int bmpStructs,nLen;
+public int gsize;//GAWI section size
+// Special PAL variables
+public int psig=1598832976;//palette section signature
+public int palVer=100;
+public int pParam1;
+public int pParam2;
+public int pParam3;
+public int pParam4;
+public int divided;
+public int psize;//palette section size
+public byte[] palBytes;
+public byte[][] palette;
+public int mainS = 111;
+public int mainE = 254;
+// Special BMP data variables
+public int[] bmpOffsets;
+public int bpos;
+public int[] bmpCount;
+public int[][] bmpSpecs;
+public byte[] bmpData;
+public int bmpDataSize;
+// Special animation variables
+public int[] animOffsets;
+public int apos;
+public byte[][] titleBytes;//[anims][32];
+public String[] title;
+public int[] numFrames;
+public int[][] frameOffsets;
+public int[][][] frameDataTop;
+public int[][][][] planeData;
+// Special FrameDataBottom variables
+public boolean notV300=false;
+public int[][] numCoordSets;
+public int[][][][] coordSets;
+public int cdBlockSize;
+public boolean hasEB=false;
+public byte[][][][] entryBlocks;
+public byte[][][][] unknownData1;
+public String[][] soundEffect;
+public byte[][][] unknownData2;
+public boolean maybeMCV=false;
+public int[][] hasMCValues;
+public int[][][] mcValues;//mcParam[0-6]
+public String[][] mcParam7;
+public byte[][][] mcParam8;
 
-    public static void setNFileVars(File nf, int src)
+// constructor for NORI class
+public NORI()
+{
+    setXmlTags();
+}
+public static void setXmlTags()
+{
+    xml_tag = new String[70];
+    xml_tag[0]  = "fsig";
+    xml_tag[1]  = "noriVer";
+    xml_tag[2]  = "nParam1";
+    xml_tag[3]  = "nParam2";
+    xml_tag[4]  = "nParam3";
+    xml_tag[5]  = "nParam4";
+    xml_tag[6]  = "nParam5";
+    xml_tag[7]  = "anims";
+    xml_tag[8]  = "woGawi";
+    xml_tag[9]  = "fsize";
+    xml_tag[10] = "gsig";
+    xml_tag[11] = "gawiVer";
+    xml_tag[12] = "bpp";
+    xml_tag[13] = "compressed";
+    xml_tag[14] = "hasPalette";
+    xml_tag[15] = "gParam4";
+    xml_tag[16] = "gParam5";
+    xml_tag[17] = "gParam6";
+    xml_tag[18] = "gParam7";
+    xml_tag[19] = "bmpStructs";
+    xml_tag[20] = "gsize";
+    xml_tag[21] = "psig";
+    xml_tag[22] = "palVer";
+    xml_tag[23] = "pParam1";
+    xml_tag[24] = "pParam2";
+    xml_tag[25] = "pParam3";
+    xml_tag[26] = "pParam4";
+    xml_tag[27] = "divided";
+    xml_tag[28] = "psize";
+    xml_tag[29] = "mainS";
+    xml_tag[30] = "mainE";
+    xml_tag[31] = "bmpOffset";
+    xml_tag[32] = "bmp_count";
+    xml_tag[33] = "bmp_size";
+    xml_tag[34] = "w";
+    xml_tag[35] = "h";
+    xml_tag[36] = "bParam4";
+    xml_tag[37] = "bmp_x";
+    xml_tag[38] = "bmp_y";
+    xml_tag[39] = "animOffset";
+    xml_tag[40] = "title";
+    xml_tag[41] = "numFrames";
+    xml_tag[42] = "frameOffset";
+    xml_tag[43] = "duration";
+    xml_tag[44] = "numPlanes";
+    xml_tag[45] = "bmp_id";
+    xml_tag[46] = "plane_x";
+    xml_tag[47] = "plane_y";
+    xml_tag[48] = "opacity";
+    xml_tag[49] = "flip";
+    xml_tag[50] = "blend_mode";
+    xml_tag[51] = "flag_param";
+    xml_tag[52] = "coordSets";
+    xml_tag[53] = "coord_x";
+    xml_tag[54] = "coord_y";
+    xml_tag[55] = "cdBlockSize";
+    xml_tag[56] = "entryBlock";
+    xml_tag[57] = "UnknownData1";
+    xml_tag[58] = "soundEffect";
+    xml_tag[59] = "UnknownData2";
+    xml_tag[60] = "hasMCValues";
+    xml_tag[61] = "mcParam0";
+    xml_tag[62] = "mcParam1";
+    xml_tag[63] = "mcParam2";
+    xml_tag[64] = "mcParam3";
+    xml_tag[65] = "mcParam4";
+    xml_tag[66] = "mcParam5";
+    xml_tag[67] = "mcParam6";
+    xml_tag[68] = "mcParam7";
+    xml_tag[69] = "mcParam8";
+}
+
+public void setNORI(File nFile)
+{
+    name = nFile.getName();// Plain file name
+    setDir(nFile);
+    setExDir();
+}
+public void setDir(File nFile)
+{
+    dir = nFile.getParent()+fs;// Directory the file is in
+    if(dir.equals("null"+fs)) dir=System.getProperty("user.dir")+fs;
+}
+public void setExDir()
+{
+    dname = name.replace('.','_');// Name without dots (useful)
+    exdir = dir+dname+fs;// Extraction directory (where bmp go)
+}
+
+// Sets noriVer-specific variables
+public void setVerSpecific()
+{
+    int nv = noriVer-300;
+    if(nv==0||nv==1||nv==2||nv==3)
     {
-        name = nf.getName();// Plain file name
-        // Using cfg file, which is the same name & dir, just 4 extra characters
-        if(src!=0)
+        notV300 = (nv!=0);
+        switch(nv)
         {
-            name = name.substring(0,name.length()-4);
-            if(!(name.endsWith(".bac")||name.endsWith(".nri")))
-            {
-                out.println("Error: Config file named incorrectly!");
-                System.exit(1);
-            }
+        case 0:
+        case 1:
+            cdBlockSize = 144;
+            break;
+        case 3:
+            maybeMCV = true;
+        case 2:
+            cdBlockSize = 96;
+            hasEB = true;
+            ebSize = 168;
+            break;
         }
-        dname = name.replace('.','_');// Name without dots (useful)
-        dir = nf.getParent()+fs;// Directory the file is in
-        exdir = dir+dname+fs;// Extraction directory (where bmp go)
     }
+    else
+    {
+        out.println("Unknown NORI Version!");
+        System.exit(1);
+    }
+}
+
+/*########################################################################*/
+/*######################## FIXER FUNCTIONS BELOW #########################*/
 
-    public static void checkDir()
+// Size and Flag fixes
+public void fixNORI(boolean create_mode)
+{
+    setVerSpecific();
+    if(create_mode) fixAnimSize();
+    if(bpp==8) fixPAL();
+    fixGawiHeader(create_mode);
+    fixNoriHeader(create_mode);
+}
+
+private void fixAnimSize()
+{
+    int sfxLen = 18;
+    int fdbStaticSizes = (cdBlockSize+ebSize+44+sfxLen+18);
+    fdbSizeSum = (totalFrames*fdbStaticSizes)+totalCoordSetsBytes;
+    if(maybeMCV) fdbSizeSum += (totalFrames*4)+mcSizeSum;
+    asize = (40*anims)+(12*totalFrames)+(28*totalPlanes)+fdbSizeSum;
+}
+private void fixPAL()
+{
+    if(psize==808) divided=1;
+}
+private void fixGawiHeader(boolean create_mode)
+{
+    if(create_mode)
     {
-        if(dir.equals("null"+fs)) dir=System.getProperty("user.dir")+fs;
-        exdir = dir+dname+fs;
+        gsize = 44;
+        compressed = 0;
+        hasPalette = 0;
+        if(bpp==8)
+        {
+            hasPalette = 1;
+            gsize += psize;
+        }
+
+        gsize += (8*bmpStructs)+(24*totalBMP)+bmpDataSize;
+    }
+    else if(gsize==0)
+    {
+        gsize = fsize-40-asize;
     }
 }
+private void fixNoriHeader(boolean create_mode)
+{
+    woGawi = 40+asize;
+    if(create_mode) fsize = gsize+woGawi;
+}
+}