diff --git a/browser/index.html b/browser/index.html
new file mode 100644
index 0000000..eb03468
--- /dev/null
+++ b/browser/index.html
@@ -0,0 +1,95 @@
+
+
+
+ Tyria 2D
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/browser/index.js b/browser/index.js
new file mode 100644
index 0000000..111aefd
--- /dev/null
+++ b/browser/index.js
@@ -0,0 +1,888 @@
+(function () {
+ 'use strict';
+
+ window.onload = () => {
+ /// T3D
+ let _lr;
+ let _context;
+ let _fileId;
+ let _fileList;
+ let _audioSource;
+ let _audioContext;
+
+ /// THREE
+ let _scene;
+ let _camera;
+ let _renderer;
+ let _models = [];
+ let _controls;
+
+ $(function () {
+ /*
+ SET MAIN UP GRID
+ */
+ const pstyle = "border: 1px solid #dfdfdf; padding: 0;";
+ $("#layout").w2layout({
+ name: "layout",
+ panels: [
+ {
+ type: "left",
+ size: 570,
+ resizable: true,
+ style: pstyle + "margin:0",
+ },
+ {
+ type: "main",
+ style: pstyle + " background-color: transparent;",
+ toolbar: {
+ style: "background-color:#eaeaea; height:40px",
+ items: [
+ {
+ type: "html",
+ id: "fileIdToolbar",
+ html:
+ '' +
+ " File ID:" +
+ ' ' +
+ ' " +
+ "
",
+ },
+ {
+ type: "html",
+ id: "contextToolbar",
+ html: '',
+ },
+ ],
+ onClick: function (event) {
+ this.owner.content("main", event);
+ },
+ },
+ },
+ ],
+ onResize: onCanvasResize,
+ });
+
+ $("#fileIdInputBtn").click(function () {
+ viewFileByFileId($("#fileIdInput").val());
+ });
+
+ /// Grid inside main left
+ $().w2layout({
+ name: "leftLayout",
+ panels: [
+ {
+ type: "left",
+ size: 150,
+ resizable: true,
+ style: pstyle,
+ content: "left",
+ },
+ {
+ type: "main",
+ size: 420,
+ resizable: true,
+ style: pstyle,
+ content: "right",
+ },
+ ],
+ });
+ w2ui["layout"].content("left", w2ui["leftLayout"]);
+
+ /*
+ SIDEBAR
+ */
+ w2ui["leftLayout"].content(
+ "left",
+ $().w2sidebar({
+ name: "sidebar",
+ img: null,
+ nodes: [{ id: "All", text: "All", img: "icon-folder", group: false }],
+ onClick: onFilterClick,
+ })
+ );
+
+ /*
+ SET UP FILE BROWSER
+ */
+ w2ui["leftLayout"].content(
+ "main",
+ $().w2grid({
+ name: "grid",
+ show: {
+ toolbar: true,
+ footer: true,
+ },
+ columns: [
+ {
+ field: "recid",
+ caption: "MFT index",
+ size: "80px",
+ sortable: true,
+ resizable: true,
+ searchable: "int",
+ },
+ {
+ field: "baseIds",
+ caption: "BaseId list",
+ size: "100%",
+ sortable: true,
+ resizable: true,
+ searchable: true,
+ },
+ {
+ field: "type",
+ caption: "Type",
+ size: "100px",
+ resizable: true,
+ sortable: true,
+ },
+ {
+ field: "fileSize",
+ caption: "Pack Size",
+ size: "85px",
+ resizable: true,
+ sortable: true,
+ },
+ ],
+ onClick: function (event) {
+ viewFileByMFT(event.recid);
+ },
+ })
+ );
+
+ /*
+ SET UP FILE VIEW 'WINDOW'
+ */
+ $(w2ui["layout"].el("main"))
+ .append($(""))
+ .append($(""))
+ .append($(""))
+ .append(
+ $("").hide()
+ )
+ .append(
+ $(
+ ""
+ ).hide()
+ )
+ .append($("").hide())
+ .append($("").hide())
+ .append(
+ $("").hide()
+ );
+
+ $("#fileTabs").w2tabs({
+ name: "fileTabs",
+ active: "tabRaw",
+ tabs: [
+ {
+ id: "tabRaw",
+ caption: "Raw",
+ disabled: true,
+ onClick: function () {
+ $(".fileTab").hide();
+ $("#fileTabsRaw").show();
+ },
+ },
+ {
+ id: "tabPF",
+ caption: "Pack File",
+ disabled: true,
+ onClick: function () {
+ $(".fileTab").hide();
+ $("#fileTabsPack").show();
+ },
+ },
+ {
+ id: "tabTexture",
+ caption: "Texture",
+ disabled: true,
+ onClick: function () {
+ $(".fileTab").hide();
+ $("#fileTabsTexture").show();
+ },
+ },
+ {
+ id: "tabString",
+ caption: "String",
+ disabled: true,
+ onClick: function () {
+ $(".fileTab").hide();
+ $("#fileTabsString").show();
+ },
+ },
+ {
+ id: "tabModel",
+ caption: "Model",
+ disabled: true,
+ onClick: function () {
+ $(".fileTab").hide();
+ $("#fileTabsModel").show();
+ },
+ },
+ {
+ id: "tabSound",
+ caption: "Sound",
+ disabled: true,
+ onClick: function () {
+ $(".fileTab").hide();
+ $("#fileTabsSound").show();
+ },
+ },
+ ],
+ });
+
+ /// Set up grid for strings view
+ ///Create grid
+ $("#stringOutput").w2grid({
+ name: "stringGrid",
+ selectType: "cell",
+ show: {
+ toolbar: true,
+ footer: true,
+ },
+ columns: [
+ { field: "recid", caption: "Row #", size: "60px" },
+ { field: "value", caption: "Text", size: "100%" },
+ ],
+ });
+
+ /*
+ SET UP TREE 3D SCENE
+ */
+ setupScene();
+
+ /// Ask for file
+ w2popup.open({
+ speed: 0,
+ title: "Load A GW2 dat",
+ modal: true,
+ showClose: false,
+ body:
+ '",
+ });
+
+ $("#filePickerPop").change(function (evt) {
+ _lr = T3D.getLocalReader(evt.target.files[0], onReaderCreated, "./static/t3dworker.js");
+ });
+
+ /// Overwrite progress logger
+ T3D.Logger.logFunctions[T3D.Logger.TYPE_PROGRESS] = function () {
+ $("#filePickerPop").prop("disabled", true);
+ $("#fileLoadProgress").html("Indexing .dat file (first visit only)
" + arguments[1] + "%
");
+ };
+ });
+
+ function onReaderCreated() {
+ T3D.getFileListAsync(
+ _lr,
+ function (files) {
+ /// Store fileList globally
+ _fileList = files;
+
+ const packNode = {
+ id: "packGroup",
+ text: "Pack Files",
+ img: "icon-folder",
+ group: false,
+ nodes: [],
+ };
+
+ const textureNode = {
+ id: "textureGroup",
+ text: "Texture files",
+ img: "icon-folder",
+ group: false,
+ nodes: [],
+ };
+
+ const unsortedNode = {
+ id: "unsortedGroup",
+ text: "Unsorted",
+ img: "icon-folder",
+ group: false,
+ nodes: [],
+ };
+
+ /// Build sidebar nodes
+ for (const fileType in _fileList) {
+ if (Object.getOwnPropertyNames(_fileList).includes(fileType)) {
+ let node = { id: fileType, img: "icon-folder", group: false };
+ //let isPack = false;
+ if (fileType.startsWith("TEXTURE")) {
+ node = {
+ id: fileType,
+ img: "icon-folder",
+ group: false,
+ text: fileType,
+ };
+ textureNode.nodes.push(node);
+ } else if (fileType === "BINARIES") {
+ node.text = "Binaries";
+ w2ui.sidebar.add(node);
+ } else if (fileType === "STRINGS") {
+ node.text = "Strings";
+ w2ui.sidebar.add(node);
+ } else if (fileType.startsWith("PF")) {
+ node = {
+ id: fileType,
+ img: "icon-folder",
+ group: false,
+ text: fileType,
+ };
+ packNode.nodes.push(node);
+ } else if (fileType === "UNKNOWN") {
+ node.text = "Unknown";
+ w2ui.sidebar.add(node);
+ } else {
+ node = {
+ id: fileType,
+ img: "icon-folder",
+ group: false,
+ text: fileType,
+ };
+ unsortedNode.nodes.push(node);
+ }
+ }
+ }
+
+ if (packNode.nodes.length > 0) {
+ w2ui.sidebar.add(packNode);
+ }
+
+ if (textureNode.nodes.length > 0) {
+ w2ui.sidebar.add(textureNode);
+ }
+
+ if (unsortedNode.nodes.length > 0) {
+ w2ui.sidebar.add(unsortedNode);
+ }
+
+ /// Close the pop
+ w2popup.close();
+
+ /// Select the "All" category
+ w2ui.sidebar.click("All");
+ } /// End getFileListAsync callback
+ );
+ }
+
+ function onFilterClick(evt) {
+ /// No filter if clicked group was "All"
+ if (evt.target === "All") {
+ showFileGroup();
+ }
+
+ /// Other events are fine to just pass
+ else {
+ showFileGroup([evt.target]);
+ }
+ }
+
+ function showFileGroup(fileTypeFilter) {
+ w2ui.grid.records = [];
+
+ const reverseTable = _lr.getReverseIndex();
+
+ for (const fileType in _fileList) {
+ /// Only show types we've asked for
+ if (fileTypeFilter && fileTypeFilter.indexOf(fileType) < 0) {
+ /// Special case for "packGroup"
+ /// Should let trough all pack types
+ /// Should NOT let trought any non-pack types
+ /// i.e. Strings, Binaries etc
+ if (fileTypeFilter.indexOf("packGroup") >= 0) {
+ if (!fileType.startsWith("PF")) {
+ continue;
+ }
+ } else if (fileTypeFilter.indexOf("textureGroup") >= 0) {
+ if (!fileType.startsWith("TEXTURE")) {
+ continue;
+ }
+ } else {
+ continue;
+ }
+ }
+
+ if (Object.keys(_fileList).includes(fileType)) {
+ const fileArr = _fileList[fileType];
+ fileArr.forEach(
+ function (mftIndex) {
+ const meta = _lr.getFileMeta(mftIndex);
+
+ const baseIds = reverseTable[mftIndex];
+ const fileSize = meta ? meta.size : "";
+
+ if (fileSize > 0 && mftIndex > 15) {
+ w2ui["grid"].records.push({
+ recid: mftIndex, /// MFT index
+ baseIds: baseIds,
+ type: fileType,
+ fileSize: fileSize,
+ });
+ }
+
+ mftIndex++;
+ } /// End for each mft in this file type
+ );
+ } /// End if _fileList[filetype]
+ } /// End for each fileType key in _fileList object
+
+ /// Update file grid
+ w2ui.grid.refresh();
+ }
+
+ function viewFileByMFT(mftIdx) {
+ const reverseTable = _lr.getReverseIndex();
+
+ const baseId = reverseTable[mftIdx] ? reverseTable[mftIdx][0] : "";
+
+ viewFileByFileId(baseId);
+ }
+
+ function viewFileByFileId(fileId) {
+ /// Clean outputs
+ $(".tabOutput").html("");
+ $("#fileTitle").html("");
+
+ /// Clean context toolbar
+ $("#contextToolbar").html("");
+
+ /// Disable tabs
+ w2ui.fileTabs.disable("tabRaw");
+ w2ui.fileTabs.disable("tabPF");
+ w2ui.fileTabs.disable("tabTexture");
+ w2ui.fileTabs.disable("tabString");
+ w2ui.fileTabs.disable("tabModel");
+ w2ui.fileTabs.disable("tabSound");
+
+ /// Remove old models from the scene
+ if (_models) {
+ _models.forEach(function (mdl) {
+ _scene.remove(mdl);
+ });
+ }
+
+ /// Make sure _context is clean
+ _context = {};
+
+ /// Run the basic DataRenderer, handles all sorts of files for us.
+ T3D.runRenderer(T3D.DataRenderer, _lr, { id: fileId }, _context, onBasicRendererDone);
+ }
+
+ function onBasicRendererDone() {
+ /// Read render output from _context VO
+ const fileId = (_fileId = T3D.getContextValue(_context, T3D.DataRenderer, "fileId"));
+
+ const rawData = T3D.getContextValue(_context, T3D.DataRenderer, "rawData");
+
+ const raw = T3D.getContextValue(_context, T3D.DataRenderer, "rawString");
+
+ const packfile = T3D.getContextValue(_context, T3D.DataRenderer, "file");
+
+ const image = T3D.getContextValue(_context, T3D.DataRenderer, "image");
+
+ const fcc = raw.substring(0, 4);
+
+ /// Update main header to show filename
+
+ const fileName = fileId + (image || !packfile ? "." + fcc : "." + packfile.header.type);
+ $("#fileTitle").html(fileName);
+
+ /// Update raw view and enable tab
+ w2ui.fileTabs.enable("tabRaw");
+
+ $("#contextToolbar").append(
+ $("").click(function () {
+ const blob = new Blob([rawData], { type: "octet/stream" });
+ saveData(blob, fileName + ".raw");
+ })
+ );
+
+ $("#rawOutput").append($("").text(raw));
+
+ const ui8aRawData = new Uint8Array(rawData);
+
+ /// Texture file
+ if (image) {
+ /// Select texture tab
+ w2ui.fileTabs.enable("tabTexture");
+ w2ui.fileTabs.click("tabTexture");
+
+ /// Display bitmap on canvas
+ const canvas = $("