diff --git a/App/css/style.css b/App/css/style.css index 63645e8..6d305c1 100644 --- a/App/css/style.css +++ b/App/css/style.css @@ -6,6 +6,7 @@ Main application stylesheet ****************************************************************************** */ + html, body { color: #fff; overflow: hidden; @@ -33,6 +34,10 @@ input[type='range'] { input[disabled='disabled'], input[disabled] { cursor: no-drop; } +input[type='checkbox'] { + margin-right: 8px; + vertical-align: middle; +} img { -webkit-user-drag: none; } @@ -318,7 +323,7 @@ img { text-align: center; border-radius: 10px; box-shadow: 0px 0px 30px #000a; - background-image: linear-gradient(180deg, #2a3a56, #0d1932); + background-image: linear-gradient(180deg, #2f405f, #131f38); } .DIV_SETTINGS_BG { top: 0px; @@ -352,7 +357,7 @@ img { text-align: left; margin-left: 8px; margin-bottom: 8px; - border-radius: 6px; + border-radius: 4px; font-family: sans-serif; background-color: #111d; width: calc(100% - 32px); @@ -368,14 +373,14 @@ img { } .DIV_settingsEntryFlex { display: flex; - flex-wrap: nowrap; align-items: center; + flex-direction: row; align-content: center; } .DIV_settingsH2 { font-size: 20px; - font-style: italic; - font-family: monospace; + font-weight: 600; + font-family: system-ui; margin: 0px 0px 8px 2px; } .DIV_settingsSave { @@ -439,6 +444,58 @@ img { margin-bottom: 2px; font-family: system-ui; } +.DIV_FPPS4_UPDATER { + top: 0px; + left: 0px; + width: 100%; + height: 100%; + z-index: 110; + cursor: wait; + display: none; + transition: 0.4s; + flex-wrap: nowrap; + position: absolute; + align-items: center; + flex-direction: row; + align-content: center; + font-family: system-ui; + justify-content: center; + backdrop-filter: blur(4px); + background-color: #03033144; +} +.DIV_PROGRESSBAR { + width: 72%; + height: 10px; + cursor: wait; + overflow: hidden; + margin-top: 44px; + border-width: 1px; + position: absolute; + border-style: solid; + background-color: #c5c5c5ad; +} +.DIV_PROGRESSBAR_INTERNAL { + top: 0px; + left: 0px; + width: 0%; + height: 100%; + transition: 0.4s; + position: absolute; + background-image: linear-gradient(90deg, #ccc, #fff); +} +.DIV_DESIGN_LINES { + left: 0px; + width: 100%; + height: 1px; + position: absolute; + background-color: #fffb; +} +.LINE_TOP { + top: 12%; +} +.LINE_BOTTOM { + bottom: 12%; +} /* Images @@ -542,12 +599,18 @@ img { } .BTN_STOP { margin: 0px 4px 0px 4px; - padding: 1px 70px 1px 70px; + padding: 1px 30px 1px 30px; } .BTN_SAVE { + border: none; font-size: 18px; min-width: 300px; min-height: 60px; + border-radius: 6px; + background-image: linear-gradient(180deg, #fff, #ccc); +} +.BTN_SAVE:active { + background-image: linear-gradient(0deg, #fff, #ccc); } .BTN_selectPath { float: right; @@ -568,6 +631,17 @@ img { padding: 4px 10px 4px 10px; background-image: linear-gradient(180deg, #fff, #bbb); } +.SETTINGS_TEXT { + color: #0f0; + border: none; + outline: none; + min-width: 160px; + margin-left: 4px; + border-radius: 4px; + background-color: #000; + font-family: monospace; + padding: 6px 0px 6px 6px; +} /* Labels @@ -586,7 +660,7 @@ img { } .LABEL_checkbox { cursor: pointer; - font-size: 16px; + font-size: 14px; font-style: italic; } .LABEL_emuColor { @@ -610,10 +684,16 @@ img { .LABEL_settingsExperimental { padding: 6px; cursor: pointer; - font-family: math; + font-family: monospace; background-color: #000; text-shadow: 0px 0px 10px #f00; } +.LABEL_FLEX_MARGIN { + margin: 4px; +} +.LABEL_monospace { + font-family: monospace; +} /* Animations diff --git a/App/img/logo.ico b/App/img/logo.ico new file mode 100644 index 0000000..33d66f2 Binary files /dev/null and b/App/img/logo.ico differ diff --git a/App/index.htm b/App/index.htm index 6f369f7..81ec892 100644 --- a/App/index.htm +++ b/App/index.htm @@ -11,210 +11,254 @@ + +
+ + +
+
+ + + + + + +
+
+
+ +
+
-
-
Launcher Settings
- - -
- - -
- - -
Language
- -
- : -
- -
-
- - -
Paths
-
- - : - - - - - -
- -
- - : - - - - -
- - -
-
Game List
- -
+
+
Launcher Settings
+ + +
+ + +
- : - - -
- -
- - : - - - % - -
- -
- - : - - % - -
- -
+ +
Language
- : +
+ : +
+ () +
+
+ + +
Paths
+
+ + : + + + + + +
- - % +
+ + : + + + + +
+ + +
+
Game List
+ +
+ + : + + +
+ +
+ + : + + + % + +
+ +
+ + : + + % + +
+ +
+ + : + + + % + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
Emu Running
+ +
+ : + + % +
+ +
+ : + + % +
+ +
+ + +
+ +
+ + +
+ + +
+
Log Options
+ +
+ : + +
+ +
+ + +
+ +
+ + +
+ +
+
fpPS4 Updater
+ +
+ + +
+ +
+ : + +
+ +
+ +
+ + +
+
Misc.
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
- - -
- - -
- -
- - -
- -
- - -
- - -
-
Emu Running
- -
- : - - % -
- -
- : - - % -
- -
- - -
- -
- - -
- - -
-
Log Options
- -
- : - -
- -
- - -
- -
- - -
- - -
-
Misc.
- -
- - -
- -
- - -
- -
- - + + +
+ +
- -
- - -
- -
+
@@ -233,6 +277,7 @@ + @@ -358,10 +403,11 @@ - + + - +
diff --git a/App/js/design.js b/App/js/design.js index 39357ca..18ec4b3 100644 --- a/App/js/design.js +++ b/App/js/design.js @@ -347,10 +347,7 @@ temp_DESIGN = { // Check if emu is present before allowing to run if (APP.fs.existsSync(APP.settings.data.emuPath) === !0 && APP.gameList.selectedGame !== ''){ - var btnRun = '', - btnLog = '', - btnRefresh = '', - btnSettings = '', + var btnDisabled = '', btnKill = 'disabled', emuRunPath = 'block', bgBlur = APP.settings.data.bgListBlur, @@ -364,10 +361,7 @@ temp_DESIGN = { if (APP.emuManager.emuRunning === !0){ btnKill = ''; - btnLog = 'disabled'; - btnRun = 'disabled'; - btnRefresh = 'disabled'; - btnSettings = 'disabled'; + btnDisabled = 'disabled'; bgBlur = APP.settings.data.bgEmuBlur; showGuiMetadata = {'display': 'flex'}; bgOpacity = APP.settings.data.bgEmuOpacity; @@ -391,12 +385,13 @@ temp_DESIGN = { TMS.css('DIV_GAMELIST_BG', {'filter': 'blur(' + bgBlur + 'px) opacity(' + bgOpacity + ')'}); // Update Buttons - document.getElementById('BTN_RUN').disabled = btnRun; document.getElementById('BTN_KILL').disabled = btnKill; - document.getElementById('BTN_CLEAR_LOG').disabled = btnLog; - document.getElementById('BTN_REFRESH').disabled = btnRefresh; - document.getElementById('BTN_SETTINGS').disabled = btnSettings; - document.getElementById('INPUT_gameListSearch').disabled = btnRun; + document.getElementById('BTN_RUN').disabled = btnDisabled; + document.getElementById('BTN_REFRESH').disabled = btnDisabled; + document.getElementById('BTN_SETTINGS').disabled = btnDisabled; + document.getElementById('BTN_CLEAR_LOG').disabled = btnDisabled; + document.getElementById('BTN_UPDATE_FPPS4').disabled = btnDisabled; + document.getElementById('INPUT_gameListSearch').disabled = btnDisabled; } else { @@ -661,6 +656,7 @@ temp_DESIGN = { document.getElementById('CHECKBOX_settingsShowBgOnGameEntry').checked = JSON.parse(cSettings.showBgOnEntry); document.getElementById('CHECKBOX_settingsShowGameMetadata').checked = JSON.parse(cSettings.showGuiMetadata); document.getElementById('CHECKBOX_settingsRemoveProjectGp4').checked = JSON.parse(cSettings.removeProjectGp4); + document.getElementById('CHECKBOX_settingsEnableFpps4Updates').checked = JSON.parse(cSettings.enableEmuUpdates); document.getElementById('CHECKBOX_settingsGameSearchCaseSensitive').checked = JSON.parse(cSettings.searchCaseSensitive); document.getElementById('CHECKBOX_settingsExternalWindowPrompt').checked = JSON.parse(cSettings.logExternalWindowPrompt); @@ -675,6 +671,9 @@ temp_DESIGN = { document.getElementById('RANGE_settingsEmuRunningBgOpacity').value = cSettings.bgEmuOpacity; document.getElementById('RANGE_settingsGridIconBorderRadius').value = cSettings.gridBorderRadius; + // Text + document.getElementById('INPUT_settingsUpdateFpps4Branch').value = cSettings.fpps4BranchName; + // Fix for grid size / border-radius if (cSettings.gridIconSize > 512){ cSettings.gridIconSize = 512; @@ -735,6 +734,7 @@ temp_DESIGN = { APP.settings.data.showPathRunning = JSON.parse(document.getElementById('CHECKBOX_settingsShowExecRunning').checked); APP.settings.data.showGuiMetadata = JSON.parse(document.getElementById('CHECKBOX_settingsShowGameMetadata').checked); APP.settings.data.removeProjectGp4 = JSON.parse(document.getElementById('CHECKBOX_settingsRemoveProjectGp4').checked); + APP.settings.data.enableEmuUpdates = JSON.parse(document.getElementById('CHECKBOX_settingsEnableFpps4Updates').checked); APP.settings.data.searchCaseSensitive = JSON.parse(document.getElementById('CHECKBOX_settingsGameSearchCaseSensitive').checked); APP.settings.data.logExternalWindowPrompt = JSON.parse(document.getElementById('CHECKBOX_settingsExternalWindowPrompt').checked); @@ -749,6 +749,9 @@ temp_DESIGN = { APP.settings.data.bgEmuOpacity = parseFloat(document.getElementById('RANGE_settingsEmuRunningBgOpacity').value); APP.settings.data.gridBorderRadius = parseFloat(document.getElementById('RANGE_settingsGridIconBorderRadius').value); + // Text + APP.settings.data.fpps4BranchName = document.getElementById('INPUT_settingsUpdateFpps4Branch').value; + /* End */ @@ -761,6 +764,44 @@ temp_DESIGN = { APP.design.toggleSettings(!0); } + }, + + /* + Updater + */ + + // Display / Hide GUI + toggleEmuUpdateGUI: function(mode){ + + var cssData; + switch (mode) { + + case 'show': + cssData = {'display': 'flex'}; + break; + + case 'hide': + cssData = {'display': 'none'}; + break; + + default: + cssData = {'display': 'none'}; + break; + + } + + // Reset progressbar status + TMS.css('DIV_PROGRESSBAR_UPDATE_FPPS4', {'width': '0%'}); + + // Update display mode + TMS.css('DIV_FPPS4_UPDATER', cssData); + + }, + + // Update status + updateProgressbarStatus: function(percentage, status){ + TMS.css('DIV_PROGRESSBAR_UPDATE_FPPS4', {'width': percentage + '%'}); + document.getElementById('LABEL_FPPS4_UPDATER_STATUS').innerHTML = status; } } \ No newline at end of file diff --git a/App/js/emumanager.js b/App/js/emumanager.js index 06e29a3..6ed0d08 100644 --- a/App/js/emumanager.js +++ b/App/js/emumanager.js @@ -4,7 +4,7 @@ emumanager.js This file contains all functions / variables about running main project - executable and game module checks. + executable, game module checks and updating fpPS4 executable. ****************************************************************************** */ @@ -13,6 +13,9 @@ temp_EMUMANAGER = { // Emulator is running emuRunning: !1, + // Update functions + update: temp_EMU_UPDATE, + // Run emu runGame: function(){ @@ -85,7 +88,7 @@ temp_EMUMANAGER = { } // Kill process and set emu running var to false - APP.getProcessInfo('fpPS4.exe', function(pData){ + APP.getProcessInfo(APP.path.parse(APP.settings.data.emuPath).base, function(pData){ process.kill(pData.th32ProcessID); this.emuRunning = !1; }); diff --git a/App/js/language.js b/App/js/language.js index 92662b2..bd5854b 100644 --- a/App/js/language.js +++ b/App/js/language.js @@ -56,7 +56,7 @@ temp_LANGUAGE = { "logWindowTitle": "Running fpPS4", "killEmuStatus": "Main process closed - close fpPS4 log window to go back", "logCleared": "INFO - Previous log was cleared!\n ", - "about": "fpPS4 Temmie\'s Launcher - Version: %VARIABLE_0%\nCreated by TemmieHeartz\n(https://twitter.com/themitosan)\n\nfpPS4 is created by red-prig\n(https://github.com/red-prig/fpPS4)\n\nPlugin memoryjs is created by Rob--\n(https://github.com/rob--/memoryjs)\n\nSVG icons were obtained from https://www.svgrepo.com/", + "about": "fpPS4 Temmie\'s Launcher - Version: %VARIABLE_0%\nCreated by TemmieHeartz\n(https://twitter.com/themitosan)\n\nfpPS4 is created by red-prig\n(https://github.com/red-prig/fpPS4)\n\nPlugin memoryjs is created by Rob--\n(https://github.com/rob--/memoryjs)\n\nPlugin node-stream-zip is created by antelle\n(https://github.com/antelle/node-stream-zip)\n\nSVG icons were obtained from https://www.svgrepo.com/", "mainLog": 'fpPS4 Temmie\'s Launcher - Version: %VARIABLE_0%\nRunning on nw.js (node-webkit) version %VARIABLE_1% [%VARIABLE_2%]', "settingsErrorCreatePath": "ERROR - Unable to create path!\n(%VARIABLE_0%)\n%VARIABLE_1%", "settingsErrorfpPS4NotFound": "ERROR - Unable to locate main fpPS4 executable!\nMake sure to select it in Settings or insert it in \"Emu\" folder and click OK.", @@ -95,7 +95,19 @@ temp_LANGUAGE = { "gameListVersion": "Version", "selectGameLoadPatchErrorParamSfo": "ERROR - Unable to read PARAM.SFO from this patch!\n%VARIABLE_0%", "path": "Path", - "gamelistGamePath404": "ERROR - Unable to find selected app / game path!\n%VARIABLE_0%" + "gamelistGamePath404": "ERROR - Unable to find selected app / game path!\n%VARIABLE_0%", + + "updateEmuFetchActionsError": "ERROR - Unable to fetch GitHub actions data!", + "updateEmuIsLatestVersion": "INFO - You are using the latest fpPS4 version available!\nCommit ID (SHA): %VARIABLE_0%", + "updateEmuShaAvailable": "INFO - A new update is available!\n\nLocal version: %VARIABLE_0%\nNew version: %VARIABLE_1%\n\nDo you want to update?", + "updateEmuShaUnavailable": "INFO - This Launcher detected that you didn\'t updated fpPS4 yet (or fpPS4 executable was not found!)\n\nYou can fix this by running fpPS4 updater process.\nDo you want to proceed?", + "updateEmuDownloadFailed": "ERROR - Unable to download fpPS4 update!\nResponse status: %VARIABLE_0% - OK: %VARIABLE_1%", + "updateEmuProcessComplete": "INFO - Update complete!\nNew fpPS4 version (commit id / sha): %VARIABLE_0%", + "updateEmu-1-4": "Downloading fpPS4 update ()", + "updateEmu-2-4": "Extracting update", + "updateEmu-3-4": "Removing leftover files", + "updateEmu-4-4": "Update complete!", + "settingsLogEmuSha": "INFO - fpPS4 version: (%VARIABLE_0%)" }, @@ -108,4 +120,4 @@ temp_LANGUAGE = { // Selected lang selected: {} -} +} \ No newline at end of file diff --git a/App/js/main.js b/App/js/main.js index b3f21db..3ee640b 100644 --- a/App/js/main.js +++ b/App/js/main.js @@ -14,9 +14,11 @@ var APP = { fs: require('fs'), win: nw.Window.get(), path: require('path'), + https: require('https'), childProcess: require('child_process'), packageJson: require('../package.json'), memoryjs: require('App/node_modules/memoryjs'), + streamZip: require('App/node_modules/node-stream-zip'), // App version title: '', @@ -257,6 +259,7 @@ delete temp_SETTINGS; delete temp_GAMELIST; delete temp_LANGUAGE; delete temp_EMUMANAGER; +delete temp_EMU_UPDATE; delete temp_FILEMANAGER; delete temp_PARAMSFO_PARSER; @@ -297,6 +300,9 @@ window.onload = function(){ // Remove all previous imported modules APP.gameList.removeAllModules(); + // Check if fpPS4 have any update (silenty) + APP.emuManager.update.check({silent: !0}); + } catch (err) { // Log error diff --git a/App/js/settings.js b/App/js/settings.js index 4fdfb5d..afeec39 100644 --- a/App/js/settings.js +++ b/App/js/settings.js @@ -45,7 +45,7 @@ temp_SETTINGS = { // Game list showBgOnEntry: !0, showPathEntry: !0, - gameListMode: 'normal', + gameListMode: 'compact', // Emu running showPathRunning: !0, @@ -66,7 +66,14 @@ temp_SETTINGS = { // (Grid) gridIconSize: 116, gridBorderRadius: 8, - + + /* + fpPS4 Update + */ + enableEmuUpdates: !0, + latestCommitSha: '', + fpps4BranchName: 'trunk', + /* Debug */ @@ -210,11 +217,16 @@ temp_SETTINGS = { if (this.data.emuPath === '' || APP.fs.existsSync(this.data.emuPath) === !1){ APP.settings.data.emuPath = mainPath + '/Emu/fpPS4.exe'; } + + // If fpPS4 is not found, reset latest commit sha and request update if (APP.fs.existsSync(this.data.emuPath) !== !0){ + this.data.latestCommitSha = ''; + APP.emuManager.update.check(); + } - logMessage = APP.lang.getVariable('settingsErrorfpPS4NotFound'); - window.alert(logMessage); - + // If latestCommitSha isn't empty, log it + if (this.data.latestCommitSha !== ''){ + APP.log(APP.lang.getVariable('settingsLogEmuSha', [APP.settings.data.latestCommitSha.slice(0, 7)])); } // Log message diff --git a/App/js/updateEmu.js b/App/js/updateEmu.js new file mode 100644 index 0000000..a032764 --- /dev/null +++ b/App/js/updateEmu.js @@ -0,0 +1,260 @@ +/* + ****************************************************************************** + fpPS4 Temmie's Launcher + updateEmu.js + + This file is responsible for feching latest data from red-prig fpPS4 actions + and update. + ****************************************************************************** +*/ + +temp_EMU_UPDATE = { + + // GitHub actions link + githubLink: 'https://api.github.com/repos/red-prig/fpPS4/actions/artifacts', + + /* + Fetch latest github actions + + options: Object + jsonData: [Object] GitHub actions list (json) + forceUpdate: [Boolean] Skip checks and download latest version available + silent: [Boolean] Don't show message if user already have latest version + */ + check: function(options){ + + if (options === void 0){ + options = { + forceUpdate: !1, + silent: !1 + }; + } + + // If Emu updates is available, has internet and fpPS4 isn't running + if (APP.settings.data.enableEmuUpdates === !0 && navigator.onLine === !0 && APP.emuManager.emuRunning === !1){ + + // Disable check for updates emu + document.getElementById('BTN_UPDATE_FPPS4').disabled = 'disabled'; + + // Get error message + const errMsg = APP.lang.getVariable('updateEmuFetchActionsError'); + + // Fetch data + fetch(this.githubLink).then(function(resp){ + + // Check if fetch status is ok + if (resp.ok === !0){ + + resp.json().then(function(jsonData){ + options['jsonData'] = jsonData; + APP.emuManager.update.processActions(options); + }); + + } else { + + + // If launcher can't get data, log error and reset button + APP.log(errMsg); + console.error(errMsg); + document.getElementById('BTN_UPDATE_FPPS4').disabled = ''; + + } + + }); + + } + + }, + + // Process github actions data + processActions: function(options){ + + const data = options.jsonData; + + if (data !== void 0){ + + var conf, updateData, updateId, + latestSha = APP.settings.data.latestCommitSha, + accpetableBranch = APP.settings.data.fpps4BranchName; + + // Read latest actions + for (var i = 0; i < Object.keys(data.artifacts).length; i++){ + + // Shortcut + const workflow = data.artifacts[i].workflow_run; + + // If user already updated and have latest changes + if (workflow.head_sha === latestSha){ + updateId = i; + break; + } + + // Check if branch is the same selected on settings, repo is from red-prig and if lash head_sha is different + if (workflow.head_branch === accpetableBranch && workflow.head_sha !== latestSha){ + updateId = i; + break; + } + + } + + // Enable fpPS4 updates button again + document.getElementById('BTN_UPDATE_FPPS4').disabled = ''; + + // Check if there's matching updates + if (updateId !== void 0){ + + // Set latest valid commit as new update + updateData = data.artifacts[updateId]; + + // Set user message + var canPrompt = !0, + msgMode = 'confirm', + msgData = APP.lang.getVariable('updateEmuShaAvailable', [latestSha.slice(0, 7), updateData.workflow_run.head_sha.slice(0, 7)]); + + // If user didn't updated yet using launcher + if (latestSha === ''){ + msgData = APP.lang.getVariable('updateEmuShaUnavailable'); + } + + // If local version is the latest + if (latestSha === updateData.workflow_run.head_sha){ + + // Update prompt + msgMode = 'alert'; + msgData = APP.lang.getVariable('updateEmuIsLatestVersion', [latestSha.slice(0, 7)]); + + // If silent is active + if (options.silent === !0){ + canPrompt = !1; + } + + } + + // Call popup + if (canPrompt === !0 && options.forceUpdate === !1){ + conf = window[msgMode](msgData); + } + + // If anren't latest version and user confirms + if (msgMode === 'confirm' && conf === !0 || options.forceUpdate === !0){ + this.getZipFile(updateData); + } + + } + + } + + }, + + /* + Get zip from specific github action run + + Since fpPS4 actions require being logged to download, nightly.links service will be used instead. + https://nightly.link + */ + getZipFile: function(actionsData){ + + // If (by some reason) fpPS4 is running - close it! + APP.emuManager.killEmu(); + + // Display GUI + APP.design.toggleEmuUpdateGUI('show'); + APP.design.updateProgressbarStatus(25, APP.lang.getVariable('updateEmu-1-4', [actionsData.workflow_run.head_sha.slice(0, 7)])); + + // Start download + fetch('https://nightly.link/red-prig/fpPS4/actions/runs/' + actionsData.workflow_run.id + '/fpPS4.zip').then(function(resp){ + + if (resp.ok === !0){ + + APP.https.get(resp.url, function(data){ + + const fPath = APP.settings.data.nwPath + '/Emu/fpPS4.zip', + writeStream = APP.fs.createWriteStream(fPath); + + data.pipe(writeStream); + + writeStream.on('finish', function(){ + + // Close writestream + writeStream.close(); + + // Extract emu executable + APP.emuManager.update.extractZip({ + newExecName: 'fpPS4_' + actionsData.workflow_run.head_sha.slice(0, 7) + '.exe', + actions: actionsData, + path: fPath + }); + + }); + + }); + + } else { + + console.error(resp); + APP.log(APP.lang.getVariable('updateEmuDownloadFailed', [resp.status, resp.ok])); + + } + + }); + + }, + + // Extract zip + extractZip: function(data){ + + // Update status + APP.design.updateProgressbarStatus(50, APP.lang.getVariable('updateEmu-2-4')); + + // Open and extract zip file + const updateFile = new APP.streamZip.async({ file: data.path }); + updateFile.extract('fpPS4.exe', APP.path.parse(data.path).dir + '/' + data.newExecName, function(err){ + if (err){ + console.error(err); + } + }).then(function(){ + + // Close zip + updateFile.close(); + + // Finish process + APP.emuManager.update.finish(data); + + }); + + }, + + // Finish process + finish: function(data){ + + // Update status + APP.design.updateProgressbarStatus(75, APP.lang.getVariable('updateEmu-3-4')); + + // Remove download file + APP.fs.unlinkSync(data.path); + + // Update settings + APP.settings.data.latestCommitSha = data.actions.workflow_run.head_sha; + APP.settings.data.emuPath = APP.path.parse(data.path).dir + '/' + data.newExecName; + + // Save settings + APP.settings.save(); + + // Display success message + const processCompleteMsg = APP.lang.getVariable('updateEmuProcessComplete', [data.actions.workflow_run.head_sha.slice(0, 7)]); + APP.design.updateProgressbarStatus(100, APP.lang.getVariable('updateEmu-4-4')); + + // Timing out just to update GUI + setTimeout(function(){ + + APP.log(processCompleteMsg); + window.alert(processCompleteMsg); + + // Hide update gui + APP.design.toggleEmuUpdateGUI('hide'); + + }, 410); + + } + +} \ No newline at end of file diff --git a/App/node_modules/node-stream-zip/LICENSE b/App/node_modules/node-stream-zip/LICENSE new file mode 100644 index 0000000..37ac867 --- /dev/null +++ b/App/node_modules/node-stream-zip/LICENSE @@ -0,0 +1,44 @@ +Copyright (c) 2021 Antelle https://github.com/antelle + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +== dependency license: adm-zip == + +Copyright (c) 2012 Another-D-Mention Software and other contributors, +http://www.another-d-mention.ro/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/App/node_modules/node-stream-zip/README.md b/App/node_modules/node-stream-zip/README.md new file mode 100644 index 0000000..98b5a56 --- /dev/null +++ b/App/node_modules/node-stream-zip/README.md @@ -0,0 +1,224 @@ +# node-stream-zip ![CI Checks](https://github.com/antelle/node-stream-zip/workflows/CI%20Checks/badge.svg) + +node.js library for reading and extraction of ZIP archives. +Features: + +- it never loads entire archive into memory, everything is read by chunks +- large archives support +- all operations are non-blocking, no sync i/o +- fast initialization +- no dependencies, no binary addons +- decompression with built-in zlib module +- deflate, sfx, macosx/windows built-in archives +- ZIP64 support + +## Installation + +```sh +npm i node-stream-zip +``` + +## Usage + +There are two APIs provided: +1. [promise-based / async](#async-api) +2. [callbacks](#callback-api) + +It's recommended to use the new, promise API, however the legacy callback API +may be more flexible for certain operations. + +### Async API + +Open a zip file +```javascript +const StreamZip = require('node-stream-zip'); +const zip = new StreamZip.async({ file: 'archive.zip' }); +``` + +Stream one entry to stdout +```javascript +const stm = await zip.stream('path/inside/zip.txt'); +stm.pipe(process.stdout); +stm.on('end', () => zip.close()); +``` + +Read a file as buffer +```javascript +const data = await zip.entryData('path/inside/zip.txt'); +await zip.close(); +``` + +Extract one file to disk +```javascript +await zip.extract('path/inside/zip.txt', './extracted.txt'); +await zip.close(); +``` + +List entries +```javascript +const entriesCount = await zip.entriesCount; +console.log(`Entries read: ${entriesCount}`); + +const entries = await zip.entries(); +for (const entry of Object.values(entries)) { + const desc = entry.isDirectory ? 'directory' : `${entry.size} bytes`; + console.log(`Entry ${entry.name}: ${desc}`); +} + +// Do not forget to close the file once you're done +await zip.close(); +``` + +Extract a folder from archive to disk +```javascript +fs.mkdirSync('extracted'); +await zip.extract('path/inside/zip/', './extracted'); +await zip.close(); +``` + +Extract everything +```javascript +fs.mkdirSync('extracted'); +const count = await zip.extract(null, './extracted'); +console.log(`Extracted ${count} entries`); +await zip.close(); +``` + +When extracting a folder, you can listen to `extract` event +```javascript +zip.on('extract', (entry, file) => { + console.log(`Extracted ${entry.name} to ${file}`); +}); +``` + +`entry` event is generated for every entry during loading +```javascript +zip.on('entry', entry => { + // you can already stream this entry, + // without waiting until all entry descriptions are read (suitable for very large archives) + console.log(`Read entry ${entry.name}`); +}); +``` + +### Callback API + +Open a zip file +```javascript +const StreamZip = require('node-stream-zip'); +const zip = new StreamZip({ file: 'archive.zip' }); + +// Handle errors +zip.on('error', err => { /*...*/ }); +``` + +List entries +```javascript +zip.on('ready', () => { + console.log('Entries read: ' + zip.entriesCount); + for (const entry of Object.values(zip.entries())) { + const desc = entry.isDirectory ? 'directory' : `${entry.size} bytes`; + console.log(`Entry ${entry.name}: ${desc}`); + } + // Do not forget to close the file once you're done + zip.close(); +}); +``` + +Stream one entry to stdout +```javascript +zip.on('ready', () => { + zip.stream('path/inside/zip.txt', (err, stm) => { + stm.pipe(process.stdout); + stm.on('end', () => zip.close()); + }); +}); +``` + +Extract one file to disk +```javascript +zip.on('ready', () => { + zip.extract('path/inside/zip.txt', './extracted.txt', err => { + console.log(err ? 'Extract error' : 'Extracted'); + zip.close(); + }); +}); +``` + +Extract a folder from archive to disk +```javascript +zip.on('ready', () => { + fs.mkdirSync('extracted'); + zip.extract('path/inside/zip/', './extracted', err => { + console.log(err ? 'Extract error' : 'Extracted'); + zip.close(); + }); +}); +``` + +Extract everything +```javascript +zip.on('ready', () => { + fs.mkdirSync('extracted'); + zip.extract(null, './extracted', (err, count) => { + console.log(err ? 'Extract error' : `Extracted ${count} entries`); + zip.close(); + }); +}); +``` + +Read a file as buffer in sync way +```javascript +zip.on('ready', () => { + const data = zip.entryDataSync('path/inside/zip.txt'); + zip.close(); +}); +``` + +When extracting a folder, you can listen to `extract` event +```javascript +zip.on('extract', (entry, file) => { + console.log(`Extracted ${entry.name} to ${file}`); +}); +``` + +`entry` event is generated for every entry during loading +```javascript +zip.on('entry', entry => { + // you can already stream this entry, + // without waiting until all entry descriptions are read (suitable for very large archives) + console.log(`Read entry ${entry.name}`); +}); +``` + +## Options + +You can pass these options to the constructor +- `storeEntries: true` - you will be able to work with entries inside zip archive, otherwise the only way to access them is `entry` event +- `skipEntryNameValidation: true` - by default, entry name is checked for malicious characters, like `../` or `c:\123`, pass this flag to disable validation errors + +## Methods + +- `zip.entries()` - get all entries description +- `zip.entry(name)` - get entry description by name +- `zip.stream(entry, function(err, stm) { })` - get entry data reader stream +- `zip.entryDataSync(entry)` - get entry data in sync way +- `zip.close()` - cleanup after all entries have been read, streamed, extracted, and you don't need the archive + +## Building + +The project doesn't require building. To run unit tests with [nodeunit](https://github.com/caolan/nodeunit): +```sh +npm test +``` + +## Known issues + +- [utf8](https://github.com/rubyzip/rubyzip/wiki/Files-with-non-ascii-filenames) file names + +## Out of scope + +- AES encrypted files: the library will throw an error if you try to open it + +## Contributors + +ZIP parsing code has been partially forked from [cthackers/adm-zip](https://github.com/cthackers/adm-zip) (MIT license). diff --git a/App/node_modules/node-stream-zip/node_stream_zip.d.ts b/App/node_modules/node-stream-zip/node_stream_zip.d.ts new file mode 100644 index 0000000..f076c72 --- /dev/null +++ b/App/node_modules/node-stream-zip/node_stream_zip.d.ts @@ -0,0 +1,199 @@ +/// + +declare namespace StreamZip { + interface StreamZipOptions { + /** + * File to read + * @default undefined + */ + file?: string; + + /** + * Alternatively, you can pass fd here + * @default undefined + */ + fd?: number; + + /** + * You will be able to work with entries inside zip archive, + * otherwise the only way to access them is entry event + * @default true + */ + storeEntries?: boolean; + + /** + * By default, entry name is checked for malicious characters, like ../ or c:\123, + * pass this flag to disable validation error + * @default false + */ + skipEntryNameValidation?: boolean; + + /** + * Filesystem read chunk size + * @default automatic based on file size + */ + chunkSize?: number; + + /** + * Encoding used to decode file names + * @default UTF8 + */ + nameEncoding?: string; + } + + interface ZipEntry { + /** + * file name + */ + name: string; + + /** + * true if it's a directory entry + */ + isDirectory: boolean; + + /** + * true if it's a file entry, see also isDirectory + */ + isFile: boolean; + + /** + * file comment + */ + comment: string; + + /** + * if the file is encrypted + */ + encrypted: boolean; + + /** + * version made by + */ + verMade: number; + + /** + * version needed to extract + */ + version: number; + + /** + * encrypt, decrypt flags + */ + flags: number; + + /** + * compression method + */ + method: number; + + /** + * modification time + */ + time: number; + + /** + * uncompressed file crc-32 value + */ + crc: number; + + /** + * compressed size + */ + compressedSize: number; + + /** + * uncompressed size + */ + size: number; + + /** + * volume number start + */ + diskStart: number; + + /** + * internal file attributes + */ + inattr: number; + + /** + * external file attributes + */ + attr: number; + + /** + * LOC header offset + */ + offset: number; + } + + class StreamZipAsync { + constructor(config: StreamZipOptions); + + entriesCount: Promise; + comment: Promise; + + entry(name: string): Promise; + entries(): Promise<{ [name: string]: ZipEntry }>; + entryData(entry: string | ZipEntry): Promise; + stream(entry: string | ZipEntry): Promise; + extract(entry: string | ZipEntry | null, outPath: string): Promise; + + on(event: 'entry', handler: (entry: ZipEntry) => void): void; + on(event: 'extract', handler: (entry: ZipEntry, outPath: string) => void): void; + + close(): Promise; + } +} + +type StreamZipOptions = StreamZip.StreamZipOptions; +type ZipEntry = StreamZip.ZipEntry; + +declare class StreamZip { + constructor(config: StreamZipOptions); + + /** + * number of entries in the archive + */ + entriesCount: number; + + /** + * archive comment + */ + comment: string; + + on(event: 'error', handler: (error: any) => void): void; + on(event: 'entry', handler: (entry: ZipEntry) => void): void; + on(event: 'ready', handler: () => void): void; + on(event: 'extract', handler: (entry: ZipEntry, outPath: string) => void): void; + + entry(name: string): ZipEntry | undefined; + + entries(): { [name: string]: ZipEntry }; + + stream( + entry: string | ZipEntry, + callback: (err: any | null, stream?: NodeJS.ReadableStream) => void + ): void; + + entryDataSync(entry: string | ZipEntry): Buffer; + + openEntry( + entry: string | ZipEntry, + callback: (err: any | null, entry?: ZipEntry) => void, + sync: boolean + ): void; + + extract( + entry: string | ZipEntry | null, + outPath: string, + callback: (err?: any, res?: number) => void + ): void; + + close(callback?: (err?: any) => void): void; + + static async: typeof StreamZip.StreamZipAsync; +} + +export = StreamZip; diff --git a/App/node_modules/node-stream-zip/node_stream_zip.js b/App/node_modules/node-stream-zip/node_stream_zip.js new file mode 100644 index 0000000..d95bbef --- /dev/null +++ b/App/node_modules/node-stream-zip/node_stream_zip.js @@ -0,0 +1,1210 @@ +/** + * @license node-stream-zip | (c) 2020 Antelle | https://github.com/antelle/node-stream-zip/blob/master/LICENSE + * Portions copyright https://github.com/cthackers/adm-zip | https://raw.githubusercontent.com/cthackers/adm-zip/master/LICENSE + */ + +let fs = require('fs'); +const util = require('util'); +const path = require('path'); +const events = require('events'); +const zlib = require('zlib'); +const stream = require('stream'); + +const consts = { + /* The local file header */ + LOCHDR: 30, // LOC header size + LOCSIG: 0x04034b50, // "PK\003\004" + LOCVER: 4, // version needed to extract + LOCFLG: 6, // general purpose bit flag + LOCHOW: 8, // compression method + LOCTIM: 10, // modification time (2 bytes time, 2 bytes date) + LOCCRC: 14, // uncompressed file crc-32 value + LOCSIZ: 18, // compressed size + LOCLEN: 22, // uncompressed size + LOCNAM: 26, // filename length + LOCEXT: 28, // extra field length + + /* The Data descriptor */ + EXTSIG: 0x08074b50, // "PK\007\008" + EXTHDR: 16, // EXT header size + EXTCRC: 4, // uncompressed file crc-32 value + EXTSIZ: 8, // compressed size + EXTLEN: 12, // uncompressed size + + /* The central directory file header */ + CENHDR: 46, // CEN header size + CENSIG: 0x02014b50, // "PK\001\002" + CENVEM: 4, // version made by + CENVER: 6, // version needed to extract + CENFLG: 8, // encrypt, decrypt flags + CENHOW: 10, // compression method + CENTIM: 12, // modification time (2 bytes time, 2 bytes date) + CENCRC: 16, // uncompressed file crc-32 value + CENSIZ: 20, // compressed size + CENLEN: 24, // uncompressed size + CENNAM: 28, // filename length + CENEXT: 30, // extra field length + CENCOM: 32, // file comment length + CENDSK: 34, // volume number start + CENATT: 36, // internal file attributes + CENATX: 38, // external file attributes (host system dependent) + CENOFF: 42, // LOC header offset + + /* The entries in the end of central directory */ + ENDHDR: 22, // END header size + ENDSIG: 0x06054b50, // "PK\005\006" + ENDSIGFIRST: 0x50, + ENDSUB: 8, // number of entries on this disk + ENDTOT: 10, // total number of entries + ENDSIZ: 12, // central directory size in bytes + ENDOFF: 16, // offset of first CEN header + ENDCOM: 20, // zip file comment length + MAXFILECOMMENT: 0xffff, + + /* The entries in the end of ZIP64 central directory locator */ + ENDL64HDR: 20, // ZIP64 end of central directory locator header size + ENDL64SIG: 0x07064b50, // ZIP64 end of central directory locator signature + ENDL64SIGFIRST: 0x50, + ENDL64OFS: 8, // ZIP64 end of central directory offset + + /* The entries in the end of ZIP64 central directory */ + END64HDR: 56, // ZIP64 end of central directory header size + END64SIG: 0x06064b50, // ZIP64 end of central directory signature + END64SIGFIRST: 0x50, + END64SUB: 24, // number of entries on this disk + END64TOT: 32, // total number of entries + END64SIZ: 40, + END64OFF: 48, + + /* Compression methods */ + STORED: 0, // no compression + SHRUNK: 1, // shrunk + REDUCED1: 2, // reduced with compression factor 1 + REDUCED2: 3, // reduced with compression factor 2 + REDUCED3: 4, // reduced with compression factor 3 + REDUCED4: 5, // reduced with compression factor 4 + IMPLODED: 6, // imploded + // 7 reserved + DEFLATED: 8, // deflated + ENHANCED_DEFLATED: 9, // deflate64 + PKWARE: 10, // PKWare DCL imploded + // 11 reserved + BZIP2: 12, // compressed using BZIP2 + // 13 reserved + LZMA: 14, // LZMA + // 15-17 reserved + IBM_TERSE: 18, // compressed using IBM TERSE + IBM_LZ77: 19, //IBM LZ77 z + + /* General purpose bit flag */ + FLG_ENC: 0, // encrypted file + FLG_COMP1: 1, // compression option + FLG_COMP2: 2, // compression option + FLG_DESC: 4, // data descriptor + FLG_ENH: 8, // enhanced deflation + FLG_STR: 16, // strong encryption + FLG_LNG: 1024, // language encoding + FLG_MSK: 4096, // mask header values + FLG_ENTRY_ENC: 1, + + /* 4.5 Extensible data fields */ + EF_ID: 0, + EF_SIZE: 2, + + /* Header IDs */ + ID_ZIP64: 0x0001, + ID_AVINFO: 0x0007, + ID_PFS: 0x0008, + ID_OS2: 0x0009, + ID_NTFS: 0x000a, + ID_OPENVMS: 0x000c, + ID_UNIX: 0x000d, + ID_FORK: 0x000e, + ID_PATCH: 0x000f, + ID_X509_PKCS7: 0x0014, + ID_X509_CERTID_F: 0x0015, + ID_X509_CERTID_C: 0x0016, + ID_STRONGENC: 0x0017, + ID_RECORD_MGT: 0x0018, + ID_X509_PKCS7_RL: 0x0019, + ID_IBM1: 0x0065, + ID_IBM2: 0x0066, + ID_POSZIP: 0x4690, + + EF_ZIP64_OR_32: 0xffffffff, + EF_ZIP64_OR_16: 0xffff, +}; + +const StreamZip = function (config) { + let fd, fileSize, chunkSize, op, centralDirectory, closed; + const ready = false, + that = this, + entries = config.storeEntries !== false ? {} : null, + fileName = config.file, + textDecoder = config.nameEncoding ? new TextDecoder(config.nameEncoding) : null; + + open(); + + function open() { + if (config.fd) { + fd = config.fd; + readFile(); + } else { + fs.open(fileName, 'r', (err, f) => { + if (err) { + return that.emit('error', err); + } + fd = f; + readFile(); + }); + } + } + + function readFile() { + fs.fstat(fd, (err, stat) => { + if (err) { + return that.emit('error', err); + } + fileSize = stat.size; + chunkSize = config.chunkSize || Math.round(fileSize / 1000); + chunkSize = Math.max( + Math.min(chunkSize, Math.min(128 * 1024, fileSize)), + Math.min(1024, fileSize) + ); + readCentralDirectory(); + }); + } + + function readUntilFoundCallback(err, bytesRead) { + if (err || !bytesRead) { + return that.emit('error', err || new Error('Archive read error')); + } + let pos = op.lastPos; + let bufferPosition = pos - op.win.position; + const buffer = op.win.buffer; + const minPos = op.minPos; + while (--pos >= minPos && --bufferPosition >= 0) { + if (buffer.length - bufferPosition >= 4 && buffer[bufferPosition] === op.firstByte) { + // quick check first signature byte + if (buffer.readUInt32LE(bufferPosition) === op.sig) { + op.lastBufferPosition = bufferPosition; + op.lastBytesRead = bytesRead; + op.complete(); + return; + } + } + } + if (pos === minPos) { + return that.emit('error', new Error('Bad archive')); + } + op.lastPos = pos + 1; + op.chunkSize *= 2; + if (pos <= minPos) { + return that.emit('error', new Error('Bad archive')); + } + const expandLength = Math.min(op.chunkSize, pos - minPos); + op.win.expandLeft(expandLength, readUntilFoundCallback); + } + + function readCentralDirectory() { + const totalReadLength = Math.min(consts.ENDHDR + consts.MAXFILECOMMENT, fileSize); + op = { + win: new FileWindowBuffer(fd), + totalReadLength, + minPos: fileSize - totalReadLength, + lastPos: fileSize, + chunkSize: Math.min(1024, chunkSize), + firstByte: consts.ENDSIGFIRST, + sig: consts.ENDSIG, + complete: readCentralDirectoryComplete, + }; + op.win.read(fileSize - op.chunkSize, op.chunkSize, readUntilFoundCallback); + } + + function readCentralDirectoryComplete() { + const buffer = op.win.buffer; + const pos = op.lastBufferPosition; + try { + centralDirectory = new CentralDirectoryHeader(); + centralDirectory.read(buffer.slice(pos, pos + consts.ENDHDR)); + centralDirectory.headerOffset = op.win.position + pos; + if (centralDirectory.commentLength) { + that.comment = buffer + .slice( + pos + consts.ENDHDR, + pos + consts.ENDHDR + centralDirectory.commentLength + ) + .toString(); + } else { + that.comment = null; + } + that.entriesCount = centralDirectory.volumeEntries; + that.centralDirectory = centralDirectory; + if ( + (centralDirectory.volumeEntries === consts.EF_ZIP64_OR_16 && + centralDirectory.totalEntries === consts.EF_ZIP64_OR_16) || + centralDirectory.size === consts.EF_ZIP64_OR_32 || + centralDirectory.offset === consts.EF_ZIP64_OR_32 + ) { + readZip64CentralDirectoryLocator(); + } else { + op = {}; + readEntries(); + } + } catch (err) { + that.emit('error', err); + } + } + + function readZip64CentralDirectoryLocator() { + const length = consts.ENDL64HDR; + if (op.lastBufferPosition > length) { + op.lastBufferPosition -= length; + readZip64CentralDirectoryLocatorComplete(); + } else { + op = { + win: op.win, + totalReadLength: length, + minPos: op.win.position - length, + lastPos: op.win.position, + chunkSize: op.chunkSize, + firstByte: consts.ENDL64SIGFIRST, + sig: consts.ENDL64SIG, + complete: readZip64CentralDirectoryLocatorComplete, + }; + op.win.read(op.lastPos - op.chunkSize, op.chunkSize, readUntilFoundCallback); + } + } + + function readZip64CentralDirectoryLocatorComplete() { + const buffer = op.win.buffer; + const locHeader = new CentralDirectoryLoc64Header(); + locHeader.read( + buffer.slice(op.lastBufferPosition, op.lastBufferPosition + consts.ENDL64HDR) + ); + const readLength = fileSize - locHeader.headerOffset; + op = { + win: op.win, + totalReadLength: readLength, + minPos: locHeader.headerOffset, + lastPos: op.lastPos, + chunkSize: op.chunkSize, + firstByte: consts.END64SIGFIRST, + sig: consts.END64SIG, + complete: readZip64CentralDirectoryComplete, + }; + op.win.read(fileSize - op.chunkSize, op.chunkSize, readUntilFoundCallback); + } + + function readZip64CentralDirectoryComplete() { + const buffer = op.win.buffer; + const zip64cd = new CentralDirectoryZip64Header(); + zip64cd.read(buffer.slice(op.lastBufferPosition, op.lastBufferPosition + consts.END64HDR)); + that.centralDirectory.volumeEntries = zip64cd.volumeEntries; + that.centralDirectory.totalEntries = zip64cd.totalEntries; + that.centralDirectory.size = zip64cd.size; + that.centralDirectory.offset = zip64cd.offset; + that.entriesCount = zip64cd.volumeEntries; + op = {}; + readEntries(); + } + + function readEntries() { + op = { + win: new FileWindowBuffer(fd), + pos: centralDirectory.offset, + chunkSize, + entriesLeft: centralDirectory.volumeEntries, + }; + op.win.read(op.pos, Math.min(chunkSize, fileSize - op.pos), readEntriesCallback); + } + + function readEntriesCallback(err, bytesRead) { + if (err || !bytesRead) { + return that.emit('error', err || new Error('Entries read error')); + } + let bufferPos = op.pos - op.win.position; + let entry = op.entry; + const buffer = op.win.buffer; + const bufferLength = buffer.length; + try { + while (op.entriesLeft > 0) { + if (!entry) { + entry = new ZipEntry(); + entry.readHeader(buffer, bufferPos); + entry.headerOffset = op.win.position + bufferPos; + op.entry = entry; + op.pos += consts.CENHDR; + bufferPos += consts.CENHDR; + } + const entryHeaderSize = entry.fnameLen + entry.extraLen + entry.comLen; + const advanceBytes = entryHeaderSize + (op.entriesLeft > 1 ? consts.CENHDR : 0); + if (bufferLength - bufferPos < advanceBytes) { + op.win.moveRight(chunkSize, readEntriesCallback, bufferPos); + op.move = true; + return; + } + entry.read(buffer, bufferPos, textDecoder); + if (!config.skipEntryNameValidation) { + entry.validateName(); + } + if (entries) { + entries[entry.name] = entry; + } + that.emit('entry', entry); + op.entry = entry = null; + op.entriesLeft--; + op.pos += entryHeaderSize; + bufferPos += entryHeaderSize; + } + that.emit('ready'); + } catch (err) { + that.emit('error', err); + } + } + + function checkEntriesExist() { + if (!entries) { + throw new Error('storeEntries disabled'); + } + } + + Object.defineProperty(this, 'ready', { + get() { + return ready; + }, + }); + + this.entry = function (name) { + checkEntriesExist(); + return entries[name]; + }; + + this.entries = function () { + checkEntriesExist(); + return entries; + }; + + this.stream = function (entry, callback) { + return this.openEntry( + entry, + (err, entry) => { + if (err) { + return callback(err); + } + const offset = dataOffset(entry); + let entryStream = new EntryDataReaderStream(fd, offset, entry.compressedSize); + if (entry.method === consts.STORED) { + // nothing to do + } else if (entry.method === consts.DEFLATED) { + entryStream = entryStream.pipe(zlib.createInflateRaw()); + } else { + return callback(new Error('Unknown compression method: ' + entry.method)); + } + if (canVerifyCrc(entry)) { + entryStream = entryStream.pipe( + new EntryVerifyStream(entryStream, entry.crc, entry.size) + ); + } + callback(null, entryStream); + }, + false + ); + }; + + this.entryDataSync = function (entry) { + let err = null; + this.openEntry( + entry, + (e, en) => { + err = e; + entry = en; + }, + true + ); + if (err) { + throw err; + } + let data = Buffer.alloc(entry.compressedSize); + new FsRead(fd, data, 0, entry.compressedSize, dataOffset(entry), (e) => { + err = e; + }).read(true); + if (err) { + throw err; + } + if (entry.method === consts.STORED) { + // nothing to do + } else if (entry.method === consts.DEFLATED || entry.method === consts.ENHANCED_DEFLATED) { + data = zlib.inflateRawSync(data); + } else { + throw new Error('Unknown compression method: ' + entry.method); + } + if (data.length !== entry.size) { + throw new Error('Invalid size'); + } + if (canVerifyCrc(entry)) { + const verify = new CrcVerify(entry.crc, entry.size); + verify.data(data); + } + return data; + }; + + this.openEntry = function (entry, callback, sync) { + if (typeof entry === 'string') { + checkEntriesExist(); + entry = entries[entry]; + if (!entry) { + return callback(new Error('Entry not found')); + } + } + if (!entry.isFile) { + return callback(new Error('Entry is not file')); + } + if (!fd) { + return callback(new Error('Archive closed')); + } + const buffer = Buffer.alloc(consts.LOCHDR); + new FsRead(fd, buffer, 0, buffer.length, entry.offset, (err) => { + if (err) { + return callback(err); + } + let readEx; + try { + entry.readDataHeader(buffer); + if (entry.encrypted) { + readEx = new Error('Entry encrypted'); + } + } catch (ex) { + readEx = ex; + } + callback(readEx, entry); + }).read(sync); + }; + + function dataOffset(entry) { + return entry.offset + consts.LOCHDR + entry.fnameLen + entry.extraLen; + } + + function canVerifyCrc(entry) { + // if bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written + return (entry.flags & 0x8) !== 0x8; + } + + function extract(entry, outPath, callback) { + that.stream(entry, (err, stm) => { + if (err) { + callback(err); + } else { + let fsStm, errThrown; + stm.on('error', (err) => { + errThrown = err; + if (fsStm) { + stm.unpipe(fsStm); + fsStm.close(() => { + callback(err); + }); + } + }); + fs.open(outPath, 'w', (err, fdFile) => { + if (err) { + return callback(err); + } + if (errThrown) { + fs.close(fd, () => { + callback(errThrown); + }); + return; + } + fsStm = fs.createWriteStream(outPath, { fd: fdFile }); + fsStm.on('finish', () => { + that.emit('extract', entry, outPath); + if (!errThrown) { + callback(); + } + }); + stm.pipe(fsStm); + }); + } + }); + } + + function createDirectories(baseDir, dirs, callback) { + if (!dirs.length) { + return callback(); + } + let dir = dirs.shift(); + dir = path.join(baseDir, path.join(...dir)); + fs.mkdir(dir, { recursive: true }, (err) => { + if (err && err.code !== 'EEXIST') { + return callback(err); + } + createDirectories(baseDir, dirs, callback); + }); + } + + function extractFiles(baseDir, baseRelPath, files, callback, extractedCount) { + if (!files.length) { + return callback(null, extractedCount); + } + const file = files.shift(); + const targetPath = path.join(baseDir, file.name.replace(baseRelPath, '')); + extract(file, targetPath, (err) => { + if (err) { + return callback(err, extractedCount); + } + extractFiles(baseDir, baseRelPath, files, callback, extractedCount + 1); + }); + } + + this.extract = function (entry, outPath, callback) { + let entryName = entry || ''; + if (typeof entry === 'string') { + entry = this.entry(entry); + if (entry) { + entryName = entry.name; + } else { + if (entryName.length && entryName[entryName.length - 1] !== '/') { + entryName += '/'; + } + } + } + if (!entry || entry.isDirectory) { + const files = [], + dirs = [], + allDirs = {}; + for (const e in entries) { + if ( + Object.prototype.hasOwnProperty.call(entries, e) && + e.lastIndexOf(entryName, 0) === 0 + ) { + let relPath = e.replace(entryName, ''); + const childEntry = entries[e]; + if (childEntry.isFile) { + files.push(childEntry); + relPath = path.dirname(relPath); + } + if (relPath && !allDirs[relPath] && relPath !== '.') { + allDirs[relPath] = true; + let parts = relPath.split('/').filter((f) => { + return f; + }); + if (parts.length) { + dirs.push(parts); + } + while (parts.length > 1) { + parts = parts.slice(0, parts.length - 1); + const partsPath = parts.join('/'); + if (allDirs[partsPath] || partsPath === '.') { + break; + } + allDirs[partsPath] = true; + dirs.push(parts); + } + } + } + } + dirs.sort((x, y) => { + return x.length - y.length; + }); + if (dirs.length) { + createDirectories(outPath, dirs, (err) => { + if (err) { + callback(err); + } else { + extractFiles(outPath, entryName, files, callback, 0); + } + }); + } else { + extractFiles(outPath, entryName, files, callback, 0); + } + } else { + fs.stat(outPath, (err, stat) => { + if (stat && stat.isDirectory()) { + extract(entry, path.join(outPath, path.basename(entry.name)), callback); + } else { + extract(entry, outPath, callback); + } + }); + } + }; + + this.close = function (callback) { + if (closed || !fd) { + closed = true; + if (callback) { + callback(); + } + } else { + closed = true; + fs.close(fd, (err) => { + fd = null; + if (callback) { + callback(err); + } + }); + } + }; + + const originalEmit = events.EventEmitter.prototype.emit; + this.emit = function (...args) { + if (!closed) { + return originalEmit.call(this, ...args); + } + }; +}; + +StreamZip.setFs = function (customFs) { + fs = customFs; +}; + +StreamZip.debugLog = (...args) => { + if (StreamZip.debug) { + // eslint-disable-next-line no-console + console.log(...args); + } +}; + +util.inherits(StreamZip, events.EventEmitter); + +const propZip = Symbol('zip'); + +StreamZip.async = class StreamZipAsync extends events.EventEmitter { + constructor(config) { + super(); + + const zip = new StreamZip(config); + + zip.on('entry', (entry) => this.emit('entry', entry)); + zip.on('extract', (entry, outPath) => this.emit('extract', entry, outPath)); + + this[propZip] = new Promise((resolve, reject) => { + zip.on('ready', () => { + zip.removeListener('error', reject); + resolve(zip); + }); + zip.on('error', reject); + }); + } + + get entriesCount() { + return this[propZip].then((zip) => zip.entriesCount); + } + + get comment() { + return this[propZip].then((zip) => zip.comment); + } + + async entry(name) { + const zip = await this[propZip]; + return zip.entry(name); + } + + async entries() { + const zip = await this[propZip]; + return zip.entries(); + } + + async stream(entry) { + const zip = await this[propZip]; + return new Promise((resolve, reject) => { + zip.stream(entry, (err, stm) => { + if (err) { + reject(err); + } else { + resolve(stm); + } + }); + }); + } + + async entryData(entry) { + const stm = await this.stream(entry); + return new Promise((resolve, reject) => { + const data = []; + stm.on('data', (chunk) => data.push(chunk)); + stm.on('end', () => { + resolve(Buffer.concat(data)); + }); + stm.on('error', (err) => { + stm.removeAllListeners('end'); + reject(err); + }); + }); + } + + async extract(entry, outPath) { + const zip = await this[propZip]; + return new Promise((resolve, reject) => { + zip.extract(entry, outPath, (err, res) => { + if (err) { + reject(err); + } else { + resolve(res); + } + }); + }); + } + + async close() { + const zip = await this[propZip]; + return new Promise((resolve, reject) => { + zip.close((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } +}; + +class CentralDirectoryHeader { + read(data) { + if (data.length !== consts.ENDHDR || data.readUInt32LE(0) !== consts.ENDSIG) { + throw new Error('Invalid central directory'); + } + // number of entries on this volume + this.volumeEntries = data.readUInt16LE(consts.ENDSUB); + // total number of entries + this.totalEntries = data.readUInt16LE(consts.ENDTOT); + // central directory size in bytes + this.size = data.readUInt32LE(consts.ENDSIZ); + // offset of first CEN header + this.offset = data.readUInt32LE(consts.ENDOFF); + // zip file comment length + this.commentLength = data.readUInt16LE(consts.ENDCOM); + } +} + +class CentralDirectoryLoc64Header { + read(data) { + if (data.length !== consts.ENDL64HDR || data.readUInt32LE(0) !== consts.ENDL64SIG) { + throw new Error('Invalid zip64 central directory locator'); + } + // ZIP64 EOCD header offset + this.headerOffset = readUInt64LE(data, consts.ENDSUB); + } +} + +class CentralDirectoryZip64Header { + read(data) { + if (data.length !== consts.END64HDR || data.readUInt32LE(0) !== consts.END64SIG) { + throw new Error('Invalid central directory'); + } + // number of entries on this volume + this.volumeEntries = readUInt64LE(data, consts.END64SUB); + // total number of entries + this.totalEntries = readUInt64LE(data, consts.END64TOT); + // central directory size in bytes + this.size = readUInt64LE(data, consts.END64SIZ); + // offset of first CEN header + this.offset = readUInt64LE(data, consts.END64OFF); + } +} + +class ZipEntry { + readHeader(data, offset) { + // data should be 46 bytes and start with "PK 01 02" + if (data.length < offset + consts.CENHDR || data.readUInt32LE(offset) !== consts.CENSIG) { + throw new Error('Invalid entry header'); + } + // version made by + this.verMade = data.readUInt16LE(offset + consts.CENVEM); + // version needed to extract + this.version = data.readUInt16LE(offset + consts.CENVER); + // encrypt, decrypt flags + this.flags = data.readUInt16LE(offset + consts.CENFLG); + // compression method + this.method = data.readUInt16LE(offset + consts.CENHOW); + // modification time (2 bytes time, 2 bytes date) + const timebytes = data.readUInt16LE(offset + consts.CENTIM); + const datebytes = data.readUInt16LE(offset + consts.CENTIM + 2); + this.time = parseZipTime(timebytes, datebytes); + + // uncompressed file crc-32 value + this.crc = data.readUInt32LE(offset + consts.CENCRC); + // compressed size + this.compressedSize = data.readUInt32LE(offset + consts.CENSIZ); + // uncompressed size + this.size = data.readUInt32LE(offset + consts.CENLEN); + // filename length + this.fnameLen = data.readUInt16LE(offset + consts.CENNAM); + // extra field length + this.extraLen = data.readUInt16LE(offset + consts.CENEXT); + // file comment length + this.comLen = data.readUInt16LE(offset + consts.CENCOM); + // volume number start + this.diskStart = data.readUInt16LE(offset + consts.CENDSK); + // internal file attributes + this.inattr = data.readUInt16LE(offset + consts.CENATT); + // external file attributes + this.attr = data.readUInt32LE(offset + consts.CENATX); + // LOC header offset + this.offset = data.readUInt32LE(offset + consts.CENOFF); + } + + readDataHeader(data) { + // 30 bytes and should start with "PK\003\004" + if (data.readUInt32LE(0) !== consts.LOCSIG) { + throw new Error('Invalid local header'); + } + // version needed to extract + this.version = data.readUInt16LE(consts.LOCVER); + // general purpose bit flag + this.flags = data.readUInt16LE(consts.LOCFLG); + // compression method + this.method = data.readUInt16LE(consts.LOCHOW); + // modification time (2 bytes time ; 2 bytes date) + const timebytes = data.readUInt16LE(consts.LOCTIM); + const datebytes = data.readUInt16LE(consts.LOCTIM + 2); + this.time = parseZipTime(timebytes, datebytes); + + // uncompressed file crc-32 value + this.crc = data.readUInt32LE(consts.LOCCRC) || this.crc; + // compressed size + const compressedSize = data.readUInt32LE(consts.LOCSIZ); + if (compressedSize && compressedSize !== consts.EF_ZIP64_OR_32) { + this.compressedSize = compressedSize; + } + // uncompressed size + const size = data.readUInt32LE(consts.LOCLEN); + if (size && size !== consts.EF_ZIP64_OR_32) { + this.size = size; + } + // filename length + this.fnameLen = data.readUInt16LE(consts.LOCNAM); + // extra field length + this.extraLen = data.readUInt16LE(consts.LOCEXT); + } + + read(data, offset, textDecoder) { + const nameData = data.slice(offset, (offset += this.fnameLen)); + this.name = textDecoder + ? textDecoder.decode(new Uint8Array(nameData)) + : nameData.toString('utf8'); + const lastChar = data[offset - 1]; + this.isDirectory = lastChar === 47 || lastChar === 92; + + if (this.extraLen) { + this.readExtra(data, offset); + offset += this.extraLen; + } + this.comment = this.comLen ? data.slice(offset, offset + this.comLen).toString() : null; + } + + validateName() { + if (/\\|^\w+:|^\/|(^|\/)\.\.(\/|$)/.test(this.name)) { + throw new Error('Malicious entry: ' + this.name); + } + } + + readExtra(data, offset) { + let signature, size; + const maxPos = offset + this.extraLen; + while (offset < maxPos) { + signature = data.readUInt16LE(offset); + offset += 2; + size = data.readUInt16LE(offset); + offset += 2; + if (consts.ID_ZIP64 === signature) { + this.parseZip64Extra(data, offset, size); + } + offset += size; + } + } + + parseZip64Extra(data, offset, length) { + if (length >= 8 && this.size === consts.EF_ZIP64_OR_32) { + this.size = readUInt64LE(data, offset); + offset += 8; + length -= 8; + } + if (length >= 8 && this.compressedSize === consts.EF_ZIP64_OR_32) { + this.compressedSize = readUInt64LE(data, offset); + offset += 8; + length -= 8; + } + if (length >= 8 && this.offset === consts.EF_ZIP64_OR_32) { + this.offset = readUInt64LE(data, offset); + offset += 8; + length -= 8; + } + if (length >= 4 && this.diskStart === consts.EF_ZIP64_OR_16) { + this.diskStart = data.readUInt32LE(offset); + // offset += 4; length -= 4; + } + } + + get encrypted() { + return (this.flags & consts.FLG_ENTRY_ENC) === consts.FLG_ENTRY_ENC; + } + + get isFile() { + return !this.isDirectory; + } +} + +class FsRead { + constructor(fd, buffer, offset, length, position, callback) { + this.fd = fd; + this.buffer = buffer; + this.offset = offset; + this.length = length; + this.position = position; + this.callback = callback; + this.bytesRead = 0; + this.waiting = false; + } + + read(sync) { + StreamZip.debugLog('read', this.position, this.bytesRead, this.length, this.offset); + this.waiting = true; + let err; + if (sync) { + let bytesRead = 0; + try { + bytesRead = fs.readSync( + this.fd, + this.buffer, + this.offset + this.bytesRead, + this.length - this.bytesRead, + this.position + this.bytesRead + ); + } catch (e) { + err = e; + } + this.readCallback(sync, err, err ? bytesRead : null); + } else { + fs.read( + this.fd, + this.buffer, + this.offset + this.bytesRead, + this.length - this.bytesRead, + this.position + this.bytesRead, + this.readCallback.bind(this, sync) + ); + } + } + + readCallback(sync, err, bytesRead) { + if (typeof bytesRead === 'number') { + this.bytesRead += bytesRead; + } + if (err || !bytesRead || this.bytesRead === this.length) { + this.waiting = false; + return this.callback(err, this.bytesRead); + } else { + this.read(sync); + } + } +} + +class FileWindowBuffer { + constructor(fd) { + this.position = 0; + this.buffer = Buffer.alloc(0); + this.fd = fd; + this.fsOp = null; + } + + checkOp() { + if (this.fsOp && this.fsOp.waiting) { + throw new Error('Operation in progress'); + } + } + + read(pos, length, callback) { + this.checkOp(); + if (this.buffer.length < length) { + this.buffer = Buffer.alloc(length); + } + this.position = pos; + this.fsOp = new FsRead(this.fd, this.buffer, 0, length, this.position, callback).read(); + } + + expandLeft(length, callback) { + this.checkOp(); + this.buffer = Buffer.concat([Buffer.alloc(length), this.buffer]); + this.position -= length; + if (this.position < 0) { + this.position = 0; + } + this.fsOp = new FsRead(this.fd, this.buffer, 0, length, this.position, callback).read(); + } + + expandRight(length, callback) { + this.checkOp(); + const offset = this.buffer.length; + this.buffer = Buffer.concat([this.buffer, Buffer.alloc(length)]); + this.fsOp = new FsRead( + this.fd, + this.buffer, + offset, + length, + this.position + offset, + callback + ).read(); + } + + moveRight(length, callback, shift) { + this.checkOp(); + if (shift) { + this.buffer.copy(this.buffer, 0, shift); + } else { + shift = 0; + } + this.position += shift; + this.fsOp = new FsRead( + this.fd, + this.buffer, + this.buffer.length - shift, + shift, + this.position + this.buffer.length - shift, + callback + ).read(); + } +} + +class EntryDataReaderStream extends stream.Readable { + constructor(fd, offset, length) { + super(); + this.fd = fd; + this.offset = offset; + this.length = length; + this.pos = 0; + this.readCallback = this.readCallback.bind(this); + } + + _read(n) { + const buffer = Buffer.alloc(Math.min(n, this.length - this.pos)); + if (buffer.length) { + fs.read(this.fd, buffer, 0, buffer.length, this.offset + this.pos, this.readCallback); + } else { + this.push(null); + } + } + + readCallback(err, bytesRead, buffer) { + this.pos += bytesRead; + if (err) { + this.emit('error', err); + this.push(null); + } else if (!bytesRead) { + this.push(null); + } else { + if (bytesRead !== buffer.length) { + buffer = buffer.slice(0, bytesRead); + } + this.push(buffer); + } + } +} + +class EntryVerifyStream extends stream.Transform { + constructor(baseStm, crc, size) { + super(); + this.verify = new CrcVerify(crc, size); + baseStm.on('error', (e) => { + this.emit('error', e); + }); + } + + _transform(data, encoding, callback) { + let err; + try { + this.verify.data(data); + } catch (e) { + err = e; + } + callback(err, data); + } +} + +class CrcVerify { + constructor(crc, size) { + this.crc = crc; + this.size = size; + this.state = { + crc: ~0, + size: 0, + }; + } + + data(data) { + const crcTable = CrcVerify.getCrcTable(); + let crc = this.state.crc; + let off = 0; + let len = data.length; + while (--len >= 0) { + crc = crcTable[(crc ^ data[off++]) & 0xff] ^ (crc >>> 8); + } + this.state.crc = crc; + this.state.size += data.length; + if (this.state.size >= this.size) { + const buf = Buffer.alloc(4); + buf.writeInt32LE(~this.state.crc & 0xffffffff, 0); + crc = buf.readUInt32LE(0); + if (crc !== this.crc) { + throw new Error('Invalid CRC'); + } + if (this.state.size !== this.size) { + throw new Error('Invalid size'); + } + } + } + + static getCrcTable() { + let crcTable = CrcVerify.crcTable; + if (!crcTable) { + CrcVerify.crcTable = crcTable = []; + const b = Buffer.alloc(4); + for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 8; --k >= 0; ) { + if ((c & 1) !== 0) { + c = 0xedb88320 ^ (c >>> 1); + } else { + c = c >>> 1; + } + } + if (c < 0) { + b.writeInt32LE(c, 0); + c = b.readUInt32LE(0); + } + crcTable[n] = c; + } + } + return crcTable; + } +} + +function parseZipTime(timebytes, datebytes) { + const timebits = toBits(timebytes, 16); + const datebits = toBits(datebytes, 16); + + const mt = { + h: parseInt(timebits.slice(0, 5).join(''), 2), + m: parseInt(timebits.slice(5, 11).join(''), 2), + s: parseInt(timebits.slice(11, 16).join(''), 2) * 2, + Y: parseInt(datebits.slice(0, 7).join(''), 2) + 1980, + M: parseInt(datebits.slice(7, 11).join(''), 2), + D: parseInt(datebits.slice(11, 16).join(''), 2), + }; + const dt_str = [mt.Y, mt.M, mt.D].join('-') + ' ' + [mt.h, mt.m, mt.s].join(':') + ' GMT+0'; + return new Date(dt_str).getTime(); +} + +function toBits(dec, size) { + let b = (dec >>> 0).toString(2); + while (b.length < size) { + b = '0' + b; + } + return b.split(''); +} + +function readUInt64LE(buffer, offset) { + return buffer.readUInt32LE(offset + 4) * 0x0000000100000000 + buffer.readUInt32LE(offset); +} + +module.exports = StreamZip; diff --git a/App/node_modules/node-stream-zip/package.json b/App/node_modules/node-stream-zip/package.json new file mode 100644 index 0000000..5fd74e0 --- /dev/null +++ b/App/node_modules/node-stream-zip/package.json @@ -0,0 +1,47 @@ +{ + "name": "node-stream-zip", + "version": "1.15.0", + "description": "node.js library for reading and extraction of ZIP archives", + "keywords": [ + "zip", + "archive", + "unzip", + "stream" + ], + "homepage": "https://github.com/antelle/node-stream-zip", + "author": "Antelle (https://github.com/antelle)", + "bugs": { + "email": "antelle.net@gmail.com", + "url": "https://github.com/antelle/node-stream-zip/issues" + }, + "license": "MIT", + "files": [ + "LICENSE", + "node_stream_zip.js", + "node_stream_zip.d.ts" + ], + "scripts": { + "lint": "eslint node_stream_zip.js test/tests.js", + "check-types": "tsc node_stream_zip.d.ts", + "test": "nodeunit test/tests.js" + }, + "main": "node_stream_zip.js", + "types": "node_stream_zip.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/antelle/node-stream-zip.git" + }, + "engines": { + "node": ">=0.12.0" + }, + "devDependencies": { + "@types/node": "^14.14.6", + "eslint": "^7.19.0", + "nodeunit": "^0.11.3", + "prettier": "^2.2.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } +} diff --git a/Lang/about-translations.md b/Lang/about-translations.md index b53ac2b..a6a9eab 100644 --- a/Lang/about-translations.md +++ b/Lang/about-translations.md @@ -6,4 +6,4 @@ I would like to thank everyone who contributed to the translation of this projec - French: [Mizmalik](https://github.com/Mizmalik) - Chinese (Simplified): [nini22P](https://github.com/nini22P) - Russian: ThatSameGuy _(Revisions by [gandalfthewhite](https://github.com/gandalfthewhite19890404))_ -- Italian: [Dan Adrian Radut (Aka. B8nee)](https://github.com/B8nee) +- Italian: [Dan Adrian Radut (Aka. B8nee)](https://github.com/B8nee) \ No newline at end of file diff --git a/Lang/fr-fr.json b/Lang/fr-fr.json index bc39b02..ebf4402 100644 --- a/Lang/fr-fr.json +++ b/Lang/fr-fr.json @@ -8,7 +8,7 @@ "logWindowTitle": "Exécution de fpPS4", "killEmuStatus": "Le processus principal a été fermé - fermez la fenêtre des logs pour continuer", "logCleared": "INFO - La liste des journaux a été effacée!\n ", - "about": "fpPS4 Lanceur de Temmie - Version: %VARIABLE_0%\nCréé par TemmieHeartz\n(https://twitter.com/themitosan)\n\nfpPS4 a été créé/développé par red-prig\n(https://github.com/red -prig/fpPS4)\n\nLe plugin Memoryjs a été créé/développé par Rob--\n(https://github.com/rob--/memoryjs)\n\nLes icônes SVG ont été obtenues à partir de\nhttps://www.svgrepo.com/", + "about": "", "mainLog": "Lanceur de fpPS4 Temmie - Version: %VARIABLE_0%\nUtilisation de nw.js (node-webkit) version %VARIABLE_1% [%VARIABLE_2%]", "settingsErrorCreatePath": "ERREUR - Impossible de créer le dossier!\n(%VARIABLE_0%)\n%VARIABLE_1%", "settingsErrorfpPS4NotFound": "ERREUR - Impossible de trouver l'exécutable fpPS4!\nSélectionnez l'exécutable dans les paramètres ou placez-le dans le dossier \"Emu\" et cliquez sur OK.", @@ -47,7 +47,18 @@ "gameListVersion": "Version", "selectGameLoadPatchErrorParamSfo": "ERREUR - Impossible de charger PARAM.SFO de ce correctif!\n%VARIABLE_0%", "path": "Chemin", - "gamelistGamePath404": "" + "gamelistGamePath404": "", + "updateEmuFetchActionsError": "", + "updateEmuIsLatestVersion": "", + "updateEmuShaAvailable": "", + "updateEmuShaUnavailable": "", + "updateEmuDownloadFailed": "", + "updateEmuProcessComplete": "", + "updateEmu-1-4": "", + "updateEmu-2-4": "", + "updateEmu-3-4": "", + "updateEmu-4-4": "", + "settingsLogEmuSha": "" }, "input_text": { @@ -95,7 +106,10 @@ "LABEL_FPPS4_OPTIONS_LAUNCHER_OPTIONS": "Options du lanceur", "LABEL_FPPS4_OPTIONS_HACKS": "Hacks", "LABEL_SETTINGS_SHOW_METADATA_GUI": "", - "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": "" + "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": "", + "DIV_SETTINGS_FPPS4_UPDATER": "", + "LABEL_SETTINGS_ENABLE_LAUNCHER_FPPS4_UPDATES": "", + "LABEL_SETTINGS_FPPS4_UPDATE_BRANCH": "" }, @@ -131,7 +145,9 @@ "BTN_FPPS4_OPTIONS_RESET_SETTINGS": "Réinitialiser les options", "BTN_launcherOptionsExportMetadata": "Exporter les métadonnées", "BTN_RUN": "Démarrer fpPS4", - "BTN_SETTINGS_RESTART_LAUNCHER": "" + "BTN_SETTINGS_RESTART_LAUNCHER": "", + "BTN_UPDATE_FPPS4": "", + "BTN_SETTINGS_FORCE_FPPS4_UPDATE": "" } } \ No newline at end of file diff --git a/Lang/it-it.json b/Lang/it-it.json index fe21ed9..11fd4ad 100644 --- a/Lang/it-it.json +++ b/Lang/it-it.json @@ -8,7 +8,7 @@ "logWindowTitle": "Eseguendo fpPS4", "killEmuStatus": "Il processo principale è stato chiuso: chiudi la finestra del log per continuare", "logCleared": "INFO - L'elenco dei log è stato cancellato!\n", - "about": "fpPS4 Temmie's Launcher - Versione: %VARIABLE_0%\nCreato da TemmieHeartz\n(https://twitter.com/themitosan)\n\nfpPS4 è stato creato/sviluppato da red-prig\n(https://github.com/red-prig/fpPS4)\n\nIl plugin Memoryjs è stato creato/sviluppato da Rob--\n(https://github.com/rob--/memoryjs)\n\nLe icone SVG sono state ottenute da https://www.svgrepo.com/", + "about": "", "mainLog": "fpPS4 Temmie's Launcher - Versione: %VARIABLE_0%\nUsando nw.js (node-webkit) versione %VARIABLE_1% [%VARIABLE_2%]", "settingsErrorCreatePath": "ERRORE - Impossibile creare la cartella!\n(%VARIABLE_0%)\n%VARIABLE_1%", "settingsErrorfpPS4NotFound": "ERRORE - Impossibile trovare l'eseguibile fpPS4!\nSelezionare l'eseguibile nelle impostazioni o posizionarlo all'interno della cartella \"Emu\" e fare clic su ok.", @@ -47,7 +47,18 @@ "gameListVersion": "Versione", "selectGameLoadPatchErrorParamSfo": "ERRORE - Impossibile caricare PARAM.SFO di questa patch!\n%VARIABLE_0%", "path": "Percorso", - "gamelistGamePath404": "INFO - La cartella app/giochi selezionata non esiste!\n%VARIABLE_0%" + "gamelistGamePath404": "INFO - La cartella app/giochi selezionata non esiste!\n%VARIABLE_0%", + "updateEmuFetchActionsError": "", + "updateEmuIsLatestVersion": "", + "updateEmuShaAvailable": "", + "updateEmuShaUnavailable": "", + "updateEmuDownloadFailed": "", + "updateEmuProcessComplete": "", + "updateEmu-1-4": "", + "updateEmu-2-4": "", + "updateEmu-3-4": "", + "updateEmu-4-4": "", + "settingsLogEmuSha": "" }, "input_text": { @@ -95,7 +106,10 @@ "LABEL_FPPS4_OPTIONS_LAUNCHER_OPTIONS": "Opzioni del launcher", "LABEL_FPPS4_OPTIONS_HACKS": "Hacks", "LABEL_SETTINGS_SHOW_METADATA_GUI": "Mostra l'icona e il nome dell'app/gioco durante l'emulazione", - "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": " Visualizza l'output dell'emulatore (stdout e stderr) nel log interno (premi F12 --> Console)" + "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": " Visualizza l'output dell'emulatore (stdout e stderr) nel log interno (premi F12 --> Console)", + "DIV_SETTINGS_FPPS4_UPDATER": "", + "LABEL_SETTINGS_ENABLE_LAUNCHER_FPPS4_UPDATES": "", + "LABEL_SETTINGS_FPPS4_UPDATE_BRANCH": "" }, @@ -131,7 +145,9 @@ "BTN_FPPS4_OPTIONS_RESET_SETTINGS": "Ripristina le impostazioni", "BTN_launcherOptionsExportMetadata": "Esportare i metadati", "BTN_RUN": "Avvia fpPS4", - "BTN_SETTINGS_RESTART_LAUNCHER": "Riavvia il launcher" + "BTN_SETTINGS_RESTART_LAUNCHER": "Riavvia il launcher", + "BTN_UPDATE_FPPS4": "", + "BTN_SETTINGS_FORCE_FPPS4_UPDATE": "" } } diff --git a/Lang/pt-br.json b/Lang/pt-br.json index b773264..80acf8b 100644 --- a/Lang/pt-br.json +++ b/Lang/pt-br.json @@ -8,7 +8,7 @@ "logWindowTitle": "Executando fpPS4", "killEmuStatus": "Processo principal foi fechado - feche a janela do log para continuar", "logCleared": "INFO - A lista de log foi limpa!\n ", - "about": "fpPS4 Temmie's Launcher - Versão: %VARIABLE_0%\nCriado por TemmieHeartz\n(https://twitter.com/themitosan)\n\nfpPS4 foi criado / desenvolvido por red-prig\n(https://github.com/red-prig/fpPS4)\n\nPlugin memoryjs foi criado / desenvolvido por Rob--\n(https://github.com/rob--/memoryjs)\n\nIcones SVG foram obtidos através do site https://www.svgrepo.com/", + "about": "fpPS4 Temmie's Launcher - Versão: %VARIABLE_0%\nCriado por TemmieHeartz\n(https://twitter.com/themitosan)\n\nfpPS4 foi criado / desenvolvido por red-prig\n(https://github.com/red-prig/fpPS4)\n\nPlugin memoryjs foi criado / desenvolvido por Rob--\n(https://github.com/rob--/memoryjs)\n\nPlugin node-stream-zip foi criado / desenvolvido por antelle\n(https://github.com/antelle/node-stream-zip)\n\nÍcones SVG foram obtidos através do site https://www.svgrepo.com/", "mainLog": "fpPS4 Temmie's Launcher - Versão: %VARIABLE_0%\nUsando nw.js (node-webkit) versão %VARIABLE_1% [%VARIABLE_2%]", "settingsErrorCreatePath": "ERRO - Não foi possível criar a pasta!\n(%VARIABLE_0%)\n%VARIABLE_1%", "settingsErrorfpPS4NotFound": "ERRO - Não foi possível encontrar o executável do fpPS4!\nSelecione o executável nas configurações ou coloque ele dentro da pasta \"Emu\" e clique em ok.", @@ -47,7 +47,18 @@ "gameListVersion": "Versão", "selectGameLoadPatchErrorParamSfo": "ERRO - Não foi possível carregar PARAM.SFO desse patch!\n%VARIABLE_0%", "path": "Caminho", - "gamelistGamePath404": "INFO - A pasta de app / games selecionada não existe!\n%VARIABLE_0%" + "gamelistGamePath404": "INFO - A pasta de app / games selecionada não existe!\n%VARIABLE_0%", + "updateEmuFetchActionsError": "ERRO - Não foi possível obter informações do GitHub Actions!", + "updateEmuIsLatestVersion": "INFO - Você já está usando a versão mais recente!\nCommit ID (SHA): %VARIABLE_0%", + "updateEmuShaAvailable": "INFO - Uma nova atualização está disponível!\n\nVersão local: %VARIABLE_0%\nNova versão: %VARIABLE_1%\n\nVocê gostaria de atualizar?", + "updateEmuShaUnavailable": "INFO - O launcher detectou que nenhuma atualização foi feita\n(Ou o executável do fpPS4 não foi encontrado!)\n\nÉ possível corrigir esse problema usando o procedimento de atualização automática.\n\nVocê deseja prosseguir?", + "updateEmuDownloadFailed": "ERRO - Não foi possível baixar a atualização do fpPS4!\nStatus de resposta: %VARIABLE_0% - OK: %VARIABLE_1%", + "updateEmuProcessComplete": "INFO - Update concluído!\nNova versão (Commit ID / SHA): %VARIABLE_0%", + "updateEmu-1-4": "Baixando update do fpPS4 ()", + "updateEmu-2-4": "Extraíndo update", + "updateEmu-3-4": "Removendo arquivos de sobra", + "updateEmu-4-4": "Update concluído!", + "settingsLogEmuSha": "INFO - Versão do fpPS4: (%VARIABLE_0%)" }, "input_text": { @@ -95,7 +106,10 @@ "LABEL_FPPS4_OPTIONS_LAUNCHER_OPTIONS": "Opções do Launcher", "LABEL_FPPS4_OPTIONS_HACKS": "Hacks", "LABEL_SETTINGS_SHOW_METADATA_GUI": "Mostrar ícone e nome do app / game durante a emulação", - "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": " Exibir output do emulador (stdout e stderr) no log interno (Aperte F12 --> Console)" + "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": " Exibir output do emulador (stdout e stderr) no log interno (Aperte F12 --> Console)", + "DIV_SETTINGS_FPPS4_UPDATER": "Atualizações do fpPS4", + "LABEL_SETTINGS_ENABLE_LAUNCHER_FPPS4_UPDATES": "Habilitar atualizador do fpPS4", + "LABEL_SETTINGS_FPPS4_UPDATE_BRANCH": "Obter atualizações da branch" }, @@ -131,7 +145,9 @@ "BTN_FPPS4_OPTIONS_RESET_SETTINGS": "Resetar configurações", "BTN_launcherOptionsExportMetadata": "Exportar metadados", "BTN_RUN": "Iniciar fpPS4", - "BTN_SETTINGS_RESTART_LAUNCHER": "Reiniciar launcher" + "BTN_SETTINGS_RESTART_LAUNCHER": "Reiniciar launcher", + "BTN_UPDATE_FPPS4": "Atualizar fpPS4", + "BTN_SETTINGS_FORCE_FPPS4_UPDATE": "Fazer atualização forçada" } } \ No newline at end of file diff --git a/Lang/ru-ru.json b/Lang/ru-ru.json index a08fc5e..58d058c 100644 --- a/Lang/ru-ru.json +++ b/Lang/ru-ru.json @@ -8,7 +8,7 @@ "logWindowTitle": "Запуск fpPS4", "killEmuStatus": "Основной процесс был закрыт - закройте окно лога для продолжения работы", "logCleared": "ИНФО - Список логов был очищен!\n ", - "about": "fpPS4 Temmie's Launcher - Версия: %VARIABLE_0%\nСоздатель - TemmieHeartz\n(https://twitter.com/themitosan)\n\nfpPS4, создатель - red-prig\n(https://github.com/red-prig/fpPS4)\n\nплагин memoryjs - разработан: Rob--\n(https://github.com/rob--/memoryjs)\n\nИконки SVG были получены с https://www.svgrepo.com/", + "about": "", "mainLog": "fpPS4 Temmie's Launcher - Версия: %VARIABLE_0%\nИспользование nw.js (node-webkit) версия %VARIABLE_1% [%VARIABLE_2%]", "settingsErrorCreatePath": "ОШИБКА - Папка не может быть создана!\n(%VARIABLE_0%)\n%VARIABLE_1%", "settingsErrorfpPS4NotFound": "ОШИБКА - Не удалось найти исполняемый файл fpPS4!\nВыберите исполняемый файл в настройках или поместите его внутрь \"Emu\" и нажмите OK.", @@ -47,7 +47,18 @@ "gameListVersion": "Версия", "selectGameLoadPatchErrorParamSfo": "ОШИБКА - Невозможно загрузить PARAM.SFO из этого патча!\n%VARIABLE_0%", "path": "Путь", - "gamelistGamePath404": "" + "gamelistGamePath404": "", + "updateEmuFetchActionsError": "", + "updateEmuIsLatestVersion": "", + "updateEmuShaAvailable": "", + "updateEmuShaUnavailable": "", + "updateEmuDownloadFailed": "", + "updateEmuProcessComplete": "", + "updateEmu-1-4": "", + "updateEmu-2-4": "", + "updateEmu-3-4": "", + "updateEmu-4-4": "", + "settingsLogEmuSha": "" }, "input_text": { @@ -95,7 +106,10 @@ "LABEL_FPPS4_OPTIONS_LAUNCHER_OPTIONS": "Параметры запуска", "LABEL_FPPS4_OPTIONS_HACKS": "Хаки", "LABEL_SETTINGS_SHOW_METADATA_GUI": "", - "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": "" + "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": "", + "DIV_SETTINGS_FPPS4_UPDATER": "", + "LABEL_SETTINGS_ENABLE_LAUNCHER_FPPS4_UPDATES": "", + "LABEL_SETTINGS_FPPS4_UPDATE_BRANCH": "" }, @@ -131,7 +145,9 @@ "BTN_FPPS4_OPTIONS_RESET_SETTINGS": "Сброс настроек", "BTN_launcherOptionsExportMetadata": "Экспорт метаданных", "BTN_RUN": "Запустить", - "BTN_SETTINGS_RESTART_LAUNCHER": "" + "BTN_SETTINGS_RESTART_LAUNCHER": "", + "BTN_UPDATE_FPPS4": "", + "BTN_SETTINGS_FORCE_FPPS4_UPDATE": "" } } diff --git a/Lang/zh-s.json b/Lang/zh-s.json index d85b01b..4fd5e07 100644 --- a/Lang/zh-s.json +++ b/Lang/zh-s.json @@ -8,7 +8,7 @@ "logWindowTitle": "Running fpPS4", "killEmuStatus": "主进程被关闭 - 关闭日志窗口以继续", "logCleared": "INFO - 日志已被清除!\n ", - "about": "fpPS4 Temmie's Launcher - 版本号: %VARIABLE_0%\n由 TemmieHeartz 创建\n(https://twitter.com/themitosan)\n\nfpPS4 由 red-prig 创建\n(https://github.com/red-prig/fpPS4)\n\nmemoryjs 插件由 Rob-- 创建\n(https://github.com/rob--/memoryjs)\n\nSVG 图标来自 https://www.svgrepo.com/", + "about": "", "mainLog": "fpPS4 Temmie's Launcher - 版本号: %VARIABLE_0%\n运行中的 nw.js (node-webkit) 版本号: %VARIABLE_1% [%VARIABLE_2%]", "settingsErrorCreatePath": "ERROR - 无法创建文件夹!\n(%VARIABLE_0%)\n%VARIABLE_1%", "settingsErrorfpPS4NotFound": "ERROR - 无法找到 fpPS4 的可执行文件!\n在设置中选择可执行文件或将其放在 \"Emu\" 文件夹中,然后点击确定。", @@ -47,7 +47,18 @@ "gameListVersion": "版本号", "selectGameLoadPatchErrorParamSfo": "ERROR - 无法从这个补丁中加载 PARAM.SFO!\n%VARIABLE_0%", "path": "路径", - "gamelistGamePath404": "" + "gamelistGamePath404": "", + "updateEmuFetchActionsError": "", + "updateEmuIsLatestVersion": "", + "updateEmuShaAvailable": "", + "updateEmuShaUnavailable": "", + "updateEmuDownloadFailed": "", + "updateEmuProcessComplete": "", + "updateEmu-1-4": "", + "updateEmu-2-4": "", + "updateEmu-3-4": "", + "updateEmu-4-4": "", + "settingsLogEmuSha": "" }, "input_text": { @@ -95,8 +106,10 @@ "LABEL_FPPS4_OPTIONS_LAUNCHER_OPTIONS": "启动器选项", "LABEL_FPPS4_OPTIONS_HACKS": "Hacks", "LABEL_SETTINGS_SHOW_METADATA_GUI": "在界面上显示图标和标题", - "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": " 在内部控制台显示 fpPS4 进程日志 (stdoutstderr) (按 F12 --> Console)" - + "LABEL_SETTINGS_EXPERIMENTAL_FPPS4_INTERNAL_LOG": " 在内部控制台显示 fpPS4 进程日志 (stdoutstderr) (按 F12 --> Console)", + "DIV_SETTINGS_FPPS4_UPDATER": "", + "LABEL_SETTINGS_ENABLE_LAUNCHER_FPPS4_UPDATES": "", + "LABEL_SETTINGS_FPPS4_UPDATE_BRANCH": "" }, @@ -132,7 +145,9 @@ "BTN_FPPS4_OPTIONS_RESET_SETTINGS": "重置设置", "BTN_launcherOptionsExportMetadata": "导出元数据", "BTN_RUN": "运行 fpPS4", - "BTN_SETTINGS_RESTART_LAUNCHER": "重新启动启动器" + "BTN_SETTINGS_RESTART_LAUNCHER": "重新启动启动器", + "BTN_UPDATE_FPPS4": "", + "BTN_SETTINGS_FORCE_FPPS4_UPDATE": "" } } diff --git a/README.md b/README.md index f29c6b8..c552100 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ If you dump your game using memoryjs - created by Rob-- -- TMS.js by TemmieHeartz (hi!) +- node-stream-zip - created by antelle +- TMS.js by TemmieHeartz (Hi!) -IMPORTANT: This software does not allow you to obtain free PS4 Games / Apps. +IMPORTANT: This software does not allow you to obtain free PS4 Games / Apps. \ No newline at end of file diff --git a/package.json b/package.json index cdf18c5..e780d70 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "width": 1186, "height": 710, "toolbar": true, - "min_width": 1062, + "min_width": 1102, "min_height": 626, "fullscreen": false, "position": "center",