mirror of
https://github.com/lax1dude/crashreport-viewer.git
synced 2024-12-21 21:44:11 -08:00
made the thing
This commit is contained in:
commit
e138dd039c
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
27
LICENSE
Normal file
27
LICENSE
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2022, lax1dude
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of {{ project }} nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
5
README.md
Normal file
5
README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
## Crash Report Viewer
|
||||||
|
|
||||||
|
web application, reads a "crash report" and deobfuscates javascript stack traces within it using a source map
|
||||||
|
|
||||||
|
WIP, to be used for eaglercraft
|
1
app/22w40a.json
Normal file
1
app/22w40a.json
Normal file
File diff suppressed because one or more lines are too long
1
app/22w41a.json
Normal file
1
app/22w41a.json
Normal file
File diff suppressed because one or more lines are too long
1
app/22w41b.json
Normal file
1
app/22w41b.json
Normal file
File diff suppressed because one or more lines are too long
133
app/crashviewer.css
Normal file
133
app/crashviewer.css
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro&family=Ubuntu:wght@400;700&display=swap');
|
||||||
|
|
||||||
|
p, h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-block-start: 0px;
|
||||||
|
margin-block-end: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 30px;
|
||||||
|
font-family: 'Ubuntu', sans-serif;
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: calc(100vw - 60px);
|
||||||
|
height: calc(100vh - 60px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#titleRow {
|
||||||
|
flex-grow: 0;
|
||||||
|
padding-left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.containerHead {
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 10px solid #666666;
|
||||||
|
padding: 10px;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.containerBody {
|
||||||
|
border-left: 10px solid #666666;
|
||||||
|
border-right: 10px solid #666666;
|
||||||
|
border-bottom: 10px solid #666666;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sourceMaps {
|
||||||
|
font-family: 'Ubuntu', sans-serif;
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleShow {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-left: 20px;
|
||||||
|
padding: 3px 0px 4px 0px;
|
||||||
|
color: #BBBBBB;
|
||||||
|
background-color: black;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleShow:hover {
|
||||||
|
color: #FFFFAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleShowDisabled, .toggleShowDisabled:hover {
|
||||||
|
color: #888888 !important;
|
||||||
|
padding: 3px 0px 0px 0px !important;
|
||||||
|
border-bottom: none !important;
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggleSelected, .toggleSelected:hover {
|
||||||
|
padding: 3px 0px 2px 0px;
|
||||||
|
color:white;
|
||||||
|
border-bottom: 2px dashed white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inputTextArea {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 10px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
font-family: 'Source Code Pro', monospace;
|
||||||
|
font-size: 18px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
#outputTextArea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#outputContent {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: 'Source Code Pro', monospace;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 10px;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fetchingMessage {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-left: 20px;
|
||||||
|
padding: 4px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.darkModeScrollbar::-webkit-scrollbar, .darkModeScrollbar::-webkit-scrollbar-track {
|
||||||
|
background-color: black;
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.darkModeScrollbar::-webkit-scrollbar-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.darkModeScrollbar::-webkit-scrollbar-thumb, .darkModeScrollbar::-webkit-scrollbar-corner {
|
||||||
|
background-color: #BBBBBB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.darkModeScrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: white;
|
||||||
|
}
|
457
app/crashviewer.js
Normal file
457
app/crashviewer.js
Normal file
|
@ -0,0 +1,457 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const sourceMap = window.sourceMap;
|
||||||
|
|
||||||
|
sourceMap.SourceMapConsumer.initialize({ "lib/mappings.wasm": "mappings.wasm" });
|
||||||
|
|
||||||
|
var appElements = null;
|
||||||
|
|
||||||
|
const sourceMapURLs = new Map();
|
||||||
|
const sourceMapLoaded = new Map();
|
||||||
|
|
||||||
|
var isShowingOriginal = true;
|
||||||
|
var hasInput = false;
|
||||||
|
|
||||||
|
var dotsInterval = -1;
|
||||||
|
var dotsCounter = 1;
|
||||||
|
|
||||||
|
function hideDots() {
|
||||||
|
if(dotsInterval !== -1) {
|
||||||
|
clearInterval(dotsInterval);
|
||||||
|
dotsInterval = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sourceMapListLoaded(jsonFile) {
|
||||||
|
var selector = appElements.sourceMaps;
|
||||||
|
selector.disabled = false;
|
||||||
|
while(selector.firstChild) {
|
||||||
|
selector.removeChild(selector.firstChild);
|
||||||
|
}
|
||||||
|
const mapsList = jsonFile.sourceMaps;
|
||||||
|
if(mapsList.length > 0) {
|
||||||
|
var customOption = document.createElement("option");
|
||||||
|
customOption.value = "_custom";
|
||||||
|
customOption.appendChild(document.createTextNode("upload..."));
|
||||||
|
selector.appendChild(customOption);
|
||||||
|
for(var i = 0; i < mapsList.length; ++i) {
|
||||||
|
var name = mapsList[i].name;
|
||||||
|
sourceMapURLs.set(name, mapsList[i].url);
|
||||||
|
var newOption = document.createElement("option");
|
||||||
|
newOption.value = name;
|
||||||
|
newOption.appendChild(document.createTextNode(name));
|
||||||
|
selector.appendChild(newOption);
|
||||||
|
}
|
||||||
|
selector.value = mapsList[0].name;
|
||||||
|
}
|
||||||
|
hideDots();
|
||||||
|
appElements.fetchingMessage.style.display = "none";
|
||||||
|
appElements.inputTextArea.style.display = "block";
|
||||||
|
appElements.outputTextArea.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function sourceMapListError(err) {
|
||||||
|
hideDots();
|
||||||
|
appElements.fetchingMessage.style.color = "#FF7777";
|
||||||
|
appElements.fetchingMessage.innerText = "Failed to load source map list!";
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDots() {
|
||||||
|
appElements.loadingDots.innerText = ".".repeat(dotsCounter);
|
||||||
|
dotsCounter = (dotsCounter + 1) % 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractVersionFromFile(lines) {
|
||||||
|
for(var i = 0; i < lines.length; ++i) {
|
||||||
|
var s = lines[i].trim();
|
||||||
|
if(s.startsWith("eaglercraft.version") || s.startsWith("version")) {
|
||||||
|
var v = s.split("=");
|
||||||
|
if(v.length === 2) {
|
||||||
|
var eq = v[1].trim();
|
||||||
|
if(eq.startsWith("\"")) {
|
||||||
|
eq = eq.substring(1);
|
||||||
|
}
|
||||||
|
if(eq.endsWith("\"")) {
|
||||||
|
eq = eq.substring(0, eq.length - 1);
|
||||||
|
}
|
||||||
|
return eq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateTimeout = -1;
|
||||||
|
var lastValue = "";
|
||||||
|
var hadPaste = false;
|
||||||
|
|
||||||
|
function textAreaInputHandler() {
|
||||||
|
if(updateTimeout !== -1) {
|
||||||
|
clearTimeout(updateTimeout);
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
const oldValue = lastValue;
|
||||||
|
var newLength = (lastValue = appElements.inputTextArea.value).trim().length;
|
||||||
|
if(oldValue.trim().length == 0) {
|
||||||
|
if(newLength > 0) {
|
||||||
|
if(hadPaste) {
|
||||||
|
hadPaste = false;
|
||||||
|
var vers = extractVersionFromFile(lastValue.split(/\r?\n/g));
|
||||||
|
if(vers !== null) {
|
||||||
|
for(var k of sourceMapURLs.keys()) {
|
||||||
|
if(vers === k) {
|
||||||
|
appElements.sourceMaps.value = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setButton(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(newLength > 0) {
|
||||||
|
if(!hasInput) {
|
||||||
|
appElements.showOriginal.style.display = "inline";
|
||||||
|
appElements.showDecoded.style.display = "inline";
|
||||||
|
hasInput = true;
|
||||||
|
}
|
||||||
|
appElements.showDecoded.classList.remove("toggleShowDisabled");
|
||||||
|
}else {
|
||||||
|
appElements.showDecoded.classList.add("toggleShowDisabled");
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
function textAreaPasteHandler() {
|
||||||
|
textAreaInputHandler();
|
||||||
|
hadPaste = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearDecodedView() {
|
||||||
|
var cnt = appElements.outputContent;
|
||||||
|
while(cnt.firstChild) {
|
||||||
|
cnt.removeChild(cnt.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showInfo(str, err) {
|
||||||
|
clearDecodedView();
|
||||||
|
var span = document.createElement("span");
|
||||||
|
span.appendChild(document.createTextNode(str));
|
||||||
|
span.style.fontSize = "20px";
|
||||||
|
if(err) {
|
||||||
|
span.style.color = "#FF9999";
|
||||||
|
}
|
||||||
|
appElements.outputContent.appendChild(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightLine(line) {
|
||||||
|
var e = document.createElement("span");
|
||||||
|
e.style.color = "#FFFF77";
|
||||||
|
e.appendChild(document.createTextNode(line));
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLine(srcMapLine) {
|
||||||
|
if(srcMapLine.line !== null) {
|
||||||
|
if(srcMapLine.name === null) {
|
||||||
|
return (srcMapLine.source === null ? "<unknown>" : srcMapLine.source) + ":" +
|
||||||
|
srcMapLine.line + (srcMapLine.column > 0 ? ":" + srcMapLine.column : "");
|
||||||
|
}else {
|
||||||
|
return "" + srcMapLine.name + " (" + (srcMapLine.source === null ? "<unknown>" :
|
||||||
|
srcMapLine.source) + ":" + srcMapLine.line + ":" + (srcMapLine.column > 0 ? ":" +
|
||||||
|
srcMapLine.column : "") + ")";
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIndent(line) {
|
||||||
|
var ret = "";
|
||||||
|
for(var i = 0; i < line.length; ++i) {
|
||||||
|
var c = line.charAt(i);
|
||||||
|
if(c === " " || c === "\t") {
|
||||||
|
ret += c;
|
||||||
|
}else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeBold(str) {
|
||||||
|
return "<span style=\"font-weight:bold;font-size:19px;\">" + str.replace("<", "<").replace(">", ">") + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function printVersionWarning(vers) {
|
||||||
|
const v = appElements.sourceMaps.value;
|
||||||
|
if(sourceMapURLs.get(v) === "LOCAL") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var str = null;
|
||||||
|
if(!vers) {
|
||||||
|
str = "WARNING: no game version (eaglercraft.version) is in this crash report!"
|
||||||
|
}else {
|
||||||
|
if(v !== vers) {
|
||||||
|
str = "WARNING: this crash report seems to be from version '" + makeBold(vers) + "', but you have source map version '" + makeBold(v) + "' selected!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(str !== null) {
|
||||||
|
var e = document.createElement("span");
|
||||||
|
e.style.color = "#FF7777";
|
||||||
|
e.style.fontSize = "16px";
|
||||||
|
e.innerHTML = str + "\n\nIf you don't select the correct source map version then the crash report will be incorrect\n\n";
|
||||||
|
appElements.outputContent.appendChild(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSource(srcMap) {
|
||||||
|
clearDecodedView();
|
||||||
|
var sourceValue = appElements.inputTextArea.value;
|
||||||
|
var lines = sourceValue.split(/\r?\n/g);
|
||||||
|
var vers = extractVersionFromFile(lines);
|
||||||
|
var hasShownWarning = false;
|
||||||
|
for(var i = 0; i < lines.length; ++i) {
|
||||||
|
var l = lines[i];
|
||||||
|
var split = l.split(":");
|
||||||
|
|
||||||
|
if(split.length > 1) {
|
||||||
|
var firstToken = split[0].toLowerCase();
|
||||||
|
if(firstToken.endsWith("error")) {
|
||||||
|
if(!hasShownWarning) {
|
||||||
|
hasShownWarning = true;
|
||||||
|
printVersionWarning(vers);
|
||||||
|
}
|
||||||
|
appElements.outputContent.appendChild(highlightLine(l + "\n"));
|
||||||
|
continue;
|
||||||
|
}else if(split.length > 2) {
|
||||||
|
var lineTrim = split[split.length - 2].trim();
|
||||||
|
var lineNo = parseInt(lineTrim);
|
||||||
|
var colTrim = split[split.length - 1].trim();
|
||||||
|
var colNo = parseInt(colTrim);
|
||||||
|
if(isNaN(colNo)) {
|
||||||
|
if(colTrim.length > 1) {
|
||||||
|
colNo = parseInt(colTrim.substring(0, colTrim.length - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!isNaN(lineNo) && !isNaN(colNo)) {
|
||||||
|
var original = formatLine(srcMap.originalPositionFor({ line: lineNo, column: colNo }));
|
||||||
|
if(original !== null) {
|
||||||
|
if(firstToken.endsWith("line")) {
|
||||||
|
appElements.outputContent.appendChild(document.createTextNode(lines[i] + " "));
|
||||||
|
appElements.outputContent.appendChild(highlightLine(original + "\n"));
|
||||||
|
}else {
|
||||||
|
if(!hasShownWarning) {
|
||||||
|
hasShownWarning = true;
|
||||||
|
printVersionWarning(vers);
|
||||||
|
}
|
||||||
|
var idt = getIndent(split[0]);
|
||||||
|
var realStart = split[0].substring(idt.length);
|
||||||
|
if(realStart.startsWith("at")) {
|
||||||
|
appElements.outputContent.appendChild(highlightLine(idt + "at " + original + "\n"));
|
||||||
|
}else {
|
||||||
|
appElements.outputContent.appendChild(highlightLine(idt + original + "\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appElements.outputContent.appendChild(document.createTextNode(lines[i] + "\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryUpdateSource(srcMap) {
|
||||||
|
sourceMap.SourceMapConsumer.with(srcMap, null, (srcMapObject) => {
|
||||||
|
try {
|
||||||
|
updateSource(srcMapObject);
|
||||||
|
}catch(e) {
|
||||||
|
isUpdating = false;
|
||||||
|
showInfo("Parse error, check devtools", true);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}).catch((e) => {
|
||||||
|
isUpdating = false;
|
||||||
|
showInfo("Source map load error, check devtools", true);
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var isUpdating = false;
|
||||||
|
|
||||||
|
function updateDecodedPane() {
|
||||||
|
if(!isUpdating) {
|
||||||
|
const v = appElements.sourceMaps.value;
|
||||||
|
if(v !== "_custom") {
|
||||||
|
var mapJSON = sourceMapLoaded.get(v);
|
||||||
|
|
||||||
|
if(mapJSON) {
|
||||||
|
try {
|
||||||
|
showInfo("Processing source...");
|
||||||
|
tryUpdateSource(mapJSON);
|
||||||
|
return;
|
||||||
|
}catch(e) {
|
||||||
|
isUpdating = false;
|
||||||
|
console.error(e);
|
||||||
|
showInfo("Internal error, check devtools", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapURL = sourceMapURLs.get(v);
|
||||||
|
|
||||||
|
if(!mapURL) {
|
||||||
|
showInfo("Unknown source map: " + v, true);
|
||||||
|
isUpdating = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo("Loading source map " + v + "...");
|
||||||
|
|
||||||
|
fetch(mapURL)
|
||||||
|
.then((r) => r.json()).then((j) => {
|
||||||
|
sourceMapLoaded.set(v, j);
|
||||||
|
try {
|
||||||
|
showInfo("Processing source...");
|
||||||
|
tryUpdateSource(j);
|
||||||
|
}catch(e) {
|
||||||
|
isUpdating = false;
|
||||||
|
showInfo("Internal error, check devtools", true);
|
||||||
|
}
|
||||||
|
}).catch((e) => {
|
||||||
|
isUpdating = false;
|
||||||
|
showInfo("Could not load source map: " + v, true);
|
||||||
|
});
|
||||||
|
}else {
|
||||||
|
clearDecodedView();
|
||||||
|
showFileChooser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setButton(decodedOrOriginal) {
|
||||||
|
if(decodedOrOriginal) {
|
||||||
|
if(isShowingOriginal) {
|
||||||
|
isShowingOriginal = false;
|
||||||
|
appElements.showDecoded.classList.add("toggleSelected");
|
||||||
|
appElements.showOriginal.classList.remove("toggleSelected");
|
||||||
|
appElements.inputTextArea.style.display = "none";
|
||||||
|
appElements.outputTextArea.style.display = "block";
|
||||||
|
updateDecodedPane();
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
if(!isShowingOriginal) {
|
||||||
|
isShowingOriginal = true;
|
||||||
|
appElements.showDecoded.classList.remove("toggleSelected");
|
||||||
|
appElements.showOriginal.classList.add("toggleSelected");
|
||||||
|
appElements.inputTextArea.style.display = "block";
|
||||||
|
appElements.outputTextArea.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function comboBoxChangeHandler() {
|
||||||
|
if(!isShowingOriginal) {
|
||||||
|
updateDecodedPane();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFileChooser() {
|
||||||
|
const fileChooserElement = document.createElement("input");
|
||||||
|
|
||||||
|
fileChooserElement.type = "file";
|
||||||
|
fileChooserElement.accept = ".json,.map";
|
||||||
|
|
||||||
|
fileChooserElement.addEventListener("change", () => {
|
||||||
|
const files = fileChooserElement.files;
|
||||||
|
if(files.length > 0) {
|
||||||
|
var phile = files[0];
|
||||||
|
var name = phile.name.trim();
|
||||||
|
if(name.endsWith(".json")) {
|
||||||
|
name = name.substring(0, name.length - 5).trim();
|
||||||
|
}
|
||||||
|
if(name.length > 0) {
|
||||||
|
if(name === "_custom") {
|
||||||
|
name = "__custom";
|
||||||
|
}
|
||||||
|
var name2 = name;
|
||||||
|
var i = 0;
|
||||||
|
while(sourceMapURLs.has(name2)) {
|
||||||
|
name2 = name + " (" + (++i) + ")";
|
||||||
|
}
|
||||||
|
const namec = name2;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener("load", () => {
|
||||||
|
try {
|
||||||
|
var jsonObj = JSON.parse(reader.result);
|
||||||
|
if(jsonObj) {
|
||||||
|
sourceMapURLs.set(namec, "LOCAL");
|
||||||
|
sourceMapLoaded.set(namec, jsonObj);
|
||||||
|
var newOption = document.createElement("option");
|
||||||
|
newOption.value = namec;
|
||||||
|
newOption.appendChild(document.createTextNode(namec));
|
||||||
|
const selector = appElements.sourceMaps;
|
||||||
|
if(selector.childNodes.length > 1) {
|
||||||
|
selector.insertBefore(newOption, selector.childNodes[1]);
|
||||||
|
}else {
|
||||||
|
selector.appendChild(newOption);
|
||||||
|
}
|
||||||
|
selector.value = namec;
|
||||||
|
if(!isShowingOriginal) {
|
||||||
|
updateDecodedPane();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
alert("Not a valid JSON source map!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reader.readAsText(phile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fileChooserElement.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
|
||||||
|
appElements = {
|
||||||
|
showOriginal: document.getElementById("showOriginal"),
|
||||||
|
showDecoded: document.getElementById("showDecoded"),
|
||||||
|
sourceMaps: document.getElementById("sourceMaps"),
|
||||||
|
inputTextArea: document.getElementById("inputTextArea"),
|
||||||
|
outputTextArea: document.getElementById("outputTextArea"),
|
||||||
|
outputContent: document.getElementById("outputContent"),
|
||||||
|
fetchingMessage: document.getElementById("fetchingMessage"),
|
||||||
|
loadingDots: document.getElementById("loadingDots")
|
||||||
|
};
|
||||||
|
|
||||||
|
dotsInterval = setInterval(updateDots, 300);
|
||||||
|
|
||||||
|
appElements.showOriginal.addEventListener("click", () => setButton(false));
|
||||||
|
|
||||||
|
appElements.showDecoded.addEventListener("click", () => {
|
||||||
|
if(appElements.inputTextArea.value.trim().length > 0) {
|
||||||
|
setButton(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
appElements.inputTextArea.addEventListener("propertychange", textAreaInputHandler);
|
||||||
|
appElements.inputTextArea.addEventListener("change", textAreaInputHandler);
|
||||||
|
appElements.inputTextArea.addEventListener("click", textAreaInputHandler);
|
||||||
|
appElements.inputTextArea.addEventListener("keyup", textAreaInputHandler);
|
||||||
|
appElements.inputTextArea.addEventListener("input", textAreaInputHandler);
|
||||||
|
appElements.inputTextArea.addEventListener("paste", textAreaPasteHandler);
|
||||||
|
|
||||||
|
appElements.sourceMaps.addEventListener("change", comboBoxChangeHandler);
|
||||||
|
|
||||||
|
fetch("sourceMaps.json")
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then(sourceMapListLoaded)
|
||||||
|
.catch(sourceMapListError);
|
||||||
|
|
||||||
|
});
|
BIN
app/favicon.ico
Normal file
BIN
app/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
40
app/index.html
Normal file
40
app/index.html
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content='reads a "crash report" and deobfuscates javascript stack traces within it using a source map' />
|
||||||
|
<meta name="keywords" content="calder, young, lax1dude, eagler" />
|
||||||
|
<meta name="author" content="lax1dude" />
|
||||||
|
<meta property="og:title" content="Crash Report Viewer" />
|
||||||
|
<meta property="og:locale" content="en-US" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:image" content="og_image.png" />
|
||||||
|
<meta property="og:description" content='reads a "crash report" and deobfuscates javascript stack traces within it using a source map' />
|
||||||
|
<title>Crash Report Viewer</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="crashviewer.css" />
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
|
||||||
|
<script type="text/javascript" src="source-map.js"></script>
|
||||||
|
<script type="text/javascript" src="crashviewer.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id="titleRow">Crash Report Viewer</h1>
|
||||||
|
<div class="containerHead">
|
||||||
|
<p style="margin:0px;">
|
||||||
|
|
||||||
|
<span style="font-size:22;font-weight:bold;margin-right:12px;">Version:</span>
|
||||||
|
<select id="sourceMaps" disabled>
|
||||||
|
<option value="xxx">  </option>
|
||||||
|
</select>
|
||||||
|
<span id="fetchingMessage">Fetching source maps, please wait<span id="loadingDots"></span></span>
|
||||||
|
<span id="showOriginal" class="toggleShow toggleSelected" style="display:none;">Show Original</span>
|
||||||
|
<span id="showDecoded" class="toggleShow toggleShowDisabled" style="display:none;">Show Decoded</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="containerBody">
|
||||||
|
<textarea id="inputTextArea" spellcheck="false" class="darkModeScrollbar" style="display:none;"></textarea>
|
||||||
|
<div id="outputTextArea" style="display:none;">
|
||||||
|
<div id="outputContent" class="darkModeScrollbar"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
app/mappings.wasm
Normal file
BIN
app/mappings.wasm
Normal file
Binary file not shown.
BIN
app/og_image.png
Normal file
BIN
app/og_image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 886 B |
3351
app/source-map.js
Normal file
3351
app/source-map.js
Normal file
File diff suppressed because it is too large
Load Diff
7
app/sourceMaps.json
Normal file
7
app/sourceMaps.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"sourceMaps": [
|
||||||
|
{ "name": "22w41b", "url": "22w41b.json" },
|
||||||
|
{ "name": "22w41a", "url": "22w41a.json" },
|
||||||
|
{ "name": "22w40a", "url": "22w40a.json" }
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user