diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 542f704..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "html/hex-viewer"] - path = html/hex-viewer - url = https://github.com/Heath123/hex-viewer diff --git a/html/hex-viewer b/html/hex-viewer deleted file mode 160000 index 33738b7..0000000 --- a/html/hex-viewer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 33738b7523da6bc5e823b8a446db734340f221fd diff --git a/html/mainPage/hex-viewer.html b/html/mainPage/hex-viewer.html new file mode 100644 index 0000000..8bf9c0a --- /dev/null +++ b/html/mainPage/hex-viewer.html @@ -0,0 +1,89 @@ + + + + + + Packet hex + + + +
No packet selected.
+ + + diff --git a/html/mainPage/index.html b/html/mainPage/index.html index 9415867..e07081a 100644 --- a/html/mainPage/index.html +++ b/html/mainPage/index.html @@ -89,8 +89,11 @@

Filtering

-

-
+

+ +
@@ -139,7 +142,7 @@

Scripting (beta)

- +
diff --git a/html/mainPage/js/filteringLogic.js b/html/mainPage/js/filteringLogic.js index 688411e..32796fb 100644 --- a/html/mainPage/js/filteringLogic.js +++ b/html/mainPage/js/filteringLogic.js @@ -1,7 +1,13 @@ exports.packetFilteredByFilterBox = function (packet, filter, hiddenPackets, inverseFiltering, regexFilter, sharedVars) { - if(!hiddenPackets) return false; - if (hiddenPackets[packet.direction].includes(packet.meta.name)) { + if (!packet || !hiddenPackets) return false + const direction = packet.direction || '' + const meta = packet.meta || {} + const packetName = meta.name === undefined ? 'unknown' : String(meta.name) + const hiddenForDirection = Array.isArray(hiddenPackets[direction]) + ? hiddenPackets[direction] + : [] + if (hiddenForDirection.includes(packetName)) { return true } @@ -9,7 +15,13 @@ exports.packetFilteredByFilterBox = function (packet, filter, hiddenPackets, inv return false } - const comparisonString = packet.hexIdString + ' ' + packet.meta.name + ' ' + JSON.stringify(packet.data) + let packetData + try { + packetData = JSON.stringify(packet.data) + } catch (err) { + packetData = String(packet.data) + } + const comparisonString = String(packet.hexIdString || '') + ' ' + packetName + ' ' + packetData if (regexFilter && typeof filter === 'string') { try { @@ -36,5 +48,5 @@ exports.packetFilteredByFilterBox = function (packet, filter, hiddenPackets, inv } exports.packetCollapsed = function (packet, filter, hiddenPackets) { - return packet.meta.name === 'position' + return Boolean(packet && packet.meta && packet.meta.name === 'position') } diff --git a/html/mainPage/js/ipcHandler.js b/html/mainPage/js/ipcHandler.js index 269d3bd..5373da8 100644 --- a/html/mainPage/js/ipcHandler.js +++ b/html/mainPage/js/ipcHandler.js @@ -5,15 +5,22 @@ exports.setup = function (passedSharedVars) { sharedVars.ipcRenderer.on('copyPacketData', (event, arg) => { const ipcMessage = JSON.parse(arg) - let data = sharedVars.allPackets[ipcMessage.id].data - data = sharedVars.proxyCapabilities.jsonData ? JSON.stringify(data, null, 2) : data.data + const packet = sharedVars.allPackets[Number(ipcMessage.id)] + if (!packet) return + const data = sharedVars.proxyCapabilities.jsonData + ? sharedVars.packetDom.serializeData(packet.data, 2) + : packet.data && packet.data.data !== undefined + ? String(packet.data.data) + : '' sharedVars.ipcRenderer.send('copyToClipboard', data) }) sharedVars.ipcRenderer.on('copyHexData', (event, arg) => { const ipcMessage = JSON.parse(arg) let data = '' - for (const byte of sharedVars.allPackets[ipcMessage.id].raw) { + const packet = sharedVars.allPackets[Number(ipcMessage.id)] + if (!packet || !packet.raw) return + for (const byte of packet.raw) { data += byte.toString(16).padStart(2, '0') data += ' ' } @@ -24,9 +31,11 @@ exports.setup = function (passedSharedVars) { sharedVars.ipcRenderer.on('copyTeleportCommand', (event, arg) => { console.log(sharedVars.allPackets) const ipcMessage = JSON.parse(arg) - const data = sharedVars.allPackets[ipcMessage.id].data + const packet = sharedVars.allPackets[Number(ipcMessage.id)] + if (!packet || !packet.data) return + const data = packet.data - let clipData = '/tp @p ' + ((datasharedVars.packetsUpdated = false.flags & 0x01) ? '~' : '') + ((data.x === 0 && (data.flags & 0x01)) ? '' : data.x) + + let clipData = '/tp @p ' + ((data.flags & 0x01) ? '~' : '') + ((data.x === 0 && (data.flags & 0x01)) ? '' : data.x) + ((data.flags & 0x02) ? ' ~' : ' ') + ((data.y === 0 && (data.flags & 0x03)) ? '' : data.y) + ((data.flags & 0x04) ? ' ~' : ' ') + ((data.z === 0 && (data.flags & 0x04)) ? '' : data.z) @@ -59,13 +68,127 @@ exports.setup = function (passedSharedVars) { window.updateFilteringPackets() }) - sharedVars.ipcRenderer.on('loadLogData', (event, arg) => { - sharedVars.allPackets = JSON.parse(arg) - for (const packet of sharedVars.allPackets) { - sharedVars.packetDom.addPacketToDOM(packet) + sharedVars.ipcRenderer.on('loadLogStart', (event, request) => { + const { requestId, payload } = request + + try { + window.activeLoadId = payload.loadId + window.loadLogRunning = true + + if (typeof window.deselectPacket === 'function') { + window.deselectPacket() + } + + sharedVars.allPackets = [] + sharedVars.allPacketsHTML = [] + sharedVars.hiddenPacketsAmount = 0 + sharedVars.packetsUpdated = false + if (sharedVars.packetList) { + sharedVars.packetList.innerHTML = '' + } + + console.log( + `Loading log: ${payload.filePath} (${payload.fileSize} bytes)` + ) + + sharedVars.ipcRenderer.send( + `loadLogStart-ack-${requestId}`, + { + success: true + } + ) + } catch (err) { + sharedVars.ipcRenderer.send( + `loadLogStart-ack-${requestId}`, + { + error: err.message + } + ) } }) + sharedVars.ipcRenderer.on('loadLogChunk', (event, request) => { + const { requestId, payload } = request + + try { + if (payload.loadId !== window.activeLoadId) { + throw new Error('Invalid load session') + } + + sharedVars.packetDom.addPackets(payload.packets, true) + + sharedVars.ipcRenderer.send( + `loadLogChunk-ack-${requestId}`, + { + success: true, + packetCount: sharedVars.allPackets.length + } + ) + } catch (err) { + sharedVars.ipcRenderer.send( + `loadLogChunk-ack-${requestId}`, + { + error: err.message + } + ) + } + }) + + sharedVars.ipcRenderer.on('loadLogFinish', (event, request) => { + const { requestId, payload } = request + + try { + if (payload.loadId !== window.activeLoadId) { + throw new Error('Invalid load session') + } + + sharedVars.packetDom.refresh() + + console.log( + `Loaded ${payload.packetCount} packets` + ) + + window.activeLoadId = null + window.loadLogRunning = false + + sharedVars.ipcRenderer.send( + `loadLogFinish-ack-${requestId}`, + { + success: true + } + ) + } catch (err) { + sharedVars.ipcRenderer.send( + `loadLogFinish-ack-${requestId}`, + { + error: err.message + } + ) + } + }) + + sharedVars.ipcRenderer.on('loadLogError', (event, payload) => { + if ( + payload.loadId && + window.activeLoadId && + payload.loadId !== window.activeLoadId + ) { + return + } + + window.activeLoadId = null + window.loadLogRunning = false + + console.error( + 'Log load failed:', + payload.error + ) + + alert( + `Log load failed: ${payload.error}` + ) + }) + sharedVars.ipcRenderer.on('loadScriptData', (event, arg) => { window.scriptEditor.getDoc().setValue(arg) sharedVars.ipcRenderer.send('scriptStateChange', JSON.stringify({ // @@ -79,9 +202,9 @@ exports.setup = function (passedSharedVars) { document.getElementById('btnScriptSave').title = arg }) -sharedVars.ipcRenderer.on('disableBtnScriptSave', (event, arg) => { - document.getElementById('btnScriptSave').disabled = true - document.getElementById('btnScriptSave').title = '' -}) + sharedVars.ipcRenderer.on('disableBtnScriptSave', (event, arg) => { + document.getElementById('btnScriptSave').disabled = true + document.getElementById('btnScriptSave').title = '' + }) } diff --git a/html/mainPage/js/main.js b/html/mainPage/js/main.js index 957d262..d222d95 100644 --- a/html/mainPage/js/main.js +++ b/html/mainPage/js/main.js @@ -21,11 +21,13 @@ function wrappedClusterizeUpdate (htmlArray) { sharedVars.hiddenPacketsAmount = 0 const newArray = [] for (const item of htmlArray) { + const row = Array.isArray(item) ? item[0] : item + if (typeof row !== 'string') continue // If the packet is hidden - if (item[0].match(/
  • /)) { + if (row.match(/
  • /)) { sharedVars.hiddenPacketsAmount += 1 } else { - newArray.push(item) + newArray.push(row) } } clusterize.update(newArray) @@ -86,17 +88,20 @@ function updateFiltering () { } } sharedVars.allPacketsHTML.forEach(function (item, index, array) { - if (!filteringLogic.packetFilteredByFilterBox(sharedVars.allPackets[index], + const packet = sharedVars.allPackets[index] + const row = Array.isArray(item) ? item[0] : item + if (!packet || typeof row !== 'string') return + if (!filteringLogic.packetFilteredByFilterBox(packet, regexFilter ? regex : sharedVars.lastFilter, sharedVars.hiddenPackets, inverseFiltering, regexFilter, sharedVars)) { // If it's hidden, show it - array[index] = [item[0].replace('filter-hidden', 'filter-shown')] + array[index] = [row.replace('filter-hidden', 'filter-shown')] } else { // If it's shown, hide it - array[index] = [item[0].replace('filter-shown', 'filter-hidden')] + array[index] = [row.replace('filter-shown', 'filter-hidden')] } }) wrappedClusterizeUpdate(sharedVars.allPacketsHTML) @@ -230,6 +235,16 @@ sharedVars.settings.setup(sharedVars) // TODO: move to own file const filteringPackets = document.getElementById('filtering-packets') +const filteringSearch = document.getElementById('filtering-search') + +window.updateFilteringPacketSearch = function () { + const query = filteringSearch.value.trim().toLowerCase() + + for (const item of filteringPackets.children) { + const matches = query === '' || item.textContent.toLowerCase().includes(query) + item.style.display = matches ? '' : 'none' + } +} function updateFilteringStorage () { setVersionSpecificVar('hiddenPackets', sharedVars.hiddenPackets) @@ -288,6 +303,7 @@ window.updateFilteringPackets = () => { } window.updateFilteringPackets() +window.updateFilteringPacketSearch() // Update every 0.05 seconds // TODO: Find a better way without updating on every packet (which causes lag) @@ -406,7 +422,7 @@ sharedVars.ipcRenderer.on('editAndResend', (event, arg) => { }) function deselectPacket () { - if (currentPacket) { + if (currentPacket !== undefined) { removeOrAddSelection(currentPacket, false) } currentPacket = undefined @@ -415,8 +431,12 @@ function deselectPacket () { document.body.classList.remove('packetSelected') document.body.classList.add('noPacketSelected') hexViewer.style.display = 'none' + const copyButton = document.getElementById('copy-data-button') + if (copyButton) copyButton.disabled = true } +window.deselectPacket = deselectPacket + window.clearPackets = function () { // window. stops standardjs from complaining deselectPacket() sharedVars.allPackets = [] @@ -436,8 +456,14 @@ window.showAllPackets = function () { // window. stops standardjs from complaini const hexViewer = document.getElementById('hex-viewer') function removeOrAddSelection (id, add) { + if (!Number.isInteger(id) || !sharedVars.allPacketsHTML[id]) return false + const row = Array.isArray(sharedVars.allPacketsHTML[id]) + ? sharedVars.allPacketsHTML[id][0] + : sharedVars.allPacketsHTML[id] + if (typeof row !== 'string') return false const fakeElement = document.createElement('div') - fakeElement.innerHTML = sharedVars.allPacketsHTML[id][0] + fakeElement.innerHTML = row + if (!fakeElement.firstChild) return false if (add) { fakeElement.firstChild.classList.add('selected') } else { @@ -447,9 +473,12 @@ function removeOrAddSelection (id, add) { wrappedClusterizeUpdate(sharedVars.allPacketsHTML) clusterize.refresh() + return true } window.packetClick = function (id) { // window. stops standardjs from complaining + if (!Number.isInteger(id) || !sharedVars.allPackets[id]) return + const packet = sharedVars.allPackets[id] // Remove selection background from old selected packet if (currentPacket !== undefined) { removeOrAddSelection(currentPacket, false) @@ -457,20 +486,22 @@ window.packetClick = function (id) { // window. stops standardjs from complainin currentPacket = id // const element = document.getElementById('packet' + id) - currentPacketType = sharedVars.allPackets[id].name + currentPacketType = packet.name || (packet.meta && packet.meta.name) removeOrAddSelection(currentPacket, true) document.body.classList.remove('noPacketSelected') document.body.classList.add('packetSelected') if (sharedVars.proxyCapabilities.jsonData) { // sidebar.innerHTML = '
    Loading packet data...
    '; - if (sharedVars.allPackets[id].data === undefined) { + if (packet.data === undefined) { sharedVars.packetDom.getTreeElement().firstElementChild.innerHTML = 'Could not parse packet' // TODO: Error message } else { - sharedVars.packetDom.getTree().loadData(sharedVars.allPackets[id].data) + sharedVars.packetDom.getTree().loadData(packet.data) } } else { - sharedVars.packetDom.getTreeElement().innerText = sharedVars.allPackets[id].data.data + sharedVars.packetDom.getTreeElement().innerText = packet.data && packet.data.data !== undefined + ? packet.data.data + : 'Could not parse packet' sharedVars.packetDom.getTreeElement().style = ` color: #0F0; white-space: pre; @@ -479,11 +510,14 @@ window.packetClick = function (id) { // window. stops standardjs from complainin display: block;` } - if (sharedVars.proxyCapabilities.rawData) { + if (sharedVars.proxyCapabilities.rawData && packet.raw) { hexViewer.style.display = 'block' - hexViewer.contentWindow.postMessage(Buffer.from(sharedVars.allPackets[id].raw)) + hexViewer.contentWindow.postMessage(Buffer.from(packet.raw), '*') } + const copyButton = document.getElementById('copy-data-button') + if (copyButton) copyButton.disabled = false + scrollWikiToCurrentPacket() } @@ -663,15 +697,166 @@ function scrollWikiToCurrentPacket () { } } } +window.saveLogRunning = false + +async function saveLog () { + if (window.saveLogRunning) { + console.warn('Save is already running') + return + } + + window.saveLogRunning = true + + const CHUNK_SIZE = 2000 + const saveButton = document.getElementById('save-log-button') + + let sessionId = null + let finished = false + + if (saveButton) { + saveButton.disabled = true + saveButton.innerText = 'Saving...' + } + + try { + const startResult = await sharedVars.ipcRenderer.invoke( + 'startSaveLog' + ) + + if (startResult.canceled) { + return + } + + if (startResult.busy) { + alert('A save operation is already running') + return + } + + sessionId = startResult.sessionId + + const totalPackets = sharedVars.allPackets.length + + console.log( + `Starting save of ${totalPackets} packets` + ) + + for ( + let index = 0; + index < totalPackets; + index += CHUNK_SIZE + ) { + const chunk = sharedVars.allPackets.slice( + index, + Math.min(index + CHUNK_SIZE, totalPackets) + ) + + const result = await sharedVars.ipcRenderer.invoke( + 'appendSaveLogChunk', + { + sessionId, + packets: chunk + } + ) + + const percent = totalPackets === 0 + ? 100 + : Math.floor( + result.packetCount / totalPackets * 100 + ) + + if (saveButton) { + saveButton.innerText = + `Saving... ${percent}% (${result.packetCount}/${totalPackets})` + } + + console.log( + `Saved ${result.packetCount}/${totalPackets}` + ) + } -function saveLog() { - sharedVars.ipcRenderer.send('saveLog', JSON.stringify(sharedVars.allPackets)) + const finishResult = + await sharedVars.ipcRenderer.invoke( + 'finishSaveLog', + { + sessionId + } + ) + + finished = true + sessionId = null + + console.log( + `Saved ${finishResult.packetCount} packets to ${finishResult.filePath}` + ) + } catch (err) { + console.error('Failed to save packet log:', err) + + if (sessionId && !finished) { + try { + await sharedVars.ipcRenderer.invoke( + 'cancelSaveLog', + { + sessionId + } + ) + } catch (cancelError) { + console.error( + 'Failed to cancel packet log save:', + cancelError + ) + } + } + + alert(`Failed to save packet log: ${err.message}`) + } finally { + window.saveLogRunning = false + + if (saveButton) { + saveButton.disabled = false + saveButton.innerText = 'Save to file' + } + } } -function loadLog() { - sharedVars.ipcRenderer.send('loadLog', '') +window.loadLogRunning = false +window.activeLoadId = null + +async function loadLog () { + if (window.loadLogRunning) { + console.warn('Load is already running') + return + } + + window.loadLogRunning = true + + try { + const result = await sharedVars.ipcRenderer.invoke( + 'startLoadLog' + ) + + if (result.canceled) { + window.loadLogRunning = false + return + } + + if (result.busy) { + window.loadLogRunning = false + alert('A log load is already running') + return + } + + window.activeLoadId = result.loadId + } catch (err) { + window.loadLogRunning = false + window.activeLoadId = null + + console.error(err) + alert(`Could not load log: ${err.message}`) + } } +window.loadLog = loadLog + function saveScript( newfile = true ) { if (newfile) { sharedVars.ipcRenderer.send('saveAsScript', window.scriptEditor.getDoc().getValue()) @@ -682,4 +867,23 @@ function saveScript( newfile = true ) { function loadScript() { sharedVars.ipcRenderer.send('loadScript', '') -} \ No newline at end of file +} + +window.copyCurrentPacketData = function () { + if (currentPacket === undefined || !sharedVars.allPackets[currentPacket]) return + const packet = sharedVars.allPackets[currentPacket] + const data = sharedVars.proxyCapabilities.jsonData + ? sharedVars.packetDom.serializeData(packet.data, 2) + : packet.data && packet.data.data !== undefined + ? String(packet.data.data) + : '' + sharedVars.ipcRenderer.send('copyToClipboard', data) + const copyButton = document.getElementById('copy-data-button') + if (copyButton) { + const originalText = copyButton.innerText + copyButton.innerText = 'Copied' + setTimeout(() => { + copyButton.innerText = originalText + }, 900) + } +} diff --git a/html/mainPage/js/packetDom.js b/html/mainPage/js/packetDom.js index 6f37bca..4bc53dd 100644 --- a/html/mainPage/js/packetDom.js +++ b/html/mainPage/js/packetDom.js @@ -1,34 +1,55 @@ let tree let treeElement +let treeContent let sharedVars const filteringLogic = require('./filteringLogic.js') -function trimData (data) { // Function to trim the size of stringified data for previews - if (data === undefined) { - // Undefined data, probably an invalid packet +function stringifyData (data, spacing) { + const seen = new WeakSet() + try { + const result = JSON.stringify(data, (key, value) => { + if (typeof value === 'bigint') return `${value}n` + if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { + return Array.from(value) + } + if (value && typeof value === 'object') { + if (seen.has(value)) return '[Circular]' + seen.add(value) + } + return value + }, spacing) + return result === undefined ? String(data) : result + } catch (err) { + return String(data) + } +} + +function trimData (data) { + if (data === undefined || data === null) { return 'Could not parse packet' } let newData if (sharedVars.proxyCapabilities.jsonData) { - newData = Object.assign({}, data) - Object.entries(newData).forEach(function (entry) { - try { - if (JSON.stringify(entry[1]).length > 15) { - if (typeof entry[1] === 'number') { - newData[entry[0]] = Math.round((entry[1] + Number.EPSILON) * 100) / 100 - } else { - newData[entry[0]] = '...' - } + const preview = {} + if (typeof data === 'object') { + Object.entries(data).forEach(([key, value]) => { + const serialized = stringifyData(value) + if (serialized && serialized.length > 15) { + preview[key] = typeof value === 'number' + ? Math.round((value + Number.EPSILON) * 100) / 100 + : '...' + } else { + preview[key] = value } - } catch (err) { - - } - }) - newData = JSON.stringify(newData) + }) + newData = stringifyData(preview) + } else { + newData = String(data) + } } else { - newData = data.data + newData = data && data.data !== undefined ? String(data.data) : 'Could not parse packet' } if (newData.length > 750) { newData = newData.slice(0, 750) @@ -37,24 +58,35 @@ function trimData (data) { // Function to trim the size of stringified data for } function formatTime (ms) { - // Based on https://stackoverflow.com/a/50409993/4012708 - return new Date(new Date(ms).getTime() - new Date().getTimezoneOffset() * 60000).toISOString().split("T")[1].replace(/[0-9]Z$/, ''); + const date = new Date(ms) + if (!Number.isFinite(date.getTime())) return '--:--:--' + return new Date(date.getTime() - new Date().getTimezoneOffset() * 60000) + .toISOString().split('T')[1].replace(/[0-9]Z$/, '') +} + +function packetHtml (packet, isHidden) { + const uid = packet.uid + const meta = packet.meta || {} + const id = packet.hexIdString === undefined ? '??' : String(packet.hexIdString) + const name = meta.name === undefined ? 'unknown' : String(meta.name) + const direction = packet.direction === undefined ? '' : String(packet.direction) + return `
  • +
    + ${escapeHtml(id)} + ${escapeHtml(name)} + ${escapeHtml(trimData(packet.data))} +
    + ${escapeHtml(formatTime(packet.time))} +
  • ` } exports.addPacketToDOM = function (packet) { + if (!packet || typeof packet !== 'object') return false const isHidden = filteringLogic.packetFilteredByFilterBox(packet, sharedVars.lastFilter, sharedVars.hiddenPackets, // TODO: cache these? sharedVars.settings.getSetting('inverseFiltering'), sharedVars.settings.getSetting('regexFilter'), sharedVars) - sharedVars.allPacketsHTML.push([ - `
  • -
    - ${escapeHtml(packet.hexIdString)} - ${escapeHtml(packet.meta.name)} - ${escapeHtml(trimData(packet.data))} -
    - ${escapeHtml(formatTime(packet.time))} -
  • `]) + sharedVars.allPacketsHTML.push([packetHtml(packet, isHidden)]) /* if (!noUpdate) {/html/mainPage/index.html/html/mainPage/index.html clusterize.append(sharedVars.allPacketsHTML.slice(-1)[0]); if (wasScrolledToBottom) { @@ -67,6 +99,38 @@ exports.addPacketToDOM = function (packet) { sharedVars.packetsUpdated = true } updateHidden() + return true +} + +exports.addPackets = function (packets, deferRender) { + if (!Array.isArray(packets)) return 0 + let added = 0 + for (const data of packets) { + if (!data || typeof data !== 'object') continue + sharedVars.allPackets.push(data) + data.uid = sharedVars.allPackets.length - 1 + + const isHidden = filteringLogic.packetFilteredByFilterBox( + data, + sharedVars.lastFilter, + sharedVars.hiddenPackets, + sharedVars.settings.getSetting('inverseFiltering'), + sharedVars.settings.getSetting('regexFilter'), + sharedVars + ) + + sharedVars.allPacketsHTML.push([packetHtml(data, isHidden)]) + added++ + + if (isHidden) { + sharedVars.hiddenPacketsAmount += 1 + } else { + sharedVars.packetsUpdated = true + } + } + + if (!deferRender) updateHidden() + return added } function refreshPackets () { @@ -96,8 +160,14 @@ exports.setup = function (passedSharedVars) { treeElement = document.getElementById('tree') tree = jsonTree.create({}, treeElement) + treeContent = treeElement.firstElementChild - treeElement.firstElementChild.innerHTML = 'No packet selected!' + treeContent.innerHTML = 'No packet selected!' + + const actions = document.createElement('div') + actions.className = 'data-actions' + actions.innerHTML = '' + treeElement.appendChild(actions) } exports.addPacket = function (data) { @@ -115,3 +185,10 @@ exports.getTreeElement = function () { exports.getTree = function () { return tree } + +exports.serializeData = stringifyData + +exports.refresh = function () { + updateHidden() + sharedVars.packetsUpdated = true +} diff --git a/html/mainPage/style.css b/html/mainPage/style.css index ecd2100..ec9c73e 100644 --- a/html/mainPage/style.css +++ b/html/mainPage/style.css @@ -1,318 +1,340 @@ -html { - box-sizing: border-box; - height: 100%; -} - -body { - height: calc(100% - 16px); -} - -.topbar { - height: 120px; -} - -.container { - height: calc(100% - 154px); -} - -.split, .gutter.gutter-horizontal { - float: left; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - height: 100%; - /* Default before everything loads */ - width: calc(50% - 5px); -} - -/* Targets the right panel to add spacing, but only before the gutter loads */ -#packets + #sidebar { - margin-left: 10px; -} - -.gutter.gutter-horizontal { - cursor: ew-resize; -} - -.box { - overflow-y: auto; - border: 1px solid #1A1A1A; - border-radius: 10px; - height: calc(100% - 24px); /* Take off toolbar height */ -} - -div.toolbar { - height: 24px; - overflow: hidden; -} - -#packets { - position: relative; /* Is this needed? TODO: Check */ -} - -.packetlist { - list-style: none; - padding: 0; - margin: 0; -} - -.packet:hover { - background: #2C2C2C; -} - -.packet { - padding: 4px 8px; - height: 22px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: rgba(255, 255, 255, 0.5); -} - -.packet.filter-hidden { - display: none; -} - -.packet.serverbound::before { - content: "▲"; - color: #00FF00; - padding-right: 4px; -} -.packet.clientbound::before { - content: "▼"; - color: #FF0000; - padding-right: 4px; -} - -.packet.invalid::before { - color: #ffff00; -} - -.name { - color: rgba(255, 255, 255, 0.8); -} - -body.noPacketSelected .whenPacketSelected { - display: none; -} - -body.packetSelected .whenNoPacketSelected { - display: none; -} - -.topbar-colour { - top: 0; - background: #121212; - height: 40px; - z-index: -1; - position: fixed; - width: 100%; - left: 0; -} - -span#hiddenPackets { - color: rgba(255, 255, 255, 0.5); -} - -.dialog-overlay { - display: none; -} - -.dialog-overlay.active { - display: block; - width: 100%; - height: 100%; - position: fixed; - top: 0; - left: 0; - z-index: 99; - background-color: rgba(0, 0, 0, 0.8); -} - -div.dialog { - position: fixed; - top: 30px; - left: 30px; - background: #242424; - height: calc(100% - 60px); - width: calc(100% - 60px); - border-radius: 20px; - padding: 0 16px 16px 16px; - box-sizing: border-box; -} - -div.dialog-small { - top: calc(50% - 100px); - left: calc(50% - 250px); - height: 200px; - width: 500px; -} - -div.dialog-medium { - top: calc(50% - 170px); - left: calc(50% - 250px); - height: 340px; - width: 500px; -} - -.CodeMirror.CodeMirror { - height: calc(100% - 120px); -} - -div#Scripting .CodeMirror.CodeMirror { - height: calc(100% - 185px); -} - -/* TODO: needed? */ -.packetLink { - text-decoration: none; -} - -#tabcontent { - box-sizing: border-box; - position: fixed; - height: calc(100% - 48px); - width: calc(100% - 16px); - z-index: 1; - display: block; - background: rgb(36, 36, 36); -} - -/* Style the tab */ -.tab { - overflow: hidden; - /* border: 1px solid #ccc; */ - /* background-color: rgba(0, 0, 0, 0.2); */ -} - -/* Style the buttons inside the tab */ -.tab.tab button { - margin: 0 2px; - float: left; - border: none; - outline: none; - cursor: pointer; - font-size: 17px; - background: #191919; -} - -.tab-button { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - color: rgba(255, 255, 255, 0.8); - background-color: inherit; - padding: 6px 16px; -} - -.back-button { - padding: 4px 8px; -} - -.tab.tab.toolbar button { - background: #1e1e1e; - padding: 3px 16px; -} - -.tab.tab.toolbar:nth-child(1) { - margin-left: 10px; -} - -/* Change background color of buttons on hover */ -.tab button:hover { - background-color: rgba(0, 0, 0, 0.35); -} - -/* Create an active/current tablink class */ -.tab button.active { - background-color: #242424; -} - -.tab.toolbar button.active { - background-color: rgba(0, 0, 0, 0.35); -} - -.tab.toolbar button.active { - background-color: rgba(0, 0, 0, 0.35); -} - - -/* Style the tab content */ -.tabcontent { - height: 65px; - display: none; - padding: 6px 12px; - /* border: 1px solid #ccc; */ - border-top: none; -} - -.tabcontent.fullpage { - position: relative; - z-index: 1; - background: #242424; - height: calc(100vh - 52px); -} - -div.search { - height: 34px; -} - -input.filter { - box-sizing: border-box; - height: 28px; - width: 100%; -} - -#iframe, #hex-viewer { - border: 0; - width: 100%; - height: 100%; -} - -div.error { - position: fixed; - right: 8px; - top: 4px; - text-align: right; - font-size: 90%; - color: red; - user-select: text; -} - -.packet.selected { - background: rgba(64, 127, 255, 0.15); -} - -#mainPresets, #extendedPresets { - display: contents; -} - -.error-dialog { - user-select: text; -} - -.bottom-button { - position: absolute; - bottom: 16px; - user-select: none; -} - -body.timeShown div.main-data { - display: inline-block; - width: calc(100% - 120px); - overflow-x: hidden; - text-overflow: ellipsis; - /* Makes up for inline-block causing extra space on the bottom */ - margin-bottom: -4px; -} - -body.timeNotShown div.main-data { - display: inline; -} - -body.timeNotShown span.time { - display: none; -} - -.settingDescription { - color: rgba(255, 255, 255, 0.5); -} +html { + box-sizing: border-box; + height: 100%; +} + +body { + height: calc(100% - 16px); +} + +.topbar { + height: 120px; +} + +.container { + height: calc(100% - 154px); +} + +.split, .gutter.gutter-horizontal { + float: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + height: 100%; + /* Default before everything loads */ + width: calc(50% - 5px); +} + +/* Targets the right panel to add spacing, but only before the gutter loads */ +#packets + #sidebar { + margin-left: 10px; +} + +.gutter.gutter-horizontal { + cursor: ew-resize; +} + +.box { + overflow-y: auto; + border: 1px solid #1A1A1A; + border-radius: 10px; + height: calc(100% - 24px); /* Take off toolbar height */ +} + +div.toolbar { + height: 24px; + overflow: hidden; +} + +#packets { + position: relative; /* Is this needed? TODO: Check */ +} + +.packetlist { + list-style: none; + padding: 0; + margin: 0; +} + +.packet:hover { + background: #2C2C2C; +} + +.packet { + padding: 4px 8px; + height: 22px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: rgba(255, 255, 255, 0.5); +} + +.packet.filter-hidden { + display: none; +} + +.packet.serverbound::before { + content: "▲"; + color: #00FF00; + padding-right: 4px; +} +.packet.clientbound::before { + content: "▼"; + color: #FF0000; + padding-right: 4px; +} + +.packet.invalid::before { + color: #ffff00; +} + +.name { + color: rgba(255, 255, 255, 0.8); +} + +body.noPacketSelected .whenPacketSelected { + display: none; +} + +body.packetSelected .whenNoPacketSelected { + display: none; +} + +.topbar-colour { + top: 0; + background: #121212; + height: 40px; + z-index: -1; + position: fixed; + width: 100%; + left: 0; +} + +span#hiddenPackets { + color: rgba(255, 255, 255, 0.5); +} + +.dialog-overlay { + display: none; +} + +.dialog-overlay.active { + display: block; + width: 100%; + height: 100%; + position: fixed; + top: 0; + left: 0; + z-index: 99; + background-color: rgba(0, 0, 0, 0.8); +} + +div.dialog { + position: fixed; + top: 30px; + left: 30px; + background: #242424; + height: calc(100% - 60px); + width: calc(100% - 60px); + border-radius: 20px; + padding: 0 16px 16px 16px; + box-sizing: border-box; +} + +div.dialog-small { + top: calc(50% - 100px); + left: calc(50% - 250px); + height: 200px; + width: 500px; +} + +div.dialog-medium { + top: calc(50% - 170px); + left: calc(50% - 250px); + height: 340px; + width: 500px; +} + +.CodeMirror.CodeMirror { + height: calc(100% - 120px); +} + +div#Scripting .CodeMirror.CodeMirror { + height: calc(100% - 185px); +} + +/* TODO: needed? */ +.packetLink { + text-decoration: none; +} + +#tabcontent { + box-sizing: border-box; + position: fixed; + height: calc(100% - 48px); + width: calc(100% - 16px); + z-index: 1; + display: block; + background: rgb(36, 36, 36); +} + +/* Style the tab */ +.tab { + overflow: hidden; + /* border: 1px solid #ccc; */ + /* background-color: rgba(0, 0, 0, 0.2); */ +} + +/* Style the buttons inside the tab */ +.tab.tab button { + margin: 0 2px; + float: left; + border: none; + outline: none; + cursor: pointer; + font-size: 17px; + background: #191919; +} + +.tab-button { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + color: rgba(255, 255, 255, 0.8); + background-color: inherit; + padding: 6px 16px; +} + +.back-button { + padding: 4px 8px; +} + +.tab.tab.toolbar button { + background: #1e1e1e; + padding: 3px 16px; +} + +.tab.tab.toolbar:nth-child(1) { + margin-left: 10px; +} + +/* Change background color of buttons on hover */ +.tab button:hover { + background-color: rgba(0, 0, 0, 0.35); +} + +/* Create an active/current tablink class */ +.tab button.active { + background-color: #242424; +} + +.tab.toolbar button.active { + background-color: rgba(0, 0, 0, 0.35); +} + +.tab.toolbar button.active { + background-color: rgba(0, 0, 0, 0.35); +} + + +/* Style the tab content */ +.tabcontent { + height: 65px; + display: none; + padding: 6px 12px; + /* border: 1px solid #ccc; */ + border-top: none; +} + +.tabcontent.fullpage { + position: relative; + z-index: 1; + background: #242424; + height: calc(100vh - 52px); +} + +div.search { + height: 34px; +} + +input.filter { + box-sizing: border-box; + height: 28px; + width: 100%; +} + +input.packet-type-filter { + margin-bottom: 8px; +} + +#iframe, #hex-viewer { + border: 0; + width: 100%; + height: 100%; +} +#tree { + position: relative; +} + +#tree .jsontree_tree { + padding-top: 38px; +} + +.data-actions { + position: absolute; + top: 8px; + right: 10px; + z-index: 2; +} + +.data-actions button { + padding: 4px 10px; +} + +div.error { + position: fixed; + right: 8px; + top: 4px; + text-align: right; + font-size: 90%; + color: red; + user-select: text; +} + +.packet.selected { + background: rgba(64, 127, 255, 0.15); +} + +#mainPresets, #extendedPresets { + display: contents; +} + +.error-dialog { + user-select: text; +} + +.bottom-button { + position: absolute; + bottom: 16px; + user-select: none; +} + +body.timeShown div.main-data { + display: inline-block; + width: calc(100% - 120px); + overflow-x: hidden; + text-overflow: ellipsis; + /* Makes up for inline-block causing extra space on the bottom */ + margin-bottom: -4px; +} + +body.timeNotShown div.main-data { + display: inline; +} + +body.timeNotShown span.time { + display: none; +} + +.settingDescription { + color: rgba(255, 255, 255, 0.5); +} diff --git a/package-lock.json b/package-lock.json index 779c198..0b28797 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "axios": "^0.21.1", + "axios": "^1.19.0", "bedrock-protocol": "^3.52.0", "clusterize.js": "^0.18.1", "commander": "^7.1.0", @@ -22,13 +22,13 @@ "electron-window-state": "^5.0.3", "escape-html": "^1.0.3", "md5-file": "^5.0.0", - "minecraft-data": "^3.102.2", + "minecraft-data": "3.112.0", "minecraft-folder-path": "^1.2.0", - "minecraft-protocol": "^1.62.0", + "minecraft-protocol": "1.66.2", "node-eval": "^2.0.0", - "patch-package": "^6.4.7", + "patch-package": "^8.0.1", "source-map-support": "^0.5.19", - "ws": "^7.4.6" + "ws": "^7.5.13" }, "devDependencies": { "@electron-forge/cli": "^6.0.0-beta.61", @@ -38,10 +38,6 @@ "@electron-forge/maker-zip": "^6.0.0-beta.61", "electron": "^16.0.5", "electron-rebuild": "^2.3.4" - }, - "optionalDependencies": { - "bufferutil": "^4.0.2", - "utf-8-validate": "^5.0.3" } }, "node_modules/@azure/msal-common": { @@ -503,7 +499,7 @@ "node_modules/@electron/node-gyp": { "version": "10.2.0-electron.1", "resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", - "integrity": "sha512-7o2pP1DFdFgkNv5CxE4m3iLsf8Xa4pus2eD/yzjdHFzEuZMXOLv4nRpJEAvaLtbkRJHsuhRueIvVa4NFx78UgQ==", + "integrity": "sha512-4MSBTT8y07YUDqf69/vSh80Hh791epYqGtWHO3zSKhYFwQg+gx9wi1PqbqP6YqC4WMsNxZ5l9oDmnWdK5pfCKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -712,9 +708,9 @@ } }, "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", - "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", "dev": true, "license": "MIT", "optional": true, @@ -989,9 +985,9 @@ } }, "node_modules/@types/readable-stream": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", - "integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==", + "version": "4.0.24", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.24.tgz", + "integrity": "sha512-NRvUNC/JFGPJvqdAfEve8oginbM6V08u5NzLWpG8MwA2kTPOLnqk+wpwuPT+mp3aUsxyuT6m2gnrPuHYCruzEg==", "license": "MIT", "dependencies": { "@types/node": "*" @@ -1072,7 +1068,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "license": "MIT", "dependencies": { "debug": "4" @@ -1249,13 +1244,13 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, "license": "ISC", "engines": { "node": ">= 4.0.0" @@ -1298,12 +1293,31 @@ "license": "MIT" }, "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" } }, "node_modules/balanced-match": { @@ -1343,9 +1357,9 @@ } }, "node_modules/bedrock-protocol": { - "version": "3.56.1", - "resolved": "https://registry.npmjs.org/bedrock-protocol/-/bedrock-protocol-3.56.1.tgz", - "integrity": "sha512-H/rd6PVcYxu+w2bEN3e5XoPGGsHA7v2atfh8Bl436KXRXWvGlMoRBPj548bgNhyxewgxCANJxE+MPJP4RWyYYA==", + "version": "3.57.0", + "resolved": "https://registry.npmjs.org/bedrock-protocol/-/bedrock-protocol-3.57.0.tgz", + "integrity": "sha512-+4YyRvDD4Wf5g8ZInxjJRoVySDJBQKy0atHBGcQqmrwPJ4DQWrlj3DavQTt78cw3iPZMTQ3ZgKvWMGfnv+jilg==", "license": "MIT", "dependencies": { "debug": "^4.3.1", @@ -1438,9 +1452,9 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -1541,20 +1555,6 @@ "node": ">=0.2.0" } }, - "node_modules/bufferutil": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", - "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/cacache": { "version": "16.1.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", @@ -1666,6 +1666,53 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", @@ -1731,10 +1778,19 @@ } }, "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "license": "MIT" + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/clean-stack": { "version": "2.2.0", @@ -1896,6 +1952,15 @@ "readable-stream": "^2.0.0 || ^1.1.13" } }, + "node_modules/cmake-js/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/cmake-js/node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -2141,7 +2206,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -2252,7 +2316,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2464,9 +2527,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", - "optional": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -2502,7 +2563,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -2564,6 +2624,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -2859,9 +2933,9 @@ } }, "node_modules/electron-installer-debian/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", "dev": true, "license": "MIT", "optional": true, @@ -3010,9 +3084,9 @@ } }, "node_modules/electron-installer-redhat/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", "dev": true, "license": "MIT", "optional": true, @@ -3124,9 +3198,9 @@ } }, "node_modules/electron-packager/node_modules/fs-extra": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", - "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", "dev": true, "license": "MIT", "dependencies": { @@ -3300,9 +3374,9 @@ } }, "node_modules/electron-rebuild/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", "dev": true, "license": "MIT", "dependencies": { @@ -3391,9 +3465,9 @@ } }, "node_modules/electron-winstaller": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", - "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.4.tgz", + "integrity": "sha512-j9ETcBGJaXxAY/b6UBpR7LZfjdU4BAO+yvr4ifqHEdyuc3UNCy91PDGkWKY5UQ4coHNYfnwFggrqD6QPeFGAlg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3403,6 +3477,7 @@ "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", + "semver": "^7.6.3", "temp": "^0.9.0" }, "engines": { @@ -3744,9 +3819,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">= 0.4" } @@ -3755,12 +3828,38 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -4022,9 +4121,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", "funding": [ { "type": "github", @@ -4192,7 +4291,6 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -4207,7 +4305,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -4255,7 +4352,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4366,6 +4462,30 @@ "global-modules": "1.0.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-info": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz", @@ -4399,6 +4519,19 @@ "dev": true, "license": "MIT" }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -4460,9 +4593,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -4585,9 +4718,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">= 0.4" }, @@ -4689,9 +4820,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", - "optional": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -4699,6 +4828,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -4706,10 +4862,9 @@ "license": "ISC" }, "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "dev": true, + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4794,7 +4949,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "6", @@ -4942,18 +5096,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "license": "MIT", - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.16.2", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", @@ -5176,6 +5318,31 @@ "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", "license": "BSD-2-Clause" }, + "node_modules/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-stable-stringify/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -5192,6 +5359,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -5666,6 +5842,15 @@ "node": ">=10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/md5-file": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz", @@ -5763,7 +5948,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -5773,7 +5957,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -5802,9 +5985,9 @@ } }, "node_modules/minecraft-data": { - "version": "3.110.2", - "resolved": "https://registry.npmjs.org/minecraft-data/-/minecraft-data-3.110.2.tgz", - "integrity": "sha512-u0aCCSpQWVreGnZGU/Lu0jmZmc0Y37M0Fvw6eQVQY0BdS/BGRDDU+ug6/qP3QDuZRJCSzi8wNW8ODnOhwpnkpA==", + "version": "3.112.0", + "resolved": "https://registry.npmjs.org/minecraft-data/-/minecraft-data-3.112.0.tgz", + "integrity": "sha512-U+BJ+3zFcTmi6X936sKNBRgocr17t7poNg40+yE3ng7B0wNWwqpndzqJ4cGfjBcclgFghEoOyXvcgIS4MPDRYQ==", "license": "MIT" }, "node_modules/minecraft-folder-path": { @@ -6126,12 +6309,13 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, "license": "MIT" }, "node_modules/node-abi": { - "version": "3.92.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", - "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", "dev": true, "license": "MIT", "dependencies": { @@ -6215,18 +6399,6 @@ "node": ">= 10.12.0" } }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-gyp/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6593,9 +6765,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">= 0.4" } @@ -6722,6 +6892,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6873,128 +7044,34 @@ } }, "node_modules/patch-package": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.5.1.tgz", - "integrity": "sha512-I/4Zsalfhc6bphmJTlrLoOcAF87jcxko4q0qsv4bGcurbr8IskEOtdnt9iCmsQVGL1B+iUhSQqweyTLJfCF9rA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz", + "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==", "license": "MIT", "dependencies": { "@yarnpkg/lockfile": "^1.1.0", "chalk": "^4.1.2", - "cross-spawn": "^6.0.5", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", "find-yarn-workspace-root": "^2.0.0", - "fs-extra": "^9.0.0", - "is-ci": "^2.0.0", + "fs-extra": "^10.0.0", + "json-stable-stringify": "^1.0.2", "klaw-sync": "^6.0.0", "minimist": "^1.2.6", "open": "^7.4.2", - "rimraf": "^2.6.3", - "semver": "^5.6.0", + "semver": "^7.5.3", "slash": "^2.0.0", - "tmp": "^0.0.33", - "yaml": "^1.10.2" + "tmp": "^0.2.4", + "yaml": "^2.2.2" }, "bin": { "patch-package": "index.js" }, "engines": { - "node": ">=10", + "node": ">=14", "npm": ">5" } }, - "node_modules/patch-package/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/patch-package/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/patch-package/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/patch-package/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/patch-package/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/patch-package/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/patch-package/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/patch-package/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7018,7 +7095,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7357,6 +7433,7 @@ "resolved": "https://registry.npmjs.org/prismarine-registry/-/prismarine-registry-1.12.0.tgz", "integrity": "sha512-OC5U6YrflY6OcAWRZEqe2HGZuNp0bIuP7H+oKEHD6rLfKNDxo8Ymx5eh2VvrZWnMVugpwID1Qj/UjA4MoCzNDw==", "license": "MIT", + "peer": true, "dependencies": { "minecraft-data": "^3.70.0", "prismarine-block": "^1.17.1", @@ -7524,6 +7601,15 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -8134,9 +8220,9 @@ } }, "node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -8191,6 +8277,23 @@ "dev": true, "license": "ISC" }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -8215,7 +8318,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -8228,7 +8330,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8687,15 +8788,12 @@ "optional": true }, "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, "engines": { - "node": ">=0.6.0" + "node": ">=14.14" } }, "node_modules/tmp-promise": { @@ -8709,17 +8807,6 @@ "tmp": "^0.2.0" } }, - "node_modules/tmp-promise/node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14.14" - } - }, "node_modules/to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -8991,20 +9078,6 @@ "node": ">=8" } }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -9093,7 +9166,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -9186,9 +9258,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -9234,18 +9306,24 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", - "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index a674e8b..1e3ff69 100644 --- a/package.json +++ b/package.json @@ -43,28 +43,24 @@ "electron-rebuild": "^2.3.4" }, "dependencies": { - "axios": "^0.21.1", + "axios": "^1.19.0", "bedrock-protocol": "^3.52.0", "clusterize.js": "^0.18.1", "commander": "^7.1.0", + "electron-fetch": "1.9.1", "electron-localshortcut": "^3.2.1", "electron-squirrel-startup": "^1.0.0", "electron-store": "^8.0.1", "electron-unhandled": "^3.0.2", "electron-window-state": "^5.0.3", - "electron-fetch": "1.9.1", "escape-html": "^1.0.3", "md5-file": "^5.0.0", - "minecraft-data": "^3.102.2", + "minecraft-data": "3.112.0", "minecraft-folder-path": "^1.2.0", - "minecraft-protocol": "^1.62.0", + "minecraft-protocol": "1.66.2", "node-eval": "^2.0.0", - "patch-package": "^6.4.7", + "patch-package": "^8.0.1", "source-map-support": "^0.5.19", - "ws": "^7.4.6" - }, - "optionalDependencies": { - "bufferutil": "^4.0.2", - "utf-8-validate": "^5.0.3" + "ws": "^7.5.13" } } diff --git a/patches/minecraft-data+3.112.0.patch b/patches/minecraft-data+3.112.0.patch new file mode 100644 index 0000000..cb3151c --- /dev/null +++ b/patches/minecraft-data+3.112.0.patch @@ -0,0 +1,28 @@ +diff --git a/node_modules/minecraft-data/index.js b/node_modules/minecraft-data/index.js +index 526dba1..8316f66 100644 +--- a/node_modules/minecraft-data/index.js ++++ b/node_modules/minecraft-data/index.js +@@ -56,6 +56,11 @@ module.exports = function (mcVersion, preNetty) { + preNetty = preNetty || false + mcVersion = String(mcVersion).replace('pe_', 'bedrock_') + ++ // Minecraft 26.2 is present in the protocol version index before its ++ // complete generated data is published. Reuse the 26.1 schema until then. ++ const compatibilityVersion = mcVersion === '26.2' ++ if (compatibilityVersion) mcVersion = '26.1' ++ + const majorVersion = toMajor(mcVersion, preNetty) + if (majorVersion == null) { return null } + const cachedName = `${majorVersion.type}_${majorVersion.majorVersion}_${majorVersion.dataVersion}` +@@ -67,6 +72,11 @@ module.exports = function (mcVersion, preNetty) { + nmcData.isNewerOrEqualTo = version => nmcData.version['>='](version) + nmcData.isOlderThan = version => nmcData.version['<'](version) + nmcData.version = Object.assign(majorVersion, nmcData.version) ++ if (compatibilityVersion) { ++ nmcData.version.minecraftVersion = '26.2' ++ nmcData.version.majorVersion = '26.2' ++ nmcData.version.version = 776 ++ } + cache[cachedName] = nmcData + nmcData.supportFeature = supportFeature(nmcData.version, protocolVersions[nmcData.type]) + return nmcData diff --git a/patches/minecraft-protocol+1.25.0.patch b/patches/minecraft-protocol+1.25.0.patch deleted file mode 100644 index cdd0da1..0000000 --- a/patches/minecraft-protocol+1.25.0.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/node_modules/minecraft-protocol/src/client/encrypt.js b/node_modules/minecraft-protocol/src/client/encrypt.js -index d66f1d1..3cad7e4 100644 ---- a/node_modules/minecraft-protocol/src/client/encrypt.js -+++ b/node_modules/minecraft-protocol/src/client/encrypt.js -@@ -22,6 +22,7 @@ module.exports = function (client, options) { - joinServerRequest(onJoinServerResponse) - } else { - if (packet.serverId !== '-') { -+ client.emit('noAuth') - debug('This server appears to be an online server and you are providing no password, the authentication will probably fail') - } - sendEncryptionKeyResponse() diff --git a/patches/minecraft-protocol+1.66.2.patch b/patches/minecraft-protocol+1.66.2.patch new file mode 100644 index 0000000..79e1c2a --- /dev/null +++ b/patches/minecraft-protocol+1.66.2.patch @@ -0,0 +1,61 @@ +diff --git a/node_modules/minecraft-protocol/src/client.js b/node_modules/minecraft-protocol/src/client.js +--- a/node_modules/minecraft-protocol/src/client.js ++++ b/node_modules/minecraft-protocol/src/client.js +@@ -53,6 +53,7 @@ class Client extends EventEmitter { + }) + + this.splitter.recognizeLegacyPing = state === states.HANDSHAKING ++ let deserializerErrorReported = false + + this.serializer.on('error', (e) => { + let parts +@@ -68,6 +69,8 @@ class Client extends EventEmitter { + }) + + this.deserializer.on('error', (e) => { ++ if (deserializerErrorReported) return ++ deserializerErrorReported = true + let parts = [] + if (e.field) { + parts = e.field.split('.') +diff --git a/node_modules/minecraft-protocol/src/client/encrypt.js b/node_modules/minecraft-protocol/src/client/encrypt.js +index 63cc2bd..1b95f5d 100644 +--- a/node_modules/minecraft-protocol/src/client/encrypt.js ++++ b/node_modules/minecraft-protocol/src/client/encrypt.js +@@ -23,6 +23,7 @@ module.exports = function (client, options) { + joinServerRequest(onJoinServerResponse) + } else { + if (packet.serverId !== '-') { ++ client.emit('noAuth') + debug('This server appears to be an online server and you are providing no password, the authentication will probably fail') + } + sendEncryptionKeyResponse() +diff --git a/node_modules/minecraft-protocol/src/server/login.js b/node_modules/minecraft-protocol/src/server/login.js +index ec40ed9..9f7cad2 100644 +--- a/node_modules/minecraft-protocol/src/server/login.js ++++ b/node_modules/minecraft-protocol/src/server/login.js +@@ -223,6 +223,13 @@ module.exports = function (client, server, options) { + + function onClientLoginAck () { + client.state = states.CONFIGURATION ++ if (options.disableDefaultConfiguration) { ++ client.once('finish_configuration', () => { ++ client.state = states.PLAY ++ server.emit('playerJoin', client) ++ }) ++ return ++ } + if (client.supportFeature('segmentedRegistryCodecData')) { + for (const key in options.registryCodec) { + const entry = options.registryCodec[key] +diff --git a/node_modules/minecraft-protocol/src/version.js b/node_modules/minecraft-protocol/src/version.js +index 51312c2..5ccad63 100644 +--- a/node_modules/minecraft-protocol/src/version.js ++++ b/node_modules/minecraft-protocol/src/version.js +@@ -2,5 +2,5 @@ + + module.exports = { + defaultVersion: '1.21.11', +- supportedVersions: ['1.7', '1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1', '1.21.3', '1.21.4', '1.21.5', '1.21.6', '1.21.8', '1.21.9', '1.21.11'] ++ supportedVersions: ['1.7', '1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1', '1.21.3', '1.21.4', '1.21.5', '1.21.6', '1.21.8', '1.21.9', '1.21.11', '26.2'] + } diff --git a/patches/prismarine-auth+2.7.0.patch b/patches/prismarine-auth+2.7.0.patch deleted file mode 100644 index 7662781..0000000 --- a/patches/prismarine-auth+2.7.0.patch +++ /dev/null @@ -1,79 +0,0 @@ -diff --git a/node_modules/prismarine-auth/src/TokenManagers/LiveTokenManager.js b/node_modules/prismarine-auth/src/TokenManagers/LiveTokenManager.js -index 062b3c0..7c714ec 100644 ---- a/node_modules/prismarine-auth/src/TokenManagers/LiveTokenManager.js -+++ b/node_modules/prismarine-auth/src/TokenManagers/LiveTokenManager.js -@@ -1,5 +1,5 @@ - const debug = require('debug')('prismarine-auth') -- -+const fetch = require('electron-fetch').default; - const { Endpoints } = require('../common/Constants') - const { checkStatus } = require('../common/Util') - -diff --git a/node_modules/prismarine-auth/src/TokenManagers/MinecraftBedrockServicesManager.js b/node_modules/prismarine-auth/src/TokenManagers/MinecraftBedrockServicesManager.js -index 06f9ed7..e0d168f 100644 ---- a/node_modules/prismarine-auth/src/TokenManagers/MinecraftBedrockServicesManager.js -+++ b/node_modules/prismarine-auth/src/TokenManagers/MinecraftBedrockServicesManager.js -@@ -1,5 +1,5 @@ - const debug = require('debug')('prismarine-auth') -- -+const fetch = require('electron-fetch').default; - const { Endpoints } = require('../common/Constants') - const { checkStatus } = require('../common/Util') - -diff --git a/node_modules/prismarine-auth/src/TokenManagers/MinecraftBedrockTokenManager.js b/node_modules/prismarine-auth/src/TokenManagers/MinecraftBedrockTokenManager.js -index 9c5036e..a91a61d 100644 ---- a/node_modules/prismarine-auth/src/TokenManagers/MinecraftBedrockTokenManager.js -+++ b/node_modules/prismarine-auth/src/TokenManagers/MinecraftBedrockTokenManager.js -@@ -1,5 +1,5 @@ - const debug = require('debug')('prismarine-auth') -- -+const fetch = require('electron-fetch').default; - const { Endpoints } = require('../common/Constants') - const { checkStatus } = require('../common/Util') - -diff --git a/node_modules/prismarine-auth/src/TokenManagers/MinecraftJavaTokenManager.js b/node_modules/prismarine-auth/src/TokenManagers/MinecraftJavaTokenManager.js -index c89e8f1..79df714 100644 ---- a/node_modules/prismarine-auth/src/TokenManagers/MinecraftJavaTokenManager.js -+++ b/node_modules/prismarine-auth/src/TokenManagers/MinecraftJavaTokenManager.js -@@ -1,6 +1,6 @@ - const debug = require('debug')('prismarine-auth') - const crypto = require('crypto') -- -+const fetch = require('electron-fetch').default; - const { Endpoints, fetchOptions } = require('../common/Constants') - const { checkStatus } = require('../common/Util') - -diff --git a/node_modules/prismarine-auth/src/TokenManagers/MsaTokenManager.js b/node_modules/prismarine-auth/src/TokenManagers/MsaTokenManager.js -index 568385d..fb24dd6 100644 ---- a/node_modules/prismarine-auth/src/TokenManagers/MsaTokenManager.js -+++ b/node_modules/prismarine-auth/src/TokenManagers/MsaTokenManager.js -@@ -1,6 +1,6 @@ - const msal = require('@azure/msal-node') - const debug = require('debug')('prismarine-auth') -- -+const fetch = require('electron-fetch').default; - class MsaTokenManager { - constructor (msalConfig, scopes, cache) { - this.msaClientId = msalConfig.auth.clientId -diff --git a/node_modules/prismarine-auth/src/TokenManagers/PlayfabTokenManager.js b/node_modules/prismarine-auth/src/TokenManagers/PlayfabTokenManager.js -index 3ba041f..4eca82c 100644 ---- a/node_modules/prismarine-auth/src/TokenManagers/PlayfabTokenManager.js -+++ b/node_modules/prismarine-auth/src/TokenManagers/PlayfabTokenManager.js -@@ -1,5 +1,5 @@ - const debug = require('debug')('prismarine-auth') -- -+const fetch = require('electron-fetch').default; - const { Endpoints } = require('../common/Constants') - - class PlayfabTokenManager { -diff --git a/node_modules/prismarine-auth/src/TokenManagers/XboxTokenManager.js b/node_modules/prismarine-auth/src/TokenManagers/XboxTokenManager.js -index dd79fd5..fc6e5ad 100644 ---- a/node_modules/prismarine-auth/src/TokenManagers/XboxTokenManager.js -+++ b/node_modules/prismarine-auth/src/TokenManagers/XboxTokenManager.js -@@ -1,5 +1,5 @@ - const crypto = require('crypto') -- -+const fetch = require('electron-fetch').default; - const XboxLiveAuth = require('@xboxreplay/xboxlive-auth') - const debug = require('debug')('prismarine-auth') - const { SmartBuffer } = require('smart-buffer') diff --git a/src/fetch-polyfill.js b/src/fetch-polyfill.js new file mode 100644 index 0000000..a989da8 --- /dev/null +++ b/src/fetch-polyfill.js @@ -0,0 +1,7 @@ +'use strict' + +if (typeof globalThis.fetch !== 'function') { + const electronFetch = require('electron-fetch') + + globalThis.fetch = electronFetch.default || electronFetch +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 3d13c68..8bc4706 100644 --- a/src/index.js +++ b/src/index.js @@ -1,24 +1,42 @@ -const { program } = require('commander'); +require('./fetch-polyfill') + +const { program } = require('commander') program .option('-a, --autostart', 'Automatically starts the program without the start window (all below options must be set)') .option('-e, --platform ', 'Platform (accepted values: java, bedrock)') .option('-v, --version ', 'The version to use (not needed for Bedrock)') .option('-c, --connect
    ', 'The address of the server to connect to') - .option('-p, --connect-port ', 'The port of the server to connect to') - .option('-P, --listen-port ', 'The port to listen on') + .option('-p, --connect-port ', 'The port of the server to connect to') + .option('-P, --listen-port ', 'The port to listen on') program.parse(process.argv) + const options = program.opts() if (options.autostart) { - if (!options.platform || !(options.version || options.platform !== 'java') || !options.connect || !options.connectPort || !options.listenPort) { - console.log('Not all required options were passed.') - program.help() - } + if ( + !options.platform || + !(options.version || options.platform !== 'java') || + !options.connect || + !options.connectPort || + !options.listenPort + ) { + console.log('Not all required options were passed.') + program.help() + } } -const {app, BrowserWindow, ipcMain, clipboard, Menu, dialog, shell } = require('electron') +const { + app, + BrowserWindow, + ipcMain, + clipboard, + Menu, + dialog, + shell +} = require('electron') + app.allowRendererProcessReuse = true const fs = require('fs') @@ -26,11 +44,13 @@ const Store = require('electron-store') const store = new Store() -let proxy // Defined later when an option is chosen -const resourcesPath = fs.existsSync(process.resourcesPath.concat('/app/')) - ? process.resourcesPath.concat('/app/') // Packaged with electron-forge - : './' // npm start +let proxy +const resourcesPath = fs.existsSync( + process.resourcesPath.concat('/app/') +) + ? process.resourcesPath.concat('/app/') + : './' const javaProxy = require('./proxy/java/proxy.js') const bedrockProxy = require('./proxy/bedrock/proxy.js') @@ -42,339 +62,1476 @@ const windowStateKeeper = require('electron-window-state') const unhandled = require('electron-unhandled') const osDataFolder = app.getPath('appData') - const dataFolder = osDataFolder + '/pakkit' -var currentScriptFile = null +let currentScriptFile = null -function makeMenu(direction, text, id, invalid, noData) { - if (direction !== 'clientbound' && direction !== 'serverbound') { - // This probably isn't a packet - return - } +let activeLogSave = null - const menuData = [ - { - icon: resourcesPath + `icons/${direction + (invalid ? '-invalid' : '')}.png`, - label: text, - enabled: false - }, - { - type: 'separator' - }, - { - label: 'Edit and resend', - click: () => { - BrowserWindow.getAllWindows()[0].send('editAndResend', JSON.stringify({ - id: id - })) - }, - visible: proxy.capabilities.modifyPackets - }, - { - label: 'Hide all packets of this type', - click: () => { - BrowserWindow.getAllWindows()[0].send('hideAllOfType', JSON.stringify({ - // Packet ID from link URL - id: id - })) - } - } - ] +function sendToWindow (win, channel, ...args) { + if ( + !win || + (typeof win.isDestroyed === 'function' && win.isDestroyed()) || + !win.webContents || + (typeof win.webContents.isDestroyed === 'function' && win.webContents.isDestroyed()) + ) { + return false + } - if (!noData) { - menuData.splice(2, 0, - { - label: proxy.capabilities.jsonData ? 'Copy JSON data' : 'Copy data', - click: () => { - BrowserWindow.getAllWindows()[0].send('copyPacketData', JSON.stringify({ - id: id - })) - } - } - ) + try { + win.webContents.send(channel, ...args) + return true + } catch (error) { + if (!String(error && error.message).toLowerCase().includes('destroyed')) { + console.error(error) } + return false + } +} - if (!noData && text.split(' ')[1] === 'position' && direction === 'clientbound') { - menuData.splice(3, 0, - { - label: 'Copy teleport as command', - click: () => { - BrowserWindow.getAllWindows()[0].send('copyTeleportCommand', JSON.stringify({ - id: id - })) - } - } - ) - } +function makeMenu (direction, text, id, invalid, noData) { + if ( + direction !== 'clientbound' && + direction !== 'serverbound' + ) { + return + } - if (proxy.capabilities.rawData) { - menuData.splice(3, 0, - { - label: 'Copy hex data', - click: () => { - BrowserWindow.getAllWindows()[0].send('copyHexData', JSON.stringify({ - id: id - })) - } - } + const menuData = [ + { + icon: + resourcesPath + + `icons/${direction + (invalid ? '-invalid' : '')}.png`, + label: text, + enabled: false + }, + { + type: 'separator' + }, + { + label: 'Edit and resend', + click: () => { + sendToWindow(BrowserWindow.getAllWindows()[0], + 'editAndResend', + JSON.stringify({ + id + }) ) + }, + visible: proxy.capabilities.modifyPackets + }, + { + label: 'Hide all packets of this type', + click: () => { + sendToWindow(BrowserWindow.getAllWindows()[0], + 'hideAllOfType', + JSON.stringify({ + id + }) + ) + } } + ] + + if (!noData) { + menuData.splice( + 2, + 0, + { + label: proxy.capabilities.jsonData + ? 'Copy JSON data' + : 'Copy data', + click: () => { + sendToWindow(BrowserWindow.getAllWindows()[0], + 'copyPacketData', + JSON.stringify({ + id + }) + ) + } + } + ) + } - return Menu.buildFromTemplate(menuData) + if ( + !noData && + text.split(' ')[1] === 'position' && + direction === 'clientbound' + ) { + menuData.splice( + 3, + 0, + { + label: 'Copy teleport as command', + click: () => { + sendToWindow(BrowserWindow.getAllWindows()[0], + 'copyTeleportCommand', + JSON.stringify({ + id + }) + ) + } + } + ) + } + + if (proxy.capabilities.rawData) { + menuData.splice( + 3, + 0, + { + label: 'Copy hex data', + click: () => { + sendToWindow(BrowserWindow.getAllWindows()[0], + 'copyHexData', + JSON.stringify({ + id + }) + ) + } + } + ) + } + + return Menu.buildFromTemplate(menuData) } -function createWindow() { - // Let us register listeners on the window, so we can update the state - // automatically (the listeners will be removed when the window is closed) - // and restore the maximized or full screen state - - // Create the browser window. - const win = new BrowserWindow({ - height: store.get('authConsentGiven') ? 550 : 650, - width: 480, - // resizable: false, - // frame: false, - webPreferences: { - nodeIntegration: true, - contextIsolation: false, - enableRemoteModule: true - }, - icon: resourcesPath + 'icons/icon.png' - }) +function createWindow () { + const win = new BrowserWindow({ + height: store.get('authConsentGiven') ? 550 : 650, + width: 480, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + enableRemoteModule: true + }, + icon: resourcesPath + 'icons/icon.png' + }) - win.setMenuBarVisibility(false) + win.setMenuBarVisibility(false) - // Open the DevTools. - // win.webContents.openDevTools() - electronLocalShortcut.register(win, 'F12', () => { - win.openDevTools() - }) + electronLocalShortcut.register(win, 'F12', () => { + win.openDevTools() + }) - win.webContents.setWindowOpenHandler(function(details) { - shell.openExternal(details.url) - return { action: 'deny' } - }) + win.webContents.setWindowOpenHandler(details => { + shell.openExternal(details.url) - unhandled({ - logger: (err) => { - win.send('error', JSON.stringify({msg: err.message, stack: err.stack})) - console.log(err.stack) - console.error(err) - }, - showDialog: false - }) + return { + action: 'deny' + } + }) - win.setMenu(null) - // and load the index.html of the app. - if (options.autostart) { - startProxy({ - // TODO: make online-mode working in headless via command-line parameters - consent: false, - onlineMode: false, - connectAddress: options.connect, - connectPort: options.connectPort, - listenPort: options.listenPort, - platform: options.platform, - version: options.version + unhandled({ + logger: err => { + sendToWindow(win, + 'error', + JSON.stringify({ + msg: err.message, + stack: err.stack }) - } else { - win.loadFile('html/startPage/index.html') - } + ) + + console.log(err.stack) + console.error(err) + }, + showDialog: false + }) + + win.setMenu(null) + + if (options.autostart) { + startProxy({ + consent: false, + onlineMode: false, + connectAddress: options.connect, + connectPort: options.connectPort, + listenPort: options.listenPort, + platform: options.platform, + version: options.version + }) + } else { + win.loadFile('html/startPage/index.html') + } } -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. app.whenReady().then(createWindow) -// Quit when all windows are closed. app.on('window-all-closed', () => { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - if (proxy) { - proxy.end() - } - app.quit() + if (process.platform !== 'darwin') { + if (proxy) { + proxy.end() + } + + if (activeLogSave) { + activeLogSave.stream.destroy() + activeLogSave = null } + + app.quit() + } }) app.on('activate', () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (BrowserWindow.getAllWindows().length === 0) { - createWindow() - } + if (BrowserWindow.getAllWindows().length === 0) { + createWindow() + } }) ipcMain.on('startProxy', (event, arg) => { - const ipcMessage = JSON.parse(arg) - startProxy(ipcMessage) + const ipcMessage = JSON.parse(arg) + startProxy(ipcMessage) }) function showAuthCode (data) { - const win = BrowserWindow.getAllWindows()[0] - win.send('showAuthCode', JSON.stringify(data)) + const win = BrowserWindow.getAllWindows()[0] + + sendToWindow(win, + 'showAuthCode', + JSON.stringify(data) + ) } function startProxy (args) { - switch(args.platform){ - case 'bedrock': - proxy = bedrockProxy - break; - case 'java': - proxy = javaProxy - break; - } + switch (args.platform) { + case 'bedrock': + proxy = bedrockProxy + break + + case 'java': + proxy = javaProxy + break + + default: + throw new Error( + `Unsupported platform: ${args.platform}` + ) + } - const win = BrowserWindow.getAllWindows()[0] + const win = BrowserWindow.getAllWindows()[0] - packetHandler.init(BrowserWindow.getAllWindows()[0], ipcMain, proxy) - proxy.startProxy(args.connectAddress, args.connectPort, args.listenPort, args.version, args.onlineMode, - args.consent, packetHandler.packetHandler, packetHandler.messageHandler , dataFolder, () => { - win.send('updateFiltering', '') - }, showAuthCode) + packetHandler.init( + win, + ipcMain, + proxy + ) - win.loadFile('html/mainPage/index.html') + proxy.startProxy( + args.connectAddress, + args.connectPort, + args.listenPort, + args.version, + args.onlineMode, + args.consent, + packetHandler.packetHandler, + packetHandler.messageHandler, + dataFolder, + () => { + sendToWindow(win, 'updateFiltering', '') + }, + showAuthCode + ) - // Load the previous state with fallback to defaults - const mainWindowState = windowStateKeeper({ - defaultWidth: 1000, - defaultHeight: 800 - }); + win.loadFile('html/mainPage/index.html') - win.setResizable(true) - //win.setPosition(mainWindowState.x, mainWindowState.y) // TODO: figure out why this causes an issue - win.setSize(mainWindowState.width, mainWindowState.height) + const mainWindowState = windowStateKeeper({ + defaultWidth: 1000, + defaultHeight: 800 + }) - mainWindowState.manage(win) + win.setResizable(true) + + win.setSize( + mainWindowState.width, + mainWindowState.height + ) + + mainWindowState.manage(win) } -ipcMain.on('proxyCapabilities', (event, arg) => { - event.returnValue = proxy.capabilities +ipcMain.on('proxyCapabilities', event => { + event.returnValue = proxy.capabilities }) ipcMain.on('copyToClipboard', (event, arg) => { - clipboard.writeText(arg) + clipboard.writeText(arg === undefined || arg === null ? '' : String(arg)) }) ipcMain.on('contextMenu', (event, arg) => { - const ipcMessage = JSON.parse(arg) - makeMenu(ipcMessage.direction, ipcMessage.text, ipcMessage.id, ipcMessage.invalid, ipcMessage.noData).popup(BrowserWindow.getAllWindows()[0]) + const ipcMessage = JSON.parse(arg) + + makeMenu( + ipcMessage.direction, + ipcMessage.text, + ipcMessage.id, + ipcMessage.invalid, + ipcMessage.noData + ).popup(BrowserWindow.getAllWindows()[0]) }) -ipcMain.on('relaunchApp', (event, arg) => { - app.relaunch() - app.exit() +ipcMain.on('relaunchApp', () => { + app.relaunch() + app.exit() }) -ipcMain.on('saveLog', async (event, arg) => { - const win = BrowserWindow.getAllWindows()[0] +function waitForStreamOpen (stream) { + return new Promise((resolve, reject) => { + if (!stream.pending) { + resolve() + return + } - const result = await dialog.showSaveDialog(win, { - filters: [ - { name: 'pakkit log files', extensions: ['pakkit-json'] }, - // { name: 'All Files', extensions: ['*'] } - ] - }) + const onOpen = () => { + cleanup() + resolve() + } - if (!result.canceled) { - const realPath = result.filePath.endsWith('.pakkit-json') ? result.filePath : result.filePath + '.pakkit-json' - console.log('Saving log to', realPath) - fs.writeFile(realPath, arg, function (err) { - if (err) throw err; - console.log('Saved!'); - }) + const onError = err => { + cleanup() + reject(err) } -}) -ipcMain.on('loadLog', async (event, arg) => { - const win = BrowserWindow.getAllWindows()[0] + const cleanup = () => { + stream.removeListener('open', onOpen) + stream.removeListener('error', onError) + } - const result = await dialog.showOpenDialog(win, { - filters: [ - { name: 'pakkit log files', extensions: ['pakkit-json'] }, - { name: 'All Files', extensions: ['*'] } - ], - properties: ['openFile'] - }) + stream.once('open', onOpen) + stream.once('error', onError) + }) +} - if (!result.canceled) { - // It's an array, but we have multi-select off so it should only have one item - console.log('Loading log from', result.filePaths[0]) - fs.readFile(result.filePaths[0], 'utf-8', function(err, data) { - if (err) throw err; - console.log('File has been read') - win.send('loadLogData', data) - }) +function writeToStream (stream, data) { + return new Promise((resolve, reject) => { + if ( + !stream || + stream.destroyed || + stream.writableEnded || + stream.closed + ) { + reject( + new Error('Log stream is already closed') + ) + return } -}) -ipcMain.on('saveAsScript', async (event, arg) => { - const win = BrowserWindow.getAllWindows()[0] + let settled = false - const result = await dialog.showSaveDialog(win, { - title: "Save user script", - filters: [ - { name: 'javascript files', extensions: ['js'] } - ] - }) + const cleanup = () => { + stream.removeListener('error', onError) + stream.removeListener('drain', onDrain) + } - if (!result.canceled) { - const realPath = result.filePath.endsWith('.js') ? result.filePath : result.filePath + '.js' - win.send('disableBtnScriptSave') - console.log('Saving script to', realPath) - fs.writeFile(realPath, arg, function (err) { - if (err) throw err; - console.log('Saved!'); - currentScriptFile = realPath - win.send('enableBtnScriptSave', currentScriptFile) - }) + const finishResolve = () => { + if (settled) { + return + } + + settled = true + cleanup() + resolve() } -}) -ipcMain.on('saveScript', async (event, arg) => { - const win = BrowserWindow.getAllWindows()[0] - const validScriptPath = (currentScriptFile != null || fs.existsSync(currentScriptFile) ) + const finishReject = err => { + if (settled) { + return + } + + settled = true + cleanup() + reject(err) + } - win.send('disableBtnScriptSave') + const onError = err => { + finishReject(err) + } - if (validScriptPath) { - console.log('Overwrite script to', currentScriptFile) - fs.writeFile(currentScriptFile, arg, function (err) { - if (err) throw err; - console.log('Saved!'); - win.send('enableBtnScriptSave', currentScriptFile) - }) + const onDrain = () => { + finishResolve() } + + stream.once('error', onError) + + let canContinue + + try { + canContinue = stream.write(data) + } catch (err) { + finishReject(err) + return + } + + if (canContinue) { + finishResolve() + } else { + stream.once('drain', onDrain) + } + }) +} + +function finishStream (stream) { + return new Promise((resolve, reject) => { + if (!stream || stream.destroyed) { + reject( + new Error('Cannot finish a destroyed stream') + ) + return + } + + let settled = false + + const cleanup = () => { + stream.removeListener('finish', onFinish) + stream.removeListener('error', onError) + } + + const onFinish = () => { + if (settled) { + return + } + + settled = true + cleanup() + resolve() + } + + const onError = err => { + if (settled) { + return + } + + settled = true + cleanup() + reject(err) + } + + stream.once('finish', onFinish) + stream.once('error', onError) + + stream.end() + }) +} + +function resetActiveLogSave (sessionId) { + if ( + activeLogSave && + activeLogSave.sessionId === sessionId + ) { + activeLogSave = null + } +} + +function getActiveSaveOrThrow (sessionId) { + if (!activeLogSave) { + throw new Error('No active log save') + } + + if (!sessionId) { + throw new Error('Missing save session ID') + } + + if (activeLogSave.sessionId !== sessionId) { + throw new Error( + 'Invalid or expired save session' + ) + } + + return activeLogSave +} + +ipcMain.handle('startSaveLog', async () => { + const win = BrowserWindow.getAllWindows()[0] + + if (activeLogSave) { + console.warn( + 'startSaveLog ignored because a save is already running:', + activeLogSave.sessionId + ) + + return { + canceled: false, + busy: true, + sessionId: activeLogSave.sessionId, + filePath: activeLogSave.filePath, + packetCount: activeLogSave.packetCount + } + } + + const result = await dialog.showSaveDialog(win, { + title: 'Save packet log', + filters: [ + { + name: 'pakkit log files', + extensions: ['pakkit-json'] + } + ] + }) + + if (result.canceled || !result.filePath) { + return { + canceled: true, + busy: false + } + } + + const filePath = + result.filePath.endsWith('.pakkit-json') + ? result.filePath + : result.filePath + '.pakkit-json' + + const sessionId = + `${Date.now()}-${Math.random() + .toString(36) + .slice(2)}` + + const stream = fs.createWriteStream( + filePath, + { + flags: 'w', + encoding: 'utf8', + highWaterMark: 256 * 1024 + } + ) + + const saveState = { + sessionId, + filePath, + stream, + packetCount: 0, + firstPacket: true, + failed: null, + queue: Promise.resolve(), + finishing: false, + canceled: false + } + + activeLogSave = saveState + + stream.on('error', err => { + saveState.failed = err + + console.error( + 'Log stream failed:', + err + ) + }) + + try { + await waitForStreamOpen(stream) + await writeToStream(stream, '[') + + console.log( + 'Saving log to', + filePath, + 'session:', + sessionId + ) + + return { + canceled: false, + busy: false, + sessionId, + filePath + } + } catch (err) { + saveState.failed = err + stream.destroy() + resetActiveLogSave(sessionId) + + try { + await fs.promises.unlink(filePath) + } catch (unlinkError) { + if (unlinkError.code !== 'ENOENT') { + console.error( + 'Could not remove failed save file:', + unlinkError + ) + } + } + + throw err + } }) -ipcMain.on('loadScript', async (event, arg) => { - const win = BrowserWindow.getAllWindows()[0] - const result = await dialog.showOpenDialog(win, { - title: "Load user script", - filters: [ - { name: 'javascript files', extensions: ['js'] } - ], - properties: ['openFile'] +ipcMain.handle( + 'appendSaveLogChunk', + async (event, request) => { + if ( + !request || + typeof request !== 'object' + ) { + throw new TypeError( + 'Invalid appendSaveLogChunk request' + ) + } + + const { + sessionId, + packets + } = request + + const saveState = + getActiveSaveOrThrow(sessionId) + + if (saveState.finishing) { + throw new Error( + 'Log save is already finishing' + ) + } + + if (saveState.canceled) { + throw new Error( + 'Log save was canceled' + ) + } + + if (!Array.isArray(packets)) { + throw new TypeError( + 'packets must be an array' + ) + } + + if (saveState.failed) { + throw saveState.failed + } + + saveState.queue = + saveState.queue.then(async () => { + if (saveState.failed) { + throw saveState.failed + } + + if (saveState.canceled) { + throw new Error( + 'Log save was canceled' + ) + } + + const serializedPackets = [] + + for (const packet of packets) { + const serialized = JSON.stringify(packet) + + if (serialized === undefined) { + continue + } + + serializedPackets.push(serialized) + saveState.packetCount++ + } + + if (serializedPackets.length > 0) { + let output = serializedPackets.join(',') + + if (!saveState.firstPacket) { + output = ',' + output + } + + saveState.firstPacket = false + + await writeToStream( + saveState.stream, + output + ) + } + }) + + await saveState.queue + + return { + success: true, + packetCount: saveState.packetCount + } + } +) + +ipcMain.handle( + 'finishSaveLog', + async (event, request) => { + if ( + !request || + typeof request !== 'object' + ) { + throw new TypeError( + 'Invalid finishSaveLog request' + ) + } + + const { + sessionId + } = request + + const saveState = + getActiveSaveOrThrow(sessionId) + + if (saveState.finishing) { + throw new Error( + 'Log save is already finishing' + ) + } + + saveState.finishing = true + + try { + await saveState.queue + + if (saveState.failed) { + throw saveState.failed + } + + if (saveState.canceled) { + throw new Error( + 'Log save was canceled' + ) + } + + await writeToStream( + saveState.stream, + ']' + ) + + await finishStream( + saveState.stream + ) + + console.log( + `Saved ${saveState.packetCount} packets to ${saveState.filePath}` + ) + + return { + success: true, + filePath: saveState.filePath, + packetCount: saveState.packetCount + } + } catch (err) { + saveState.failed = err + + if ( + saveState.stream && + !saveState.stream.destroyed + ) { + saveState.stream.destroy() + } + + throw err + } finally { + resetActiveLogSave(sessionId) + } + } +) + +ipcMain.handle( + 'cancelSaveLog', + async (event, request) => { + if ( + !request || + typeof request !== 'object' || + !request.sessionId + ) { + console.warn( + 'Ignoring cancelSaveLog without sessionId' + ) + + return { + success: false, + ignored: true + } + } + + const saveState = activeLogSave + + if (!saveState) { + return { + success: true, + alreadyClosed: true + } + } + + if ( + saveState.sessionId !== + request.sessionId + ) { + console.warn( + 'Ignoring cancelSaveLog for another session' + ) + + return { + success: false, + ignored: true + } + } + + saveState.canceled = true + + resetActiveLogSave( + saveState.sessionId + ) + + if ( + saveState.stream && + !saveState.stream.destroyed + ) { + saveState.stream.destroy() + } + + try { + await fs.promises.unlink( + saveState.filePath + ) + } catch (err) { + if (err.code !== 'ENOENT') { + console.error( + 'Could not remove canceled log file:', + err + ) + } + } + + return { + success: true + } + } +) + +let activeLogLoad = null + +function waitForRendererAck (webContents, channel, payload) { + return new Promise((resolve, reject) => { + const requestId = + `${Date.now()}-${Math.random().toString(36).slice(2)}` + + const responseChannel = + `${channel}-ack-${requestId}` + + const timeout = setTimeout(() => { + ipcMain.removeAllListeners(responseChannel) + + reject( + new Error( + `Renderer did not acknowledge ${channel}` + ) + ) + }, 30000) + + ipcMain.once(responseChannel, (event, response) => { + clearTimeout(timeout) + + if (response && response.error) { + reject( + new Error(response.error) + ) + return + } + + resolve(response) }) - if (!result.canceled) { - win.send('disableBtnScriptSave') - - // It's an array, but we have multi-select off so it should only have one item - console.log('Loading script from', result.filePaths[0]) - fs.readFile(result.filePaths[0], 'utf-8', function(err, data) { - if (err) throw err; - console.log('File has been read') - currentScriptFile = result.filePaths[0] - win.send('loadScriptData', data) - win.send('enableBtnScriptSave', currentScriptFile) - }) + if (!sendToWindow({ webContents }, channel, { + requestId, + payload + })) { + clearTimeout(timeout) + ipcMain.removeAllListeners(responseChannel) + reject(new Error(`Renderer is unavailable for ${channel}`)) + } + }) +} + +async function streamJsonArrayFile ( + filePath, + onChunk, + options = {} +) { + const readChunkSize = + options.readChunkSize || 1024 * 1024 + + const outputChunkSize = + options.outputChunkSize || 1000 + + const outputChunkBytes = + options.outputChunkBytes || 2 * 1024 * 1024 + + const stream = fs.createReadStream(filePath, { + encoding: 'utf8', + highWaterMark: readChunkSize + }) + + let buffer = '' + let scanIndex = 0 + + let arrayStarted = false + let arrayFinished = false + + let objectStart = -1 + let depth = 0 + let inString = false + let escaped = false + + let packetChunk = [] + let packetChunkBytes = 0 + let packetCount = 0 + + const flushPackets = async () => { + if (packetChunk.length === 0) { + return + } + + const chunk = packetChunk + packetChunk = [] + packetChunkBytes = 0 + + await onChunk(chunk, packetCount) + } + + for await (const data of stream) { + buffer += data + + while (scanIndex < buffer.length) { + const char = buffer[scanIndex] + + if (!arrayStarted) { + if (/\s/.test(char)) { + scanIndex++ + continue + } + + if (char !== '[') { + throw new Error( + 'Invalid pakkit log: expected JSON array' + ) + } + + arrayStarted = true + scanIndex++ + continue + } + + if (arrayFinished) { + if (!/\s/.test(char)) { + throw new Error( + 'Invalid data after JSON array' + ) + } + + scanIndex++ + continue + } + + if (objectStart === -1) { + if ( + /\s/.test(char) || + char === ',' + ) { + scanIndex++ + continue + } + + if (char === ']') { + arrayFinished = true + scanIndex++ + continue + } + + if (char !== '{') { + throw new Error( + `Invalid packet JSON near character ${scanIndex}` + ) + } + + objectStart = scanIndex + depth = 1 + inString = false + escaped = false + scanIndex++ + continue + } + + if (inString) { + if (escaped) { + escaped = false + scanIndex++ + continue + } + + if (char === '\\') { + escaped = true + scanIndex++ + continue + } + + if (char === '"') { + inString = false + } + + scanIndex++ + continue + } + + if (char === '"') { + inString = true + scanIndex++ + continue + } + + if (char === '{' || char === '[') { + depth++ + scanIndex++ + continue + } + + if (char === '}' || char === ']') { + depth-- + + if (depth === 0) { + const jsonText = buffer.slice( + objectStart, + scanIndex + 1 + ) + + let packet + + try { + packet = JSON.parse(jsonText) + } catch (err) { + throw new Error( + `Invalid packet JSON near packet ${packetCount}: ${err.message}` + ) + } + + packetChunk.push(packet) + packetChunkBytes += Buffer.byteLength(jsonText, 'utf8') + packetCount++ + + scanIndex++ + objectStart = -1 + + if ( + packetChunk.length >= outputChunkSize || + packetChunkBytes >= outputChunkBytes + ) { + await flushPackets() + } + + buffer = buffer.slice(scanIndex) + scanIndex = 0 + + continue + } + + scanIndex++ + continue + } + + scanIndex++ + } + + if (objectStart === -1 && scanIndex > 0) { + buffer = buffer.slice(scanIndex) + scanIndex = 0 + } + } + + if (!arrayStarted) { + throw new Error( + 'Invalid pakkit log: empty file' + ) + } + + if (objectStart !== -1) { + throw new Error( + 'Invalid pakkit log: incomplete packet object' + ) + } + + if (!arrayFinished) { + throw new Error( + 'Invalid pakkit log: missing closing bracket' + ) + } + + await flushPackets() + + return packetCount +} + +ipcMain.handle('startLoadLog', async () => { + const win = BrowserWindow.getAllWindows()[0] + + if (activeLogLoad) { + return { + canceled: false, + busy: true } + } + + const result = await dialog.showOpenDialog( + win, + { + title: 'Load packet log', + filters: [ + { + name: 'pakkit log files', + extensions: ['pakkit-json'] + }, + { + name: 'All Files', + extensions: ['*'] + } + ], + properties: ['openFile'] + } + ) + + if ( + result.canceled || + !result.filePaths || + !result.filePaths[0] + ) { + return { + canceled: true, + busy: false + } + } + + const filePath = result.filePaths[0] + + const loadId = + `${Date.now()}-${Math.random().toString(36).slice(2)}` + + activeLogLoad = { + loadId, + filePath, + canceled: false + } + + const stat = await fs.promises.stat(filePath) + + console.log( + 'Loading log from', + filePath + ) + + setImmediate(async () => { + const loadState = activeLogLoad + + try { + await waitForRendererAck( + win.webContents, + 'loadLogStart', + { + loadId, + filePath, + fileSize: stat.size + } + ) + + const packetCount = + await streamJsonArrayFile( + filePath, + async packets => { + if ( + !activeLogLoad || + activeLogLoad.loadId !== loadId || + loadState.canceled + ) { + throw new Error( + 'Log load canceled' + ) + } + + await waitForRendererAck( + win.webContents, + 'loadLogChunk', + { + loadId, + packets + } + ) + }, + { + readChunkSize: 1024 * 1024, + outputChunkSize: 1000, + outputChunkBytes: 2 * 1024 * 1024 + } + ) + + await waitForRendererAck( + win.webContents, + 'loadLogFinish', + { + loadId, + packetCount + } + ) + + console.log( + `Loaded ${packetCount} packets from ${filePath}` + ) + } catch (err) { + console.error( + 'Could not load log:', + err + ) + + sendToWindow(win, + 'loadLogError', + { + loadId, + error: err.message + } + ) + } finally { + if ( + activeLogLoad && + activeLogLoad.loadId === loadId + ) { + activeLogLoad = null + } + } + }) + + return { + canceled: false, + busy: false, + loadId, + filePath, + fileSize: stat.size + } }) -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and require them here. \ No newline at end of file +ipcMain.handle('cancelLoadLog', async (event, request) => { + if (!activeLogLoad) { + return { + success: true + } + } + + if ( + !request || + request.loadId !== activeLogLoad.loadId + ) { + return { + success: false, + ignored: true + } + } + + activeLogLoad.canceled = true + + return { + success: true + } +}) + +ipcMain.on( + 'saveAsScript', + async (event, arg) => { + const win = + BrowserWindow.getAllWindows()[0] + + const result = + await dialog.showSaveDialog( + win, + { + title: 'Save user script', + filters: [ + { + name: 'javascript files', + extensions: ['js'] + } + ] + } + ) + + if ( + result.canceled || + !result.filePath + ) { + return + } + + const realPath = + result.filePath.endsWith('.js') + ? result.filePath + : result.filePath + '.js' + + sendToWindow(win, + 'disableBtnScriptSave' + ) + + console.log( + 'Saving script to', + realPath + ) + + fs.writeFile( + realPath, + arg, + err => { + if (err) { + console.error( + 'Could not save script:', + err + ) + + sendToWindow(win, + 'enableBtnScriptSave', + currentScriptFile + ) + + return + } + + console.log('Saved!') + + currentScriptFile = realPath + + sendToWindow(win, + 'enableBtnScriptSave', + currentScriptFile + ) + } + ) + } +) + +ipcMain.on( + 'saveScript', + async (event, arg) => { + const win = + BrowserWindow.getAllWindows()[0] + + const validScriptPath = + currentScriptFile !== null && + fs.existsSync(currentScriptFile) + + sendToWindow(win, + 'disableBtnScriptSave' + ) + + if (!validScriptPath) { + console.error( + 'No valid script file selected' + ) + + sendToWindow(win, + 'enableBtnScriptSave', + currentScriptFile + ) + + return + } + + console.log( + 'Overwrite script to', + currentScriptFile + ) + + fs.writeFile( + currentScriptFile, + arg, + err => { + if (err) { + console.error( + 'Could not overwrite script:', + err + ) + + sendToWindow(win, + 'enableBtnScriptSave', + currentScriptFile + ) + + return + } + + console.log('Saved!') + + sendToWindow(win, + 'enableBtnScriptSave', + currentScriptFile + ) + } + ) + } +) + +ipcMain.on( + 'loadScript', + async () => { + const win = + BrowserWindow.getAllWindows()[0] + + const result = + await dialog.showOpenDialog( + win, + { + title: 'Load user script', + filters: [ + { + name: 'javascript files', + extensions: ['js'] + } + ], + properties: ['openFile'] + } + ) + + if ( + result.canceled || + !result.filePaths || + !result.filePaths[0] + ) { + return + } + + sendToWindow(win, + 'disableBtnScriptSave' + ) + + const filePath = + result.filePaths[0] + + console.log( + 'Loading script from', + filePath + ) + + fs.readFile( + filePath, + 'utf8', + (err, data) => { + if (err) { + console.error( + 'Could not load script:', + err + ) + + sendToWindow(win, + 'enableBtnScriptSave', + currentScriptFile + ) + + return + } + + console.log( + 'File has been read' + ) + + currentScriptFile = filePath + + sendToWindow(win, + 'loadScriptData', + data + ) + + sendToWindow(win, + 'enableBtnScriptSave', + currentScriptFile + ) + } + ) + } +) diff --git a/src/packetHandler.js b/src/packetHandler.js index 987e9af..9a83847 100644 --- a/src/packetHandler.js +++ b/src/packetHandler.js @@ -1,9 +1,9 @@ const _eval = require('node-eval') BigInt.prototype.toJSON = function () { - const int = Number.parseInt(this.toString()); - return int ?? this.toString(); -}; + const value = Number(this) + return Number.isSafeInteger(value) ? value : this.toString() +} let mainWindow let ipcMain @@ -12,6 +12,27 @@ let scriptingEnabled = false let currentScript let currentScriptModule +function sendToRenderer (channel, payload) { + if ( + !mainWindow || + (typeof mainWindow.isDestroyed === 'function' && mainWindow.isDestroyed()) || + !mainWindow.webContents || + (typeof mainWindow.webContents.isDestroyed === 'function' && mainWindow.webContents.isDestroyed()) + ) { + return false + } + + try { + mainWindow.send(channel, payload) + return true + } catch (error) { + if (!String(error && error.message).toLowerCase().includes('destroyed')) { + console.error(error) + } + return false + } +} + const server = { sendPacket: function (meta, data) { proxy.writeToServer(meta, data, true) @@ -29,6 +50,12 @@ exports.init = function (window, passedIpcMain, passedProxy) { ipcMain = passedIpcMain proxy = passedProxy + if (window && typeof window.once === 'function') { + window.once('closed', () => { + if (mainWindow === window) mainWindow = null + }) + } + ipcMain.on('injectPacket', (event, arg) => { const ipcMessage = JSON.parse(arg) if (ipcMessage.direction === 'clientbound') { @@ -52,7 +79,7 @@ exports.init = function (window, passedIpcMain, passedProxy) { }) } -exports.packetHandler = function (direction, meta, data, id, canUseScripting, packetValid) { +exports.packetHandler = function (direction, meta, data, id, canUseScripting, packetValid, raw) { try { // TODO: Maybe write raw data? if (proxy.capabilities.scriptingSupport && canUseScripting && scriptingEnabled) { @@ -62,13 +89,15 @@ exports.packetHandler = function (direction, meta, data, id, canUseScripting, pa currentScriptModule.upstreamHandler(meta, data, server, client) } } - let raw = proxy.getRaw(meta.name, data) - mainWindow.send('packet', JSON.stringify({ meta: meta, data: data, direction: direction, hexIdString: id, raw: raw, time: Date.now(), packetValid: packetValid })) + const packetRaw = raw || proxy.getRaw(direction, meta.name, data) + sendToRenderer('packet', JSON.stringify({ meta: meta, data: data, direction: direction, hexIdString: id, raw: packetRaw, time: Date.now(), packetValid: packetValid })) } catch (err) { - console.error(err) + if (!String(err && err.message).toLowerCase().includes('destroyed')) { + console.error(err) + } } } exports.messageHandler = function (header, info, fatal) { - mainWindow.send('message', JSON.stringify({ header: header, info: info, fatal: fatal })) + sendToRenderer('message', JSON.stringify({ header: header, info: info, fatal: fatal })) } diff --git a/src/proxy/java/proxy.js b/src/proxy/java/proxy.js index 8479394..087761f 100644 --- a/src/proxy/java/proxy.js +++ b/src/proxy/java/proxy.js @@ -1,34 +1,112 @@ -// Modified from https://github.com/PrismarineJS/node-minecraft-protocol/blob/master/examples/proxy/proxy.js +// Modified from: +// https://github.com/PrismarineJS/node-minecraft-protocol/blob/master/examples/proxy/proxy.js const mc = require('minecraft-protocol') const minecraftFolder = require('minecraft-folder-path') -const {getRaw} = require("../bedrock/proxy"); const states = mc.states +let proxyServer let realClient let realServer -let toClientMappings -let toServerMappings +let toClientMappings = {} +let toServerMappings = {} let storedCallback let scriptingEnabled = false +let authWindowOpen = false -// https://gist.github.com/timoxley/1689041 -function isPortTaken (port, fn) { +function isPortTaken (port, callback) { const net = require('net') + const tester = net.createServer() - .once('error', function (err) { - if (err.code != 'EADDRINUSE') return fn(err) - fn(null, true) + .once('error', function (error) { + if (error.code !== 'EADDRINUSE') { + callback(error) + return + } + + callback(null, true) }) - .once('listening', function() { - tester.once('close', function() { fn(null, false) }) + .once('listening', function () { + tester + .once('close', function () { + callback(null, false) + }) .close() }) .listen(port) } +function isPlayState (state) { + return state === states.PLAY || state === 'play' +} + +function isConfigurationState (state) { + return state === states.CONFIGURATION || state === 'configuration' +} + +function getMappingId (mappings, packetName) { + if (!mappings || typeof packetName !== 'string') { + return undefined + } + + return Object.keys(mappings) + .find(id => mappings[id] === packetName) +} + +function safeCall (callback, ...args) { + if (typeof callback === 'function') { + try { + callback(...args) + } catch (error) { + if (!String(error && error.message).toLowerCase().includes('destroyed')) { + console.error(error) + } + } + } +} + +function createCustomPackets (mcdata) { + const majorVersion = mcdata.version && mcdata.version.majorVersion + if (!majorVersion) return undefined + + const particleType = mcdata.protocol && + mcdata.protocol.play && + mcdata.protocol.play.toClient && + mcdata.protocol.play.toClient.types && + mcdata.protocol.play.toClient.types.packet_world_particles + + if (!Array.isArray(particleType) || !Array.isArray(particleType[1])) { + return undefined + } + + const usesParticleRegistry = particleType[1] + .some(field => field && field.type === 'Particle') + + if (!usesParticleRegistry) return undefined + + const fields = particleType[1].map((field, index) => ({ + name: index === 0 ? 'raw' : (field.name || `field${index}`), + type: 'restBuffer' + })) + + return { + [majorVersion]: { + play: { + toClient: { + types: { + packet_world_particles: [ + 'container', + fields + ] + } + } + } + } + } +} + exports.capabilities = { modifyPackets: true, jsonData: true, @@ -36,269 +114,802 @@ exports.capabilities = { scriptingSupport: true, clientboundPackets: [], serverboundPackets: [], - // TODO: Only for latest, or fetch older pages wikiVgPage: 'https://wiki.vg/Protocol', versionId: undefined } -let authWindowOpen = false - -exports.startProxy = function (host, port, listenPort, version, onlineMode, authConsent, callback, messageCallback, dataFolder, - updateFilteringCallback, authCodeCallback) { +exports.startProxy = function ( + host, + port, + listenPort, + version, + onlineMode, + authConsent, + callback, + messageCallback, + dataFolder, + updateFilteringCallback, + authCodeCallback +) { storedCallback = callback - authConsent = false - // . cannot be in a JSON property name with electron-store - exports.capabilities.versionId = 'java-node-minecraft-protocol-' + version.split('.').join('-') + port = Number(port) + listenPort = Number(listenPort) + + exports.capabilities.versionId = + 'java-node-minecraft-protocol-' + + version.split('.').join('-') + + const mcdata = require('minecraft-data')(version) + + if (!mcdata) { + safeCall( + messageCallback, + 'Unable to start pakkit', + `Minecraft data is unavailable for version ${version}`, + true + ) + return + } + + const playProtocol = mcdata.protocol && + mcdata.protocol.play + + if (!playProtocol) { + safeCall( + messageCallback, + 'Unable to start pakkit', + `Play protocol data is unavailable for version ${version}`, + true + ) + return + } + + const clientPacketType = + playProtocol.toClient && + playProtocol.toClient.types && + playProtocol.toClient.types.packet && + playProtocol.toClient.types.packet[1] && + playProtocol.toClient.types.packet[1][0] && + playProtocol.toClient.types.packet[1][0].type && + playProtocol.toClient.types.packet[1][0].type[1] + + const serverPacketType = + playProtocol.toServer && + playProtocol.toServer.types && + playProtocol.toServer.types.packet && + playProtocol.toServer.types.packet[1] && + playProtocol.toServer.types.packet[1][0] && + playProtocol.toServer.types.packet[1][0].type && + playProtocol.toServer.types.packet[1][0].type[1] + + toClientMappings = + (clientPacketType && clientPacketType.mappings) || {} - const mcdata = require('minecraft-data')(version) // Used to get packets, may remove if I find a better way - toClientMappings = mcdata.protocol.play.toClient.types.packet[1][0].type[1].mappings - toServerMappings = mcdata.protocol.play.toServer.types.packet[1][0].type[1].mappings + toServerMappings = + (serverPacketType && serverPacketType.mappings) || {} - exports.capabilities.clientboundPackets = mcdata.protocol.play.toClient.types.packet[1][0].type[1].mappings - exports.capabilities.serverboundPackets = mcdata.protocol.play.toServer.types.packet[1][0].type[1].mappings + exports.capabilities.clientboundPackets = toClientMappings + exports.capabilities.serverboundPackets = toServerMappings - if (host.indexOf(':') !== -1) { - port = host.substring(host.indexOf(':') + 1) - host = host.substring(0, host.indexOf(':')) + const customPackets = createCustomPackets(mcdata) + + const separatorIndex = host.lastIndexOf(':') + + if ( + separatorIndex !== -1 && + host.indexOf(':') === separatorIndex + ) { + const hostPort = Number(host.substring(separatorIndex + 1)) + + if (Number.isFinite(hostPort)) { + port = hostPort + host = host.substring(0, separatorIndex) + } } - isPortTaken(listenPort, (err, taken) => { - // TODO: Handle errors - console.log(err, taken) + + isPortTaken(listenPort, function (error, taken) { + if (error) { + safeCall( + messageCallback, + 'Unable to start pakkit', + error.message, + true + ) + return + } + if (taken) { - console.log('call') - // Wait for the renderer to be ready - setTimeout(() => { - messageCallback('Unable to start pakkit', 'The port ' + listenPort + ' is in use. ' + - 'Make sure to close any other instances of pakkit running on the same port or try a different port.', true) - }, 1000) - } else { - let srv - try { - srv = mc.createServer({ - 'online-mode': false, - port: listenPort, - keepAlive: false, - version: version - }) - console.log('Proxy started (Java)!') - } catch (err) { - let header = 'Unable to start pakkit' - let message = err.message - if (err.message.includes('EADDRINUSE')) { - message = 'The port ' + listenPort + ' is in use. ' + - 'Make sure to close any other instances of pakkit running on the same port or try a different port.' - } - messageCallback(header, message) - return + safeCall( + messageCallback, + 'Unable to start pakkit', + `The port ${listenPort} is in use. ` + + 'Close other Pakkit instances or choose another port.', + true + ) + return + } + + try { + proxyServer = mc.createServer({ + 'online-mode': false, + port: listenPort, + keepAlive: false, + disableDefaultConfiguration: true, + customPackets, + hideErrors: true, + version + }) + } catch (error) { + safeCall( + messageCallback, + 'Unable to start pakkit', + error.message, + true + ) + return + } + + console.log('Proxy started (Java)!') + + proxyServer.on('login', function (client) { + realClient = client + + const remoteAddress = + client.socket && client.socket.remoteAddress + + console.log( + 'Incoming connection', + `(${remoteAddress || 'unknown'})` + ) + + let endedClient = false + let endedTargetClient = false + + const pendingClientbound = [] + const pendingServerbound = [] + + const MAX_PENDING_CLIENTBOUND = 4096 + const MAX_PENDING_SERVERBOUND = 512 + + const forwardingErrors = new Set() + + const clientOptions = { + host, + port, + username: client.username, + keepAlive: false, + version, + profilesFolder: authConsent + ? minecraftFolder + : dataFolder, + auth: onlineMode + ? 'microsoft' + : 'offline', + customPackets, + hideErrors: true, + + onMsaCode: function (data) { + authWindowOpen = true + + safeCall(authCodeCallback, data) + } } - srv.on('login', function (client) { - realClient = client - const addr = client.socket.remoteAddress - console.log('Incoming connection', '(' + addr + ')') - let endedClient = false - let endedTargetClient = false - client.on('end', function () { - endedClient = true - console.log('Connection closed by client', '(' + addr + ')') - if (!endedTargetClient) { targetClient.end('End') } - }) - client.on('error', function (err) { - endedClient = true - console.log('Connection error by client', '(' + addr + ')') - console.log(err.stack) - if (!endedTargetClient) { targetClient.end('Error') } - }) - // if (authConsent) { - // console.log('Will attempt to use launcher_profiles.json for online mode login data') - // } else { - // console.warn('Consent not given to use launcher_profiles.json - automatic online mode will not work') - // } - const clientOptions = { - host: host, - port: port, - username: client.username, - keepAlive: false, - version: version, - profilesFolder: authConsent ? minecraftFolder : dataFolder, - auth: onlineMode ? 'microsoft' : 'offline', - onMsaCode: function (data) { - console.log('MSA code:', data.user_code) - authWindowOpen = true - authCodeCallback(data) - } + + const targetClient = mc.createClient(clientOptions) + + realServer = targetClient + + client.on('disconnect', reason => { + console.error('Client disconnect packet:', reason) + }) + client.on('kick_disconnect', reason => { + console.error('Client kick disconnect:', reason) + }) + targetClient.on('kick_disconnect', reason => { + console.error('Server disconnect packet:', reason) + }) + targetClient.on('disconnect', reason => { + console.error('Server disconnect:', reason) + }) + + function bothSidesReady () { + return !endedClient && + !endedTargetClient && + isPlayState(client.state) && + isPlayState(targetClient.state) + } + + function enqueuePacket ( + queue, + maxSize, + data, + meta, + raw + ) { + if (queue.length >= maxSize) { + queue.shift() } - let targetClient = mc.createClient(clientOptions) - targetClient.on('session', function (session) { - // Login complete - the dialog can be closed - console.log('Login done') - authWindowOpen = false - authCodeCallback('close') + + queue.push({ + data, + meta: Object.assign({}, meta), + raw }) + } + + function reportForwardingError ( + direction, + meta, + error + ) { + const packetName = + meta && meta.name !== undefined + ? meta.name + : 'unknown' + + const state = + meta && meta.state !== undefined + ? meta.state + : 'unknown' + + const key = + `${direction}:${state}:${packetName}` + + if (forwardingErrors.has(key)) { + return + } - realServer = targetClient + forwardingErrors.add(key) - function getId (meta, mappings) { - let id - if (typeof meta.name === 'number') { - // Unknown packet ID - id = '0x' + meta.name.toString(16).padStart(2, '0') - meta.name = 'unknown' + console.error( + `Failed to forward ${key}: ${error.message}` + ) + } + + function forwardPacket ( + destination, + meta, + data, + direction, + raw + ) { + if (!destination || !meta) { + return false + } + + if (typeof meta.name !== 'string') { + return false + } + + try { + if (raw) { + destination.writeRaw(raw) } else { - id = Object.keys(mappings).find(key => mappings[key] === meta.name) + destination.write(meta.name, data) } - return id - } - - function handleServerboundPacket (data, meta, raw, packetValid) { - // console.log('serverbound packet', meta, data) - if (targetClient.state === states.PLAY && meta.state === states.PLAY) { - const id = getId(meta, toServerMappings) - - // Stops standardjs from complaining (no-callback-literal) - const direction = 'serverbound' - const canUseScripting = true - - // callback(direction, meta, data, id) - if (!endedTargetClient) { - // When scripting is enabled, the script sends packets - if (!scriptingEnabled) { - // targetClient.write(meta.name, data) - targetClient.writeRaw(raw) - } - callback(direction, meta, data, id, canUseScripting, packetValid) - } + return true + } catch (error) { + reportForwardingError( + direction, + meta, + error + ) + + return false + } + } + + function notifyPacket ( + direction, + meta, + data, + mappings, + raw + ) { + const id = typeof meta.name === 'number' + ? `0x${meta.name.toString(16).padStart(2, '0')}` + : getMappingId(mappings, meta.name) + + safeCall( + callback, + direction, + meta, + data, + id, + true, + true, + raw + ) + } + + function handleServerboundPacket ( + data, + meta, + fromQueue, + raw + ) { + if ( + !meta || + (!isPlayState(meta.state) && + !isConfigurationState(meta.state)) + ) { + return + } + + if (endedTargetClient) { + return + } + + if (isPlayState(meta.state) && !bothSidesReady()) { + if (!fromQueue) { + enqueuePacket( + pendingServerbound, + MAX_PENDING_SERVERBOUND, + data, + meta, + raw + ) } + + return } - function handleClientboundPacket (data, meta, raw, packetValid) { - if (meta.state === states.PLAY && client.state === states.PLAY) { - const id = getId(meta, toClientMappings) - - // Stops standardjs from complaining (no-callback-literal) - const direction = 'clientbound' - const canUseScripting = true - - // callback(direction, meta, data, id) - if (!endedClient) { - // When scripting is enabled, the script sends packets - if (!scriptingEnabled) { - // client.write(meta.name, data) - client.writeRaw(raw) - } - callback(direction, meta, data, id, canUseScripting, packetValid) - if (meta.name === 'set_compression') { - client.compressionThreshold = data.threshold - } // Set compression + + if (isConfigurationState(meta.state)) { + const handledByTargetClient = new Set([ + 'select_known_packs', + 'finish_configuration', + 'accept_code_of_conduct' + ]) + + if (handledByTargetClient.has(meta.name)) { + return + } + + if (!isConfigurationState(targetClient.state)) { + if (!fromQueue) { + enqueuePacket( + pendingServerbound, + MAX_PENDING_SERVERBOUND, + data, + meta, + raw + ) } + return } } - const bufferEqual = require('buffer-equal') - targetClient.on('packet', function (data, meta, buffer, fullBuffer) { - if (client.state !== states.PLAY || meta.state !== states.PLAY) { return } - let packetValid = false - try { - const packetBuff = this.getRaw({ name: meta.name, params: data }) - if (!bufferEqual(fullBuffer, packetBuff)) { - console.log('client<-server: Error in packet ' + meta.state + '.' + meta.name) - console.log('received buffer', fullBuffer.toString('hex')) - console.log('produced buffer', packetBuff.toString('hex')) - console.log('received length', fullBuffer.length) - console.log('produced length', packetBuff.length) - } else { - packetValid = true - } - } catch (e) { - // TODO: handle? + if (!scriptingEnabled) { + const forwarded = forwardPacket( + targetClient, + meta, + data, + 'serverbound', + isPlayState(meta.state) ? raw : undefined + ) + + if (!forwarded) { + return } - handleClientboundPacket(data, meta, fullBuffer, packetValid) - /* if (client.state === states.PLAY && brokenPackets.indexOf(packetId.value) !=== -1) - { - console.log(`client<-server: raw packet); - console.log(packetData); - if (!endedClient) - client.writeRaw(buffer); - } */ - }) - client.on('packet', function (data, meta, buffer, fullBuffer) { - if (meta.state !== states.PLAY || targetClient.state !== states.PLAY) { return } - const packetData = client.deserializer.parsePacketBuffer(fullBuffer).data.params - let packetValid = false - try { - const packetBuff = this.getRaw({ name: meta.name, params: packetData }) - if (!bufferEqual(fullBuffer, packetBuff)) { - console.log('client->server: Error in packet ' + meta.state + '.' + meta.name) - console.log('received buffer', fullBuffer.toString('hex')) - console.log('produced buffer', packetBuff.toString('hex')) - console.log('received length', fullBuffer.length) - console.log('produced length', packetBuff.length) - } else { - packetValid = true - } - } catch (e) { - // TODO: handle? + } + + if (isPlayState(meta.state)) { + notifyPacket('serverbound', meta, data, toServerMappings, raw) + } + } + + function handleClientboundPacket ( + data, + meta, + fromQueue, + raw + ) { + if ( + !meta || + (!isConfigurationState(meta.state) && + !isPlayState(meta.state)) + ) { + return + } + + if (endedClient) { + return + } + + if (isPlayState(meta.state) && !bothSidesReady()) { + if (!fromQueue) { + enqueuePacket( + pendingClientbound, + MAX_PENDING_CLIENTBOUND, + data, + meta, + raw + ) } - if (typeof meta.name === 'number') { - // Unknown packet ID so packet is invalid - packetValid = false + + return + } + + if (!scriptingEnabled) { + const forwarded = forwardPacket( + client, + meta, + data, + 'clientbound', + raw + ) + + if (!forwarded) { + return } - handleServerboundPacket(packetData, meta, fullBuffer, packetValid) - }) - targetClient.on('end', function () { - endedTargetClient = true - console.log('Connection closed by server', '(' + host + ':' + port + ')') - if (!endedClient) { client.end('Connection closed by server ' + '(' + host + ':' + port + ')') } - }) - targetClient.on('error', function (err) { - endedTargetClient = true - console.log('Connection error by server', '(' + host + ':' + port + ') ', err) - console.log(err.stack) - if (authWindowOpen) return - let header = 'Unable to connect to server' - let message = err.message - if (err.message.includes('ECONNREFUSED')) { - message = 'Unable to connect to the Java server at ' + - host + ':' + port + - '. Make sure the server is online.' + } + + if (isPlayState(meta.state)) { + notifyPacket('clientbound', meta, data, toClientMappings, raw) + } + } + + function flushPendingPackets () { + while (bothSidesReady() && pendingClientbound.length > 0) { + const packet = pendingClientbound.shift() + + handleClientboundPacket( + packet.data, + packet.meta, + true, + packet.raw + ) + } + + const pendingCount = pendingServerbound.length + for (let i = 0; i < pendingCount; i++) { + const packet = pendingServerbound.shift() + + const ready = isPlayState(packet.meta.state) + ? bothSidesReady() + : isConfigurationState(targetClient.state) + + if (!ready) { + pendingServerbound.push(packet) + continue } - messageCallback(header, message) - if (!endedClient) { client.end('pakkit - ' + header + '\n' + message) } - }) + + handleServerboundPacket( + packet.data, + packet.meta, + true, + packet.raw + ) + } + } + + const readinessTimer = setInterval(function () { + if (endedClient || endedTargetClient) { + clearInterval(readinessTimer) + return + } + + flushPendingPackets() + }, 10) + + if ( + typeof readinessTimer.unref === 'function' + ) { + readinessTimer.unref() + } + + client.on('packet', function (data, meta, buffer, fullBuffer) { + handleServerboundPacket( + data, + meta, + false, + fullBuffer || buffer + ) }) - } + + targetClient.on( + 'packet', + function (data, meta, buffer, fullBuffer) { + handleClientboundPacket( + data, + meta, + false, + fullBuffer || buffer + ) + } + ) + + targetClient.on('session', function () { + authWindowOpen = false + + safeCall(authCodeCallback, 'close') + }) + + client.on('end', function () { + if (endedClient) { + return + } + + endedClient = true + clearInterval(readinessTimer) + + pendingClientbound.length = 0 + pendingServerbound.length = 0 + + console.log( + 'Connection closed by client', + `(${remoteAddress || 'unknown'})` + ) + + if (!endedTargetClient) { + try { + targetClient.end('End') + } catch (error) {} + } + }) + + client.on('error', function (error) { + if (endedClient) { + return + } + + endedClient = true + clearInterval(readinessTimer) + + pendingClientbound.length = 0 + pendingServerbound.length = 0 + + console.error( + 'Connection error by client:', + error.message + ) + + if (!endedTargetClient) { + try { + targetClient.end('Error') + } catch (endError) {} + } + }) + + targetClient.on('end', function () { + if (endedTargetClient) { + return + } + + endedTargetClient = true + clearInterval(readinessTimer) + + pendingClientbound.length = 0 + pendingServerbound.length = 0 + + console.log( + 'Connection closed by server', + `(${host}:${port})` + ) + + if (!endedClient) { + try { + client.end( + `Connection closed by server (${host}:${port})` + ) + } catch (error) {} + } + }) + + targetClient.on('error', function (error) { + if (endedTargetClient) { + return + } + + endedTargetClient = true + clearInterval(readinessTimer) + + pendingClientbound.length = 0 + pendingServerbound.length = 0 + + console.error( + `Connection error by server (${host}:${port}):`, + error.message + ) + + if (authWindowOpen) { + return + } + + let message = error.message + + if ( + error.message && + error.message.includes('ECONNREFUSED') + ) { + message = + `Unable to connect to ${host}:${port}. ` + + 'Make sure the server is online.' + } + + safeCall( + messageCallback, + 'Unable to connect to server', + message, + true + ) + + if (!endedClient) { + try { + client.end( + 'pakkit - Unable to connect to server\n' + + message + ) + } catch (endError) {} + } + }) + }) + + proxyServer.on('error', function (error) { + safeCall( + messageCallback, + 'Pakkit proxy error', + error.message, + true + ) + }) }) } -exports.end = function () {} - -exports.getRaw = function (name, params) { +exports.end = function () { if (realClient) { - return [...realClient.serializer.createPacketBuffer({ name, params })] + try { + realClient.end('Proxy stopped') + } catch (error) {} + + realClient = undefined + } + + if (realServer) { + try { + realServer.end('Proxy stopped') + } catch (error) {} + + realServer = undefined + } + + if (proxyServer) { + try { + if ( + typeof proxyServer.close === 'function' + ) { + proxyServer.close() + } else if ( + proxyServer.socketServer && + typeof proxyServer.socketServer.close === 'function' + ) { + proxyServer.socketServer.close() + } + } catch (error) {} + + proxyServer = undefined } } -exports.writeToClient = function (meta, data, noCallback) { +exports.getRaw = function ( + direction, + name, + params +) { + const connection = + direction === 'serverbound' + ? realServer + : realClient + + if ( + !connection || + !connection.serializer + ) { + return undefined + } + + try { + return connection.serializer + .createPacketBuffer({ + name, + params + }) + } catch (error) { + return undefined + } +} + +exports.writeToClient = function ( + meta, + data, + noCallback +) { + if (!realClient) { + return false + } + if (typeof meta === 'string') { - meta = { name: meta } + meta = { + name: meta, + state: states.PLAY + } } - realClient.write(meta.name, data) - const id = Object.keys(toClientMappings).find(key => toClientMappings[key] === meta.name) - if (!noCallback) { - storedCallback('clientbound', meta, data, id) // TODO: indicator for injected packets + + try { + realClient.write(meta.name, data) + } catch (error) { + return false + } + + const id = getMappingId( + toClientMappings, + meta.name + ) + + if ( + !noCallback && + typeof storedCallback === 'function' + ) { + storedCallback( + 'clientbound', + meta, + data, + id, + true, + true + ) } + + return true } -exports.writeToServer = function (meta, data, noCallback) { +exports.writeToServer = function ( + meta, + data, + noCallback +) { + if (!realServer) { + return false + } + if (typeof meta === 'string') { - meta = { name: meta } + meta = { + name: meta, + state: states.PLAY + } } - realServer.write(meta.name, data) - const id = Object.keys(toServerMappings).find(key => toServerMappings[key] === meta.name) - if (!noCallback) { - storedCallback('serverbound', meta, data, id) + + try { + realServer.write(meta.name, data) + } catch (error) { + return false } + + const id = getMappingId( + toServerMappings, + meta.name + ) + + if ( + !noCallback && + typeof storedCallback === 'function' + ) { + storedCallback( + 'serverbound', + meta, + data, + id, + true, + true + ) + } + + return true } -exports.setScriptingEnabled = function (isEnabled) { - scriptingEnabled = isEnabled -} \ No newline at end of file +exports.setScriptingEnabled = function ( + isEnabled +) { + scriptingEnabled = Boolean(isEnabled) +}